Streaming
ai.stream sends a model's text to the client as it is written, rather
than waiting for the whole answer, as a chat app does. The action
returns the stream to its route, which sends it as server-sent events
(Streaming a model's answer).
-
ai.stream(prompt)takes the prompt andmodel:,max_output:andtimeout:, written out and checked asai.generate's are.as:andattempts:aretype.named_argument_unknown, with the reason. Only an action calls it: in a function it iseffect.function_persistence, and in a testsemantic.not_callable. It goes through theai:connection (ai.connection_required) and gives anAiStream, a built-in type with no fields and no JSON form, which an action returns to its route. Through a package's connection, it goes through the package'sstream:(Streams, embeddings and the OpenAI and Anthropic packages). -
Its four conditions each need an arm naming a failure of kind
unavailable, asgenerate's do:refused: a refusal, or acontent_filterfinish, before any text;timed_out: no first piece withintimeout:;over_budget: alengthfinish before any text;too_long: the server'scontext_length_exceeded.
Text isn't decoded, so a stream is never
malformed. A server that can't be reached is sent the request again, asgenerate's is, until the answer starts to come; then it isunavailable. -
The request is
generate's, with"stream": trueand noresponse_format. It asks for OpenAI's streamed chat completion: server-sent events ofchat.completion.chunks, each piece achoices.0.delta.content, ended bydata: [DONE]. A finish ofstopwith no[DONE]is whole. A body that ends without either is broken, and so is anerrorevent. A server that ignoresstreamand answers one whole chat completion is read as one piece. Usage chunks are skipped, since a stream is charged when it is opened (How budgets are counted). -
After the first piece, a refusal, a cut-off, the time running out or a broken connection ends the route's stream with an
errorevent. It isunavailable, the runtime's own failure, since the action has returned and its mapping can't run. -
Answers reused on a rerun. A stream takes the invocation's next outside-call step. An action run again after a concurrent change is given back the stream its first run opened, which nothing has read from yet, for the same request. A stream is never a step of a record (Several changing calls in one action): its answer goes to the client, not the record, and only a route's action returns one.
-
The fake answers a streamed prompt from its script, in the built-in driver's own form, with one of:
- a text (a value of
Text, orraw("...")), cut into pieces of three words, each keeping the space after it, thenstopand[DONE]; - a condition, before the first piece;
partial("...", <condition>): the text's pieces, thenrefused(acontent_filterfinish),over_budget(alengthfinish) ortimed_out(the stream stops), each anerrorevent at the route.
partial(...)of a prompt no action streams, or withtoo_long, which is found before anything is sent, issemantic.fake_invalid. A prompt both generated and streamed takes the five conditions,raw(...)andpartial(...), just as a prompt asked for as two types takes no value. An unscripted stream gives a generated text naming its prompt. - a text (a value of
-
jadpo devstreams from the model's server as it answers, each piece reaching the client as it is read. The call log keeps the request in full, asgenerate's, and not the streamed answer.
Markdown for agents: /docs/streaming.md.Built from Jadpo main at a707efb.