# Conflicts: # .agents/notes/implemented/architecture/2026-06-21-bounded-llm-request-recovery.md # docs/architecture.i18n.yaml # docs/config-catalog.md # docs/cordis-catalog/events.md # docs/cordis-catalog/services.md # docs/core-data-structures/llm-streaming.i18n.yaml # docs/core-data-structures/llm-streaming.md # docs/core-data-structures/llm-streaming.zh.md # docs/event-producer-consumer.md # docs/module-graph.md # examples/headless-agent/tests/headless.snapshot.ts # packages/compact/compact-basic/tests/compact-loop-repro.spec.ts # packages/cordis/tool-cordis/src/api-catalog.ts # packages/core/agent-loop/README.md # packages/core/agent-loop/src/loop.ts # packages/examples/agent-spine-demo/README.md # packages/llm/README.md # packages/llm/llm-deepseek/src/adapter.ts # packages/llm/llm-pi-ai/src/adapter.ts # packages/llm/llm-retry/README.md # packages/llm/llm/README.md # packages/llm/llm/src/index.ts # packages/llm/llm/tests/service.spec.ts # packages/support/llm-replay/src/index.ts # packages/support/llm-replay/tests/llm-replay.spec.ts
53 lines
4.5 KiB
Markdown
53 lines
4.5 KiB
Markdown
# `@deepseek-ai/dsh-llm-retry`
|
||
|
||
[English](README.md) | 中文
|
||
|
||
一个函数插件,在 agent loop 的已关闭步骤恢复 seam 上应用确切提供方的重试策略。它不包装 `ctx.llm.stream()`:每次适配器调用仍是一次提供方尝试,每次重试都会开启新的编号步骤。
|
||
|
||
每个提供方适配器都拥有可选的嵌套 `retryPolicy`;路由在 `ctx.llm` 上注册时会捕获该策略,任何到达该注册最终适配器边界的调用都会携带它。如果之后释放或替换路由,进行中的失败仍会保留为其提供服务的策略;在选中任何最终适配器前发生的失败没有提供方策略,会继续委托。省略策略时使用 normal mode:为 `EMPTY_RESPONSE`、`RATE_LIMIT`、`SERVER`、`TIMEOUT` 和 `TRANSPORT` 重试两次,并采用从 500 ms 到 10 秒的有界指数退避与 10% jitter。`EMPTY_RESPONSE` 是适配器对未产生任何持久内容的退化提供方完成所作的分类,因此可安全重复。normal 策略可以更改其有限预算、合格 code 和退避配置。always mode 会先请求下游恢复,再无次数上限地重试每个模型请求失败;成功、取消或插件 dispose 会在活跃的委托恢复完全停稳后终止它。
|
||
|
||
两种 mode 都使用带对称 jitter 的有界指数退避。有效 `providerRetryAfterMs` 不超过 `maxDelayMs` 时会替换本地退避,并且不加 jitter。超出上限的提供方延迟会使 normal mode 继续委托;always mode 则改用已配置的本地退避,避免该指令终止重试。
|
||
|
||
等待之前,插件会追加一个非表层 `llm/retry` 事件,携带提供方、mode、规范的解析策略 key、失败与计划延迟。该 key 包含每个会影响行为的字段,并会对 normal mode 的 code 排序,因为合格性采用集合成员关系判断。只有提供方与完整策略 key 相同的事件才会延续重试编号,因此,如果替换后的路由具有不同的限制、code 成员关系或退避配置,就会开启自己的历史。normal 事件包含有限上限;always 事件省略该上限,UI 会渲染 `∞`。取消与插件 dispose 会中止活跃退避,在应用中止前排空活跃的委托恢复,并使 dispose 前捕获的 callback 快速失败。
|
||
|
||
单独发布的 `./invariant` 配套模块会检查每个重试记录是否指向当前开启轮次及其最新已关闭步骤,是否与失败请求的持久提供方匹配,是否携带非空的提供方与策略身份,是否满足 mode 特定边界,是否拥有唯一步骤记录和正确的提供方策略重试编号,以及是否携带有界定时器延迟。完整 jitter 可以在下界调度为零毫秒。
|
||
|
||
```yaml
|
||
- name: '@deepseek-ai/dsh-llm-deepseek'
|
||
config:
|
||
apiKey: !!js process.env.DEEPSEEK_API_KEY
|
||
retryPolicy:
|
||
mode: always
|
||
backoff:
|
||
initialDelayMs: 1000
|
||
maxDelayMs: 30000
|
||
jitterRatio: 0.2
|
||
|
||
- name: '@deepseek-ai/dsh-llm-retry'
|
||
```
|
||
|
||
执行器没有策略配置。`dsh-llm-pi-ai` 等多提供方适配器会把 `retryPolicy` 放在每个提供方 profile 内,避免维护第二份提供方名称列表。
|
||
|
||
## 模型体验
|
||
|
||
### 模型请求恢复
|
||
|
||
#### 模型看到的内容
|
||
|
||
模型不会看到重试事件、延迟、提供方错误或失败的部分输出。下一个编号步骤会从持久表层历史中重建相同的显式提供方/模型请求,除非下游恢复策略有意更改该表层。
|
||
|
||
#### Token 影响
|
||
|
||
每次重试都是新的提供方请求,可能重复计费输入 token。normal mode 具有有限预算;always mode 可以在成功或取消前消耗无界数量的请求。`llm/retry` 自身不产生 token。
|
||
|
||
#### KV Cache 影响
|
||
|
||
重建请求保留之前的前缀,并可根据该提供方的规则复用 cache。非表层重试事件不会改变 cache 身份。
|
||
|
||
## 已知限制与暂缓事项
|
||
|
||
- **Agent 步骤是唯一重试边界**:直接 `ctx.llm.stream()` 消费方仍只尝试一次,因为原始流无法将已发出 chunk 持久分隔为不同尝试。
|
||
- **always mode 会重试永久性失败**:身份验证、配额、无效请求、协议和无法恢复的上下文错误都会继续重试,直至成功、取消或 dispose;部署负责提供方特定的成本与延迟控制。
|
||
- **恢复策略按 waterfall 顺序组合**:always mode 会先接受下游的重试,再应用自己的回退。后续策略如果忽略取消且永不结算,也会阻止回退、轮次完全停稳和插件 dispose 完成。
|
||
- **`llm/retry` 记录调度,不是完成**:后续步骤与轮次事件用于确立成功、耗尽或取消。
|