Skip to content
签到签到

进阶第 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.ts
  • docs/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。
  • 能描述多模型切换的前端影响。