SimpleLLMDocs
Build with SimpleLLM
Markdown ↗

Streaming and cancellation

Read SSE events, handle partial responses, and cancel generation.

Streaming lets your application display output as it arrives. Set stream: true; the response uses Server-Sent Events instead of one JSON document.

Request a stream

bash
curl --no-buffer --fail-with-body https://api.simplellm.eu/v1/chat/completions \
  -H "Authorization: Bearer $SIMPLELLM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "qwen3.8-flash-next",
    "messages": [{"role": "user", "content": "Explain how an HTTP request is handled."}],
    "max_tokens": 2048,
    "stream": true,
    "stream_options": {"include_usage": true}
  }'

The usage option requests a final usage report where supported. It cannot guarantee a report after an interrupted connection.

Understand the events

This shortened example illustrates event framing rather than a complete provider-specific response:

text
data: {"choices":[{"index":0,"delta":{"role":"assistant"},"finish_reason":null}]}

data: {"choices":[{"index":0,"delta":{"content":"An HTTP request"},"finish_reason":null}]}

data: {"choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}

data: {"choices":[],"usage":{"prompt_tokens":20,"completion_tokens":40,"total_tokens":60}}

data: [DONE]

Use an existing SDK's stream reader when possible. Do not count events or network chunks as exact tokens.

Python stream with explicit cleanup

Install the openai package and set SIMPLELLM_API_KEY and SIMPLELLM_MODEL as described in the Quickstart.

python
import os
from openai import OpenAI

client = OpenAI(
    base_url="https://api.simplellm.eu/v1",
    api_key=os.environ["SIMPLELLM_API_KEY"],
    max_retries=0,
    timeout=600.0,
)
stream = client.chat.completions.create(
    model=os.environ["SIMPLELLM_MODEL"],
    messages=[{"role": "user", "content": "Explain request streaming."}],
    max_tokens=2048,
    stream=True,
    stream_options={"include_usage": True},
)
try:
    for chunk in stream:
        if chunk.usage is not None:
            print("\nUsage:", chunk.usage)
        for choice in chunk.choices:
            if choice.delta.content:
                print(choice.delta.content, end="", flush=True)
finally:
    stream.close()

The client timeout here is an application choice, not a SimpleLLM platform timeout guarantee. Interrupting the program exits the loop and closes the stream in the cleanup block.

Cancel the upstream request

When the user presses Stop, close or abort the HTTP request; merely hiding the output in the UI does not cancel generation. If a browser talks to your backend, propagate the browser's disconnect to your backend's upstream request as well.

In the JavaScript SDK, abort the stream's controller when the application cancels:

javascript
// Inside the stream loop or your application's Stop handler:
stream.controller.abort();

The complete JavaScript client setup is in client examples.

An interrupted stream with zero recorded output tokens is not charged. Already delivered output can still incur a charge, and missing final usage can require an estimate. See partial-usage billing.

Interrupted streams

Treat connection closure without the expected completion signals as an incomplete result. Preserve delivered text with an incomplete/cancelled state so the user can distinguish it from a successful answer.

An HTTP 200 only confirms that streaming began. After headers have been sent, a later failure cannot reliably become a new HTTP error status; inspect stream events and connection errors too.

Do not silently replay a request after partial output. Retrying creates a new generation that can duplicate content, tool actions, and charges.

Search the complete documentation.