DeepSeek Harness LLM 模块:模型 Adapter、Streaming、Retry 与 Token Meter
JotaroRD

模块定位

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
2
3
4
5
6
7
flowchart LR
A["agent/request"] --> B["ctx.llm.prepareCall"]
B --> C["adapter registration"]
C --> D["llm/stream"]
D --> E["StreamChunk*"]
E --> F["assistant/chunk*"]
F --> G["assistant/message"]

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
2
3
4
5
6
7
8
9
sequenceDiagram
participant Loop
participant LLM
participant Retry
Loop->>LLM: dispatch prepared call
LLM-->>Loop: adapter failure
Loop->>Retry: agent/request-error
Retry-->>Loop: retry or unhandled
Loop->>LLM: retry if handled

Token Meter

Token meter 不是简单对当前 messages 估算一次。它要和 session replay、usage chunks、assistant/message usage fallback 配合。它的目标是让 token usage 和历史投影能被一致解释。

配置与凭据

DeepSeek adapter 读取:

1
2
DEEPSEEK_API_KEY=sk-...
DEEPSEEK_BASE_URL=https://... # 可选

真实 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.md
  • packages/llm/llm/README.md
  • packages/llm/llm-deepseek/README.md
  • packages/llm/llm-retry/README.md
  • docs/subsystems/llm-streaming.md