{"slug": "show-hn-jev-to-json-schema", "title": "Show HN: Jev to JSON-Schema", "summary": "TypeSafe released jev_jsonschema, a Python library that converts JSON Schema into questions for its Jev structured-output model and returns JSON validating against the original schema. The library maps boolean schemas to Jev's \"noul\" question type thresholded at 0.5, string and integer enums to \"choice\" questions with up to 255 options, and integer ranges to \"score\" questions spanning at most 10 values, and it exposes confidence scores, per-option probabilities, and token usage alongside the output. Installable via pip install jev_jsonschema or uv add jev_jsonschema, the client reads a TYPESAFE_API_KEY and offers an AsyncJevClient with identical methods.", "body_md": "**Use a JSON Schema with [Jev](https://docs.typesafe.ai/introduction). Get JSON back.**\n\n[**Quick Start**](#quick-start) •\n[**What Maps to What**](#what-maps-to-what) •\n[**What You Get Back**](#what-you-get-back) •\n[**Without the Client**](#without-the-client) •\n**Limits**\n\nJev is a new kind of model from TypeSafe: it only returns structured output, it's blazing fast, and it's cheap. That's great, but it means Jev doesn't speak JSON Schema, and most LLM apps use JSON Schema for structured output.\n\nThis library sits in between. Give it your schema and your content, and you get back JSON that validates against the schema you started with.\n\n```\nJSON Schema  ──▶  Jev questions  ──▶  [ Jev ]  ──▶  answers  ──▶  JSON Schema output\npip install jev_jsonschema   # or: uv add jev_jsonschema\npython\nfrom jev_jsonschema import JevClient\n\nschema = {\n    \"type\": \"object\",\n    \"properties\": {\n        \"sentiment\": {\"type\": \"string\", \"enum\": [\"positive\", \"neutral\", \"negative\"]},\n        \"is_spam\": {\"type\": \"boolean\", \"description\": \"The message is spam.\"},\n        \"quality\": {\n            \"type\": \"integer\",\n            \"minimum\": 1,\n            \"maximum\": 5,\n            \"description\": \"Overall writing quality.\",\n        },\n    },\n}\n\nwith JevClient() as jev:  # reads TYPESAFE_API_KEY, or pass api_key=\"...\"\n    result = jev.evaluate(schema, state=\"Loved it. Shipped in a day.\")\n\nresult.output\n# {\"sentiment\": \"positive\", \"is_spam\": False, \"quality\": 5}\n```\n\n`result.output` validates against `schema`. Hand it to the same code that used to parse your model's JSON.\n\nThere's an `AsyncJevClient` with identical methods:\n\n``` python\nfrom jev_jsonschema import AsyncJevClient\n\nasync with AsyncJevClient() as jev:\n    result = await jev.evaluate(schema, state=\"Loved it. Shipped in a day.\")\n```\n\nConverting a schema is pure work, so if you're calling the same schema in a loop, convert once and reuse it:\n\n```\nquestion_set = jev.convert(schema)\n\nfor review in reviews:\n    result = jev.ask(question_set, state=review)\n```\n\nJev has [three question types](https://docs.typesafe.ai/introduction#typesafe-primitives). Here's the JSON Schema that reaches each one:\n\n| JSON Schema Type | JSON Schema Example | Jev Question Type | Details | \n|---|---|---|---|\n| Boolean | `{\"type\": \"boolean\", \"description\": \"...\"}` | noul | Thresholded at `0.5` . | \n| Number, 0 to 1 | `{\"type\": \"number\", \"minimum\": 0, \"maximum\": 1, \"description\": \"...\"}` | noul | Returns the raw probability. Must have exactly `\"minimum\": 0, \"maximum\": 1` . | \n| String enum | `{\"type\": \"string\", \"enum\": [\"low\", \"high\"]}` | choice | Up to 255 options. | \n| Integer enum | `{\"type\": \"integer\", \"enum\": [1, 2, 3]}` | choice | Up to 255 options. | \n| Integer range | `{\"type\": \"integer\", \"minimum\": 1, \"maximum\": 5}` | score | Needs both bounds. Jev has 2 to 10 levels, so the range can span at most 10 values. | \n\nYou get back the type in the first column. One question per schema property, in the order the schema declares them.\n\nJev needs to know what it's judging, so each question gets instructions from the property's `description`, falling back to its `title`, then to the property name. A real `description` is the biggest lever you have on answer quality: `is_spam` alone is a thin thing to ask about. Set `instructions_fallback_to_key=False` if you'd rather the library reject a `boolean` or `number` that has neither.\n\nJev answers with distributions, not just values, and none of that is thrown away:\n\n```\nresult.output\n# {\"sentiment\": \"positive\", \"is_spam\": False, \"quality\": 5}\n\nresult.confidence\n# {\"sentiment\": 0.97, \"is_spam\": None, \"quality\": 0.81}\n\nresult.probabilities\n# {\"sentiment\": {\"positive\": 0.97, \"neutral\": 0.02, \"negative\": 0.01},\n#  \"is_spam\": {\"true\": 0.03, \"false\": 0.97},\n#  \"quality\": {\"1\": 0.0, \"2\": 0.0, \"3\": 0.02, \"4\": 0.1, \"5\": 0.88}}\n\nresult.usage\n# SystemOneUsage(input_tokens=120, output_tokens=12)\n\nresult.response\n# the raw SystemOneResponse, if you want it\n```\n\n`probabilities` is keyed by your schema's values, not Jev's internal labels, so a score of `1`–` 5` reads as `\"1\"`–`\"5\"` and not `\"0\"`–`\"4\"`. Noul questions carry no confidence of their own, so `confidence` is `None` for booleans and numbers.\n\n``` python\nfrom jev_jsonschema import IncompatibleSchemaError, JevApiError\n\ntry:\n    result = jev.evaluate(schema, state=review)\nexcept IncompatibleSchemaError as e:\n    ...  # your schema has properties Jev can't answer. See below.\nexcept JevApiError as e:\n    ...  # e.status_code, e.retryable, e.request_id\n```\n\n`JevApiError` messages are written to be shown to your users as-is, and `retryable` tells you whether trying again could help (timeouts, 429s, 5xxs). The client does one POST and never retries on its own, so the backoff policy stays yours.\n\nJev answers questions from a fixed set of options. Plenty of JSON Schema doesn't fit, and this library refuses it loudly rather than inventing a mapping:\n\n- Free-form `string` (anything without an`enum` ),`array` ,`object` ,`null`\n- `anyOf` ,`oneOf` ,`allOf` ,`$ref` ,`const` ,`not` , and multi-type`\"type\": [...]`\n- `number` with any bounds other than`minimum: 0` /`maximum: 1`\n- `integer` ranges wider than 10 values, and enums with more than 255 values\n- `integer` without both`minimum` and`maximum`\n\nYou find out before anything is sent, and you find out about **every** bad property, not just the first:\n\n```\ntry:\n    jev.evaluate(schema, state=review)\nexcept IncompatibleSchemaError as e:\n    for failure in e.failures:\n        print(failure.key, failure.reason)\n# summary uses 'anyOf', which is not supported\n# tags type 'array' is not supported\n```\n\nError messages here are also written to be shown to your users as-is.\n\nThe conversion is a separate, pure layer. If you'd rather make the HTTP call yourself (your own retries, your own auth, TypeSafe's official SDK), use the two converters directly and skip `JevClient` entirely:\n\n``` python\nfrom jev_jsonschema import JSONSchema2Jev, JevResult2JsonSchema\n\nquestion_set = JSONSchema2Jev().convert(schema)\n\nbody = question_set.request(\n    state=\"Loved it. Shipped in a day.\", model=\"jev-latest\"\n).to_body()\nanswers = your_http_post(\"https://api.typesafe.ai/v1/systemone\", json=body)[\"answers\"]\n\nresult = JevResult2JsonSchema().convert(question_set, answers)\nresult.output\n```\n\nThe `QuestionSet` is the thing to hold onto between the two halves: it remembers how each property was mapped, which is why decoding needs it. It's a plain frozen dataclass.\n\n``` python\nfrom jev_jsonschema import JevClient, MappingOptions, ScoreDecode\n\noptions = MappingOptions(\n    noul_threshold=0.5,  # where a noul probability becomes True\n    max_score_levels=10,  # lower the cap on integer ranges\n    score_decode=ScoreDecode.argmax,  # or ScoreDecode.expected, for the rounded mean\n    instructions_fallback_to_key=True,  # use the property name when there's no description\n)\n\njev = JevClient(options=options)\n```\n\nUsing the converters directly? Pass the same options to both halves. The decoder needs to know how the questions were built.\n\n- **Two layers, and you can take just one.**`JSONSchema2Jev` and`JevResult2JsonSchema` are pure and know nothing about HTTP.`JevClient` is a thin wrapper that adds the POST.\n- **Small dependency footprint** :`httpx` and`pydantic` , both of which most apps already have.\n- **No hidden retries, no hidden concurrency.** One call is one POST.\n- **Never logs your data.** Failures log the status and TypeSafe's request id, never the body, which would echo your state and questions.\n- **Fully typed** , ships a`py.typed` marker.\n\n```\nuv sync           # install everything\nuv run pytest     # tests\nuv run ruff check --fix && uv run ruff format   # lint + format\nuv run ty check   # typecheck\nuv build          # build the wheel and sdist\n```\n\nNo test hits the network. The client's tests run against [respx](https://lundberg.github.io/respx/). CI runs all of the above on Python 3.10 through 3.14.\n\nMIT. See [LICENSE](https://github.com/Kiln-AI/jev_jsonschema/blob/main/LICENSE).", "url": "https://wpnews.pro/news/show-hn-jev-to-json-schema", "canonical_source": "https://github.com/Kiln-AI/jev_jsonschema", "published_at": "2026-09-19 19:03:33+00:00", "updated_at": "2026-09-19 19:24:17.186106+00:00", "lang": "en", "topics": ["ai-tools", "structured-data", "developer-tools", "large-language-models", "ai-products"], "entities": ["TypeSafe", "Jev", "jev_jsonschema", "JevClient", "AsyncJevClient", "JSON Schema", "SystemOneResponse", "SystemOneUsage"], "alternates": {"html": "https://wpnews.pro/news/show-hn-jev-to-json-schema", "markdown": "https://wpnews.pro/news/show-hn-jev-to-json-schema.md", "text": "https://wpnews.pro/news/show-hn-jev-to-json-schema.txt", "jsonld": "https://wpnews.pro/news/show-hn-jev-to-json-schema.jsonld"}}