fix(rectification): 分类器失败、引擎忙、整轮超时不再归因错
点选题把分类器两次异常说成用户没说清(BUG-722);引擎 429 被压成坏了且重算静默失败(BUG-723);两次 attempt 总预算大于路由 maxDuration(BUG-724)。本单只改归因:classifier_unavailable 请用户重发、busy 分档并可见「这次没有重新比较」、整轮 225s 预算不够则 host fallback。不改计费、分类模型、并发闸门。
This commit is contained in:
@@ -11205,3 +11205,52 @@
|
||||
- 相关记录:ERR-108、BUG-065、BUG-161、BUG-718
|
||||
- 复发自:无
|
||||
- 修复版本:待发布
|
||||
|
||||
## BUG-722 | 意图分类器两次异常被说成用户没说清,经历被丢掉
|
||||
|
||||
- 状态:resolved
|
||||
- 首次发现:2026-09-15
|
||||
- 最近更新:2026-09-16
|
||||
- 影响面:`classifyTurnIntentWithRetry`、`POST /api/rectification/agent` 点选题与 `ask_candidate_discriminator` 分支
|
||||
- 用户现象:点选题下回一句带年月的经历,助手说「我不太确定这句是不是在回答上面的问题」。焦点仍在,但这句话没有当成经历记下,用户只能自己再说一遍。
|
||||
- 触发条件:`action=message` 且当前焦点是点选题;意图分类器两次尝试都抛异常(超时、供应商 5xx、网络抖动),或两次都解析不出结构化结果。
|
||||
- 根因:`classified === null`(模型没答上来)与 `classified.intent === "unclear"`(用户确实说不清)被合并进同一条分支,回 `unclearFocusReply` 并落库。采集题分支在 BUG-643 已把 null 记成 `collectIntent=unclassified` 并 fail-open;点选题没有跟上。
|
||||
- 修复:`classifyTurnIntentWithRetry` 增加 `outcome: classified | unclear | classifier_unavailable`。点选题与 discriminator 分支:`unclear` 维持原回复;`classifier_unavailable` 回「这边没接上,把刚才那句再发一次就行。」,不写证据、不推进焦点、不扣点。服务端打 `rectification_classifier_unavailable` 日志(只含耗时与次数,不含原文或案例 ID)。采集题 `collectIntent=unclassified` 语义不变。不得用关键词/正则兜底。
|
||||
- 验证:`frontend/tests/rectification-turn-intent-classifier.test.ts`:两次抛异常 → `outcome=classifier_unavailable`;模型返回 `intent: unclear` → `outcome=unclear`;两条路由文案不同。源码合同禁止 `!classified || classified.intent === "unclear"`。
|
||||
- 防复发:路由不得把分类器 null 与用户 `unclear` 合并。分类器失败不得回退到关键词、正则或词表。
|
||||
- 相关记录:BUG-643、BUG-635、BUG-522
|
||||
- 复发自:无(BUG-522 记录末尾写明「意图分类器超时仍是既有缺口,本单不修」,本单关闭该缺口)
|
||||
- 修复版本:待发布
|
||||
|
||||
## BUG-723 | 校正引擎 429 被当成引擎坏了,重算静默失败、范围不动
|
||||
|
||||
- 状态:resolved
|
||||
- 首次发现:2026-09-15
|
||||
- 最近更新:2026-09-16
|
||||
- 影响面:`engine-client.ts` `readEngineJson`、`runV9CandidateScore` / `runV9BlockScan`、`autoRescoreAfterEvidenceChange`、证据轮主持人正文
|
||||
- 用户现象:记下经历后助手写「记下了:…」,范围一动不动。没有「这次没有重新比较」之类的说明。只在两个人同时校正、重计算闸门饱和时出现。
|
||||
- 触发条件:`record-evidence-batch` 已 accepted 后自动重算;Python 返回 429 + `Retry-After` + `ERR_COMPUTE_BUSY`。
|
||||
- 根因:所有非 2xx 压成 `engine_request_failed`。`autoRescoreAfterEvidenceChange` 整段 `try/catch` 返回 `status=failed` 且不打日志;系统提示词要求工具静默,模型继续写「记下了」。
|
||||
- 修复:引擎调用按 `busy / http_error / timeout / bad_payload` 分档,读 `Retry-After`,每种非 ok 打 `console.warn`(路径、状态码或错误名、耗时;不含出生资料)。score / block_scan 对 `busy` 按 `Retry-After` 退避,上限 `RECTIFICATION_ENGINE_BUSY_RETRY_LIMIT=2`、总退避 `RECTIFICATION_ENGINE_BUSY_BACKOFF_BUDGET_MS=6_000`。仍失败时 projection 带回 `error_kind`,主持人正文写「这次没有重新比较」,并去掉「收窄」类进度句。不改并发闸门。
|
||||
- 验证:`frontend/tests/rectification-v9-engine-contract.test.ts` 桩 429 / 500 / 超时 / 坏 JSON;只有 429 触发退避;退避成功后 score 与从未失败路径相同。`rectification-v9-stream.test.ts` 重算失败正文含「这次没有重新比较」、不含「收窄」。
|
||||
- 防复发:任何调用 Python 引擎的客户端都不得把非 2xx 压平成单一错误码;429 必须单独成档。引擎调用不得用 `catch {}` 吞掉原因,失败必须留服务端日志且用户文案按原因分档。
|
||||
- 相关记录:BUG-715
|
||||
- 复发自:BUG-715(那一单的范围写死在星盘页三个文件,防复发没有扫到 `engine-client.ts`)
|
||||
- 修复版本:待发布
|
||||
|
||||
## BUG-724 | 两次模型尝试总预算大于路由 maxDuration,重试时无错误码断流
|
||||
|
||||
- 状态:resolved
|
||||
- 首次发现:2026-09-15
|
||||
- 最近更新:2026-09-16
|
||||
- 影响面:`runV9AgentTurn`、`RECTIFICATION_RUN_BUDGET_MS`、agent / regenerate 路由 `maxDuration`
|
||||
- 用户现象:发生一次可重试失败(`empty_stream`、`evidence_not_written`)后,用户等三五分钟拿到一个连错误码都没有的断流。
|
||||
- 触发条件:单次 attempt 210s × 2 = 420s,路由 `maxDuration` 240s。
|
||||
- 根因:BUG-388 只约束「单次 attempt < maxDuration」,没约束两次尝试总预算。`RECTIFICATION_AGENT_ATTEMPT_TIMEOUT_MS = 210_000` 还在 `agent-run.ts` 与 `rectification-activity-labels.ts` 各写一遍。
|
||||
- 修复:引入整轮 `RECTIFICATION_RUN_BUDGET_MS=225_000`(小于 240s)。单次超时取 `min(210s, 剩余预算)`。重试还要 `剩余预算 ≥ RECTIFICATION_MIN_RETRY_ATTEMPT_MS`(20s),否则走 host fallback 给出可见正文,不启动注定被砍的第二次尝试。210_000 只在 `rectification-run-budget.ts` 定义一处。不把 attempt 砍到 110s,不把 `maxDuration` 提到 430s。已盖戳 `open_question` 的超时轮保持现状 fail-closed(见既有 stream 测试),不把题干贴进超时回复。
|
||||
- 验证:`RECTIFICATION_RUN_BUDGET_MS < maxDuration`,`maxDuration` 从 agent 与 regenerate 路由源码读取。第一次尝试耗尽预算后返回 retryable,不得发起第二次尝试,必须 host fallback。源码合同:`src/` 里只有一处 `210_000`。
|
||||
- 防复发:两次模型尝试的总预算必须由测试断言钉死为小于路由 `maxDuration`,不能只约束单次 attempt。attempt 超时常量只能有一处定义。
|
||||
- 相关记录:BUG-059、BUG-388
|
||||
- 复发自:BUG-059(总预算约束);BUG-388 的防复发只写了单次尝试,因此没拦住
|
||||
- 修复版本:待发布
|
||||
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
# PROGRESS · 三处把系统故障说成别的东西(2026-09-15 / 2026-09-16)
|
||||
|
||||
工作树:`.worktrees/rectification-failure-attribution-20260915`
|
||||
分支:`codex/rectification-failure-attribution-20260915`
|
||||
任务书基线:`6b3248bf`(已被后续合入);代码基线 `origin/staging` @ `a8d29d1b`
|
||||
本机 Windows。
|
||||
|
||||
Skill **未 bump**。未改 `frontend/src/app/page.tsx`、计费口径、分类所用模型、并发闸门、`docs/tasks/README.md` 状态列。未 push `HEAD:staging`。
|
||||
|
||||
## 开工基线
|
||||
|
||||
| 项 | 数字 |
|
||||
| --- | --- |
|
||||
| `frontend/src/app/page.tsx` | **1951** 行(未改) |
|
||||
| 最大 BUG 号 | **BUG-720**(本单占用 **722 / 723 / 724**;721 留给 engine-memoization) |
|
||||
| `tsc --noEmit` | 收尾 0 错 |
|
||||
| `npm run lint` | 0 error(全仓既有 warning 119;本单未新增 error) |
|
||||
|
||||
`frontend/node_modules` 已存在,未新建 junction。
|
||||
|
||||
## 任务状态
|
||||
|
||||
| 任务 | 状态 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| 6.1 分类器 unavailable vs unclear | 完成 | `outcome` 三档;点选题与 discriminator 不再合并 null / unclear;采集题 `unclassified` 未改 |
|
||||
| 6.3 整轮超时预算 | 完成 | `RECTIFICATION_RUN_BUDGET_MS=225s < maxDuration 240s`;不足最小重试时长走 host fallback |
|
||||
| 6.2 引擎分档 + 可见「这次没有重算」 | 完成 | `busy/http_error/timeout/bad_payload`;429 有限退避;失败正文含「这次没有重新比较」 |
|
||||
| 6.4 Bug 历史 | 完成 | BUG-722 / 723 / 724 |
|
||||
|
||||
## 实现要点
|
||||
|
||||
- `classifyTurnIntentWithRetry` 返回 `outcome`。`classifier_unavailable` 专指两次都没拿到结构化分类(抛异常或解析空)。点选题回「这边没接上,把刚才那句再发一次就行。」焦点保持 active,不写证据、不扣点。
|
||||
- `readEngineJson` 对齐星盘页的四档失败。score / block_scan 对 429 按 `Retry-After` 退避,上限 2 次、总退避 6s(具名常量)。`autoRescoreAfterEvidenceChange` 失败打 `rectification_rescore_failed`,projection 带 `error_kind`。主持人用 `withRescoreSkippedNotice` 补「这次没有重新比较」,并剥掉「收窄 / 范围从」进度句。
|
||||
- 整轮 deadline 225s。第二次尝试只在剩余 ≥ 20s 时启动。`210_000` 只定义在 `rectification-run-budget.ts`,`activity-labels` 与 `agent-run` 从那里 import。
|
||||
- `agent-run` 不再顶层 import `@/mastra/agentic-rectification`(该模块会在加载时创建 skill symlink)。步进预算抽到 `step-budget.ts`,避免 Windows 无 symlink 权限时 stream 测试整文件无法加载。`agentic-rectification.ts` 再导出,既有 import 路径不变。
|
||||
|
||||
## 既有断言改动
|
||||
|
||||
| 文件 | 原值 | 新值 | 原因 |
|
||||
| --- | --- | --- | --- |
|
||||
| `rectification-answer-choice.test.ts` 超时合同 | `agent-run` 内 `= 210_000`,只断言 210s < 240s | `210_000` 只在 `rectification-run-budget.ts`;整轮 225s < 两条路由读出的 `maxDuration` | BUG-724 总预算必须小于路由墙 |
|
||||
| `rectification-window-cluster-cap-20260909.test.ts` 分类调用 | `await classifyRectificationTurnIntent` | `await classifyTurnIntentWithRetry` | BUG-722 discriminator 与点选题同一入口 |
|
||||
| `rectification-collect-stall.test.ts` 点选题切片 | `classified.intent ===` | `classified?.intent ===` | BUG-722 classified 在 unavailable 时为 null |
|
||||
| `rectification-v9-status-security.test.ts` stale policy 重算失败形状 | 原始 Error 文案 `stale policy must rescore` | `RectificationEngineError` / `http_error`,且确实打了 `/score` | BUG-723 抛错不得把上游原文泄漏给工具层 |
|
||||
|
||||
未弱化其它既有断言。`timeout after a stamped open_question fails closed` 保持现状(不把题干贴进超时回复)。
|
||||
|
||||
## 测试
|
||||
|
||||
| 命令 | 结果 |
|
||||
| --- | --- |
|
||||
| `tsc --noEmit` | **0 错** |
|
||||
| `npm run lint` | **0 error / 119 warning**(与既有全仓 warning 同量级) |
|
||||
| 任务书所列 tsx 测试(除 `rectification-v9-agent.test.ts`) | 定向套件绿:classifier / stream(50) / answer-choice / spoken-collect / unwritten-evidence / chart-view-engine / engine-contract / voice-copy / window-cluster / eight-method |
|
||||
| `rectification-v9-agent.test.ts` | **环境缺口**:模块加载 `agentic-rectification` 时 `symlinkSync(..., "dir")` EPERM。本机 Node 只能建 junction。该失败在改本单之前就存在,不是本单回归。 |
|
||||
| `page.tsx` | 仍 1951 行,未改 |
|
||||
| 全量 `npm test` | `# tests 3189 / # pass 3104 / # fail 71 / # skipped 14`(修 collect-stall / status-security 两处源码合同后,这两条已绿)。失败集中在无 Docker 的 database-*、部署/Caddy/shell、Windows symlink EPERM(`rectification-v9-agent.test.ts`、`api-service-unavailable` 动态 import 路由、skill-binding)。不含本单定向套件。 |
|
||||
| `npx next build --webpack` | compile + TypeScript 过;Collecting page data 死在既有 `SkillPackageRegistryError` EPERM symlink(`/api/daily-starlanguage`),与本单无关。Turbopack 因 `frontend/node_modules` junction 指向主仓而直接 panic。未能本机核对 `/` 是否 `○ Static`。 |
|
||||
|
||||
## 环境缺口
|
||||
|
||||
- 无 Docker:数据库套件红,不伪装成通过。
|
||||
- 无登录态、无 Chrome:浏览器级验收不在本单。
|
||||
- Windows 无 symlink 权限:`symlinkSync(..., "dir")` EPERM。凡顶层加载 `getRectificationV9Agent` / `RECTIFICATION_V9_SKILL_PATH` 的测试或 `next build` 收集路由都会红。本机 Node 只能建 junction。stream / unwritten-evidence 因 runner 不再顶层加载该模块而可跑。
|
||||
- `next build`:webpack 编译过、tsc 过;收集页面数据时 skill runtime symlink EPERM。不伪装成 `/` ○ Static 已核过。
|
||||
Reference in New Issue
Block a user