Pemecahan masalahPanduan
Stream gagal setelah HTTP 200: tangkap error di tengah stream di Python
Mengapa stream bisa rusak setelah status 200, cara mengenali event: error, dan retry Python yang menyimpan teks parsial tanpa menduplikasi jawaban.

Di halaman ini
UI chat Anda mengalirkan setengah jawaban, lalu membeku, dan log menampilkan 200 OK. Itu bukan kontradiksi: kode 200 hanya berarti stream dimulai, bukan bahwa stream selesai. Jika terjadi masalah di kemudian hari, server hanya bisa mengirim error di dalam stream, sehingga pemeriksaan kode status tidak akan pernah melihatnya. Solusinya adalah menganggap stream berhasil hanya jika diakhiri dengan sinyal selesainya, dan mencoba ulang dari buffer yang bersih jika tidak.
Sisa halaman ini menunjukkan seperti apa stream yang rusak, dua handler Python (Anthropic Messages dan OpenAI SDK), serta retry yang tidak mencetak awal jawaban dua kali. Kedua handler menggunakan CheapTokens sebagai endpoint API: dokumentasi berisi base URL, dan model contohnya adalah DeepSeek-V4-Flash (id deepseek-, konteks 380K, hingga 32.768 token output per jawaban, satu harga kapan pun — lihat halaman model).
Apa yang sebenarnya dijanjikan kode 200#
Bayangkan seorang kurir yang menandatangani paket di depan pintu: tanda tangan membuktikan ia telah tiba, bukan bahwa paketnya utuh. HTTP bekerja dengan cara yang sama. Server mengirim baris status dan header terlebih dahulu, lalu body tiba sepotong-sepotong. Jika backend model kelebihan beban di tengah jalan, baris status sudah terlanjur terkirim.
Halaman error API Anthropic (diperiksa 1 November 2026) menyatakannya secara langsung: saat Anda menerima respons streaming melalui SSE, error dapat terjadi setelah API mengembalikan 200, dan penanganan error standar tidak berlaku. Dokumentasi streaming-nya memberikan bentuk event tersebut:
event: error
data: {"type": "error", "error": {"type": "overloaded_error", "message": "Overloaded"}}overloaded_error biasanya berkorespondensi dengan HTTP 529 pada panggilan non-streaming (kelebihan beban sementara akibat lalu lintas tinggi). Halaman yang sama mencantumkan 500 api_error (coba ulang dengan exponential backoff) dan 504 timeout_error (gunakan streaming untuk permintaan panjang). Di jaringan, stream yang rusak terlihat seperti ini:

