Streaming

June 12, 2026 · View on GitHub

All streaming is driven by persona.stream(messages) — a synchronous generator yielding text chunks. Each router wraps this generator in an async generator and returns a StreamingResponse.


OpenAI — Server-Sent Events

Endpoint: POST /openai/v1/chat/completions with "stream": true

Media type: text/event-stream

Format: Each chunk is prefixed data: followed by a JSON object and \n\n. The stream ends with data: [DONE]\n\n.

data: {"id":"chatcmpl-...","object":"chat.completion.chunk","created":...,"model":"...","choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null}]}\n\n
data: {"id":"chatcmpl-...","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"role":"assistant","content":"hello"},"finish_reason":null}]}\n\n
data: {"id":"chatcmpl-...","object":"chat.completion.chunk","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}\n\n
data: [DONE]\n\n

Source: ovos_persona_server/chat.pystreaming_chat_response() inner generator, lines 125–193.

Usage in final chunk: Included only if stream_options.include_usage is true in the request.


Ollama — Newline-delimited JSON

Endpoint: POST /ollama/api/chat or POST /ollama/api/generate with "stream": true

Media type: application/json

Format: Each chunk is a complete JSON object followed by \n. No data: prefix. The final chunk has "done": true.

Chat (/chat) chunks:

{"model":"...","created_at":"...","message":{"role":"assistant","content":"hello"},"done":false,"total_duration":...}\n
{"model":"...","created_at":"...","message":{"role":"assistant","content":" world"},"done":false}\n
{"model":"...","created_at":"...","message":{"role":"assistant","content":""},"done":true,"done_reason":"stop",...}\n

Generate (/generate) chunks — note "response" key instead of "message":

{"model":"...","created_at":"...","response":"hello","done":false}\n
{"model":"...","created_at":"...","response":"","done":true,"done_reason":"stop"}\n

Source: ovos_persona_server/ollama.pystreaming_ollama_chat_response() (line 136) and streaming_ollama_generate_response() (line 274).


Anthropic — SSE with named events

Endpoint: POST /anthropic/v1/messages with "stream": true

Media type: text/event-stream

Format: Each event has an event: line followed by data: line and \n\n. Event sequence:

event: message_start
data: {"type":"message_start","message":{"id":"msg_...","type":"message","role":"assistant","content":[],"model":"...","stop_reason":null,"usage":{"input_tokens":0,"output_tokens":0}}}

event: content_block_start
data: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""}}

event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"hello"}}

event: content_block_stop
data: {"type":"content_block_stop","index":0}

event: message_delta
data: {"type":"message_delta","delta":{"stop_reason":"end_turn"},"usage":{"output_tokens":0}}

event: message_stop
data: {"type":"message_stop"}

Source: ovos_persona_server/anthropic.py_stream() inner generator, lines 82–115.


Gemini — SSE of GeminiResponse objects

Endpoint: POST /gemini/v1beta/models/{model_id}:streamGenerateContent

Media type: text/event-stream

Format: Each event is data: <GeminiResponse JSON>\n\n. The GeminiResponse has the same structure as the non-streaming response.

data: {"candidates":[{"content":{"role":"model","parts":[{"text":"hello"}]},"finishReason":"STOP","index":0}]}\n\n
data: {"candidates":[{"content":{"role":"model","parts":[{"text":" world"}]},"finishReason":"STOP","index":0}]}\n\n

Source: ovos_persona_server/gemini.py_stream() inner generator, lines 115–124.


Cohere — Newline-delimited JSON with event_type

Endpoint: POST /cohere/v1/chat with "stream": true

Media type: application/json

Format: Newline-delimited JSON objects. Each chunk has an event_type field. The final chunk has event_type: "stream-end".

{"event_type":"text-generation","text":"hello"}\n
{"event_type":"text-generation","text":" world"}\n
{"event_type":"stream-end","finish_reason":"COMPLETE","response":{"id":"...","text":"hello world","message":{...}}}\n

Source: ovos_persona_server/cohere.py_stream() inner generator, lines 107–128.


HuggingFace TGI — SSE of token events

Endpoint: POST /tgi/generate_stream

Media type: text/event-stream

Format: Each event is data:<token JSON>\n\n. The final event has "generated_text" set to the full text and a "details" block.

data:{"token":{"id":1,"text":"hello","logprob":-1.0,"special":false},"generated_text":null,"details":null}\n\n
data:{"token":{"id":2,"text":" world","logprob":-1.0,"special":false},"generated_text":null,"details":null}\n\n
data:{"token":{"id":0,"text":"","logprob":0.0,"special":true},"generated_text":"hello world","details":{"finish_reason":"eos_token","generated_tokens":2}}\n\n

Source: ovos_persona_server/huggingface_tgi.py_stream() inner generator, lines 93–117.


AWS Bedrock — SSE of outputText events

Endpoint: POST /bedrock/model/{model_id}/invoke-with-response-stream

Media type: text/event-stream

Format: Each event is data:<JSON>\n\n. Chunks have "outputText" with the chunk text. The final event has "completionReason".

data:{"outputText":"hello","index":0,"totalOutputTextTokenCount":null}\n\n
data:{"outputText":" world","index":0,"totalOutputTextTokenCount":null}\n\n
data:{"outputText":"","index":0,"totalOutputTextTokenCount":2,"completionReason":"FINISH"}\n\n

Source: ovos_persona_server/aws_bedrock.py_stream() inner generator, lines 174–185.