1つのSSEストリーム、7つのLLMプロバイダー

7つの異なるLLMプロバイダーをサポートするNext.jsアプリを構築しました。

OpenAI、Claude、Gemini、Ollama、Mistral、Groq、そしてAzure。

私は一つの厳格なルールを設けました。「ブラウザは、すべてのプロバイダーに対して全く同じコードパスを使用しなければならない」というルールです。

これは困難な作業です。なぜなら、これらのAPIは同一ではないからです。トランスポート方式が異なり、送信されるデータの形式(shape)も異なります。SSEを使用するものもあれば、NDJSONを使用するものもあります。

もしこれらの違いをそのままUIまで持ち込んでしまうと、コードは「if」文の塊で混乱してしまいます。プロバイダーを追加するたびに、フロントエンドの複雑さが増していくことになります。

私は、単一の「コントラクト(規約)」を作成することでこれを解決しました。すべてのプロバイダーは、ブラウザに対して以下の形式でデータを送信する必要があります。

• data: {"delta":""} • data: {"error":""} • data: [DONE]

ブラウザが理解すべきことは、delta、error、[DONE] の3つだけです。

構築方法は以下の通りです。

  1. 非同期ジェネレーター(Async Generators)を使用する 各プロバイダーを、プレーンテキストをyield(生成)するジェネレーターとして扱います。これにより、APIの複雑さを隠蔽できます。

  2. ラッパーパターン createSSEStream というラッパー関数を作成しました。このラッパーが通信フォーマットを管理します。また、ストリームが必ず終了するように制御します。プロバイダーが途中で失敗した場合でも、ラッパーがエラーと [DONE] シグナルを送信するため、クライアントがハングアップするのを防げます。

  3. 類似したAPIのグループ化 OpenAI、Mistral、Groq、Azureはすべて同じ形式(方言)を使用しています。私はこれらすべてに対して一つの実装を書きました。これにより、互換性のある新しいプロバイダーの追加は、わずか1行のコードで済むようになりました。

  4. 特異なケースへの対応 AnthropicとOllamaは動作が異なります。Anthropicは特定の型付きイベントを使用し、OllamaはNDJSONを使用します。これらにはカスタムパーサーを書きましたが、どちらも最終的には同じラッパーにテキストをyieldします。ブラウザ側はその違いを意識することはありません。

プライバシーとシンプルさ このアプリは「Bring Your Own Key(独自のAPIキーを持ち込む)」モデルを採用しています。

• ユーザーは自身のAPIキーを貼り付けます。 • キーはローカルストレージに保持されます。 • サーバーは純粋なプロキシとして機能します。 • キーがデータベースに保存されることはありません。

このアプローチにより、複雑な認証やシークレット管理の必要性がなくなります。また、アプリのセルフホストも容易になります。

教訓はシンプルです。各統合(インテグレーション)を、「実際に欲しいもの」を生成するジェネレーターとしてモデル化してください。それを一度ラップします。差異はジェネレーターの中に閉じ込め、メインのロジックをクリーンに保つのです。

出典: https://dev.to/ikeli0320/one-sse-stream-seven-llm-providers-giving-a-nextjs-app-a-single-streaming-code-path-1fh2

オプションの学習コミュニティ: https://t.me/GyaanSetuAi