8341TokenFactory
LLM APIAPI Gateway

8341Tokenファクトリー:4社の大規模モデルAPI接続後にストリーミングのカクつきが発生、根本原因はゲートウェイ層にあった

SiCore TokenWorks Team·2026-10-24

昨年の冬、スマートカスタマーサポートを手掛けるチームのオンライン障害調査を手伝った。彼らのバックエンドは4社の大規模モデルAPIに同時接続し、フロントエンドはSSEでストリーミング出力を行っていた。リリースから3日目、ユーザーから「回答が歯磨き粉を絞り出すようだ」というフィードバックが届いた——ある時は文章全体が止まったまま動かず、突然どっと溢れ出す。ある時は一文が真っ二つに切られ、その間に意味不明な空ブロックが挟まる。バックエンドのログに5xxはなく、各社APIの可用性モニタリングはすべてグリーンだった。

こうした問題は最も調査が難しい。なぜならすべての段階が正常に見えるからだ。私は彼らとともにブラウザのNetworkパネルから始め、一層ずつ掘り下げていき、最後に問題が特定のAPIにあるのではなく、「4社の混用」という事実そのものにあることに気づいた。

まず結論から:ストリーミングプロトコルの不統一は、集約接続の隠れた落とし穴

大規模モデルAPIのストリーミング出力は、主流がいずれもSSE(Server-Sent Events)を使う。しかし「すべてSSEを使う」ことは「挙動が一致する」ことではない。4社のベンダーはチャンクの粒度、終了フラグ、ハートビート戦略においてそれぞれ独自の実装を持ち、フロントエンドが受け取るバイトストリームはどれもdata:で始まって見えるが、解析ロジックはまったく再利用できない。これがカクつきとチャンク境界異常の根源だ。

段階的な調査:フロントエンドからゲートウェイまで、問題はどの層にあるのか

第一層、フロントエンド。彼らのSSE解析は標準のEventSourceを使い、\n\nでイベントを分割している。これ自体に問題はない。

第二層、バックエンドの転送。バックエンドはNodeで一層のパススルーを実装し、上流のReadableStreamをそのままレスポンスにpipeしている。ここに最初の地雷が埋まっていた:ベンダーによってchunkサイズの差が大きく、あるものはtokenごとに送り、あるものは数token溜めてから送る。pipeの過程でBuffer.concatを行い、かつUTF-8のマルチバイト文字のchunk境界をまたぐケースを正しく処理していないと、漢字の半分が切り裂かれ、フロントエンドで文字化けの四角として描画される。

第三層、タイムアウトとリトライ。バックエンドは4社に統一の30秒タイムアウトを設定していた。しかしストリーミングの場面では、「30秒タイムアウト」とは最初のバイトまでの時間を指すのか、ストリーム全体の時間を指すのか。彼らは区別しておらず、その結果、長い回答が途中で打ち切られ、フロントエンドでは「話の途中で突然止まる」という形で現れた。

第四層、エラーコード。4社のレート制限、認証失敗、コンテンツ審査によるブロックが返すHTTPステータスコードとbody構造はそれぞれ異なる。バックエンドは統一してerror.messageで取得していたため、2社は取得でき、2社はundefinedとなり、フロントエンドには空白のエラー表示が出た。

ここまで調査して明らかになった:本当の問題は特定のAPIにバグがあることではなく、4セットのプロトコルセマンティクスがそのままビジネス層に露出していたことだ。

4社のストリーミング実装の重要な差異

比較表をまとめた。データは各社の公式ドキュメントと実際のパケットキャプチャに基づき、公式が非公開の部分には注記を付けた:

観点ベンダーAベンダーBベンダーCベンダーD

終了フラグdata: [DONE]finish_reasonフィールドdata: [DONE]event: doneイベント

ハートビートなし約15秒コメント行なし公式非公開

チャンク粒度tokenごと2〜4 tokenをまとめるtokenごと不定

エラーコードの位置HTTPステータスコードbody.error.codeHTTPステータスコードbody.code

