SAGEA LogoDocs & API
Studio

Streaming

Streaming sends each token as it is generated, so users see the first word in milliseconds instead of waiting for the full reply.

In this guide

  • Read the SSE event format Studio returns
  • Stream tokens in Python and curl
  • Render partial tokens safely in the browser
  • Decide when to stream, when to batch, and how to reconnect

Understand the SSE event format

Set stream: true on a chat request. The response is text/event-stream — a sequence of data: lines, each holding a small JSON delta, terminated by data: [DONE].

data: {"choices": [{"delta": {"content": "नमस्ते"}}]}
data: {"choices": [{"delta": {"content": "!"}}]}
data: [DONE]

Deltas append in order. Concatenate choices[0].delta.content to rebuild the reply, and watch for empty heartbeat lines the proxy may inject. Full field details are in the Chat Completions reference.

Stream with Python and curl

Use the SDK iterator in Python for chat UIs and workers. Use curl -N to inspect raw events when debugging prompt or latency issues.

curl -N https://api.sagea.space/v1/chat/completions \
  -H "Authorization: Bearer $SAGEA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "sage-2-5-celer",
    "stream": true,
    "messages": [{"role": "user", "content": "Kathmandu बाट Pokhara बस कति घण्टा लाग्छ?"}]
  }'

Prefer sage-2-5-celer for snappy streamed chat and sage-2-4-actus when streamed reasoning quality matters more than first-token speed.

Render tokens in the browser

Read the ReadableStream with fetch, split on newlines, and append each data: payload to the bubble. Buffer partial lines — a multibyte Nepali character can split across chunks.

const res = await fetch("https://api.sagea.space/v1/chat/completions", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${apiKey}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    model: "sage-2-5-celer",
    stream: true,
    messages: [{ role: "user", content: "Dashain अफर के छन्?" }],
  }),
});
 
const reader = res.body.getReader();
const decoder = new TextDecoder();
let buffer = "";
 
while (true) {
  const { done, value } = await reader.read();
  if (done) break;
  buffer += decoder.decode(value, { stream: true });
  const lines = buffer.split("\n");
  buffer = lines.pop();
  for (const line of lines) {
    if (!line.startsWith("data:")) continue;
    const payload = line.slice(5).trim();
    if (payload === "[DONE]") return;
    const token = JSON.parse(payload).choices[0]?.delta?.content ?? "";
    appendToBubble(token);
  }
}

Flush Markdown rendering progressively but debounce heavy layouts. Never eval streamed content — treat it as text.

Stream vs batch, and how to reconnect

Stream for anything a human watches: support chat, playgrounds, voice-agent transcripts. Prefer non-streamed or Batch API calls for nightly jobs, grading, and bulk translation where throughput beats interactivity.

PathFirst tokenBest for
Streamingsub-secondLive chat, demos, agent UX
Realtime callsecondsSingle short answers, webhooks
Batchminutes to hoursThousands of rows, 50 percent off

Reconnects are normal on mobile networks. Retry strategy:

import time
 
for attempt in range(3):
    try:
        render_stream()
        break
    except ConnectionError:
        time.sleep(2 ** attempt)  # 1s, 2s, 4s backoff

Send Last-Event-ID or your own turn ID when reconnecting, and dedupe by it server-side so a retried turn does not double-charge or double-send an SMS.

Best practices

  • Show a typing indicator until the first token arrives, then stream words.
  • Set tight timeouts on first-token (5s) and looser ones on full completion.
  • Accumulate the full text server-side for logging even when streaming to the client.
  • Fall back to a non-streamed call after two reconnect failures.
  • Test with long Nepali replies to catch grapheme-splitting bugs early.

What's next

On this page