@@ -30,7 +30,7 @@ Requires Python 3.10+.
3030The client takes two arguments. Each is read from the constructor first, then
3131from the environment.
3232
33- - ` api_key ` (` str | None ` ): falls back to ` DOTTXT_API_KEY ` . Required — the
33+ - ` api_key ` (` str | None ` ): falls back to ` DOTTXT_API_KEY ` . Required, the
3434 constructor raises ` ValueError ` if neither is set.
3535- ` base_url ` (` str | None ` ): falls back to ` DOTTXT_BASE_URL ` , then to
3636 ` https://api.dottxt.ai/v1 ` .
@@ -209,11 +209,81 @@ result = client.generate(
209209print (result) # {'severity': 'high', 'team': 'checkout'}
210210```
211211
212+ ## Streaming Fields (Patch Stream)
213+
214+ ` AsyncDotTxt.stream(...) ` yields ` PatchEvent ` objects as the model fills in a
215+ schema-constrained response. It is built on the gateway's ` stream: "patch" `
216+ mode, which emits RFC 6902 JSON Patch operations in schema order, so
217+ downstream work can start the moment a field arrives, without waiting for
218+ the closing brace.
219+
220+ Parameters mirror ` generate(...) ` :
221+
222+ - ` model ` (` str ` )
223+ - ` input ` (` str | list[dict] ` )
224+ - ` response_format ` (` Any ` ) — any schema input accepted by ` generate(...) `
225+ - ` temperature ` , ` max_tokens ` , ` seed ` , ` timeout ` — optional
226+ - ` extra ` (` dict | None ` ) — extra chat-completions body fields
227+
228+ Each ` PatchEvent ` carries:
229+
230+ - ` event.op ` — the raw RFC 6902 operation (` {"op": "add", "path": ..., "value": ...} ` )
231+ - ` event.snapshot ` — an independent deep copy of the JSON object built up to
232+ and including this op
233+ - ` event.is_leaf ` / ` event.field ` / ` event.value ` convenience demux for
234+ the common case of reacting to one field at a time. ` is_leaf ` is ` True `
235+ for non-structural adds (skipping the root seed and empty-container init
236+ ops); ` field ` is the JSON Pointer with the leading ` / ` stripped
237+ (` "intent" ` , ` "steps/0" ` , ` "address/city" ` ).
238+
239+ ``` python
240+ import asyncio
241+ from typing import Literal
242+ from pydantic import BaseModel
243+ from dottxt import AsyncDotTxt
244+
245+ class SupportTicket (BaseModel ):
246+ # Field order = arrival order. Put what unblocks downstream work first.
247+ intent: Literal[" billing" , " technical" , " account" ]
248+ urgency: Literal[" low" , " medium" , " high" , " critical" ]
249+ reply: str
250+
251+ async def main ():
252+ client = AsyncDotTxt()
253+ stream = client.stream(
254+ model = " openai/gpt-oss-20b" ,
255+ response_format = SupportTicket,
256+ input = " I was charged twice this month, please refund the duplicate." ,
257+ )
258+ async for event in stream:
259+ if not event.is_leaf:
260+ continue
261+ match event.field:
262+ case " intent" :
263+ asyncio.create_task(dispatch_to_queue(event.value))
264+ case " urgency" if event.value == " critical" :
265+ asyncio.create_task(page_oncall())
266+ case " reply" :
267+ await send(event.value)
268+
269+ asyncio.run(main())
270+ ```
271+
272+ The routing decision fires the moment ` intent ` arrives, typically tens of
273+ milliseconds in while ` reply ` continues to stream. If you need the full
274+ object so far (e.g. to log progress or hand a partial object to another
275+ service), use ` event.snapshot ` .
276+
277+ Errors:
278+
279+ - ` dottxt.PatchStreamError ` : raised when the gateway returns a non-200
280+ status. Exposes ` status_code ` and ` body ` .
281+
212282## OpenAI-Compatible Text Generation
213283
214284If you prefer the standard OpenAI SDK surface, you can call
215285` chat.completions.create(...) ` directly. The client passes the call through
216- unchanged and returns the raw chat completion object — parsing and
286+ unchanged and returns the raw chat completion object, parsing and
217287validation are up to the caller.
218288
219289For structured output, pass the wrapped OpenAI-style ` response_format `
@@ -268,3 +338,11 @@ Runnable examples live in the [`examples/`](../examples) directory:
268338- [ ` list_models.py ` ] ( ../examples/list_models.py ) : list available models
269339- [ ` openai_chat_completions.py ` ] ( ../examples/openai_chat_completions.py ) : use
270340 the OpenAI-compatible ` chat.completions.create ` surface
341+ - [ ` stream_field_printer.py ` ] ( ../examples/stream_field_printer.py ) : minimal
342+ ` stream ` demo — print each leaf field and value as it lands
343+ - [ ` stream_early_routing.py ` ] ( ../examples/stream_early_routing.py ) : route on
344+ ` /intent ` while ` /reply ` is still streaming
345+ - [ ` stream_hitl_approval.py ` ] ( ../examples/stream_hitl_approval.py ) : approve a
346+ proposed action mid-stream and discard the reply if the operator declines
347+ - [ ` stream_fanout.py ` ] ( ../examples/stream_fanout.py ) : fan research tasks out
348+ on each ` /steps/N ` as the planner emits them
0 commit comments