进阶第 5 章:LLM Provider Seam
业务问题
业务会切换模型供应商,也可能同时使用多个模型。前端和 Agent 核心都不能绑死某一个供应商。
交互图解:一次 Agent Turn 完整调用链 · 查看图解导读
核心原理
Provider Seam 通常包含三部分:
- Service Definition:定义消息、请求、流和错误。
- Service Provider:实现具体模型协议。
- Consumer:Agent Loop 或业务代码。
Provider 只负责把 GenerateOptions 转成 StreamChunk。路由、默认值、重试策略和流式调用由上层管理。
真实项目中的映射
AI 编码助手 / 客服 Agent 可能需要:
- 默认模型用于普通对话。
- 更强模型用于复杂任务。
- 便宜模型用于摘要和标题。
- 切换模型时前端要展示模型名和能力。
DeepSeek Harness 对应实现
DeepSeek Harness 的 LlmRuntime 管理 adapter registry。
prepareCall() 返回一次性 prepared call,固定 adapter registration。这样可以避免异步解析和流式 dispatch 之间混用不同 adapter。
llm/stream 是 waterfall,listener 可以记录、重试或短路模型流。
进一步阅读:
packages/llm/llm/src/index.tsdocs/subsystems/llm-streaming.md
代码示例
ts
interface LlmAdapter {
provider: string
stream(request: unknown): AsyncIterable<string>
}
class LlmRuntime {
private adapters = new Map<string, LlmAdapter>()
register(adapter: LlmAdapter): void {
this.adapters.set(adapter.provider, adapter)
}
async stream(provider: string, request: unknown): Promise<AsyncIterable<string>> {
const adapter = this.adapters.get(provider)
if (!adapter) throw new Error(`no adapter for "${provider}"`)
return adapter.stream(request)
}
}前端不需要知道 provider 协议,只需要知道 provider 名称和可用的流式接口。
面试追问
问:为什么要抽象 LLM Provider?
回答要点:
- 支持多模型。
- 便于替换供应商。
- 便于测试和 mock。
- 避免业务代码绑定具体 SDK。
问:结构化输出和普通文本输出有什么区别?
回答要点:
- 结构化输出要求模型遵守 schema。
- 前端需要处理 partial JSON。
- 普通文本直接流式展示。
- 结构化输出更适合工具参数和表单生成。
问:切换模型时前端要注意什么?
回答要点:
- 模型能力不同。
- 最大输出 token 不同。
- 上下文窗口不同。
- 错误和重试策略可能不同。
实践任务
设计一个 LlmPort 接口,分别用一个 mock provider 和一个真实 provider 实现,说明前端如何做到无感切换。
验收标准
- 能解释 Provider Seam 三角色。
- 能说明
prepareCall()为什么固定 adapter。 - 能描述多模型切换的前端影响。