Appearance
2.4 LLM 层:适配器与流式调用
本章概览:
ctx.llm是 DSH 与模型世界之间的"普通话"——provider 中立的词汇表、适配器注册表、单一流式调用 API,以及配套的 token 计量与重试策略。
定位:provider 中立的语言
dsh-llm 定义"agent loop、会话日志与每个插件都讲的规范语言"。它做两件事:
- 适配器注册表:provider 路由(如
deepseek)→ 适配器实例; - 单一流式调用 API:
ctx.llm.stream(options): AsyncIterable<StreamChunk>,可经llm/streamwaterfall 拦截。
出处:
packages/llm/llm/README.md(deepseek-harness-src/packages/llm/llm/README.md)
适配器注册与替换
ts
ctx.llm.registerAdapter(['deepseek'], adapter) // 返回 AdapterRegistrationHandle
handle.replace(['deepseek', 'pi-ai']) // 原子替换路由集- 注册全有或全无,随调用 fiber dispose;
replace()先整体校验候选路由集再动任何东西——与另一适配器冲突时当前路由保持注册与服务;- 适配器是
LlmAdapter子类,可覆盖providerRetryPolicy()、providerInfo()、异步listModels()、resolveModel()(精确身份/容量/输出默认/推理努力)。
流式协议:单一终态
ctx.llm.stream() 把来自适配器抛错 / finish {kind:'error'|'aborted'} / 迭代器构造失败 / 迭代失败全部规范化为流协议的唯一终态:finish { kind: 'error' | 'aborted', failure }。消费者用 BlockAssembler 把 token 级 delta 组装成块/消息。
IMPORTANT
契约双端遵守(defensive-patterns):LlmAdapter.stream() 实现可以抛错或发终态,但 LlmRuntime.stream() 只以终态 finish 暴露模型请求失败;中间件、嵌套调用、适配器清理、下游消费者的缺陷保持抛出——消费者无需猜测异常来自提供方、包裹器、chunk 日志还是自己的组装。
调用生命周期:prepareCall
ctx.llm.prepareCall(config, signal) 在一次精确模型查找中解析配置 + 脱离的上下文元数据 + "哪些字段由适配器默认提供"标记,并捕获当前适配器注册与不可变重试策略,返回一次性 PreparedLlmCall——防止 HMR 把一个适配器的能力结果与另一个适配器的请求组合。复用该句柄或改其字段即 INVALID_PREPARED_CALL。
重试策略
- 注册时捕获 provider 拥有的重试策略(
ctx.llm.providerRetryPolicy(provider)返回,带正常默认解析); - 已发出 chunk 后的重试没有持久尝试边界——所以官方 shipped 的重试策略走
agent/request-error事件而非llm/stream包裹器(packages/llm/llm-retry 与 2.2 轮次流程 的恢复瀑布)。
token 计量:token-meter
ctx.tokenMeter(llm/token-meter)拥有隔离的每会话回放折叠(replay folds),压力消费者共享不可变、带修订号的测量值。它是压缩(compaction)压力触发的基础(见 3.9)。
出处:
docs/capability-seams.md(ctx.tokenMeter行)
模型发现与可配置 provider
ctx.llm.registerModelDiscovery(settingsNs, discover):提供方声明"能询问端点广告哪些模型"(配置期工作,凭据只用于这一次询问、不存储);ctx.llm.registerConfigurableProviders(entries):声明适配器可通过配置激活的 provider 路由(注册或休眠),配置界面据此显示;ctx.llm.listProviders()/listModels()是发现面,不是路由白名单:消费者不得因模型未列出而拒绝请求;llm/adapters-updated事件在每次拓扑提交点(注册/注销/目录增删)后发出,消费方重读而非轮询。
精确模型元数据
resolveModelInfo() 向拥有精确 provider/model 路由的适配器询问一次:context、defaultMaxTokens(适配器配置的每请求输出上限,仅请求省略 maxTokens 时物化)、reasoning(不透明适配器自有标识符)。resolveCallConfig() 校验显式 effort 并物化适配器默认(不 clamp)。
适配器全家
| 适配器 | 说明 |
|---|---|
llm-deepseek | DeepSeek provider(生产默认) |
llm-pi-ai | Pi AI provider |
llm-replay | 测试支持:确定性回放 |
出处:
packages/llm/llm-deepseek/README.md与packages/test-support/llm-replay/README.md
小结
LLM 层用"注册表 + 单一流式 API + 契约化终态"把模型世界封装成可替换、可拦截、可计量的能力缝。添加新 provider = 写一个 LlmAdapter 并注册(教程见 4.2 添加一个 LLM 适配器)。
参考资料
- packages/llm/llm/README.md — LLM 词汇与适配器 seam(
deepseek-harness-src/packages/llm/llm/README.md) - docs/subsystems/llm-streaming.md — LLM 流式子系统页(
deepseek-harness-src/docs/subsystems/llm-streaming.md) - packages/llm/token-meter/README.md — token 计量
- docs/defensive-patterns.md — "Honor public contracts on BOTH sides"