Skip to content

4.2 添加一个 LLM 适配器

本章概览:接入一个新模型提供方的完整方法:适配器基本形态、协议义务(usage、工具参数、错误路径、signal、replayState)、精确模型元数据与验证要求。

基本形态

ts
class MyAdapter extends LlmAdapter {
  async * stream(options: GenerateOptions): AsyncIterable<StreamChunk> { … }
}

export const name = 'llm-myprovider'
export const inject = ['llm']
export const Config: z<Config> = z.object({ apiKey: z.string(), … })

export function apply(ctx: Context, config: Config) {
  ctx.llm.registerAdapter(['my-provider'], new MyAdapter(…))
}

要点:

  • 注册基于副作用,可安全支持 HMR;每个 provider 路由仅一个适配器,重复注册抛错,多路由注册全有或全无;
  • options.provider 选择适配器,options.model 是提供方模型 ID(动态模型目录适配器无需重新配置即可提供新模型);
  • 密钥管理:schemastery Config 带环境变量回退,通过 cordis.yml 的 !!js process.env.MY_KEY 注入;切勿在代码里读取自行约定的密钥文件
  • 参考实现:llm-deepseek(直接 HTTP,SSE 由 eventsource-parser 分帧)、llm-pi-ai(封装 LLM 库)。先读 packages/llm/llm/src/types.tsStreamChunk 文档。

出处:docs/cookbook/adding-an-llm-adapter.mddeepseek-harness-src/docs/cookbook/adding-an-llm-adapter.md

协议义务(两个实现共同验证的约定)

  1. finish 之前发出 usagefinish 之后不再发出任何内容。稳健做法:缓冲 finish/usage 直到提供方流的结束标记再统一 flush;
  2. 工具调用的 arguments 全程是原始 JSON 字符串;流式片段以 argumentsDelta 发送。提供方返回已解析对象时,在 block-end 重新 stringify;
  3. 按首次出现的流顺序分配块 index;同一块的每次 delta 复用该 index;
  4. 错误只有两条合法路径:从 stream() 抛出(传输与协议故障——用带稳定 code 的 LlmError),或以 finish {kind: 'error' | 'aborted'} 结束流(提供方带内故障)。按故障类别选路径并文档化;
  5. 遵守 options.signal(传给 fetch 或你的 SDK);
  6. 不支持的字段抛 LlmError(..., 'UNSUPPORTED'),不静默丢弃(例如提供方不支持 stop sequences);
  7. finish.replayState:提供方后续调用需要的响应 ID/签名等原生元数据,投影为最小无损 JSON 发出。重建历史时验证该状态;状态缺失时,不得仅凭提供方/模型名称推断原生回放LlmRuntime 只在历史路由与目标路由当前由完全相同的适配器实例拥有时才传递它。

精确模型元数据

实现 resolveModel() 提供 provider/model 身份与可选 contextreasoning 字段:

  • 仅当存在配置指定的默认值时声明 defaultEffort;遵守解析模型时的可选 AbortSignal
  • reasoning effort 是由适配器映射到提供方请求的有序不透明 ID:保留适配器给出的权威可选列表(含适配器支持时定义的 off);不暴露最终协议值的具体拼写,不自动调整不支持的值;ID 无需与其协议表示相同;
  • 提供方特有思考模式开关放在适配器的 Config 中。

实现结构

让协议格式类型、请求序列化、传输解析、分片转换、适配器类分别承担独立职责(llm-deepseek 是参考布局)。

验证

遵循仓库测试策略(docs/testing.md):适配器覆盖、真实提供方检查(test:e2e,需 DEEPSEEK_API_KEY)、已发布入口要求(keyless snapshot)。

小结

接入新模型 = 写一个 LlmAdapter + 注册。最难的部分是协议义务——尤其"错误两条路径"与 replayState,它们保证会话日志可回放、恢复后可继续。更完整的适配器语义见 2.4 LLM 层

参考资料

本文档为学习用途的原创讲解,基于 MIT 许可的开源项目 deepseek-ai/deepseek-harness 编写;所有引用均注明出处。