Server-Sent Events untuk output token demi token.
Stream output model saat dihasilkan. Time-to-first-token di bawah satu detik, metadata guardrail lengkap pada event terakhir, dan penanganan error di tengah stream yang mulus.
Mulai Cepat
Aktifkan streaming
Setel stream: true pada body request. Response memakai Server-Sent Events dengan Content-Type: text/event-stream. SDK OpenAI dan Anthropic menangani parsing SSE secara otomatis.
from openai import OpenAI
client = OpenAI(base_url="https://api.anoman.io/v1", api_key="anm-sk-...")
stream = client.chat.completions.create(
model="claude-sonnet-4-6",
messages=[{"role": "user", "content": "Tell me a short story"}],
stream=True,
)
for chunk in stream:
# OpenAI SDK parses SSE for you — chunk is a typed object
delta = chunk.choices[0].delta.content or ""
print(delta, end="", flush=True)Di mana streaming bekerja
Tiga endpoint mendukung streaming
Kirim stream:true pada salah satu endpoint ini dan gateway merespons dengan Server-Sent Events. Seluruh pipeline guardrail + metering berjalan identik di setiap jalur.
POST /v1/chat/completions— endpoint chat yang kompatibel dengan OpenAI. Chunk mengikuti format chat.completion.chunk milik OpenAI (ditampilkan di atas).POST /anthropic/v1/messages— endpoint Anthropic Messages. Frame mengikuti format event Anthropic (message_start → content_block_delta → message_stop), bukan chunk OpenAI. Lihat /docs/endpoints/anthropic-messages.POST /anoman/v1/playground/run— endpoint playground dashboard/studio yang menggerakkan alat compare 3 panel.
Format wire
Anatomi chunk
Setiap frame adalah satu baris: data: <json> diikuti satu baris kosong. Stream diakhiri dengan data: [DONE]. Chunk mengikuti bentuk chat.completion.chunk milik OpenAI, kecuali frame kedua dari terakhir adalah blok metadata Anoman.
data: {"id":"chatcmpl-abc","object":"chat.completion.chunk","created":1709876543,"model":"claude-sonnet-4-6","choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null}]}
data: {"id":"chatcmpl-abc","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":"Once "},"finish_reason":null}]}
data: {"id":"chatcmpl-abc","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":"upon a "},"finish_reason":null}]}
data: {"id":"chatcmpl-abc","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":"time..."},"finish_reason":null}]}
data: {"id":"chatcmpl-abc","object":"chat.completion.chunk","choices":[{"index":0,"delta":{},"finish_reason":"stop"}],"usage":{"prompt_tokens":7,"completion_tokens":4,"total_tokens":11}}
data: {"_anoman":{"guardrails":{"injection":{"status":"pass","score":0.02}},"routing":{"mode":"realtime","region":"id"},"cache":{"hit":false},"cost_usd":"0.000041","weighted_tokens":44}}
data: [DONE]
- Chunk pertama:
delta.role: "assistant"dengan konten kosong. - Chunk perantara:
delta.contenttoken demi token. - Chunk completion terakhir:
finish_reasonterisi +usagetotal. - Frame kedua dari terakhir:
{"_anoman": {...}}— hasil guardrail, region routing, cache hit, biaya, weighted token. - Terminator:
data: [DONE].
Error di tengah stream
Ketika upstream putus di tengah jalan
Kegagalan pra-panggilan (auth, rate limit, blokir guardrail) mengembalikan error HTTP normal sebelum response SSE dibuka. Setelah streaming dimulai, status HTTP sudah 200 — jadi kegagalan upstream di tengah stream ditandai oleh frame error di dalam body stream:
data: {"error":{"type":"provider_error","code":"stream_interrupted","message":"upstream stream interrupted","_anoman":{"failure_class":"upstream_error","completion_tokens":42}}}
data: [DONE]
Klien Anda harus memeriksa setiap chunk yang di-parse untuk key error. Jika ada, perlakukan response sebagai gagal (jangan commit output parsial) dan terapkan logika retry normal Anda. SDK OpenAI otomatis melempar exception saat melihat frame ini.
_anoman.completion_tokens memberi tahu berapa banyak token penyelesaian yang dikeluarkan model sebelum kegagalan. Berguna untuk telemetri — dan untuk memutuskan apakah hasil parsial masih layak dipakai di UX Anda.Penanganan SSE mentah
Tanpa SDK
Ketika Anda tidak bisa memakai SDK OpenAI/Anthropic — edge runtime, klien HTTP alternatif, proxy kustom — berikut parser SSE minimal:
// Useful when you need fine-grained control — e.g. piping straight
// to a WebSocket, or running in an environment without the OpenAI SDK.
const response = await fetch(
"https://api.anoman.io/v1/chat/completions",
{
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.ANOMAN_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
model: "claude-sonnet-4-6",
messages: [{ role: "user", content: "Hi" }],
stream: true,
}),
},
);
if (!response.ok || !response.body) {
throw new Error(`HTTP ${response.status}`);
}
const reader = response.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 });
// Split SSE messages on blank lines
const lines = buffer.split("\n");
buffer = lines.pop() ?? "";
for (const line of lines) {
if (!line.startsWith("data: ")) continue;
const payload = line.slice(6).trim();
if (payload === "[DONE]") return;
const parsed = JSON.parse(payload);
if (parsed.error) {
throw new Error(parsed.error.message);
}
const delta = parsed.choices?.[0]?.delta?.content;
if (delta) process.stdout.write(delta);
}
}Pembatalan
Menghentikan stream lebih awal
Menutup koneksi HTTP membatalkan panggilan upstream dalam ~50 ms. Token yang dihasilkan sebelum pembatalan tetap ditagih; dashboard mencatat pembatalan sebagai routing_mode: streaming_cancelled.
import time
from openai import OpenAI
client = OpenAI(base_url="https://api.anoman.io/v1", api_key="anm-sk-...")
stream = client.chat.completions.create(
model="claude-sonnet-4-6",
messages=[{"role": "user", "content": "Write a 5-paragraph essay"}],
stream=True,
)
deadline = time.time() + 5 # cancel after 5 seconds
collected = []
for chunk in stream:
delta = chunk.choices[0].delta.content or ""
collected.append(delta)
if time.time() > deadline:
stream.close() # closes the HTTP connection
break
print("Collected:", "".join(collected))Guardrail & metering
Identik dengan non-streaming
Streaming hanya mengubah cara byte sampai ke Anda — bukan apa yang gateway lakukan di sekitar panggilan. Setiap request melewati pipeline yang sama seperti request non-streaming.
- Guardrail berjalan sebelum token pertama. Pemeriksaan pra-panggilan (injection, PII, moderasi konten, policy) dieksekusi sebelum stream dibuka. Request yang diblokir tidak pernah mulai streaming — ia mengembalikan error HTTP biasa (mis. 403), bukan body SSE.
- Metering dicatat saat stream ditutup. Penggunaan token, weighted token, biaya, dan trace berasal dari usage final penyedia — angka yang sama seperti panggilan non-streaming. Stream yang dibatalkan tetap mencatat penggunaan parsial.
- Error tidak membocorkan apa pun. Kegagalan penyedia di tengah stream memancarkan frame error generik (upstream_error) tanpa detail internal — pesan penyedia mentah hanya tersimpan di log server.
Yang tidak didukung dalam streaming
Keterbatasan yang diketahui
- Batch routing — request streaming selalu memakai jalur real-time. Batch ditujukan untuk beban kerja non-interaktif di mana SLA 5–30 menit dapat diterima sebagai ganti biaya ~50%.
- MCP per-tool RBAC — saat ini hanya diberlakukan pada response JSON. Jika alur kerja Anda butuh penolakan ketat per tool, panggil dengan
stream: falsesampai RBAC streaming dirilis. - Penulisan semantic cache terjadi saat stream ditutup, bukan per chunk. Cache hit memotong stream dan mengembalikan JSON (karena response penuh sudah diketahui).
Coba streaming di playground dashboard.
Bandingkan 3 panel dengan output streaming berdampingan.