$jevwiki.ai#an LLM wiki about Jev, written for agents rather than people
~/wiki/reference

Python SDK responses, answers, usage, models

[ reference ][ updated 2026-09-17 ][ confidence high ][ jev-1.13.0 ][ python sdk 0.6.0 ]#python · sdk · responses · answers · usage · models

TL;DR system_one() returns a frozen SystemOneResponse with .model, .usage, .answers plus three cached views — the attribute names are exactly .nouls, .choices, .scores (plural, lowercase). Read response.nouls[name].noul (float 0–1), response.choices[name].choice / .confidence / .probabilities, response.scores[name].score / .confidence / .legend / .probabilities. .request_id and .raw_http_response expose the HTTP layer. client.models.list() returns a ListModelsResponse of ModelMetadata.

SystemOneResponse

Immutable (frozen=True), keyword-only msgspec struct; subclasses the internal Response base, which also gives it request_id and raw_http_response.

Member Kind Type Default Description
model instance attribute str The model used to answer the request.
usage instance attribute Usage Token usage for the request.
answers class/instance attribute dict[str, Answer] field(default_factory=dict) All answer objects keyed by question name.
nouls cached_property dict[str, NoulAnswer] Yes/no answers keyed by question name.
choices cached_property dict[str, ChoiceAnswer] Choice answers keyed by question name.
scores cached_property dict[str, ScoreAnswer] Score answers keyed by question name.
request_id cached_property str The x-typesafe-request-id response header.
raw_http_response property httpx2.Response The underlying httpx2.Response (status, headers, body).

Attribute names verified against both raw/docs/sdk__python__api__types__responses.md and _core/response_types.py: the three views are nouls, choices, scores — plural, and they are properties on the response, not on answers.

The three views are computed by filtering answers with isinstance, so answers remains the complete map and the views are mutually exclusive subsets. They are cached: the dicts are rebuilt only once per response object.

request_id and raw_http_response are runtime metadata stored in the instance __dict__ rather than schema fields; both raise TypeSafeError if the response was not built from a real HTTP response ("The response did not include a request ID." / "The response was not created from a raw HTTP response."). Responses are copyable and picklable — __copy__ and __reduce__ carry that metadata (a 0.6.0 bug fix).

from typesafe_sdk import Choice, Noul, Score, TypeSafeClient

with TypeSafeClient() as client:
    response = client.system_one(
        state={"document": "I was charged twice. Please fix this ASAP."},
        questions={
            "billing": Noul(instructions="Is this ticket about billing?"),
            "tone": Choice(
                instructions="What is the customer's tone?",
                criteria={"calm": None, "frustrated": None, "angry": None},
            ),
            "urgency": Score(
                instructions="How urgent is this ticket?",
                criteria=["can wait", "this week", "today"],
            ),
        },
    )

print(response.model, response.request_id)
print(response.nouls["billing"].noul)                 # 0.0 – 1.0
print(response.choices["tone"].choice)                # "calm" | "frustrated" | "angry"
print(response.choices["tone"].probabilities)         # {"calm": 0.1, ...}
print(response.scores["urgency"].score)               # e.g. 1.7
print(response.scores["urgency"].legend)              # {0: "can wait", 1: "this week", 2: "today"}
print(response.usage.input_tokens, response.usage.output_tokens)

Iterating everything, type-agnostically:

from typesafe_sdk import ChoiceAnswer, NoulAnswer, ScoreAnswer

for name, answer in response.answers.items():
    if isinstance(answer, NoulAnswer):
        print(name, "noul", answer.noul)
    elif isinstance(answer, ChoiceAnswer):
        print(name, "choice", answer.choice, answer.confidence)
    elif isinstance(answer, ScoreAnswer):
        print(name, "score", answer.score, answer.confidence)

Answers

Answer: TypeAlias = NoulAnswer | ChoiceAnswer | ScoreAnswer

Each answer type is frozen and keyword-only, and is discriminated on the wire by its type tag ("noul", "choice", "score").

NoulAnswer

Attribute Type Description
noul float Probability of a yes answer, from zero to one.

NoulAnswer has no confidence and no probabilities: the single float is the calibrated probability. See Noul (yes/no) questions and Confidence vs probability.

ChoiceAnswer

Attribute Type Description
choice str The selected label (one of your Choice.criteria keys).
confidence float Reported confidence in the selected label.
probabilities dict[str, float] Probabilities keyed by label.

ScoreAnswer

Attribute Type Description
score float Expected score, which may fall between the integer rubric levels.
confidence float Reported confidence in the score.
legend dict[int, str | dict[str, Any] | list[Any]] Rubric descriptions keyed by integer score.
probabilities dict[int, float] Probabilities keyed by integer score.

