Musim dingin tahun lalu saya membantu sebuah tim yang mengerjakan layanan pelanggan cerdas untuk menelusuri masalah produksi. Backend mereka terhubung ke empat API model besar sekaligus, dan frontend menggunakan SSE untuk output streaming. Mulai hari ketiga setelah diluncurkan, pengguna melaporkan "jawaban seperti pasta gigi yang diperas" — kadang seluruh paragraf teks tersendat tak bergerak, lalu tiba-tiba membanjir sekaligus; kadang satu kalimat terpotong menjadi dua, dengan blok kosong aneh menyelip di tengahnya. Di log backend tidak ada 5xx, dan pemantauan ketersediaan setiap API semuanya hijau.
Masalah seperti ini paling sulit dilacak, karena setiap bagian tampak normal. Saya ikut mereka mulai dari panel Network di browser, mengupas lapis demi lapis ke bawah, dan akhirnya menemukan bahwa masalahnya sama sekali bukan pada salah satu API, melainkan pada "pencampuran empat API" itu sendiri.
Kesimpulan di awal: protokol streaming yang tidak seragam adalah jebakan tersembunyi integrasi agregat
Output streaming API model besar, secara umum menggunakan SSE (Server-Sent Events). Tetapi "semuanya memakai SSE" tidak berarti "perilakunya konsisten". Keempat vendor memiliki implementasi masing-masing dalam hal granularitas pemotongan, penanda akhir, dan strategi heartbeat. Aliran byte yang diterima frontend tampak semuanya diawali data:, tetapi logika penguraiannya sama sekali tidak dapat digunakan ulang. Inilah akar dari tersendat dan anomali batas pemotongan.
Penelusuran lapis demi lapis: dari frontend ke gateway, di lapisan mana masalahnya
Lapisan pertama, frontend. Penguraian SSE mereka menggunakan EventSource standar, memotong event berdasarkan \n\n. Ini sendiri tidak masalah.
Lapisan kedua, penerusan backend. Backend menggunakan Node untuk membuat satu lapisan pass-through, menyalurkan ReadableStream dari hulu langsung di-pipe ke respons. Di sini tertanam ranjau pertama: ukuran chunk antar vendor sangat berbeda, ada yang mengirim per token, ada yang mengumpulkan beberapa token dulu baru dikirim. Jika dalam proses pipe dilakukan Buffer.concat tanpa menangani batas antar-chunk karakter multibyte UTF-8 dengan benar, akan muncul setengah karakter Han yang terpotong, dan frontend merendernya menjadi kotak karakter rusak.
Lapisan ketiga, timeout dan retry. Backend mengonfigurasi timeout seragam 30 detik untuk keempatnya. Tetapi dalam skenario streaming, "timeout 30 detik" itu sebenarnya merujuk pada waktu byte pertama atau waktu seluruh aliran? Mereka tidak membedakannya, sehingga jawaban panjang terpotong di tengah jalan, dan di frontend tampak sebagai "berhenti tiba-tiba di tengah kalimat".
Lapisan keempat, kode error. Kode status HTTP dan struktur body yang dikembalikan keempatnya saat pembatasan laju, kegagalan autentikasi, dan intersepsi moderasi konten semuanya berbeda. Backend secara seragam mengambil melalui error.message, hasilnya dua vendor bisa mengambilnya, dua vendor mendapat undefined, dan frontend menampilkan pesan error kosong.
Setelah penelusuran sampai di sini, sudah jelas: masalah sebenarnya bukan bug pada salah satu API, melainkan semantik empat set protokol yang langsung diekspos ke lapisan bisnis.
Perbedaan kunci implementasi streaming keempat vendor
Saya menyusun sebuah perbandingan, datanya berasal dari dokumentasi resmi masing-masing vendor dan tangkapan paket aktual, bagian yang tidak diungkapkan resmi saya tandai:
DimensiVendor AVendor BVendor CVendor D
Penanda akhirdata: [DONE]field finish_reasondata: [DONE]event: done event
Heartbeattidak ada sekitar 15 detik baris komentar tidak ada tidak diungkapkan resmi
Granularitas pemotonganper token mengumpulkan 2-4 token per token tidak tetap
Posisi kode errorstatus HTTPbody.error.codestatus HTTPbody.code
Melihat tabel ini jelaslah mengapa logika penguraian frontend tidak dapat digunakan ulang. Penanda akhir ada tiga cara penulisan, kode error ada dua posisi, strategi heartbeat ada di dua vendor dan tidak ada di dua vendor lainnya. Jika dalam kode bisnis langsung if (chunk.includes('[DONE]')), saat bertemu vendor B tidak akan pernah berakhir, koneksi menggantung sampai timeout.
Empat poin desain kunci untuk menyatukan di lapisan gateway
Pertama, menyeragamkan format event SSE. Gateway menormalisasi semua event dari berbagai vendor hulu menjadi satu struktur: {type: "delta" | "done" | "error", content, code}. Lapisan bisnis hanya mengenali satu set ini, tidak lagi peduli siapa hulunya. Platform agregat API model besar token8341 yang pernah kami gunakan dalam proyek melakukan hal ini, ia hanya mengekspos satu protokol streaming ke luar, kode bisnis tidak perlu diubah saat berganti model.
Kedua, memusatkan autentikasi ke gateway. Empat vendor empat set Key, tersebar di konfigurasi bisnis, sekali rotasi harus mengubah empat tempat. Kumpulkan Key ke dalam gateway, lapisan bisnis hanya memegang satu token internal. Nilai dari lapisan agregat seperti platform agregat API model besar token8341 ada di sini, satu Key memanggil banyak model, rotasi Key dan kontrol kuota semuanya diselesaikan di sisi gateway.
Ketiga, timeout harus dikonfigurasi dalam dua segmen. Timeout byte pertama (TTFB) dan timeout idle aliran dipisahkan. TTFB diatur lebih pendek, misalnya 10 detik, gagal cepat; timeout idle aliran diatur lebih panjang, misalnya 60 detik, memberi waktu model untuk berpikir. Keduanya berbagi 30 detik pasti akan bermasalah.
Keempat, retry hanya untuk error idempoten. Permintaan streaming begitu mulai mengeluarkan output, retry akan menyebabkan konten duplikat. Lapisan gateway harus membedakan: kegagalan pada fase pembentukan koneksi dapat di-retry, kegagalan setelah aliran mulai mengeluarkan output hanya bisa dilaporkan sebagai error ke frontend, membiarkan pengguna mengirim ulang secara manual. Jika batas ini tidak jelas, akan muncul "kalimat yang sama diucapkan dua kali".
Tabel pemetaan kode error harus dipelihara sendiri
Kode pembatasan laju keempat vendor: dua mengembalikan 429, satu mengembalikan 200 tetapi body berisi error, satu mengembalikan kode kustom. Lapisan gateway harus membangun tabel pemetaan, menyeragamkan berbagai error hulu menjadi kode error internal, misalnya RATE_LIMIT, AUTH_FAILED, CONTENT_BLOCKED. Lapisan bisnis melakukan degradasi berdasarkan kode internal, tidak lagi menebak semantik hulu. Tabel ini tidak ada jalan pintas, hanya bisa disusun satu per satu dengan tangkapan paket dicocokkan dengan dokumentasi. Platform agregat API model besar token8341 memberikan referensi pemetaan siap pakai dalam dokumentasinya, menghemat banyak waktu.
Dalam kondisi apa koneksi langsung justru lebih sederhana
Harus diakui, tidak semua skenario layak memakai gateway. Jika proyek hanya terhubung ke satu model, atau hanya melakukan inferensi batch non-streaming, koneksi langsung ke SDK resmi memiliki kode paling sedikit, jalur terpendek, dan penelusuran paling sederhana. Lapisan gateway menyelesaikan kompleksitas yang dibawa oleh "integrasi terpadu multi-model", dalam skenario model tunggal memasukkan gateway berarti satu lapisan penerusan tambahan dan satu titik kegagalan tambahan. Kriteria penilaiannya sangat langsung: model yang terhubung lebih dari dua, atau perlu berganti model saat runtime, barulah manfaat gateway melebihi biayanya.
Penutup
Insiden tersebut akhirnya diselesaikan dengan menambahkan lapisan gateway untuk menyatukan, logika penguraian SSE frontend dipangkas dari empat cabang menjadi satu set, tersendat dan anomali pemotongan pun hilang. Melihat ke belakang, masalahnya tidak pernah pada salah satu API model besar, melainkan pada "meneruskan langsung perbedaan protokol berbagai vendor ke lapisan bisnis". Jika Anda juga sedang mengerjakan integrasi multi-model, selaraskan dulu empat hal — protokol streaming, kode error, autentikasi, timeout — di lapisan gateway, baru bicarakan fitur lapisan atas.
Penulis: Sun Haoran
Tanggal publikasi: 25 Oktober 2026