Show HN: Jev to JSON-Schema 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. Use a JSON Schema with Jev https://docs.typesafe.ai/introduction . Get JSON back. Quick Start quick-start • What Maps to What what-maps-to-what • What You Get Back what-you-get-back • Without the Client without-the-client • Limits Jev 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. This library sits in between. Give it your schema and your content, and you get back JSON that validates against the schema you started with. JSON Schema ──▶ Jev questions ──▶ Jev ──▶ answers ──▶ JSON Schema output pip install jev jsonschema or: uv add jev jsonschema python from jev jsonschema import JevClient schema = { "type": "object", "properties": { "sentiment": {"type": "string", "enum": "positive", "neutral", "negative" }, "is spam": {"type": "boolean", "description": "The message is spam."}, "quality": { "type": "integer", "minimum": 1, "maximum": 5, "description": "Overall writing quality.", }, }, } with JevClient as jev: reads TYPESAFE API KEY, or pass api key="..." result = jev.evaluate schema, state="Loved it. Shipped in a day." result.output {"sentiment": "positive", "is spam": False, "quality": 5} result.output validates against schema . Hand it to the same code that used to parse your model's JSON. There's an AsyncJevClient with identical methods: python from jev jsonschema import AsyncJevClient async with AsyncJevClient as jev: result = await jev.evaluate schema, state="Loved it. Shipped in a day." Converting a schema is pure work, so if you're calling the same schema in a loop, convert once and reuse it: question set = jev.convert schema for review in reviews: result = jev.ask question set, state=review Jev has three question types https://docs.typesafe.ai/introduction typesafe-primitives . Here's the JSON Schema that reaches each one: | JSON Schema Type | JSON Schema Example | Jev Question Type | Details | |---|---|---|---| | Boolean | {"type": "boolean", "description": "..."} | noul | Thresholded at 0.5 . | | Number, 0 to 1 | {"type": "number", "minimum": 0, "maximum": 1, "description": "..."} | noul | Returns the raw probability. Must have exactly "minimum": 0, "maximum": 1 . | | String enum | {"type": "string", "enum": "low", "high" } | choice | Up to 255 options. | | Integer enum | {"type": "integer", "enum": 1, 2, 3 } | choice | Up to 255 options. | | 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. | You get back the type in the first column. One question per schema property, in the order the schema declares them. Jev 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. Jev answers with distributions, not just values, and none of that is thrown away: result.output {"sentiment": "positive", "is spam": False, "quality": 5} result.confidence {"sentiment": 0.97, "is spam": None, "quality": 0.81} result.probabilities {"sentiment": {"positive": 0.97, "neutral": 0.02, "negative": 0.01}, "is spam": {"true": 0.03, "false": 0.97}, "quality": {"1": 0.0, "2": 0.0, "3": 0.02, "4": 0.1, "5": 0.88}} result.usage SystemOneUsage input tokens=120, output tokens=12 result.response the raw SystemOneResponse, if you want it 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. python from jev jsonschema import IncompatibleSchemaError, JevApiError try: result = jev.evaluate schema, state=review except IncompatibleSchemaError as e: ... your schema has properties Jev can't answer. See below. except JevApiError as e: ... e.status code, e.retryable, e.request id 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. Jev 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: - Free-form string anything without an enum , array , object , null - anyOf , oneOf , allOf , $ref , const , not , and multi-type "type": ... - number with any bounds other than minimum: 0 / maximum: 1 - integer ranges wider than 10 values, and enums with more than 255 values - integer without both minimum and maximum You find out before anything is sent, and you find out about every bad property, not just the first: try: jev.evaluate schema, state=review except IncompatibleSchemaError as e: for failure in e.failures: print failure.key, failure.reason summary uses 'anyOf', which is not supported tags type 'array' is not supported Error messages here are also written to be shown to your users as-is. The 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: python from jev jsonschema import JSONSchema2Jev, JevResult2JsonSchema question set = JSONSchema2Jev .convert schema body = question set.request state="Loved it. Shipped in a day.", model="jev-latest" .to body answers = your http post "https://api.typesafe.ai/v1/systemone", json=body "answers" result = JevResult2JsonSchema .convert question set, answers result.output The 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. python from jev jsonschema import JevClient, MappingOptions, ScoreDecode options = MappingOptions noul threshold=0.5, where a noul probability becomes True max score levels=10, lower the cap on integer ranges score decode=ScoreDecode.argmax, or ScoreDecode.expected, for the rounded mean instructions fallback to key=True, use the property name when there's no description jev = JevClient options=options Using the converters directly? Pass the same options to both halves. The decoder needs to know how the questions were built. - 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. - Small dependency footprint : httpx and pydantic , both of which most apps already have. - No hidden retries, no hidden concurrency. One call is one POST. - Never logs your data. Failures log the status and TypeSafe's request id, never the body, which would echo your state and questions. - Fully typed , ships a py.typed marker. uv sync install everything uv run pytest tests uv run ruff check --fix && uv run ruff format lint + format uv run ty check typecheck uv build build the wheel and sdist No 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. MIT. See LICENSE https://github.com/Kiln-AI/jev jsonschema/blob/main/LICENSE .