Skip to Content

Streaming responses

Streaming delivers output incrementally instead of waiting for the entire answer. It changes how you read the response, not which capabilities a model supports. Start with a model that supports the selected protocol and streaming.

Prepare the client

Install openai in your server-side Python environment and set FENNO_API_KEY and FENNO_MODEL as described in the quickstart. Do not expose the key to a browser.

import os from openai import OpenAI client = OpenAI( base_url="https://api.fenno.ai/v1", api_key=os.environ["FENNO_API_KEY"], timeout=60.0, max_retries=0, ) stream = client.chat.completions.create( model=os.environ["FENNO_MODEL"], messages=[{"role": "user", "content": "Explain streaming in three sentences."}], stream=True, ) try: for chunk in stream: if chunk.choices: text = chunk.choices[0].delta.content if text: print(text, end="", flush=True) finally: stream.close()

An empty choices array or a delta without content is not necessarily an error. Role changes, tool arguments and usage can arrive separately. Accumulate tool arguments by call index and parse JSON only after the call is complete.

Responses uses typed events

Run this with the same client and a Responses-capable model:

stream = client.responses.create( model=os.environ["FENNO_MODEL"], input="Explain streaming in three sentences.", stream=True, ) try: for event in stream: if event.type == "response.output_text.delta": print(event.delta, end="", flush=True) elif event.type in {"response.failed", "response.incomplete", "error"}: raise RuntimeError(str(event)) finally: stream.close()

Do not parse Responses as choices[0].delta. Its output can include reasoning and function-call items in addition to text.

Protocol completion signals

ProtocolText incrementCompletion and error handling
Chat Completionschoices[].delta.contentInspect finish_reason; the SSE stream ends with [DONE]
Responsesresponse.output_text.deltaInspect response.completed, response.incomplete, response.failed and error
Anthropic Messagescontent_block_delta with text_deltaInspect message_delta.stop_reason, message_stop and error events
GeminiCandidate content partsInspect candidate finishReason and safety feedback; REST SSE uses alt=sse

SSE events are separated by blank lines. Network reads do not align with events or UTF-8 characters. Use an SDK or an incremental SSE parser instead of splitting each network read into JSON.

Interrupted streams

  • Close the stream when the caller cancels. Set both a request timeout and an application deadline.
  • A successful HTTP status does not guarantee the stream later completes successfully.
  • Once output has been delivered, mark an interrupted answer as partial. Do not silently append a newly generated answer to it.
  • Retrying may create a second billable generation. Never replay an executed tool as part of an automatic retry.
  • When forwarding SSE, check your own proxy’s buffering and idle-timeout settings; do not log credentials or full customer prompts.

See errors and retries, tool calling, and the protocol-specific API reference.