chore: 删除临时计划文件 PLAN.md
This commit is contained in:
parent
457f6b2408
commit
949ba650ee
129
PLAN.md
129
PLAN.md
@ -1,129 +0,0 @@
|
||||
# 模型访问重试机制 — 第一性原理分析
|
||||
|
||||
## 一、问题本质
|
||||
|
||||
### 1.1 为什么需要重试?
|
||||
LLM 请求存在**客观的瞬态失败**:网络抖动、服务端 502/503/504、限流 429、连接重置。这些故障在秒级内可自愈,但当前代码遇到即终止整个用户回合,用户必须手动重发。这是体验断裂点。
|
||||
|
||||
### 1.2 重试的本质权衡
|
||||
重试 = 用 **资源(额外请求/计费)+ 延迟(等待+退避)** 换取 **成功概率提升**。
|
||||
|
||||
边界条件:
|
||||
- **可恢复错误**重试有意义:timeout、502/503/504、429、connection reset
|
||||
- **不可恢复错误**重试是纯浪费:401/403(认证)、400(参数)、404(模型不存在)、内容审查拒绝、token 超限
|
||||
- **用户取消**必须立即生效,重试不能凌驾于取消之上
|
||||
|
||||
### 1.3 重试的副作用
|
||||
| 副作用 | 严重性 | 对策 |
|
||||
|--------|--------|------|
|
||||
| 计费翻倍 | 中 | 限制次数,仅对瞬态错误 |
|
||||
| 请求放大加剧服务过载 | 低(单机场景) | 退避等待 |
|
||||
| 流式已 emit 内容后重试→重复输出 | 高 | 流式仅在建连阶段重试 |
|
||||
| 延迟累积(N×请求+退避) | 中 | 退避不宜过长 |
|
||||
|
||||
## 二、架构决策的第一性原理
|
||||
|
||||
### 2.1 重试决策权归属
|
||||
原则:**决策权给最了解错误语义的层**,同时**不破坏既有解耦边界**。
|
||||
|
||||
**决策:AgentLoop 层重试**。理由:
|
||||
1. cancel_token 在 AgentLoop,重试与取消协作自然(Provider 内部重试无法响应取消)
|
||||
2. memory_maintenance 已采用"调用方重试"模式,保持一致
|
||||
3. 不修改 LLMProvider trait,零侵入
|
||||
|
||||
**承认的既有债务**:AgentLoop 的 `is_recoverable_llm_error()` 字符串匹配本身就是解耦缺陷——业务层不该知道 "504" 是 HTTP 错误。根因是 trait 返回 `Box<dyn Error>` 丢失类型信息。彻底解耦需给 trait 加类型化错误 enum,超出本次范围,沿用既有字符串匹配作为增量改进。
|
||||
|
||||
### 2.2 配置层级归属(保留解耦边界)
|
||||
|
||||
**关键约束**:`ProviderRuntimeConfig` 的语义是"构造 provider 实例的最小参数包"——其每个字段都被 `create_provider()` 消费。`max_retries` 不参与 provider 构造,塞进去会破坏该语义。
|
||||
|
||||
**决策**:
|
||||
- 用户配置层:`ProviderConfig.max_retries`(与 `llm_timeout_secs` 同级,符合"provider 级网络参数"分组)
|
||||
- 聚合配置层:`LLMProviderConfig.max_retries`(透传)
|
||||
- **不进 `ProviderRuntimeConfig`**(保持 provider 构造包纯净)
|
||||
- 改放进 `AgentRuntimeConfig.max_retries`(该结构本就含 `max_tool_iterations` 等 agent 行为参数,"agent 对 provider 瞬态失败的容忍策略"归属 agent 行为层合理)
|
||||
|
||||
```
|
||||
config.json ProviderConfig.max_retries (用户配置)
|
||||
↓
|
||||
LLMProviderConfig.max_retries (聚合配置)
|
||||
↓
|
||||
AgentRuntimeConfig.max_retries (agent 行为参数)
|
||||
↓
|
||||
AgentLoop.runtime_config.max_retries (业务层读取)
|
||||
↓
|
||||
chat_with_retry() (AgentLoop 内部循环)
|
||||
```
|
||||
|
||||
`ProviderRuntimeConfig` 保持不变。
|
||||
|
||||
### 2.3 退避策略
|
||||
原则:**退避长度应匹配故障恢复时间尺度**。
|
||||
|
||||
LLM 服务瞬态故障通常秒级恢复。指数退避 1s/2s/4s 总等待 7s,对单机本地代理场景已足够。
|
||||
|
||||
**决策:硬编码指数退避 `[1000, 2000, 4000]` ms,不暴露给用户。** 理由:
|
||||
- 单机场景无需 jitter(jitter 解决分布式客户端同步重试,单机不存在)
|
||||
- 退避细节是实现策略,非用户可调参数(YAGNI)
|
||||
- 用户只关心"重试几次",不关心"等多久"
|
||||
|
||||
### 2.4 流式重试的边界
|
||||
**核心矛盾**:`chat_with_streaming` 一旦 emit delta,重试会重复输出。
|
||||
|
||||
**决策:流式调用仅在"未 emit 任何 delta"时重试。** 用 `Arc<AtomicBool>` 跟踪 emit 状态,首次 delta 后置 true,true 时不再重试。
|
||||
|
||||
这覆盖了最常见的瞬态场景:建连失败、首次响应超时。已开始流式传输后的失败通常是网络中断,重试意义不大且会重复。
|
||||
|
||||
## 三、实施计划
|
||||
|
||||
### 后端(4 个文件)
|
||||
|
||||
**1. `src/config/mod.rs`**
|
||||
- `ProviderConfig` 增 `max_retries: u32`(`#[serde(default = "default_max_retries")]`,默认 3)
|
||||
- `LLMProviderConfig` 增 `max_retries: u32`
|
||||
- `resolve_provider_config()` 和 `override_provider_model()` 传递该字段
|
||||
- 新增 `fn default_max_retries() -> u32 { 3 }`
|
||||
- **不改 `ProviderRuntimeConfig`**
|
||||
|
||||
**2. `src/providers/traits.rs`**
|
||||
- **保持不变**(`ProviderRuntimeConfig` 不增字段,保持 provider 构造包纯净)
|
||||
|
||||
**3. `src/agent/runtime_config.rs`**
|
||||
- `AgentRuntimeConfig` 增 `max_retries: u32`
|
||||
- `From<LLMProviderConfig>` 传递该字段
|
||||
|
||||
**4. `src/agent/agent_loop.rs`**
|
||||
- 新增 `RETRY_DELAYS_MS: &[u64] = &[1000, 2000, 4000]`
|
||||
- 新增 `chat_with_retry()`:包装 `provider.chat()`,循环 `max_retries+1` 次
|
||||
- 新增 `chat_with_streaming_with_retry()`:包装 `chat_with_streaming()`,用 `Arc<AtomicBool>` 跟踪 emit 状态
|
||||
- 两处重试循环均 `tokio::select!` 监听 cancel_signal
|
||||
- 替换 `:1068` 和 `:1424` 两处直接调用
|
||||
- tracing 日志:`warn!(attempt, retry_in_ms, error, "LLM request failed, retrying")`
|
||||
- 单元测试:可恢复错误重试成功、不可恢复错误立即失败、重试中取消生效、流式已 emit 不重试
|
||||
|
||||
### 前端(2 个文件)
|
||||
|
||||
**5. `web/src/components/Settings/types.ts`**
|
||||
- `ProviderConfig` 增 `max_retries: number`
|
||||
|
||||
**6. `web/src/components/Settings/ConfigPage.tsx`**
|
||||
- provider 表单增 "最大重试次数" 输入框(与 "LLM 超时" 同组)
|
||||
- 新增 provider 默认值 `max_retries: 3`
|
||||
|
||||
## 四、关键设计约束
|
||||
|
||||
1. **向后兼容**:`#[serde(default)]` 保证旧 config.json 无需修改
|
||||
2. **取消优先**:重试 sleep 期间 `select!` 监听 cancel_signal,立即响应
|
||||
3. **max_retries=0**:不重试,行为与现状完全一致
|
||||
4. **不修改 LLMProvider trait**:零侵入,不影响 channels/subagents 调用链
|
||||
5. **memory_maintenance 不受影响**:它有独立重试逻辑,不经过 AgentLoop
|
||||
|
||||
## 五、验证清单
|
||||
|
||||
- [ ] `cargo build` 通过
|
||||
- [ ] `cargo test --lib` 全绿(含新增重试测试)
|
||||
- [ ] 前端 `npm run build` 类型检查通过
|
||||
- [ ] max_retries=0 时行为与现状一致
|
||||
- [ ] max_retries=3 时可恢复错误重试 3 次后失败
|
||||
- [ ] 不可恢复错误(如 401 模拟)立即失败不重试
|
||||
- [ ] 重试 sleep 期间触发取消立即生效
|
||||
Loading…
x
Reference in New Issue
Block a user