{"slug": "show-hn-jevper-the-jev-interface-on-top-of-any-openai-compatible-model", "title": "Show HN: Jevper – the Jev interface on top of any OpenAI-compatible model", "summary": "Developer zhulinchng released jevper, an independent Python implementation of the documented TypeSafe AI System One wire format that runs the Jev interface on any OpenAI-compatible model, including a self-hosted llama.cpp server. The library, installable via `pip install jevper` on Python 3.10+ with pydantic>=2.7 as its only runtime dependency, returns typed answers — Noul (yes/no with one probability), Choice (up to 255 options), and Score (2–10 ordered levels) — with probabilities and confidence, and supports four elicitation methods: logprobs, grammar, structured, and discrete. jevper does not call the hosted TypeSafe API and is not affiliated with, endorsed by, or supported by TypeSafe AI.", "body_md": "The [Jev](https://docs.typesafe.ai) interface — `state` in, typed `questions` (`noul`, `choice`, `score`) out,\nanswers carrying probabilities and confidence — on top of any OpenAI-compatible model.\n\nSame call as `typesafe-sdk`, different backend: point `jevper` at a hosted LLM or a self-hosted llama.cpp\nserver and code written for Jev keeps working, unchanged.\n\nIt does not call the hosted TypeSafe API and does not depend on `typesafe-sdk` or `openai` at runtime — the\nclient object is duck-typed. Any object exposing `responses.create` or `chat.completions.create` works,\nincluding a self-hosted llama.cpp server.\n\n`jevper` is an independent implementation of the documented System One wire format. It is not affiliated\nwith, endorsed by, or supported by TypeSafe AI — questions about the API itself belong in\n[their docs](https://docs.typesafe.ai).\n\n``` python\nfrom openai import OpenAI\nfrom jevper import Choice, SystemOneClient\n\nclient = SystemOneClient(OpenAI(), model=\"gpt-5.6-terra\", method=\"logprobs\")\n\nresponse = client.system_one(\n    state=\"I was charged twice for the same subscription this month.\",\n    questions={\n        \"intent\": Choice(\n            instructions=\"Pick the intent of the message.\",\n            criteria={\n                \"billing\": \"money, invoices, refunds, charges\",\n                \"technical\": \"errors, crashes, login or performance problems\",\n                \"sales\": \"pricing, plans, purchasing, upgrades\",\n            },\n        )\n    },\n)\n\nanswer = response.answers[\"intent\"]\nanswer.choice        # \"billing\"\nanswer.probabilities # {\"billing\": 0.88, \"technical\": 0.08, \"sales\": 0.03}\nanswer.confidence    # 0.83\npip install jevper\n```\n\nPython 3.10+. The only runtime dependency is `pydantic>=2.7`.\n\nFor development:\n\n```\ngit clone https://github.com/zhulinchng/jevper && cd jevper\nuv venv && uv pip install -e '.[test]'\npytest -q\nphp\nflowchart LR\n    A[\"state + questions\"] --> B[\"build_messages: system prompt, state turns, few-shot turns, question block\"]\n    B --> C{\"method\"}\n    C -->|logprobs| D[\"logprobs=true, top_logprobs=20\"]\n    C -->|grammar| E[\"+ GBNF grammar in extra_body\"]\n    C -->|structured| F[\"strict JSON schema: probabilities\"]\n    C -->|discrete| G[\"strict JSON schema: one label\"]\n    D --> H[\"first label token -> softmax over the labels\"]\n    E --> H\n    F --> I[\"probability dict from JSON\"]\n    G --> J[\"one-hot from the chosen label\"]\n    H --> K[\"Answer: choice / noul / score\"]\n    I --> K\n    J --> K\n```\n\nEach question becomes its own provider call, so questions are independent and run concurrently\n(`max_concurrency`, default 8). Answers come back keyed by your question ids, in insertion order.\n\nThree types, mirroring the Jev API — `Noul` answers yes/no with one probability, `Choice` picks one of your\nlabelled options, `Score` rates on an ordered scale:\n\n| Type | Criteria | Answer | \n|---|---|---|\n| `Noul(instructions=..., criteria={\"true\": ..., \"false\": ...})` | optional | `{\"type\": \"noul\", \"noul\": 0.93}` | \n| `Choice(instructions=..., criteria={\"billing\": \"...\", ...})` | 2–255 keys | `{\"type\": \"choice\", \"choice\": \"billing\", \"probabilities\": {...}, \"confidence\": 0.83}` | \n| `Score(instructions=..., criteria=[\"Calm\", \"Frustrated\", \"Very angry\"])` | 2–10 levels | `{\"type\": \"score\", \"score\": 1.05, \"legend\": {...}, \"probabilities\": {...}, \"confidence\": 0.92}` | \n\n`Score.score` is the probability-weighted level index (`Σ i·pᵢ`, levels zero-based), as in the Jev API.\n`Choice` takes up to 255 options, the Jev API limit. The two methods that read a label *token* —\n`logprobs` and `grammar` — stop at 26, because the first token of `\"AA\"` is `\"A\"`; past 26 options they\nraise `InvalidQuestionError` pointing at `structured` and `discrete`, which answer in JSON and use\ntwo-letter labels.\n\nQuestions can also be passed as raw mappings (`{\"type\": \"choice\", \"criteria\": {...}}`) and are validated the\nsame way.\n\n`method=` decides how the decision is elicited. All four share the same label→option mapping, so switching\nmethods does not change your types; only the label alphabet differs (`logprobs` and `grammar` need\nsingle-letter labels, so they cap at 26 options).\n\n| Method | Request | Readout | Needs | \n|---|---|---|---|\n| `logprobs` (default) | `logprobs=true, top_logprobs=20` | softmax over the labels' logprobs of the first answer token | a provider that returns chat logprobs (or the Responses surface with `include` logprobs) | \n| `grammar` | the same plus a GBNF `grammar` in`extra_body` | same as `logprobs` | a Chat Completions server that accepts `grammar` (llama.cpp and friends) | \n| `structured` | strict JSON schema, model returns a probability per option | the model's own numbers, rescaled to sum 1 when off by more than `1e-6` | JSON-schema structured output | \n| `discrete` | strict JSON schema, model returns one option | one-hot distribution | JSON-schema structured output | \n\n`logprobs` is the default because it needs no provider-specific field beyond `logprobs`, and it reads the\nmodel's real distribution rather than a sampled answer. See [docs/methods.md](https://github.com/zhulinchng/jevper/blob/main/docs/methods.md) for the exact\nrequest bodies, readout rules and failure modes.\n\nPass `reasoning=ReasoningConfig(...)` to make the model think before it classifies:\n\n``` python\nfrom jevper import ReasoningConfig, reasoning_text\n\nclient = SystemOneClient(OpenAI(), model=\"gpt-5.6-terra\", reasoning=ReasoningConfig(effort=\"medium\"))\n\nresponse = client.system_one(state=..., questions=...)\nreasoning_text(response.reasoning)   # the trace, as text\n```\n\n`mode=\"auto\"` (the default) uses native provider reasoning on the Responses surface and a two-step\nthink-then-classify path on Chat Completions, where the analysis text is replayed as an assistant turn before\nthe answer. The trace always lands on `response.reasoning`, and the two-step analysis call's usage is counted\nin `response.usage`. See [docs/reasoning.md](https://github.com/zhulinchng/jevper/blob/main/docs/reasoning.md).\n\nExamples are chat turns (example state + question block, then the expected answer), so the demonstration is always in the format the active method expects. They can be attached at three levels:\n\n``` python\nfrom jevper import Choice, Example, SystemOneClient\n\nquestion = Choice(\n    criteria={\"billing\": \"...\", \"technical\": \"...\"},\n    examples=[Example(state=\"Charged twice for one order\", answer=\"billing\")],\n)\n\nclient = SystemOneClient(OpenAI(), model=\"gpt-5.6-terra\",\n                         examples=[Example(state=\"Login fails\", answer=\"technical\")])  # fallback for every question\n\nclient.system_one(state=..., questions={\"intent\": question},\n                  examples={\"intent\": [...]})  # or a bare sequence for all questions\n```\n\nPrecedence is question → per call → constructor, and the first non-empty level wins. `examples` is excluded\nfrom `model_dump()`, so question dumps keep exactly the Jev wire keys. See\n[docs/few-shot.md](https://github.com/zhulinchng/jevper/blob/main/docs/few-shot.md).\n\n```\nresponse.model                 # the model actually used\nresponse.answers               # {\"intent\": ChoiceAnswer(...)}\nresponse.nouls / .choices / .scores   # filtered views\nresponse.usage                 # input_tokens, output_tokens, reasoning_tokens, n_calls, n_retries, latency\nresponse.reasoning             # tuple[ReasoningContentPart, ...]\nresponse.debug                 # per-attempt requests/responses, retry reasons, normalization notes\n```\n\n`response.model_dump_json()` serializes to the Jev answer shape — the answer field names and JSON keys match\n`POST /v1/systemone`. Token counts are `None` when any constituent call omitted them; `n_calls` counts every\nprovider call including analysis passes and corrective retries, while `n_retries` counts transient-failure\nretries only. See [docs/api.md](https://github.com/zhulinchng/jevper/blob/main/docs/api.md) for the full reference.\n\nLocal problems fail before any request is sent: an invalid question, an empty `questions` mapping, an\nunusable `state`, or `grammar` on a surface that cannot carry a grammar.\n\n| Error | Raised when | \n|---|---|\n| `InvalidQuestionError` | question or few-shot example is locally invalid | \n| `UnsupportedMethodError` | `method=\"grammar\"` on the Responses surface | \n| `ClientCapabilityError` | the client lacks the attribute the chosen surface needs, or returned no choices | \n| `LabelReadoutError` | the first answer token is not a label, or no logprobs (or no logprob for that token) came back | \n| `MalformedAnswerError` | the JSON answer had an unusable shape after corrective retries | \n| `ProviderError` | a provider call failed; `.attempts` carries the attempt history | \n| `JevperError` | constructor misuse, a bad `state` message, or content that is not JSON-serializable | \n\nTransient failures (HTTP 429/500/502/503/504/529, connection and timeout errors — including the `httpx`\ntransport errors whose class names carry neither word) are retried per call with\n`RetryPolicy(n_retries=2, base_delay=0.5, max_delay=8.0)` and exponential\nbackoff `min(base_delay · 3ⁿ, max_delay)`. Unreadable answers get one corrective retry\n(`n_retry_malformed`) with the failure appended to the conversation. `ProviderError` propagates after all\nquestions have settled, in question insertion order.\n\n```\npytest -q                      # the whole suite runs against a local stub HTTP server; no network, no API keys\nruff check src tests           # clean except three PYI034 hints (see docs/internals.md)\n```\n\nThe suite drives a real `openai` SDK client at a stdlib `ThreadingHTTPServer` stub, so the SDK's own\nserialization path is exercised; see [docs/internals.md](https://github.com/zhulinchng/jevper/blob/main/docs/internals.md#testing).\n\nOptional live check, skipped unless both variables are set:\n\n```\nLLM_MODEL=gpt-5.6-terra OPENAI_API_KEY=... pytest -q tests/test_live.py\n```\n\n- [docs/api.md](https://github.com/zhulinchng/jevper/blob/main/docs/api.md) — constructor and`system_one` parameters, answer/usage/debug shapes, errors\n- [docs/methods.md](https://github.com/zhulinchng/jevper/blob/main/docs/methods.md) — the four methods, request bodies, readout rules, surface selection\n- [docs/reasoning.md](https://github.com/zhulinchng/jevper/blob/main/docs/reasoning.md) — native vs two-step reasoning, traces, encrypted content\n- [docs/few-shot.md](https://github.com/zhulinchng/jevper/blob/main/docs/few-shot.md) — example levels, precedence, rendering, structured examples\n- [docs/internals.md](https://github.com/zhulinchng/jevper/blob/main/docs/internals.md) — module map, call flow, concurrency, retries, testing\n\nApache-2.0 — see [LICENSE](https://github.com/zhulinchng/jevper/blob/main/LICENSE).", "url": "https://wpnews.pro/news/show-hn-jevper-the-jev-interface-on-top-of-any-openai-compatible-model", "canonical_source": "https://github.com/zhulinchng/jevper", "published_at": "2026-09-23 12:25:21+00:00", "updated_at": "2026-09-23 13:00:12.024128+00:00", "lang": "en", "topics": ["ai-tools", "large-language-models", "developer-tools", "ai-products"], "entities": ["jevper", "zhulinchng", "TypeSafe AI", "System One", "llama.cpp", "OpenAI", "pydantic", "typesafe-sdk"], "alternates": {"html": "https://wpnews.pro/news/show-hn-jevper-the-jev-interface-on-top-of-any-openai-compatible-model", "markdown": "https://wpnews.pro/news/show-hn-jevper-the-jev-interface-on-top-of-any-openai-compatible-model.md", "text": "https://wpnews.pro/news/show-hn-jevper-the-jev-interface-on-top-of-any-openai-compatible-model.txt", "jsonld": "https://wpnews.pro/news/show-hn-jevper-the-jev-interface-on-top-of-any-openai-compatible-model.jsonld"}}