Dokumentasi juga memperingatkan bahwa tipe event baru mungkin ditambahkan, jadi kode Anda harus menangani event yang tidak dikenal dengan baik. Itulah sebabnya handler di bawah ini tidak mencoba mengenali setiap byte error. Mereka mengajukan satu pertanyaan: apakah stream berakhir dengan sinyal selesainya?
- Anthropic Messages: event terakhir adalah
message_stop, danstop_reasondimessage_deltamenyatakan alasannya berakhir (end_turn,max_tokens,stop_sequence). - OpenAI Chat Completions: chunk terakhir membawa
finish_reasonyang tidak kosong (stop,length, …).
Tidak ada sinyal selesai, atau terjadi exception di tengah jalan — jawabannya tidak lengkap, apa pun kata statusnya.
Tangkap di Python#
Ambil contoh bot dukungan yang mengalirkan balasan ke browser. Loop mengumpulkan teks ke dalam buffer dan melaporkan tiga hal: teks sejauh ini, apakah stream selesai, dan error jika ada. Pilih tab untuk SDK yang Anda gunakan.
Satu handler, dua SDK
Anthropic SDK
import os
import anthropic
client = anthropic.Anthropic(
base_url='https://api.cheaptokens.dev', # no /v1 for the Anthropic SDK
api_key=os.environ['CHEAPTOKENS_API_KEY'],
)
MODEL = 'deepseek-ai/DeepSeek-V4-Flash-0731'
def ask_once(prompt, on_text=lambda t: None):
parts, finished = [], False
try:
with client.messages.stream(
model=MODEL,
max_tokens=1024,
messages=[{'role': 'user', 'content': prompt}],
) as stream:
for event in stream:
if event.type == 'content_block_delta' and event.delta.type == 'text_delta':
parts.append(event.delta.text)
on_text(event.delta.text)
elif event.type == 'message_stop':
finished = True
except anthropic.APIError as err:
return ''.join(parts), False, err
return ''.join(parts), finished, NoneOpenAI SDK
import os
import openai
client = openai.OpenAI(
base_url='https://api.cheaptokens.dev/v1',
api_key=os.environ['CHEAPTOKENS_API_KEY'],
)
MODEL = 'deepseek-ai/DeepSeek-V4-Flash-0731'
def ask_once(prompt, on_text=lambda t: None):
parts, finish = [], None
try:
stream = client.chat.completions.create(
model=MODEL,
max_tokens=1024,
stream=True,
messages=[{'role': 'user', 'content': prompt}],
)
for chunk in stream:
if not chunk.choices:
continue
choice = chunk.choices[0]
if choice.delta.content:
parts.append(choice.delta.content)
on_text(choice.delta.content)
if choice.finish_reason:
finish = choice.finish_reason
except openai.APIError as err:
return ''.join(parts), False, err
return ''.join(parts), finish is not None, NoneDua detail perlu diperhatikan. Pertama, kedua handler menangkap kelas error dasar SDK (anthropic., openai.APIError), jadi tidak masalah subclass mana yang dilempar SDK untuk kegagalan tertentu. Kedua, flag finished terpisah dari exception: stream juga bisa berakhir lebih awal tanpa exception sama sekali, dan kasus itu harus dihitung sebagai kegagalan juga.
README openai-python menambahkan apa yang diharapkan di sisi OpenAI (diperiksa 1 November 2026): timeout baca dalam stream memunculkan APITimeoutError, kegagalan HTTPX lainnya memunculkan APIConnectionError dengan exception asli di __cause__. Jika stream macet alih-alih gagal, lihat timeout dan retry di OpenAI Python SDK.
Retry tanpa mencetak jawaban dua kali#
Ini bug yang mudah terkirim. Percobaan pertama mencetak "Kebijakan pengembalian dana adalah…", mati, retry dimulai dari awal dan mencetak "Kebijakan pengembalian dana adalah…" lagi — sekarang pengguna melihat awalnya dua kali, atau kalimat yang menempel pada pengulangannya sendiri. Penyebab umumnya adalah satu buffer bersama: answer += text berada di luar percobaan, sehingga percobaan kedua menambahkan ke percobaan pertama.
Obatnya membosankan. Satu percobaan memiliki satu buffer, teks dari percobaan yang gagal dibuang (atau disimpan hanya untuk log), dan retry dimulai dari awal:
import time
def ask_with_retry(prompt, attempts=3, on_text=lambda t: None, on_restart=lambda: None):
for attempt in range(attempts):
text, finished, err = ask_once(prompt, on_text)
if finished:
return text
status = getattr(err, 'status_code', None)
if status is not None and 400 <= status < 500 and status not in (408, 409, 429):
raise err # a 400/401/404 will not fix itself
print(f'attempt {attempt + 1} failed after {len(text)} chars: {err!r}')
if attempt == attempts - 1:
break
on_restart() # clear the message box in your UI
time.sleep(2 ** attempt) # 1 s, 2 s
raise RuntimeError('stream failed after all attempts')on_restart adalah bagian yang menghentikan duplikasi. Di UI web, ini mengosongkan gelembung pesan sebelum percobaan berikutnya mengalir masuk. Di terminal biasa Anda tidak bisa menghapus cetakan, jadi cetak hanya setelah finished bernilai true, atau cetak baris --- retrying --- yang terlihat. Retry dari awal mengirim ulang seluruh prompt, sehingga input ditagih lagi untuk percobaan baru; untuk sebagian besar balasan chat, itu harga yang wajar untuk jawaban yang bersih.
Urutkan
Urutkan langkah retry sesuai urutan yang seharusnya dijalankan kode Anda
Seret item atau gunakan panah
- Hentikan pembacaan stream yang rusak dan simpan teks parsialnya untuk log
- Periksa error: 4xx selain 408, 409, dan 429 berarti berhenti, bukan retry
- Kosongkan kotak pesan dan mulai buffer baru
- Tunggu dengan exponential backoff (1 dtk, lalu 2 dtk)
- Kirim ulang permintaan lengkap dan tampilkan hanya jawaban baru
SDK Anthropic sudah mencoba ulang kegagalan sementara — error koneksi, rate limit, 5xx — dengan exponential backoff, default 2 kali, menghormati retry-after, dan max_retries mengubahnya. Itu mencakup permintaannya. Stream yang mati setelah 200 adalah cerita berbeda: dokumentasi menyatakan penanganan error standar tidak berlaku di sana, jadi loop di atas adalah tanggung jawab Anda. Untuk 429 secara khusus, ada panduan terpisah: 429 Too Many Requests: retry dengan backoff.
Tidak setiap penghentian dini adalah error#
Sebelum retry, periksa mengapa jawaban berakhir. Stream yang selesai dengan stop_ (Anthropic) atau finish_ (OpenAI) memang selesai — hanya saja mencapai batas yang Anda tetapkan. Mencoba ulang akan mengembalikan teks terpotong yang sama dan tagihan yang sama. Naikkan batasnya; panduan jawaban terpotong membahasnya.
Ada kelompok ketiga: jaringan. Koneksi idle dapat terputus di tengah jalan, yang muncul sebagai error koneksi atau stream yang berhenti tanpa event apa pun. Tangani sama seperti error di tengah stream, dan lihat error koneksi umum jika terus terjadi. Untuk generasi panjang, Anthropic merekomendasikan streaming alih-alih max_tokens non-streaming yang besar, jadi Anda sudah berada di sisi yang benar.
Sebelum merilis#
Simpan request id dari setiap percobaan yang gagal. Body error Anthropic membawa field request_id dan header respons request-id, dan SDK Python mengekspos message.; dengan id itu, kegagalan dapat dilacak alih-alih hanya anekdot.
Daftar periksa
Penanganan error streaming, dilakukan dengan benar
Jika Anda ingin melihat handler berjalan, arahkan ke kunci Anda sendiri: buat API key di dokumentasi, set CHEAPTOKENS_, dan panggil ask_. Untuk dasar-dasar streaming di Python, mulai dengan respons streaming di Python; sisi Anthropic dibahas di Anthropic Python SDK dengan CheapTokens.




