typellm 0.3.0.dev1__tar.gz → 0.3.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. {typellm-0.3.0.dev1/typellm.egg-info → typellm-0.3.1}/PKG-INFO +61 -63
  2. {typellm-0.3.0.dev1 → typellm-0.3.1}/README.md +60 -62
  3. {typellm-0.3.0.dev1 → typellm-0.3.1}/pyproject.toml +1 -1
  4. {typellm-0.3.0.dev1 → typellm-0.3.1}/tests/test_batching.py +24 -25
  5. {typellm-0.3.0.dev1 → typellm-0.3.1}/tests/test_concurrency.py +74 -38
  6. {typellm-0.3.0.dev1 → typellm-0.3.1}/tests/test_dependencies.py +18 -18
  7. {typellm-0.3.0.dev1 → typellm-0.3.1}/tests/test_grammar_fields.py +14 -14
  8. {typellm-0.3.0.dev1 → typellm-0.3.1}/tests/test_images.py +17 -17
  9. {typellm-0.3.0.dev1 → typellm-0.3.1}/tests/test_nullable.py +12 -12
  10. {typellm-0.3.0.dev1 → typellm-0.3.1}/tests/test_permutations.py +13 -13
  11. {typellm-0.3.0.dev1 → typellm-0.3.1}/tests/test_prefill.py +18 -30
  12. {typellm-0.3.0.dev1 → typellm-0.3.1}/tests/test_probabilities.py +3 -3
  13. {typellm-0.3.0.dev1 → typellm-0.3.1}/tests/test_prompts.py +5 -14
  14. {typellm-0.3.0.dev1 → typellm-0.3.1}/tests/test_protocols.py +22 -0
  15. {typellm-0.3.0.dev1 → typellm-0.3.1}/tests/test_text.py +18 -21
  16. {typellm-0.3.0.dev1 → typellm-0.3.1}/tests/test_typellm.py +261 -116
  17. {typellm-0.3.0.dev1 → typellm-0.3.1}/typellm/__init__.py +2 -0
  18. {typellm-0.3.0.dev1 → typellm-0.3.1}/typellm/cli.py +4 -3
  19. {typellm-0.3.0.dev1 → typellm-0.3.1}/typellm/runtime.py +177 -98
  20. {typellm-0.3.0.dev1 → typellm-0.3.1}/typellm/schema.py +10 -23
  21. {typellm-0.3.0.dev1 → typellm-0.3.1}/typellm/sglang.py +95 -86
  22. {typellm-0.3.0.dev1 → typellm-0.3.1/typellm.egg-info}/PKG-INFO +61 -63
  23. {typellm-0.3.0.dev1 → typellm-0.3.1}/LICENSE +0 -0
  24. {typellm-0.3.0.dev1 → typellm-0.3.1}/setup.cfg +0 -0
  25. {typellm-0.3.0.dev1 → typellm-0.3.1}/typellm/__main__.py +0 -0
  26. {typellm-0.3.0.dev1 → typellm-0.3.1}/typellm/benchmark.py +0 -0
  27. {typellm-0.3.0.dev1 → typellm-0.3.1}/typellm/images.py +0 -0
  28. {typellm-0.3.0.dev1 → typellm-0.3.1}/typellm/protocol.py +0 -0
  29. {typellm-0.3.0.dev1 → typellm-0.3.1}/typellm.egg-info/SOURCES.txt +0 -0
  30. {typellm-0.3.0.dev1 → typellm-0.3.1}/typellm.egg-info/dependency_links.txt +0 -0
  31. {typellm-0.3.0.dev1 → typellm-0.3.1}/typellm.egg-info/entry_points.txt +0 -0
  32. {typellm-0.3.0.dev1 → typellm-0.3.1}/typellm.egg-info/requires.txt +0 -0
  33. {typellm-0.3.0.dev1 → typellm-0.3.1}/typellm.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: typellm
3
- Version: 0.3.0.dev1
3
+ Version: 0.3.1
4
4
  Summary: Type-Safe Decoding for Autoregressive LLMs
5
5
  License-Expression: Apache-2.0
6
6
  Project-URL: Homepage, https://typellm.ai/
