Python SDK
Python 3.10 or later. The only runtime dependency is httpx, plus typing-extensions on Python 3.10. The package ships py.typed.
What Python can and cannot infer
Python has no mapped types and no conditional return types. It cannot read your label names out of a dict display. It can solve a TypeVar from an annotated constant. One annotation buys the whole chain.
Without the annotation you get ClassifyResult[str]. Nothing breaks. You lose only the names. Keep every label set in one constants file, and the annotation lands where the constants already live.
classify_tree returns label: str. Python has no expression that reads literals out of a nested dict.
Client options
timeout, max_retries and headers also work per call: dm.classify(text, LABELS, max_retries=5, timeout=10.0).
The client is a context manager. close() closes only a pool the SDK opened itself.
The eight capabilities
Describe every label. The label text is the instruction, and the model reads it literally. Described labels score measurably better than bare names.
Annotate a tree you keep in a constant, as you annotate a label set. A bare TAXONOMY = {...} infers a wider type, and classify_tree then refuses it.
Batching
Pass a list of texts for a batch. The reply follows your request, never the other way.
Results is a list with a usage attribute. The limits are 32 texts per call, and 20,000 characters per text. The SDK never splits a batch for you. Splitting costs money and changes failure modes, so you decide. See Batching.
Extraction
Four schema shapes work: a plain dict JSON Schema, a TypedDict, a dataclass, and a pydantic v2 model. The SDK never imports pydantic. It calls model_json_schema() by duck typing.
Declare every field | None. A missing value comes back as None.
A dataclass and a pydantic v2 model work the same way, and return your own type:
A plain dict carries descriptions, which raise accuracy:
Four degradations are real, and no Python annotation can hide them:
- a missing value is
None; - an array of objects always comes back
[]; - an array of scalars comes back as a list of strings;
- an enum is not checked on the server, so a value outside your
Literalcan arrive.
See Extract.
Usage and rate limits
Request-limit values count inference request units, not HTTP calls. A batch with 24 texts consumes 24 units; text and statement or question counts multiply.
Every result carries the usage of the call that produced it. A single-text extract and post() are the two exceptions. Both return your own object, which has no place for the usage. Send a one-text batch to reach it: dm.extract([text], Invoice).usage.
The API reserves units before processing the call. Rate-limit headers show a snapshot after admission; concurrent calls can change the remaining balance.
Honor retry-after on rate-limit refusals. See Limits and rate limits.
Errors and retries
Every exception subclasses MillisecondsError, so one except catches the lot.
The SDK retries 429 rate_limit_exceeded, 502 runner_error, 529 overloaded, and transport failures. Every capability is a pure function, so a retry is always safe. It never retries 400, 401, 403 or 429 insufficient_quota. A timer retry cannot fix a spent quota.
Pass max_retries=0 to turn retries off.
Some checks run before any HTTP call. They raise InvalidRequestError with code client_error and status 0. Nothing was sent, so no token was billed.
Errors lists every code.
Async, and your own pool
The async client has the same methods and the same options. Both clients share one transport module, so the retries, the error parsing and the header parsing cannot drift. Pass http_client= to bring your own httpx.Client or httpx.AsyncClient. The SDK closes only a pool it opened itself.
Any path, any body
Gotchas
yes-noandanswerare the two endpoints with notextrefinement on the server. A body with neithertextnortextsreturns200and{"results": []}. The SDK always sends one of the two, so that body cannot reach the API.- A
labelsortypesdict has no size limit on the server. The 2-to-64 rule binds the list form only, and the SDK checks the same way. classify_treere-sends the text at every level. The per-levelinput_charstherefore do not sum tousage.input_chars, which counts one pass over the body.- The SDK reports
x-input-tokens. It never estimates a cost.