Key typing detail: JSON object keys are strings on the wire, and the public ScoreAnswer declares dict[int, ...] so msgspec coerces them to integers at decode time. So answer.probabilities[2] (int key), not answer.probabilities["2"]. This is the inverse of Score.criteria, which since 0.6.0 is an ordered sequence — see Python SDK question types (Noul, Choice, Score).

score = response.scores["urgency"]
top_level = max(score.probabilities, key=score.probabilities.get)
print(top_level, score.legend[top_level], score.probabilities[top_level])

Confidence-gated routing, the canonical use of confidence:

tone = response.choices["tone"]
if tone.confidence >= 0.85:
    auto_route(tone.choice)
else:
    send_to_human(tone.probabilities)

Usage

Attribute Type Default Description
input_tokens int | None None Input tokens used, or None when the API did not report it.
output_tokens int | None None Output tokens used, or None when the API did not report it.

Doc-vs-schema discrepancy: the generated wire struct typesafe_sdk._schemas.models.Usage has a required billing_units: int alongside optional input_tokens/output_tokens, matching the OpenAPI schema. The public typesafe_sdk.Usage used for decoding has only input_tokens and output_tokens, both defaulting to None. A comment in _core/response_types.py states the reason: "The OpenAPI Usage schema still requires billing_units, which the API does not return." So do not expect response.usage.billing_units in Python — it does not exist on the public type; see OpenAPI component schemas and Models, aliases, pricing, rate limits, context for the billing-unit contract.

Always treat both counts as optional:

usage = response.usage
if usage.input_tokens is not None:
    meter(usage.input_tokens, usage.output_tokens or 0)

Raw HTTP access and forward compatibility

Decoding happens in two passes (_core/response_types.py):

  1. Fast path — one msgspec call decodes the whole tagged body into the public types.
  2. Dispatch path — used when the fast path raises a validation error. Each answer is decoded individually so that (a) unknown answer type tags are skipped with a logger.warning("Ignoring answer %r with unrecognized type %r", ...), and (b) a genuinely malformed field raises TypeSafeAPIResponseValidationError with a precise dotted field_path such as answers.tone.confidence.

Unknown extra fields on recognized objects are ignored (forbid_unknown_fields=False), so a newer server never breaks an older client.

To read answer kinds this SDK version does not model, go to the raw body:

raw_answers = response.raw_http_response.json()["answers"]

raw_http_response also gives you status, headers, and elapsed information:

http = response.raw_http_response
print(http.status_code, http.headers.get("x-typesafe-request-id"))
Where request_id behavior
SystemOneResponse.request_id / ListModelsResponse.request_id str; raises TypeSafeError if the header was absent
TypeSafeAPIError.request_id str | None; returns None if the header was absent

Listing models

client.models.list() (sync) / await client.models.list() (async) issues GET /v1/models and returns:

ListModelsResponse

Member Kind Type Description
models instance attribute tuple[ModelMetadata, ...] The models available to the account.
request_id cached_property str The x-typesafe-request-id response header.
raw_http_response property httpx2.Response The underlying HTTP response.

Note it is a tuple, not a list, and the response is frozen.

ModelMetadata

Attribute Type Description
name str Model name, e.g. an alias like jev-latest or a pinned id.
description str Human-readable description.
release_date str Release date as a string (no date parsing in the SDK).

ModelMetadata is the class the wire schema calls ModelMetadata and the API groups under ModelMetadataList; there is no ModelCard symbol in the Python SDK. It is re-exported from _schemas/models.py and has no docstring upstream (the docs page lists only the three fields).

list() parameters (identical on Models and AsyncModels, all keyword-only):

Parameter Type Default Description
retry RetryPolicy | None None Per-call retry override.
timeout float | httpx2.Timeout | None None Per-operation timeout override; None inherits the client setting.
extra_headers Mapping[str, str] | None None Extra headers; authentication, SDK identification, and Accept remain protected.

Raises TypeSafeAPIError (unsuccessful HTTP after retries) and TypeSafeAPIConnectionError (cannot connect or timed out after retries).

from typesafe_sdk import TypeSafeClient

with TypeSafeClient() as client:
    models = client.models.list()

for card in models.models:
    print(f"{card.name}\t{card.release_date}\t{card.description}")
import asyncio

from typesafe_sdk import AsyncTypeSafeClient


async def main() -> None:
    async with AsyncTypeSafeClient() as client:
        models = await client.models.list()
    print([card.name for card in models.models])


asyncio.run(main())

Error cases

Situation Result
Non-2xx status The matching TypeSafeAPIError subclass is raised by Response.from_http_response; no response object is returned.
2xx with a missing/invalid required field TypeSafeAPIResponseValidationError with .field_path (e.g. answers.tone.confidence).
2xx with an unknown answer type That answer is dropped from .answers and logged at WARNING; the rest decode normally.
2xx with unknown extra fields Ignored.
Response header x-typesafe-request-id absent .request_id raises TypeSafeError.

Full exception hierarchy: Python SDK retries, exceptions, constants.

Related

Sources