@@ -23,12 +23,14 @@ Dynamic: license-file
23
23
  <a href="https://typellm.ai/">Homepage</a>&nbsp; • &nbsp;
24
24
  <a href="https://typellm.ai/blog">Blog</a>&nbsp; • &nbsp;
25
25
  <a href="https://typellm.ai/docs">Docs</a>&nbsp; • &nbsp;
26
- <a href="https://typellm.ai/early-access">Early Access</a>&nbsp; • &nbsp;
26
+ <a href="https://typellm.ai/examples">Examples</a>&nbsp; • &nbsp;
27
+ <a href="https://typellm.ai/dashboard">API</a>&nbsp; • &nbsp;
27
28
  <a href="https://typellm.ai/contact">Contact</a>&nbsp;
28
29
  </h4>
29
30
  </div>
30
31
 
31
32
  ## Updates
33
+ - **[2026/09/29]** The [TypeLLM API](https://typellm.ai) is live: typed outputs without serving a model yourself, and $5 of credit for new accounts. [Get early access](https://typellm.ai/early-access) · [Quick start](#typellm-api-cloud) · [Examples](https://typellm.ai/examples).
32
34
  - **[2026/09/24]** Added [image input](#image-input) for vision-language models, tested with Qwen3.8-27B.
33
35
  - **[2026/09/23]** Added [JevBench results](https://github.com/TypeLLM/TypeLLM/blob/main/evals/jevbench/README.md): TypeLLM scored 195/231 without thinking and 228/231 with thinking.
34
36
  - **[2026/09/23]** Added [permutation averaging](#per-question-permutation-averaging) to improve the predictive distribution. See the [blog post](https://typellm.ai/blog/fair-die).
@@ -66,37 +68,33 @@ Evaluated on 231 public [JevBench](https://github.com/fstandhartinger/jevbench)
66
68
 
67
69
  ## Quick start
68
70
 
69
- ### Hosted API (early access)
71
+ ```bash
72
+ pip install -U typellm
73
+ ```
70
74
 
71
- No GPU? [Early-access](https://typellm.ai/early-access) users get an API key
72
- by email, and the hosted API runs the model:
75
+ Call the hosted TypeLLM API, or run TypeLLM against a model you serve yourself.
76
+ Both clients take the same requests.
73
77
 
74
- ```python
75
- from typellm import TypeLLMClient
78
+ ### TypeLLM API (cloud)
79
+
80
+ [Sign in](https://typellm.ai/login), enter your access code on the
81
+ [API keys](https://typellm.ai/dashboard/keys) page and create a key. No code yet?
82
+ [Get early access](https://typellm.ai/early-access).
76
83
 
77
- client = TypeLLMClient(api_key="YOUR_API_KEY") # calls https://api.typellm.ai
84
+ ```bash
85
+ export TYPELLM_API_KEY="tl-sk-..."
78
86
  ```
79
87
 
80
- `generate()` then works as in the example below, with these differences:
88
+ ```python
89
+ import os
90
+ from typellm import TypeLLMClient
81
91
 
82
- - The service compiles and runs the schema. Use `model`, `mode`, `temperature`
83
- (for sampling), `seed` and `timeout`; local compilation settings do not apply.
84
- A call runs for at most 300 seconds, 60 by default. Set `timeout` on the
85
- client for a new default, or on `generate()` to override it for one call.
86
- - Pass `questions`; `schema=`, `cancel` and `print_final_prompt` need your
87
- own server. Images can be files, bytes, PIL images or data: URIs, not URLs.
88
- - Each key runs a limited number of calls at once, told with the key. Calls
89
- beyond it fail right away with HTTP 429.
90
- - Errors raise `SGLangError` with the HTTP status in `.status`, and a call
91
- that runs out of time raises `GenerationTimeout`. The service caps the
92
- context size and the images, answering 400 past them, and stops string
93
- answers at 128 tokens (`maxLength` is refused).
94
- - Successful calls set `last_usage` from the service's input and thinking token
95
- counts. Failed hosted calls may not return usage.
92
+ client = TypeLLMClient(api_key=os.environ["TYPELLM_API_KEY"])
93
+ ```
96
94
 
97
- To run on your own GPU instead, start here:
95
+ See the [API reference](https://typellm.ai/docs/api) to call it over HTTP instead.
98
96
 
99
- ### 1. Serve a model with SGLang
97
+ ### Self-hosted with SGLang
100
98
 
101
99
  Use [SGLang](https://github.com/sgl-project/sglang) to configure and serve a
102
100
  compatible autoregressive model on your local GPU server. This example uses
@@ -106,12 +104,6 @@ to start it with prefix caching enabled.
106
104
 
107
105
  See [Supported models](#supported-models) for tested checkpoints and thinking behavior.
108
106
 
109
- ### 2. Run TypeLLM
110
-
111
- ```bash
112
- pip install -U typellm
113
- ```
114
-
115
107
  Point `TypeLLMClient` at the SGLang server's HTTP endpoint:
116
108
 
117
109
  ```python
@@ -123,10 +115,12 @@ client = TypeLLMClient(
123
115
  )
124
116
  ```
125
117
 
126
- Example request:
118
+ ### Make a request
119
+
120
+ With either client:
127
121
 
128
122
  ```python
129
- result = client.generate(
123
+ response = client.generate(
130
124
  context="""
131
125
  Receipt from Hilton London
132
126
  Total: £324.50
@@ -158,10 +152,12 @@ result = client.generate(
158
152
  },
159
153
  )
160
154
 
161
- print(result)
155
+ print(response.result)
162
156
  ```
163
157
 
164
- Example output:
158
+ `generate()` returns what the HTTP API does: the typed answers in `.result`, the
159
+ reasoning of each field that thought in `.thinking`, and the call's tokens in
160
+ `.usage`. Example `.result`:
165
161
 
166
162
  ```python
167
163
  {
@@ -190,7 +186,7 @@ Enum choices support `string`, `integer`, and `number` types, with at most 24 va
190
186
  A string without `enum` generates free text:
191
187
 
192
188
  ```python
193
- result = client.generate(
189
+ response = client.generate(
194
190
  context="The train ticket is for a client meeting.",
195
191
  questions={
196
192
  "summary": {"type": "string", "instructions": "Summarize in one sentence."},
@@ -199,12 +195,13 @@ result = client.generate(
199
195
  ```
200
196
 
201
197
  Free text stops after 128 tokens. Pass `text_max_tokens=` to the client for
202
- longer answers, or set `maxLength` on a field to cap its characters.
198
+ longer answers, and ask for the length you want in the field's instructions.
199
+ `maxLength` is not supported.
203
200
 
204
201
  Ask for a numeric answer without enumerating every possible value:
205
202
 
206
203
  ```python
207
- result = client.generate(
204
+ response = client.generate(
208
205
  context="Calculate the requested value accurately.",
209
206
  questions={
210
207
  "answer": {
@@ -214,7 +211,7 @@ result = client.generate(
214
211
  },
215
212
  )
216
213
 
217
- print(result)
214
+ print(response.result)
218
215
  # {"answer": 70.0}
219
216
  ```
220
217
 
@@ -240,7 +237,7 @@ Add `"null"` to the type to allow a missing value. The field returns `None`
240
237
  when the input has no value for it:
241
238
 
242
239
  ```python
243
- result = client.generate(
240
+ response = client.generate(
244
241
  context="Read the attached receipt.",
245
242
  images=["receipt.jpg"],
246
243
  questions={
@@ -266,7 +263,7 @@ Thinking is off by default. Turn it on for the fields that need it; the others
266
263
  answer at once, and the fields that think reason side by side:
267
264
 
268
265
  ```python
269
- result = client.generate(context=context, questions={
266
+ response = client.generate(context=context, questions={
270
267
  "total": {"type": "number"},
271
268
  "category": {"type": "string", "enum": ["meal", "travel", "equipment"]},
272
269
  "policy_ok": {"type": "boolean", "instructions": "Does it meet the travel policy?",
@@ -274,13 +271,12 @@ result = client.generate(context=context, questions={
274
271
  })
275
272
  ```
276
273
 
277
- `thinking_budget` caps a field's reasoning; there is no budget by default.
278
- `TypeLLMClient(..., thinking_budget=2048)` sets one for every field that thinks
279
- without its own. When reasoning reaches the budget, TypeLLM closes it and moves
280
- on to the typed answer.
274
+ `thinking_budget` caps a field's reasoning. Without one, a field may reason until
275
+ the model's context is full. When reasoning reaches the budget, TypeLLM closes it
276
+ and moves on to the typed answer.
281
277
 
282
- After a call, `client.last_thinking` maps each field that thought to its
283
- reasoning, and `client.last_usage.thinking_tokens` counts the reasoning tokens.
278
+ `response.thinking` maps each field that thought to its reasoning, and
279
+ `response.usage.thinking_tokens` counts the reasoning tokens.
284
280
 
285
281
  Models with always-on thinking reason on every field; `thinking_budget` applies
286
282
  to them too. See [Supported models](#supported-models).
@@ -291,7 +287,7 @@ Pass images with `images=` alongside the text context. The served model must be
291
287
  a vision-language model, such as `Qwen/Qwen3.8-27B`.
292
288
 
293
289
  ```python
294
- result = client.generate(
290
+ response = client.generate(
295
291
  context="The customer says this receipt was charged twice.",
296
292
  images=["receipt.png"],
297
293
  questions={
@@ -311,7 +307,7 @@ Fields run together by default, and each sees only the original context.
311
307
  When a field needs earlier results, list them in `depends_on`:
312
308
 
313
309
  ```python
314
- result = client.generate(
310
+ response = client.generate(
315
311
  context="The payments service is returning errors after a deployment.",
316
312
  questions={
317
313
  "system": {
@@ -349,7 +345,7 @@ step reuses its parent's cached prompt. Unknown names and cycles raise
349
345
  Set `return_probabilities` on individual enum or boolean fields:
350
346
 
351
347
  ```python
352
- result = client.generate(
348
+ response = client.generate(
353
349
  context=context,
354
350
  questions={
355
351
  "expense_type": {
@@ -377,19 +373,20 @@ result = client.generate(
377
373
  Only opted-in fields return `value` and `probabilities`; other fields return plain values.
378
374
  The option is not supported on open Numeric or Text fields.
379
375
 
380
- Argmax is the default. To enable sampling:
376
+ `temperature` is 0 by default: each field gets its most likely answer. Above 0,
377
+ TypeLLM samples at that temperature:
381
378
 
382
379
  ```python
383
380
  client = TypeLLMClient(
384
381
  "http://127.0.0.1:30000",
385
- mode="sample",
386
382
  temperature=0.8,
387
383
  seed=42,
388
384
  )
389
385
  ```
390
386
 
391
387
  Sampling applies to finite candidates for Choice fields and to token generation
392
- for Numeric and Text fields. `temperature` controls sampling in each case.
388
+ for Numeric and Text fields. `generate(temperature=...)` sets it for one call.
389
+ The older `mode="argmax"` / `mode="sample"` still works for now but is deprecated.
393
390
 
394
391
  A seed fixes TypeLLM's own random choices. SGLang's log probabilities can shift
395
392
  with its prefix cache and batching, so the same seed may still give different
@@ -400,7 +397,7 @@ For a one-off request, use the convenience function:
400
397
  ```python
401
398
  from typellm import run_schema
402
399
 
403
- result = run_schema(
400
+ response = run_schema(
404
401
  context=context,
405
402
  questions=questions,
406
403
  base_url="http://127.0.0.1:30000",
@@ -413,7 +410,7 @@ result = run_schema(
413
410
  Add `permutations` to an `enum` question to reduce option-order bias. TypeLLM averages the probabilities and keeps the same return format.
414
411
 
415
412
  ```python
416
- result = client.generate(
413
+ response = client.generate(
417
414
  context="A single roll of a fair die.",
418
415
  questions={"roll": {
419
416
  "type": "string",
@@ -440,25 +437,25 @@ support this option.
440
437
  ## Serving
441
438
 
442
439
  One `TypeLLMClient` can be shared by many threads. Each call keeps its own
443
- prompts and usage, and connections to SGLang are reused.
440
+ prompts and usage, and connections to SGLang or the hosted API are reused.
444
441
 
445
442
  ```python
446
443
  import threading
447
444
 
448
445
  cancel = threading.Event()
449
- result = client.generate(
446
+ response = client.generate(
450
447
  context=context,
451
448
  questions=questions,
452
449
  seed=7, # this call's random choices only
453
450
  timeout=30, # seconds for the whole call; raises GenerationTimeout
454
451
  cancel=cancel, # set it from another thread; raises GenerationCancelled
455
452
  )
456
- print(client.last_usage)
457
- # Usage(requests=4, prompt_tokens=1830, cached_tokens=1504, completion_tokens=7, thinking_tokens=0, input_tokens=410)
453
+ print(response.usage)
454
+ # Usage(input_tokens=410, thinking_tokens=0, requests=4, prompt_tokens=1830, cached_tokens=1504, completion_tokens=7)
458
455
  ```
459
456
 
460
- `last_usage` reports the tokens of the last call made in the current thread,
461
- including a call that failed partway. `input_tokens` is what you sent, each part
457
+ `usage` reports the tokens of the call. When a call fails partway, the
458
+ exception's `.usage` holds what it spent. `input_tokens` is what you sent, each part
462
459
  counted once: the context, the questions as JSON, and the images. The other
463
460
  counts are SGLang's for the call's requests, where every prompt carries the
464
461
  shared context. A timeout or cancel stops
@@ -497,8 +494,9 @@ Other sizes in the Qwen3.5 and Qwen3.8 families are expected to be compatible.
497
494
 
498
495
  [Image input](#image-input) has been tested with `Qwen/Qwen3.8-27B`.
499
496
 
500
- Use the checkpoint ID as `model=`. If the server's tokenizer path is unavailable
501
- locally, set `tokenizer=` to its matching Hugging Face ID or local directory.
497
+ Use the checkpoint ID as `model=`. TypeLLM loads the tokenizer from the server's
498
+ paths, then its served model name; if none of those loads locally, set
499
+ `tokenizer=` to the matching Hugging Face ID or local directory.
502
500
  The tokenizer must load from standard artifacts without custom model code.
503
501
 
504
502
  ## Comparison with Jev-style models
@@ -8,12 +8,14 @@
8
8
  <a href="https://typellm.ai/">Homepage</a>&nbsp; • &nbsp;
9
9
  <a href="https://typellm.ai/blog">Blog</a>&nbsp; • &nbsp;
10
10
  <a href="https://typellm.ai/docs">Docs</a>&nbsp; • &nbsp;
11
- <a href="https://typellm.ai/early-access">Early Access</a>&nbsp; • &nbsp;
11
+ <a href="https://typellm.ai/examples">Examples</a>&nbsp; • &nbsp;
12
+ <a href="https://typellm.ai/dashboard">API</a>&nbsp; • &nbsp;
12
13
  <a href="https://typellm.ai/contact">Contact</a>&nbsp;
13
14
  </h4>
14
15
  </div>
15
16
 
16
17
  ## Updates
18
+ - **[2026/09/29]** The [TypeLLM API](https://typellm.ai) is live: typed outputs without serving a model yourself, and $5 of credit for new accounts. [Get early access](https://typellm.ai/early-access) · [Quick start](#typellm-api-cloud) · [Examples](https://typellm.ai/examples).
17
19
  - **[2026/09/24]** Added [image input](#image-input) for vision-language models, tested with Qwen3.8-27B.
18
20
  - **[2026/09/23]** Added [JevBench results](https://github.com/TypeLLM/TypeLLM/blob/main/evals/jevbench/README.md): TypeLLM scored 195/231 without thinking and 228/231 with thinking.
19
21
  - **[2026/09/23]** Added [permutation averaging](#per-question-permutation-averaging) to improve the predictive distribution. See the [blog post](https://typellm.ai/blog/fair-die).
@@ -51,37 +53,33 @@ Evaluated on 231 public [JevBench](https://github.com/fstandhartinger/jevbench)
51
53
 
52
54
  ## Quick start
53
55
 
54
- ### Hosted API (early access)
56
+ ```bash
57
+ pip install -U typellm
58
+ ```
55
59
 
56
- No GPU? [Early-access](https://typellm.ai/early-access) users get an API key
57
- by email, and the hosted API runs the model:
60
+ Call the hosted TypeLLM API, or run TypeLLM against a model you serve yourself.
61
+ Both clients take the same requests.
58
62
 
59
- ```python
60
- from typellm import TypeLLMClient
63
+ ### TypeLLM API (cloud)
64
+
65
+ [Sign in](https://typellm.ai/login), enter your access code on the
66
+ [API keys](https://typellm.ai/dashboard/keys) page and create a key. No code yet?
67
+ [Get early access](https://typellm.ai/early-access).
61
68
 
62
- client = TypeLLMClient(api_key="YOUR_API_KEY") # calls https://api.typellm.ai
69
+ ```bash
70
+ export TYPELLM_API_KEY="tl-sk-..."
63
71
  ```
64
72
 
65
- `generate()` then works as in the example below, with these differences:
73
+ ```python
74
+ import os
75
+ from typellm import TypeLLMClient
66
76
 
67
- - The service compiles and runs the schema. Use `model`, `mode`, `temperature`
68
- (for sampling), `seed` and `timeout`; local compilation settings do not apply.
69
- A call runs for at most 300 seconds, 60 by default. Set `timeout` on the
70
- client for a new default, or on `generate()` to override it for one call.
71
- - Pass `questions`; `schema=`, `cancel` and `print_final_prompt` need your
72
- own server. Images can be files, bytes, PIL images or data: URIs, not URLs.
73
- - Each key runs a limited number of calls at once, told with the key. Calls
74
- beyond it fail right away with HTTP 429.
75
- - Errors raise `SGLangError` with the HTTP status in `.status`, and a call
76
- that runs out of time raises `GenerationTimeout`. The service caps the
77
- context size and the images, answering 400 past them, and stops string
78
- answers at 128 tokens (`maxLength` is refused).
79
- - Successful calls set `last_usage` from the service's input and thinking token
80
- counts. Failed hosted calls may not return usage.
77
+ client = TypeLLMClient(api_key=os.environ["TYPELLM_API_KEY"])
78
+ ```
81
79
 
82
- To run on your own GPU instead, start here:
80
+ See the [API reference](https://typellm.ai/docs/api) to call it over HTTP instead.
83
81
 
84
- ### 1. Serve a model with SGLang
82
+ ### Self-hosted with SGLang
85
83
 
86
84
  Use [SGLang](https://github.com/sgl-project/sglang) to configure and serve a
87
85
  compatible autoregressive model on your local GPU server. This example uses
@@ -91,12 +89,6 @@ to start it with prefix caching enabled.
91
89
 
92
90
  See [Supported models](#supported-models) for tested checkpoints and thinking behavior.
93
91
 
94
- ### 2. Run TypeLLM
95
-
96
- ```bash
97
- pip install -U typellm
98
- ```
99
-
100
92
  Point `TypeLLMClient` at the SGLang server's HTTP endpoint:
101
93
 
102
94
  ```python
@@ -108,10 +100,12 @@ client = TypeLLMClient(
108
100
  )
109
101
  ```
110
102
 
111
- Example request:
103
+ ### Make a request
104
+
105
+ With either client:
112
106
 
113
107
  ```python
114
- result = client.generate(
108
+ response = client.generate(
115
109
  context="""
116
110
  Receipt from Hilton London
117
111
  Total: £324.50
@@ -143,10 +137,12 @@ result = client.generate(
143
137
  },
144
138
  )
145
139
 
146
- print(result)
140
+ print(response.result)
147
141
  ```
148
142
 
149
- Example output:
143
+ `generate()` returns what the HTTP API does: the typed answers in `.result`, the
144
+ reasoning of each field that thought in `.thinking`, and the call's tokens in
145
+ `.usage`. Example `.result`:
150
146
 
151
147
  ```python
152
148
  {
@@ -175,7 +171,7 @@ Enum choices support `string`, `integer`, and `number` types, with at most 24 va
175
171
  A string without `enum` generates free text:
176
172
 
177
173
  ```python
178
- result = client.generate(
174
+ response = client.generate(
179
175
  context="The train ticket is for a client meeting.",
180
176
  questions={
181
177
  "summary": {"type": "string", "instructions": "Summarize in one sentence."},
@@ -184,12 +180,13 @@ result = client.generate(
184
180
  ```
185
181
 
186
182
  Free text stops after 128 tokens. Pass `text_max_tokens=` to the client for
187
- longer answers, or set `maxLength` on a field to cap its characters.
183
+ longer answers, and ask for the length you want in the field's instructions.
184
+ `maxLength` is not supported.
188
185
 
189
186
  Ask for a numeric answer without enumerating every possible value:
190
187
 
191
188
  ```python
192
- result = client.generate(
189
+ response = client.generate(
193
190
  context="Calculate the requested value accurately.",
194
191
  questions={
195
192
  "answer": {
@@ -199,7 +196,7 @@ result = client.generate(
199
196
  },
200
197
  )
201
198
 
202
- print(result)
199
+ print(response.result)
203
200
  # {"answer": 70.0}
204
201
  ```
205
202
 
@@ -225,7 +222,7 @@ Add `"null"` to the type to allow a missing value. The field returns `None`
225
222
  when the input has no value for it:
226
223
 
227
224
  ```python
228
- result = client.generate(
225
+ response = client.generate(
229
226
  context="Read the attached receipt.",
230
227
  images=["receipt.jpg"],
231
228
  questions={
@@ -251,7 +248,7 @@ Thinking is off by default. Turn it on for the fields that need it; the others
251
248
  answer at once, and the fields that think reason side by side:
252
249
 
253
250
  ```python
254
- result = client.generate(context=context, questions={
251
+ response = client.generate(context=context, questions={
255
252
  "total": {"type": "number"},
256
253
  "category": {"type": "string", "enum": ["meal", "travel", "equipment"]},
257
254
  "policy_ok": {"type": "boolean", "instructions": "Does it meet the travel policy?",
@@ -259,13 +256,12 @@ result = client.generate(context=context, questions={
259
256
  })
260
257
  ```
261
258
 
262
- `thinking_budget` caps a field's reasoning; there is no budget by default.
263
- `TypeLLMClient(..., thinking_budget=2048)` sets one for every field that thinks
264
- without its own. When reasoning reaches the budget, TypeLLM closes it and moves
265
- on to the typed answer.
259
+ `thinking_budget` caps a field's reasoning. Without one, a field may reason until
260
+ the model's context is full. When reasoning reaches the budget, TypeLLM closes it
261
+ and moves on to the typed answer.
266
262
 
267
- After a call, `client.last_thinking` maps each field that thought to its
268
- reasoning, and `client.last_usage.thinking_tokens` counts the reasoning tokens.
263
+ `response.thinking` maps each field that thought to its reasoning, and
264
+ `response.usage.thinking_tokens` counts the reasoning tokens.
269
265
 
270
266
  Models with always-on thinking reason on every field; `thinking_budget` applies
271
267
  to them too. See [Supported models](#supported-models).
@@ -276,7 +272,7 @@ Pass images with `images=` alongside the text context. The served model must be
276
272
  a vision-language model, such as `Qwen/Qwen3.8-27B`.
277
273
 
278
274
  ```python
279
- result = client.generate(
275
+ response = client.generate(
280
276
  context="The customer says this receipt was charged twice.",
281
277
  images=["receipt.png"],
282
278
  questions={
@@ -296,7 +292,7 @@ Fields run together by default, and each sees only the original context.
296
292
  When a field needs earlier results, list them in `depends_on`:
297
293
 
298
294
  ```python
299
- result = client.generate(
295
+ response = client.generate(
300
296
  context="The payments service is returning errors after a deployment.",
301
297
  questions={
302
298
  "system": {
@@ -334,7 +330,7 @@ step reuses its parent's cached prompt. Unknown names and cycles raise
334
330
  Set `return_probabilities` on individual enum or boolean fields:
335
331
 
336
332
  ```python
337
- result = client.generate(
333
+ response = client.generate(
338
334
  context=context,
339
335
  questions={
340
336
  "expense_type": {
@@ -362,19 +358,20 @@ result = client.generate(
362
358
  Only opted-in fields return `value` and `probabilities`; other fields return plain values.
363
359
  The option is not supported on open Numeric or Text fields.
364
360
 
365
- Argmax is the default. To enable sampling:
361
+ `temperature` is 0 by default: each field gets its most likely answer. Above 0,
362
+ TypeLLM samples at that temperature:
366
363
 
367
364
  ```python
368
365
  client = TypeLLMClient(
369
366
  "http://127.0.0.1:30000",
370
- mode="sample",
371
367
  temperature=0.8,
372
368
  seed=42,
373
369
  )
374
370
  ```
375
371
 
376
372
  Sampling applies to finite candidates for Choice fields and to token generation
377
- for Numeric and Text fields. `temperature` controls sampling in each case.
373
+ for Numeric and Text fields. `generate(temperature=...)` sets it for one call.
374
+ The older `mode="argmax"` / `mode="sample"` still works for now but is deprecated.
378
375
 
379
376
  A seed fixes TypeLLM's own random choices. SGLang's log probabilities can shift
380
377
  with its prefix cache and batching, so the same seed may still give different
@@ -385,7 +382,7 @@ For a one-off request, use the convenience function:
385
382
  ```python
386
383
  from typellm import run_schema
387
384
 
388
- result = run_schema(
385
+ response = run_schema(
389
386
  context=context,
390
387
  questions=questions,
391
388
  base_url="http://127.0.0.1:30000",
@@ -398,7 +395,7 @@ result = run_schema(
398
395
  Add `permutations` to an `enum` question to reduce option-order bias. TypeLLM averages the probabilities and keeps the same return format.
399
396
 
400
397
  ```python
401
- result = client.generate(
398
+ response = client.generate(
402
399
  context="A single roll of a fair die.",
403
400
  questions={"roll": {
404
401
  "type": "string",
@@ -425,25 +422,25 @@ support this option.
425
422
  ## Serving
426
423
 
427
424
  One `TypeLLMClient` can be shared by many threads. Each call keeps its own
428
- prompts and usage, and connections to SGLang are reused.
425
+ prompts and usage, and connections to SGLang or the hosted API are reused.
429
426
 
430
427
  ```python
431
428
  import threading
432
429
 
433
430
  cancel = threading.Event()
434
- result = client.generate(
431
+ response = client.generate(
435
432
  context=context,
436
433
  questions=questions,
437
434
  seed=7, # this call's random choices only
438
435
  timeout=30, # seconds for the whole call; raises GenerationTimeout
439
436
  cancel=cancel, # set it from another thread; raises GenerationCancelled
440
437
  )
441
- print(client.last_usage)
442
- # Usage(requests=4, prompt_tokens=1830, cached_tokens=1504, completion_tokens=7, thinking_tokens=0, input_tokens=410)
438
+ print(response.usage)
439
+ # Usage(input_tokens=410, thinking_tokens=0, requests=4, prompt_tokens=1830, cached_tokens=1504, completion_tokens=7)
443
440
  ```
444
441
 
445
- `last_usage` reports the tokens of the last call made in the current thread,
446
- including a call that failed partway. `input_tokens` is what you sent, each part
442
+ `usage` reports the tokens of the call. When a call fails partway, the
443
+ exception's `.usage` holds what it spent. `input_tokens` is what you sent, each part
447
444
  counted once: the context, the questions as JSON, and the images. The other
448
445
  counts are SGLang's for the call's requests, where every prompt carries the
449
446
  shared context. A timeout or cancel stops
@@ -482,8 +479,9 @@ Other sizes in the Qwen3.5 and Qwen3.8 families are expected to be compatible.
482
479
 
483
480
  [Image input](#image-input) has been tested with `Qwen/Qwen3.8-27B`.
484
481
 
485
- Use the checkpoint ID as `model=`. If the server's tokenizer path is unavailable
486
- locally, set `tokenizer=` to its matching Hugging Face ID or local directory.
482
+ Use the checkpoint ID as `model=`. TypeLLM loads the tokenizer from the server's
483
+ paths, then its served model name; if none of those loads locally, set
484
+ `tokenizer=` to the matching Hugging Face ID or local directory.
487
485
  The tokenizer must load from standard artifacts without custom model code.
488
486
 
489
487
  ## Comparison with Jev-style models
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "typellm"
7
- version = "0.3.0.dev1"
7
+ version = "0.3.1"
8
8
  description = "Type-Safe Decoding for Autoregressive LLMs"
9
9
  readme = "README.md"
10
10
  license = "Apache-2.0"