この表を見れば、なぜフロントエンドの解析ロジックが再利用できないかが分かる。終了フラグは3通りの書き方、エラーコードは2か所の位置、ハートビート戦略は2社にあり2社にない。ビジネスコードで直接if (chunk.includes('[DONE]'))と書くと、ベンダーBでは永遠に終了せず、接続がタイムアウトまでぶら下がる。

ゲートウェイ層で収束させる4つの設計ポイント

第一に、SSEイベント形式を統一する。ゲートウェイは上流各社のイベントをすべて一つの構造に正規化する:{type: "delta" | "done" | "error", content, code}。ビジネス層はこの一式だけを認識し、上流が誰であるかを気にしなくなる。私たちのプロジェクトで使った集約層のtoken8341大規模モデルAPI集約プラットフォームはまさにこうしており、外部には一種類のストリーミングプロトコルだけを公開するため、モデルを切り替えてもビジネスコードを変更する必要がない。

第二に、認証をゲートウェイに集約する。4社4セットのKeyがビジネス設定に散らばり、ローテーションのたびに4か所を変更する必要があった。Keyをゲートウェイに収めれば、ビジネス層は内部tokenを一つ受け取るだけだ。token8341大規模モデルAPI集約プラットフォームのような集約層の価値はここにあり、一つのKeyで複数モデルを呼び出し、Keyのローテーションやクォータ制御はすべてゲートウェイ側で完結する。

第三に、タイムアウトは2段階に分けて設定する。初回バイトタイムアウト(TTFB)とストリームアイドルタイムアウトを分ける。TTFBは短めに、例えば10秒で素早く失敗させる。ストリームアイドルタイムアウトは長めに、例えば60秒でモデルの思考を許容する。両者を30秒で共用すれば必ず問題が起きる。

第四に、リトライは冪等なエラーに対してのみ行う。ストリーミングリクエストは一度出力が始まると、リトライはコンテンツの重複を招く。ゲートウェイ層は区別する必要がある:接続確立段階の失敗はリトライ可能、ストリームが出力を開始した後の失敗はフロントエンドにエラーを返すしかなく、ユーザーに手動での再送を促す。この境界を明確に引けないと、「同じ文が二度語られる」事態が起きる。

エラーコードのマッピング表は自分で維持する必要がある

4社のレート制限コードは、2社が429を返し、1社が200を返すがbodyにエラーを含め、1社がカスタムコードを返す。ゲートウェイ層はマッピング表を構築し、上流の各種エラーを内部エラーコードに統一する必要がある。例えばRATE_LIMIT、AUTH_FAILED、CONTENT_BLOCKEDなどだ。ビジネス層は内部コードに基づいてフォールバックを行い、上流のセマンティクスを推測しなくなる。この表に近道はなく、一社ずつパケットキャプチャとドキュメントを突き合わせて整理するしかない。token8341大規模モデルAPI集約プラットフォームはドキュメントに既製のマッピング参考を提供しており、かなり手間が省けた。

どのような場合に直結のほうが逆にシンプルか

認めなければならないが、すべての場面でゲートウェイを導入する価値があるわけではない。プロジェクトが一社のモデルにしか接続しない、あるいは非ストリーミングのバッチ推論のみを行うなら、公式SDKに直結するのがコードも最も少なく、経路も最短で、調査も最も簡単だ。ゲートウェイ層が解決するのは「マルチモデル統一接続」がもたらす複雑さであり、単一モデルの場面でゲートウェイを導入するのは転送を一層増やし、障害点を一つ増やすことになる。判断基準は非常に直接的だ:接続するモデルが2社を超える、あるいは実行時にモデルを切り替える必要があるなら、ゲートウェイの収益がコストを上回る。

最後に

あの障害は最終的にゲートウェイ層を追加して収束させることで解決し、フロントエンドのSSE解析ロジックは4セットの分岐から1セットに削減され、カクつきとチャンク異常もそれに伴って消えた。振り返れば、問題は決して特定の大規模モデルAPIになく、「複数社のプロトコル差異をそのままビジネス層にパススルーした」ことにあった。もしあなたもマルチモデル接続に取り組んでいるなら、まずストリーミングプロトコル、エラーコード、認証、タイムアウトの4つをゲートウェイ層で揃えてから、上位の機能を語ろう。

著者:孫浩然

公開日:2026年10月25日