fix: retry transient supervisor model failures once

This commit is contained in:
mesalogo
2026-10-05 10:47:09 +08:00
parent b4df493be7
commit e50ca6d149
8 changed files with 346 additions and 50 deletions
@@ -10,6 +10,8 @@
回顾获得执行位置后,读取一次连接配置;同次执行中的批次、输出重试、汇总、故事与经验阶段保持该模型。修改监督者选择、应用默认模型或连接配置,仅影响后续执行。继续暂停或失败的回顾、单独重试故事、生成或重试建议时重新读取当前选择;跨重启不保存模型或凭据快照。显式选择的连接被删除、改为图片协议或缺少所需 API Key 时,执行报错并提示修改设置,不自动换成默认模型。
对应 FR-S6。监督模型遇到可识别的临时失败时,统一等待 500ms 后重试一次:HTTP 429、500/502/503/504,以及短暂网络故障或响应流中断。取消、模型超时、认证、配置、额度或容量错误直接停止;无法确认可恢复的错误也不重试。失败尝试的部分正文丢弃,已报告用量和已保存批次保留。叶子输出校验后的缩块重试、导航输出重试保持原规则;请求生命周期见[模型阶段超时与调度](./technical-design.md#模型阶段超时与调度)。
## 整体逻辑架构
报告、监督叶子与导航整理共用监督专用 FIFO 并发池,普通聊天不入池;监督服务外层 run 仍串行。报告在排队前冻结超时,获槽后领取,实际模型请求前校验并刷新租约;报告提交或无变化后先释放槽位,再执行监督。监督超时在 run 创建时保存,节点获槽并解析 Runtime 后计时。阶段超时、租约、传输时限、后台检查与活动轮询各自独立,取值及生效规则见[当前设置合同](./review-scheduling-design.md#当前设置合同)。
@@ -4,6 +4,17 @@
当前记录以已验证生产行为为准。监督者尚未覆盖全部 user stories。
## 2026-10-05 监督模型临时失败重试
对应 FR-S6。`runSupervisionModel` 已接入一次可取消的临时失败重试,覆盖监督者各模型阶段;错误分类、清理及每次尝试的超时规则见[技术设计](./technical-design.md#模型阶段超时与调度)。直连文本请求保留 HTTP status,并交出失败流中已报告的用量。OpenCode/Continue、直连传输重试和叶子/导航输出重试未改。监督者仍在桌面 Main 直连模型,不经过远程 Agent 或模型桥。
验证结果:
- `npx vitest run src/main/assistant/supervision-model-retry.test.ts src/main/assistant/supervision-production.test.ts src/main/assistant/supervision-review.test.ts src/main/assistant/supervision-model-pool.test.ts src/main/agent/model-runtime.test.ts`:5 个文件,177 项通过,1 项原有跳过。覆盖 429、可恢复 5xx、部分正文后 terminated、连续失败最多两次包装层调用、认证/配置/容量/超时不重试、等待中取消、流和会话清理、用量保留,以及已保存叶子在失败和继续后不重算。
- `npx tsc --noEmit -p tsconfig.node.json`、`npx tsc --noEmit -p tsconfig.agent.json`、4 个改动 TypeScript 文件的定向 ESLint 和 `git diff --check` 通过。
请求验证使用生产 `ModelAgentRuntime` 和注入的假 fetch/响应流,数据保存使用内存 SQLite;外部模型调用为 0。未运行全量测试、真实模型或远程 Host 验证,未提交或推送。
## 2026-10-05 后端阶段审查 A03/A04/A05
对应 SL-5、SL-8、FR-S6、FR-S10。生产候选读取现在将批次项目和跨项目设置传到只读 Worker;最终候选读取返回后检查暂停/取消;故事归属和拆分在原写事务内拒绝已移除或合并的目标。存储、停止与重试规则见[技术设计](./technical-design.md#单次执行与取消)。没有 schema 变更,原始消息、历史结果及人工确认字段保留。
@@ -156,7 +167,7 @@ FR-S13 已接通监督者“设置 → 模型”、应用设置持久化和 Main
- 叶子批次输出不可用(无效 JSON、结构不符、引用不存在的来源或实体)时,在同一段待处理正文上缩小到 1/2、再到 1/4(不少于 500 字符)重试,最多 2 次;缩小只是先处理前一部分,其余正文留在待处理里,不跳过、不截断。
- 导航合并输出不可用时,用相同输入再问 1 次。
- 提供方错误、超时、取消和响应超出容量不重试,仍按原来的方式失败并保留已保存批次。
- 当时提供方错误、超时、取消和响应超出容量不重试,失败保留已保存批次。2026-10-05 起临时提供方错误采用上文的一次重试;超时、取消和响应超出容量仍不重试。
改后结果(DeepSeek `deepseek-flash`):
@@ -12,6 +12,8 @@
Main 的手动 IPC 和心跳完成回调均使用 `createProductionSupervisorService`,该工厂连接 `SupervisorService`、`SupervisionReviewStore`、共享模型池及无工具 Runtime。生产路径不再调用旧的 20 会话 × 20 消息 collector,也不使用旧服务分支的 100 条 / 48,000 字符裁剪。旧的有界服务注入方式仍供原有测试和实验脚本使用;心跳报告本身继续使用原报告预算。
2026-10-05 起,监督模型包装层对可识别的临时失败统一重试一次,包含 429、可恢复 5xx 和网络/响应流中断;取消、模型超时、配置及容量错误不重试。每次尝试独立计时,等待可取消,成功批次保留。具体错误与生命周期合同见[技术设计](./technical-design.md#模型阶段超时与调度);下文旧草案中“不增加节点网络重试”的描述已由此规则替代。
### 已接入的调度与存储
schema 47 增加运行配置、来源清单、成功批次、导航节点四张表和一个实时来源视图。迁移只创建新结构,保留此前迁移、结果及用户数据。清单只保存来源身份、版本、长度及增量起点;正文随后按主键和有界 `substr` 读取,派发顺序仍由 `(project_id, conversation_id, sequence, source)` 决定。
@@ -50,7 +52,7 @@ schema 47 增加运行配置、来源清单、成功批次、导航节点四张
| `supervisionReview.executionSeconds` | 300 | 30..3600 | 旧设置兼容字段,执行时忽略,设置页不再展示;恢复旧 run 同样不按此值暂停 |
| `supervisionReview.crossProject` | 关闭 | 开关 | 创建回顾时冻结;关闭时每批只以本项目已有实体为候选,也不生成跨项目故事;开启后候选来自全部项目,故事归属可建立跨项目故事 |
| `supervisionReview.storyThreadEvents` | 20 | 4..500 | 创建回顾时冻结;功能的事件数低于此值时,模型提出的子线索并入功能 |
| `supervisorOrganizeTimeoutSeconds` | 240 | 30..600 | 创建回顾时冻结,节点获槽并解析 Runtime 后计时 |
| `supervisorOrganizeTimeoutSeconds` | 240 | 30..600 | 创建回顾时冻结,节点获槽并解析 Runtime 后,每次模型尝试独立计时 |
| `heartbeatReportTimeoutSeconds` | 240 | 30..600 | 维持报告发起时冻结及既有租约规则 |
| `supervisorModelConcurrency` | 1 | 1..4 | 回顾保存接纳并发;共享池上限仍随应用设置实时变化,实际执行取较小值 |
@@ -55,9 +55,13 @@ schema 43 在结果上增加 `graph_snapshot_json`,保存当次实体名称、
监督整理超时由 run 创建时冻结;共享模型池仍采用实时设置上限。`supervision:pause` 取消该 run 的排队和在途工作,`supervision:resume` 继续已保存批次;监督整理输出在流中按 `supervisionReview.responseKiB` 检查响应容量,默认 1024 KiB,可在设置页调整为 100 至 16384 KiB。该容量每次请求读取当前设置,继续旧运行也采用新值;超限保留成功批次并提示调高后继续。生成摘要及事实内容不设独立短字符限制,监督旧摘要读取保留全文,具体边界见[生产接线](./review-scheduling-design.md#0-生产接线与剩余边界)。以下报告领取和租约规则继续适用。
手动和自动监督均在桌面 Main 调用生产工厂,Runtime 解析请求不携带项目、Runtime 选择或执行空间,也不再注入 `workMode`。`resolveRequestRuntime` 返回桌面默认 Runtime,不进入 SSH 项目或 `selectedRuntimes.getRuntime` 分支;内部分析使用无工具构造,回顾 scope 只筛选证据。摘要校验、合并和持久化在 Main 完成,single-flight、取消及 UI 控制不改变 `gbagent`、远端 Runtime 启动或桌面到 Agent 协议。
手动和自动监督均在桌面 Main 调用生产工厂,由 `resolveSupervisorRuntime` 解析上文的直连模型,不进入 SSH 项目或 `selectedRuntimes.getRuntime` 分支;内部分析使用无工具构造,回顾 scope 只筛选证据。摘要校验、合并和持久化在 Main 完成,single-flight、取消及 UI 控制不改变 `gbagent`、远端 Runtime 启动或桌面到 Agent 协议。
`ApplicationSettingsStore` 持久化报告和整理模型超时,均为 30..600 整数秒,默认 240。报告排队前冻结,监督创建 run 时冻结;计时从获槽及 Runtime 解析后开始,覆盖模型执行及其内部重试。流结束校验 signal 和完成事件,finally 释放临时会话,心跳报告保持原有 100KB 上限,监督整理使用上文的可调响应容量。采集、排队、保存和清理不计入模型超时。
`ApplicationSettingsStore` 持久化报告和整理模型超时,均为 30..600 整数秒,默认 240。报告排队前冻结,监督创建 run 时冻结;计时从获槽及 Runtime 解析后开始,覆盖单次 Runtime 执行及其内部重试。监督包装层的第二次尝试重新按同一秒数计时,500ms 等待和临时会话清理不计入该时限。流结束校验 signal 和完成事件,finally 释放临时会话,心跳报告保持原有 100KB 上限,监督整理使用上文的可调响应容量。采集、排队、保存和清理不计入模型超时。
`runSupervisionModel` 为提取、导航、故事、经验和建议共用一次临时错误重试,错误范围见[逻辑规则](./logic-design.md#监督者模型选择)。每次尝试使用新的 request/conversation ID 和隐藏任务,正文缓冲单独创建;用量按各次任务保存。失败后先清理计时器并释放临时会话,再等待;等待继续持有监督池槽位,受父 signal 和池取消控制,结束或失败后释放槽位。`error` 事件的 `status: cancelled` 优先于错误文字;抛出的结构化错误检查 status、statusCode 和 cause,字符串只识别明确的 HTTP 状态或已知网络/流中断格式。
直连文本请求保留 HTTP 错误的 status,并在响应流失败前交出已收到的用量;没有提供方用量时不估算补记。重试决策只在监督包装层,直连传输原有重试、OpenCode 和 Continue 策略不变。429 使用同一 500ms 等待,不新增 Retry-After、UI 或配置项。
`supervisorModelConcurrency` 为 1 至 4 的整数,默认 1。报告和监督整理共用 Main 内 `SupervisionModelPool` FIFO 池,普通聊天不入池;降低上限不终止在途请求。报告槽位从领取前持有至报告完成或无变化提交后,在下游监督开始前释放,避免并发 1 时相互等待。监督服务外层运行仍串行;该池不是完整分块调度器,也不提供跨聊天与监督的提供商全局并发、RPM、TPM 或 Retry-After 控制。