Streaming
Beta. Updated 2026-10-29.
"stream": true returns the answer as server-sent events
in the OpenAI chat.completion.chunk format. Stream any request that can take longer than 100 s.
Example
curl
curl -N https://citadel.albastov.com/v1/chat/completions \
-H "Authorization: Bearer $CITADEL_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model": "qwen3.8-27b", "stream": true, "max_tokens": 80,
"messages": [{"role": "user", "content": "Explain prefix caching in two sentences."}],
"chat_template_kwargs": {"enable_thinking": false}}'Python
import os
from openai import OpenAI
client = OpenAI(base_url="https://citadel.albastov.com/v1", api_key=os.environ["CITADEL_API_KEY"])
stream = client.chat.completions.create(
model="qwen3.8-27b",
messages=[{"role": "user", "content": "Explain prefix caching in two sentences."}],
stream=True,
stream_options={"include_usage": True},
max_tokens=80,
extra_body={"chat_template_kwargs": {"enable_thinking": False}},
)
for chunk in stream:
if chunk.choices and chunk.choices[0].delta.content:
print(chunk.choices[0].delta.content, end="", flush=True)
if chunk.usage:
print("\n", chunk.usage)JavaScript
const res = await fetch("https://citadel.albastov.com/v1/chat/completions", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.CITADEL_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
model: "qwen3.8-27b",
stream: true,
stream_options: { include_usage: true },
max_tokens: 80,
messages: [{ role: "user", content: "Explain prefix caching in two sentences." }],
chat_template_kwargs: { enable_thinking: false },
}),
});
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
const decoder = new TextDecoder();
let buffer = "";
for await (const bytes of res.body) {
buffer += decoder.decode(bytes, { stream: true });
let end;
while ((end = buffer.indexOf("\n\n")) >= 0) {
const event = buffer.slice(0, end);
buffer = buffer.slice(end + 2);
for (const line of event.split("\n")) {
if (!line.startsWith("data:")) continue; // ": loading ..." comments and blank lines
const data = line.slice(5).trim();
if (data === "[DONE]") continue;
const chunk = JSON.parse(data);
if (chunk.error) throw new Error(`${chunk.error.type}: ${chunk.error.message}`);
const text = chunk.choices?.[0]?.delta?.content;
if (text) process.stdout.write(text);
if (chunk.usage) console.log("\n", chunk.usage);
}
}
}Note. Call the API from a server. A key in browser code is visible to every visitor.
Events
200, Content-Type: text/event-stream, Cache-Control: no-cache. Each event is one data: line followed by a blank
line.
data: {"id":"chatcmpl-a1fb7d45","object":"chat.completion.chunk","created":1793305229,"model":"qwen3.8-27b","choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null}]}
data: {"id":"chatcmpl-a1fb7d45","object":"chat.completion.chunk","created":1793305229,"model":"qwen3.8-27b","choices":[{"index":0,"delta":{"content":"One"},"finish_reason":null}]}
data: {"id":"chatcmpl-a1fb7d45","object":"chat.completion.chunk","created":1793305229,"model":"qwen3.8-27b","choices":[{"index":0,"delta":{"content":""},"finish_reason":"stop"}]}
data: {"id":"chatcmpl-a1fb7d45","object":"chat.completion.chunk","created":1793305229,"model":"qwen3.8-27b","choices":[],"usage":{"prompt_tokens":22,"completion_tokens":11,"total_tokens":33}}
data: [DONE]| Event | Field |
|---|---|
| First chunk | delta.role |
| Reasoning | delta.reasoning |
| Answer | delta.content |
| Tool calls | delta.tool_calls |
| Last choice chunk | finish_reason |
| Usage chunk | usage, empty choices |
| End | data: [DONE] |
| Comment | line starting with : |
The usage chunk requires "stream_options": {"include_usage": true}. Ignore comment lines.
Model loading
A streamed request without X-LLM-Wait waits up to 15 minutes for a loading model.
- After 10 s, the response starts:
200,text/event-stream, and the comment: loading <model>, repeated every 15 s. - After the load, the model's events follow on the same connection.
- If the load fails, the stream sends one
data: {"error": {...}}event, thendata: [DONE].
Check every data: event for an error key. With X-LLM-Wait: 0, a loading model returns 503 model_loading before
the stream starts.
Errors
| Event | Next event |
|---|---|
data: {"error": {"code": "model_loading", ...}} | data: [DONE] |
data: {"error": {"code": "gpu-busy", ...}} | data: [DONE] |
data: {"error": {"type": "engine_restarted", "code": 502, ...}} | connection closes |
In an engine_restarted event, code is the number 502. Read type. Errors before the first event are HTTP errors
with a JSON body (Chat completions errors).