模块定位
packages/llm 是模型能力家族。它把“模型请求抽象”和“具体 provider 适配器”分开,让 loop 不绑定 DeepSeek、Pi AI 或其他未来 provider。
源码入口:
packages/llm/llm/packages/llm/llm-deepseek/packages/llm/llm-pi-ai/packages/llm/llm-retry/packages/llm/token-meter/docs/subsystems/llm-streaming.md
LLM Seam 的角色
这个能力家族包含:
| 包 | 角色 |
|---|---|
llm/ |
Service Definition、消息/内容块词汇、stream chunk assembler |
llm-deepseek/ |
DeepSeek API adapter |
llm-pi-ai/ |
多 provider pi-ai adapter |
llm-retry/ |
provider-scoped retry policy |
token-meter/ |
replay-aware token measurement |
Loop 只知道 ctx.llm,不关心 provider wire protocol。
Streaming 为什么要统一词汇
不同模型 provider 的 streaming 格式不一样。Harness 在 llm 包里定义统一 content blocks、message、stream chunk、request config、adapter failure 类型。
Adapter 负责把 provider wire 转成 Harness stream vocabulary。Loop 再把 chunks 写成 durable assistant/chunk,最终形成 assistant/message anchor。
1 | flowchart LR |
Adapter Defaults
prepareCall() 会让 adapter materialize 自己拥有的默认值,例如 reasoning effort 或 max tokens。Loop 会把 effective config 写进 request/header,并标记哪些字段来自 adapter default。
为什么这么细?因为下一步请求、resume、route change 都要区分:
- 用户显式设置。
- adapter 当时自动补的默认值。
- 新 provider 需要重新 materialize 的字段。
Retry 不在 Adapter 里
llm-retry 监听 agent/request-error,按 provider scoped policy 做 backoff 和 retry。这样 retry 是可替换 consumer,不污染具体 adapter,也不会要求 loop 内置某个 provider 的错误策略。
失败路径大致是:
1 | sequenceDiagram |
Token Meter
Token meter 不是简单对当前 messages 估算一次。它要和 session replay、usage chunks、assistant/message usage fallback 配合。它的目标是让 token usage 和历史投影能被一致解释。
配置与凭据
DeepSeek adapter 读取:
1 | DEEPSEEK_API_KEY=sk-... |
真实 API e2e 没 key 会自跳过。生产运行没有 key 时,模型请求会在 provider 路径清晰失败,而不是在 loop 里猜测原因。
常见坑
第一,不要让工具或 UI 直接依赖某个 provider adapter。它们应该依赖 ctx.llm 或 session projection。
第二,adapter private replay state 属于 assistant message provenance,不能随便丢。
第三,重试策略要绑定 provider facts,不能对所有错误盲目同等 retry。
第四,请求 header 不增加模型 token,但它是重建请求的关键事实。
继续读源码
packages/llm/README.mdpackages/llm/llm/README.mdpackages/llm/llm-deepseek/README.mdpackages/llm/llm-retry/README.mddocs/subsystems/llm-streaming.md