一个 SSE 流,七个 LLM 提供商
我构建了一个支持七种不同 LLM 提供商的 Next.js 应用。
OpenAI、Claude、Gemini、Ollama、Mistral、Groq 和 Azure。
我设定了一个严格的规则:浏览器对每个提供商必须使用完全相同的代码路径。
这很难,因为这些 API 并不相同。它们使用不同的传输方式,发送不同的数据结构。有些使用 SSE,而另一些则使用 NDJSON。
如果你让这些差异渗透到 UI 层,你的代码就会变成一堆乱七八糟的 "if" 语句。每当你增加一个提供商,你的前端就会变得更加复杂。
我通过创建一个统一的契约解决了这个问题。每个提供商必须向浏览器发送以下格式:
• data: {"delta":"
浏览器只需要理解三件事:delta、error 和 [DONE]。
以下是我的实现方式:
使用 Async Generators 我将每个提供商视为一个产出纯文本的生成器。这隐藏了 API 的复杂性。
包装器模式 (The Wrapper Pattern) 我创建了一个名为
createSSEStream的包装函数。这个包装器负责管理传输格式,并确保流始终能正常结束。即使某个提供商在中途失败,包装器也会发送一个错误和 [DONE] 信号,从而防止客户端挂起。归类相似的 API OpenAI、Mistral、Groq 和 Azure 都使用相同的协议风格。我为它们编写了统一的实现。现在,添加一个新的兼容提供商只需要一行代码。
处理特殊情况 Anthropic 和 Ollama 的工作方式不同。Anthropic 使用特定的类型化事件,而 Ollama 使用 NDJSON。我为它们编写了自定义解析器,但它们最终都会将文本产出到同一个包装器中。浏览器对此完全感知不到差异。
隐私与简洁性 该应用采用“自带密钥 (Bring Your Own Key)”模式。
• 用户粘贴自己的 API 密钥。 • 密钥保存在本地存储中。 • 服务器仅充当纯代理。 • 密钥绝不会存储在数据库中。
这种方法消除了对复杂身份验证或密钥管理的需求,使应用易于自托管。
教训很简单:将每次集成都建模为你真正想要的内容的生成器。对其进行一次包装。让差异存在于生成器中,从而保持主逻辑的整洁。
可选学习社区:https://t.me/GyaanSetuAi