stream
Set stream: true to receive the completion as server-sent events. Each data: frame holds the next part of the completion, and the data: [DONE] frame ends the stream.
Whenever 5 seconds pass with no data, the API sends a comment line. The line is : keepalive, or the startup stage while a cold model starts. A hand-written parser must skip both, along with any other line that starts with :.
A stream that ends without data: [DONE] is incomplete, even when no error frame arrived.
curl
curl -N -s "$ACS_API_BASE/completions" \
-H "Authorization: Bearer $ACS_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model": "llama-8b", "prompt": "Five reasons to learn Rust:\n1.", "max_tokens": 80,
"stream": true, "stream_options": {"include_usage": true}}'
-N turns off curl's output buffering so that each frame prints when it arrives.
Python
stream = client.completions.create(
model="llama-8b",
prompt="Five reasons to learn Rust:\n1.",
max_tokens=80,
stream=True,
stream_options={"include_usage": True},
)
for chunk in stream:
if chunk.choices: # final usage chunk has choices=[]
print(chunk.choices[0].text, end="", flush=True)
elif chunk.usage:
print(f"\n[tokens: {chunk.usage.total_tokens}]")
The if chunk.choices: guard skips the usage chunk, whose empty choices list would make chunk.choices[0] raise IndexError. Keep the guard when you drop stream_options, so that the loop works with either setting.
Example response
This excerpt leaves out created from every frame and system_fingerprint from the usage frame. It also leaves out logprobs, stop_reason, prompt_token_ids, and token_ids, which are null in every content frame here.
data: {"id":"cmpl-ae7c4833c82a33d6","object":"text_completion","model":"meta-llama/Llama-3.1-8B","choices":[{"index":0,"text":" It","finish_reason":null}]}
data: {"id":"cmpl-ae7c4833c82a33d6","object":"text_completion","model":"meta-llama/Llama-3.1-8B","choices":[{"index":0,"text":"’s","finish_reason":null}]}
data: {"id":"cmpl-ae7c4833c82a33d6","object":"text_completion","model":"meta-llama/Llama-3.1-8B","choices":[{"index":0,"text":" fast","finish_reason":null}]}
data: {"id":"cmpl-ae7c4833c82a33d6","object":"text_completion","model":"meta-llama/Llama-3.1-8B","choices":[{"index":0,"text":".\n","finish_reason":null}]}
... (one chunk per token until max_tokens or stop) ...
data: {"id":"cmpl-ae7c4833c82a33d6","object":"text_completion","model":"meta-llama/Llama-3.1-8B","choices":[{"index":0,"text":" again","finish_reason":"length"}]}
data: {"id":"cmpl-ae7c4833c82a33d6","object":"text_completion","model":"meta-llama/Llama-3.1-8B","choices":[],"usage":{"prompt_tokens":9,"completion_tokens":80,"total_tokens":89}}
data: [DONE]
Each choice has finish_reason: null until its own last content chunk. With stream_options.include_usage: true, a chunk with choices: [] and the token counts in usage arrives before [DONE]. Without that flag or continuous_usage_stats, the stream has no token counts, but the API meters usage either way.
A streaming request rejects any prompt_logprobs value other than 0 with 400. Prompt logprobs come back only in a non-streaming response.
Check every frame for an error
The API answers HTTP 200 and opens the stream once its checks pass, before it contacts the model. A validation, authentication, budget, or circuit-breaker failure happens before the stream opens, so it gets the status and headers in Errors.
Every later failure, including a cold boot, the request deadline, and a model-server error, arrives as a data: {"error": ...} frame inside the HTTP 200 response. Check each frame for an error key before you read choices. The OpenAI Python SDK raises APIError on such a frame.