Inference API
Streaming
Receive the response token by token as it is generated, instead of waiting for the full reply.
Streaming and non-streaming requests take the same wall-clock time to finish, the difference is when you start seeing output. For anything user-facing, streaming turns seconds of blank waiting into immediate feedback, and for reasoning models it lets you show the thinking phase while the final answer is still forming. Set stream: true:
import os
from openai import OpenAI
client = OpenAI(
base_url="https://inference.pearlresearch.ai/v1",
api_key=os.environ.get("PEARL_API_KEY"),
)
stream = client.chat.completions.create(
model="zai-org/GLM-5.2",
messages=[{"role": "user", "content": "Write a haiku about tide pools"}],
stream=True,
)
for chunk in stream:
if chunk.choices and chunk.choices[0].delta.content:
print(chunk.choices[0].delta.content, end="", flush=True)What's on the wire
A streamed response is server-sent events: data: lines, each carrying a JSON chunk, terminated by a literal data: [DONE]. The SDKs parse this for you; it only matters when you consume the HTTP stream yourself:
data: {"id":"chatcmpl-…","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"role":"assistant","content":""}}]}
data: {"id":"chatcmpl-…","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":"Low"}}]}
data: {"id":"chatcmpl-…","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":" tide"}}]}
data: {"id":"chatcmpl-…","object":"chat.completion.chunk","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}
data: [DONE]- Each chunk carries a
delta, the increment since the previous chunk:contenttext,reasoningon reasoning models, ortool_callsfragments when the model is composing a tool call. - The final content chunk carries
finish_reason(stop,length, ortool_calls); concatenating every delta reproduces exactly the message a non-streamed call would have returned.
Streaming a reasoning model
With a reasoning model (GLM-5.2, DeepSeek V4 Pro, or V4 Flash), read both delta fields, thinking arrives in reasoning before the answer arrives in content:
for chunk in stream:
if not chunk.choices:
continue
delta = chunk.choices[0].delta
# Reasoning models interleave two delta fields: thinking first, then the answer.
reasoning = getattr(delta, "reasoning", None)
if reasoning:
print(reasoning, end="", flush=True) # e.g. render dimmed
if delta.content:
print(delta.content, end="", flush=True) # the actual replyDetecting incomplete streams
Once streaming begins, the HTTP status is already sent, a mid-generation failure ends the stream rather than delivering an error object. Treat a stream that closes without a finish_reason (or, on the raw wire, without [DONE]) as an incomplete response: keep the partial text if partial output is useful, otherwise retry the request.
Set max_tokens deliberately and watch for finish_reason: "length", a truncated stream looks exactly like a finished one to a user unless you check.