Kegagalan streaming jarang disebabkan oleh model—biasanya disebabkan oleh infrastruktur antara Anda dan model. Proxy dengan batas waktu idle mematikan koneksi yang hening, buffering nginx menghilangkan event, dan stream yang hanya dikonsumsi sebagian tampak seperti “API berhenti merespons”. Periksa infrastrukturnya terlebih dahulu.
Error
- Stream stops mid-sentence, connection closed (no error event)
- Client hangs after the last token, never sees [DONE]
- usage is null on streamed responses
- Works in curl, dies behind nginx / a corporate proxyRingkasan penyebab dan perbaikan
| Penyebab | Perbaikan |
|---|---|
| Batas waktu idle proxy/load balancer (default 60 detik di banyak konfigurasi) | Naikkan batas waktu baca untuk jalur API; jeda berpikir yang panjang terlihat idle bagi proxy. |
| Buffering di depan SSE (nginx proxy_buffering, beberapa CDN) | Nonaktifkan buffering untuk rute streaming (X-Accel-Buffering: no / proxy_buffering off). |
| Klien berhenti mengonsumsi (await hilang, iterator dibuang) | Konsumsi hingga selesai atau tutup secara eksplisit—iterator yang dikumpulkan GC di tengah stream tidak dapat dibedakan dari stream yang terputus. |
| Mengharapkan penggunaan tanpa memintanya | OpenAI-wire: teruskan stream_options: {"include_usage": true}—penggunaan tiba di chunk terakhir. |
Reproduksi dengan curl -N langsung ke API
Lewati semua proxy. Jika SSE mentah mengalir dengan baik selama seluruh generasi, jalur aplikasi Anda yang bermasalah—tambahkan kembali hop satu per satu:
curl -N https://api.kunavo.com/v1/chat/completions \
-H "Authorization: Bearer $KUNAVO_API_KEY" \
-H "content-type: application/json" \
-d '{"model":"claude-sonnet-5","stream":true,
"stream_options":{"include_usage":true},
"max_tokens":300,
"messages":[{"role":"user","content":"Count slowly to 20 in words."}]}'Perbaiki hop yang menyebabkannya terputus
nginx: proxy_buffering off + proxy_read_timeout 300s untuk rute tersebut. Serverless: periksa batas streaming respons platform. Proxy perusahaan: beberapa tidak dapat menangani SSE sama sekali—gunakan non-streaming sebagai fallback di sana.
Tangani bagian akhir dengan benar
Data penagihan streaming tiba terakhir: chunk terakhir membawa penggunaan (jika diminta) sebelum [DONE]. Agregasikan delta, baca penggunaan dari chunk terakhir, dan perlakukan penutupan awal (tanpa finish_reason) sebagai kondisi yang dapat dicoba ulang.
“SSE stream ended without [DONE]”—apakah jawabannya lengkap?
Pesan itu adalah pemeriksaan milik klien Anda sendiri, bukan kesalahan yang dikirim API: koneksi ditutup sebelum baris data: [DONE] yang mengakhiri stream kompatibel OpenAI. Tiga hal dapat menyebabkannya: hop di antaranya menutup koneksi (batas waktu proxy dan buffering yang dijelaskan di atas); server gagal di tengah jawaban dan menutup koneksi tanpa frame terminal; atau jawaban sebenarnya lengkap dan hanya sentinel yang hilang. Chunk terakhir yang Anda terima menentukan penyebabnya: finish_reason di sana berarti teks lengkap, sedangkan tanpa finish_reason berarti teks terpotong dan harus dicoba lagi. Jangan serahkan pemeriksaan ini kepada SDK—OpenAI Python SDK mengakhiri loop secara diam-diam ketika koneksi ditutup tanpa [DONE], dan hanya menimbulkan error ketika chunk membawa objek kesalahan. Letakkan pemeriksaan dalam kode Anda sendiri:
from openai import OpenAI
client = OpenAI(base_url="https://api.kunavo.com/v1", api_key="sk-kn-...")
finish_reason, parts = None, []
stream = client.chat.completions.create(
model="claude-sonnet-5",
messages=[{"role": "user", "content": "Count slowly to 20 in words."}],
stream=True,
)
for chunk in stream: # raises openai.APIError on a chunk that carries "error"
for choice in chunk.choices:
parts.append(choice.delta.content or "")
finish_reason = choice.finish_reason or finish_reason
if finish_reason is None:
raise RuntimeError("stream closed without a finish_reason: cut off, retry it")
print("".join(parts))Stream kosong: 200, lalu tidak ada apa-apa
Terkadang stream terbuka dengan HTTP 200 lalu berakhir tanpa satu pun chunk konten—tanpa teks, tanpa pemanggilan alat, terkadang bahkan tanpa chunk role. Hampir selalu berarti upstream gagal setelah header dikirim: provider kelebihan beban, gateway yang upstream-nya sendiri menolak permintaan, atau proxy yang membuang body. Perlakukan sebagai stream terputus dan coba lagi dengan backoff, serta catat body mentah dari satu respons yang gagal, karena penyebabnya sering berupa event kesalahan di dalam stream yang dilewati SDK Anda. Claude Code merespons kondisi yang sama dengan mencoba ulang permintaan tanpa streaming.
Jika Anda melakukan panggilan melalui Kunavo
Kunavo mengalirkan SSE standar OpenAI-wire (dengan dukungan stream_options.include_usage) dan event Anthropic-wire pada /v1/messages, sehingga reproduksi curl di atas juga merupakan pengujian kompatibilitas. Untuk model Claude dan GPT pada /v1/chat/completions, aliran Kunavo berakhir dengan data: [DONE] baik jawaban selesai maupun tidak: setelah chunk finish_reason jika selesai, dan setelah chunk error — type upstream_error, code upstream_disconnect atau upstream_timeout — jika upstream terputus di tengah jawaban, sehingga SDK OpenAI memunculkan APIError alih-alih menyerahkan fragmen tersebut sebagai seluruh balasan. Aliran upstream yang berakhir sebelum konten apa pun, atau gagal sebelum token pertama, tidak pernah sampai kepada Anda sebagai 200 kosong: percobaan diulang pada kanal lain jika model tersebut memilikinya, dan jika tidak, dikembalikan sebagai error HTTP. Permintaan yang dialirkan dan gagal sebelum output apa pun sampai kepada Anda tidak ditagih; permintaan yang terputus di tengah jawaban belum tentu demikian — kirim ulang permintaan tersebut, jangan berasumsi bahwa itu gratis.
Pertanyaan umum
Mengapa usage bernilai null pada respons streaming saya?
Pada API OpenAI-wire, usage tidak disertakan dalam stream kecuali Anda meneruskan stream_options: {"include_usage": true}; usage kemudian muncul pada chunk terakhir. Stream native Anthropic melaporkan usage dalam event message_start/message_delta.
Bagaimana cara mendeteksi stream yang terputus dibandingkan stream yang selesai?
Stream yang selesai berakhir dengan finish_reason (atau message_stop milik Anthropic), lalu [DONE]. Koneksi yang tertutup tanpa penanda tersebut berarti terputus — perlakukan sebagai kegagalan yang dapat dicoba ulang, bukan jawaban singkat.
Apa arti “SSE stream ended without [DONE]”?
Klien Anda membaca stream sampai akhir koneksi dan tidak pernah melihat baris data: [DONE] yang menutup stream yang kompatibel dengan OpenAI. API tidak mengirim pesan tersebut; klien atau agen Anda yang menuliskannya. Jika chunk terakhir membawa finish_reason, jawaban sudah lengkap dan hanya penutup koneksinya yang hilang. Jika tidak, jawaban terputus oleh proxy, timeout, atau kegagalan sisi server, dan permintaan harus dicoba lagi.
Apa arti “stream ended without finish_reason”?
Klien membaca stream hingga selesai dan tidak ada chunk yang membawa finish_reason — field yang digunakan stream kompatibel dengan OpenAI untuk menyatakan bahwa jawaban selesai (stop, length, tool_calls). Tanpanya, teks yang Anda miliki hanyalah fragmen, betapapun alami kalimat terakhirnya. Coba lagi permintaannya; jika terulang, periksa timeout proxy atau buffering antara Anda dan API.
Mengapa API LLM dapat mengembalikan stream kosong?
Karena kegagalan terjadi setelah header HTTP dikirim: penyedia mengalami beban berlebih, upstream gateway menolak permintaan, atau proxy menjatuhkan body. Baris status tetap menunjukkan 200, sehingga pemeriksaan status tidak mendeteksinya. Perlakukan stream tanpa chunk konten sebagai permintaan yang gagal, coba lagi dengan backoff, dan catat satu respons mentah untuk menemukan event error di dalamnya.
Apakah aman mengabaikan [DONE] yang hilang jika saya sudah memiliki teks?
Hanya jika chunk terakhir membawa finish_reason. Tanpanya, teks yang Anda miliki adalah fragmen yang dapat berakhir di tengah kalimat atau di tengah tool-call, dengan argumen JSON yang belum lengkap. Coba lagi alih-alih menyimpannya sebagai jawaban.
Panduan terkait
- Panduan API yang kompatibel dengan OpenAI — dari endpoint lokal Ollama hingga model frontier ter-host
- “Streaming interrupted. Waiting for the complete message” — artinya dan cara memperbaikinya
- Claude Code “Respons terhenti di tengah streaming” dan “Respons streaming berakhir sebelum data lengkap apa pun diterima” — apa yang menghentikan streaming
Detail semantik error lainnya tersedia di referensi error; mendapatkan kunci hanya perlu satu menit melalui pendaftaran dan panduan autentikasi.