typellm 0.3.0.dev2__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.dev2/typellm.egg-info → typellm-0.3.1}/PKG-INFO +57 -60
  2. {typellm-0.3.0.dev2 → typellm-0.3.1}/README.md +56 -59
  3. {typellm-0.3.0.dev2 → typellm-0.3.1}/pyproject.toml +1 -1
  4. {typellm-0.3.0.dev2 → typellm-0.3.1}/tests/test_batching.py +24 -25
  5. {typellm-0.3.0.dev2 → typellm-0.3.1}/tests/test_concurrency.py +42 -38
  6. {typellm-0.3.0.dev2 → typellm-0.3.1}/tests/test_dependencies.py +18 -18
  7. {typellm-0.3.0.dev2 → typellm-0.3.1}/tests/test_grammar_fields.py +14 -14
  8. {typellm-0.3.0.dev2 → typellm-0.3.1}/tests/test_images.py +17 -17
  9. {typellm-0.3.0.dev2 → typellm-0.3.1}/tests/test_nullable.py +12 -12
  10. {typellm-0.3.0.dev2 → typellm-0.3.1}/tests/test_permutations.py +12 -12
  11. {typellm-0.3.0.dev2 → typellm-0.3.1}/tests/test_prefill.py +18 -30
  12. {typellm-0.3.0.dev2 → typellm-0.3.1}/tests/test_probabilities.py +3 -3
  13. {typellm-0.3.0.dev2 → typellm-0.3.1}/tests/test_prompts.py +5 -14
  14. {typellm-0.3.0.dev2 → typellm-0.3.1}/tests/test_protocols.py +22 -0
  15. {typellm-0.3.0.dev2 → typellm-0.3.1}/tests/test_text.py +18 -21
  16. {typellm-0.3.0.dev2 → typellm-0.3.1}/tests/test_typellm.py +235 -116
  17. {typellm-0.3.0.dev2 → typellm-0.3.1}/typellm/__init__.py +2 -0
  18. {typellm-0.3.0.dev2 → typellm-0.3.1}/typellm/cli.py +1 -1
  19. {typellm-0.3.0.dev2 → typellm-0.3.1}/typellm/runtime.py +137 -85
  20. {typellm-0.3.0.dev2 → typellm-0.3.1}/typellm/schema.py +10 -23
  21. {typellm-0.3.0.dev2 → typellm-0.3.1}/typellm/sglang.py +62 -66
  22. {typellm-0.3.0.dev2 → typellm-0.3.1/typellm.egg-info}/PKG-INFO +57 -60
  23. {typellm-0.3.0.dev2 → typellm-0.3.1}/LICENSE +0 -0
  24. {typellm-0.3.0.dev2 → typellm-0.3.1}/setup.cfg +0 -0
  25. {typellm-0.3.0.dev2 → typellm-0.3.1}/typellm/__main__.py +0 -0
  26. {typellm-0.3.0.dev2 → typellm-0.3.1}/typellm/benchmark.py +0 -0
  27. {typellm-0.3.0.dev2 → typellm-0.3.1}/typellm/images.py +0 -0
  28. {typellm-0.3.0.dev2 → typellm-0.3.1}/typellm/protocol.py +0 -0
  29. {typellm-0.3.0.dev2 → typellm-0.3.1}/typellm.egg-info/SOURCES.txt +0 -0
  30. {typellm-0.3.0.dev2 → typellm-0.3.1}/typellm.egg-info/dependency_links.txt +0 -0
  31. {typellm-0.3.0.dev2 → typellm-0.3.1}/typellm.egg-info/entry_points.txt +0 -0
  32. {typellm-0.3.0.dev2 → typellm-0.3.1}/typellm.egg-info/requires.txt +0 -0
  33. {typellm-0.3.0.dev2 → 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.dev2
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`, `temperature`, `seed`
83
- 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": {
@@ -401,7 +397,7 @@ For a one-off request, use the convenience function:
401
397
  ```python
402
398
  from typellm import run_schema
403
399
 
404
- result = run_schema(
400
+ response = run_schema(
405
401
  context=context,
406
402
  questions=questions,
407
403
  base_url="http://127.0.0.1:30000",
@@ -414,7 +410,7 @@ result = run_schema(
414
410
  Add `permutations` to an `enum` question to reduce option-order bias. TypeLLM averages the probabilities and keeps the same return format.
415
411
 
416
412
  ```python
417
- result = client.generate(
413
+ response = client.generate(
418
414
  context="A single roll of a fair die.",
419
415
  questions={"roll": {
420
416
  "type": "string",
@@ -441,25 +437,25 @@ support this option.
441
437
  ## Serving
442
438
 
443
439
  One `TypeLLMClient` can be shared by many threads. Each call keeps its own
444
- prompts and usage, and connections to SGLang are reused.
440
+ prompts and usage, and connections to SGLang or the hosted API are reused.
445
441
 
446
442
  ```python
447
443
  import threading
448
444
 
449
445
  cancel = threading.Event()
450
- result = client.generate(
446
+ response = client.generate(
451
447
  context=context,
452
448
  questions=questions,
453
449
  seed=7, # this call's random choices only
454
450
  timeout=30, # seconds for the whole call; raises GenerationTimeout
455
451
  cancel=cancel, # set it from another thread; raises GenerationCancelled
456
452
  )
457
- print(client.last_usage)
458
- # 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)
459
455
  ```
460
456
 
461
- `last_usage` reports the tokens of the last call made in the current thread,
462
- 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
463
459
  counted once: the context, the questions as JSON, and the images. The other
464
460
  counts are SGLang's for the call's requests, where every prompt carries the
465
461
  shared context. A timeout or cancel stops
@@ -498,8 +494,9 @@ Other sizes in the Qwen3.5 and Qwen3.8 families are expected to be compatible.
498
494
 
499
495
  [Image input](#image-input) has been tested with `Qwen/Qwen3.8-27B`.
500
496
 
501
- Use the checkpoint ID as `model=`. If the server's tokenizer path is unavailable
502
- 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.
503
500
  The tokenizer must load from standard artifacts without custom model code.
504
501
 
505
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`, `temperature`, `seed`
68
- 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": {
@@ -386,7 +382,7 @@ For a one-off request, use the convenience function:
386
382
  ```python
387
383
  from typellm import run_schema
388
384
 
389
- result = run_schema(
385
+ response = run_schema(
390
386
  context=context,
391
387
  questions=questions,
392
388
  base_url="http://127.0.0.1:30000",
@@ -399,7 +395,7 @@ result = run_schema(
399
395
  Add `permutations` to an `enum` question to reduce option-order bias. TypeLLM averages the probabilities and keeps the same return format.
400
396
 
401
397
  ```python
402
- result = client.generate(
398
+ response = client.generate(
403
399
  context="A single roll of a fair die.",
404
400
  questions={"roll": {
405
401
  "type": "string",
@@ -426,25 +422,25 @@ support this option.
426
422
  ## Serving
427
423
 
428
424
  One `TypeLLMClient` can be shared by many threads. Each call keeps its own
429
- prompts and usage, and connections to SGLang are reused.
425
+ prompts and usage, and connections to SGLang or the hosted API are reused.
430
426
 
431
427
  ```python
432
428
  import threading
433
429
 
434
430
  cancel = threading.Event()
435
- result = client.generate(
431
+ response = client.generate(
436
432
  context=context,
437
433
  questions=questions,
438
434
  seed=7, # this call's random choices only
439
435
  timeout=30, # seconds for the whole call; raises GenerationTimeout
440
436
  cancel=cancel, # set it from another thread; raises GenerationCancelled
441
437
  )
442
- print(client.last_usage)
443
- # 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)
444
440
  ```
445
441
 
446
- `last_usage` reports the tokens of the last call made in the current thread,
447
- 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
448
444
  counted once: the context, the questions as JSON, and the images. The other
449
445
  counts are SGLang's for the call's requests, where every prompt carries the
450
446
  shared context. A timeout or cancel stops
@@ -483,8 +479,9 @@ Other sizes in the Qwen3.5 and Qwen3.8 families are expected to be compatible.
483
479
 
484
480
  [Image input](#image-input) has been tested with `Qwen/Qwen3.8-27B`.
485
481
 
486
- Use the checkpoint ID as `model=`. If the server's tokenizer path is unavailable
487
- 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.
488
485
  The tokenizer must load from standard artifacts without custom model code.
489
486
 
490
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.dev2"
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"