docs(report): add section-writer failure task brief — CJK token budget and refs echo
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016P5RoqzmUQEbeC2qjAkeGr
This commit is contained in:
@@ -0,0 +1,146 @@
|
||||
# 任务书 · 个人报告写作阶段 report_schema_invalid 修复(2026-09-02)
|
||||
|
||||
基线:`origin/staging` 最新(开工时 `git fetch` 后以 `origin/staging` HEAD 为准;本任务书出稿时为 `d9404976` 之后)。
|
||||
|
||||
## 事故实证
|
||||
|
||||
blocked-repairs(`7faf8555`)部署后,2026-09-02 21:19(UTC 13:19,requestId `77b79067-bd70-465e-ac23-adc4500d4738`)真实生成 personal_full(career/marriage/wealth/timing)**failed,failureCode = `report_schema_invalid`**。历史同码失败另有 4 次(08-30 三次、09-02 05:10 一次)。
|
||||
|
||||
**证据链已收窄到模型写作路径**:委托方已在本地对最新 staging 代码做过确定性全管线复现——真实引擎四主题响应 → `buildReportEvidenceBundleV2`(4 claim cards / 0 blocked)→ `generatePersonalReport`(注入 schema 合规的确定性假 writer + 内存 sectionService)→ **PIPELINE-READY,4 章节装配、guard、最终解析全部通过**。即 bundle 校验、plan 校验、按章过滤(`filterReportEvidenceBundleForSection` 的 finalize 校验)、装配与文档合同都没有确定性缺陷;失败只可能来自真实模型输出未通过校验的路径:每章两次尝试均失败 → 章节 blocked → 四章全 blocked → `all_sections_blocked`,或摘要生成失败。
|
||||
|
||||
## 两个候选根因(按嫌疑排序,任务 0 定案)
|
||||
|
||||
**主嫌 · 输出预算的中文 token 口径反了。**
|
||||
`frontend/src/lib/personal-report-generation.ts`:
|
||||
|
||||
```ts
|
||||
const SECTION_OUTPUT_TOKEN_BUDGET = 3072;
|
||||
const SUMMARY_OUTPUT_TOKEN_BUDGET = 1536;
|
||||
// 每章:
|
||||
maxOutputTokens: Math.min(3072, Math.max(1024, Math.ceil(entry.targetCharacters.max / 2)))
|
||||
```
|
||||
|
||||
standard 深度 `targetCharacters.max = 1200`(中文字符)→ 每章预算 `max(1024, 600) = 1024` tokens。`/2` 隐含"2 字符 ≈ 1 token",这是英文口径;**中文约 1 字 ≈ 1 token 或更多**。writer 被系统提示与 plan 要求写 260–1200 字中文 narrative + title + actions + caveats + evidenceRefs 的完整 JSON,写入目标区间上半段必然超 1024 → `finishReason: "length"` → JSON 截断 → schema 校验失败 → 修复重试**预算不变**再截断 → 耗尽 2 次 → 章节 blocked。四章同构 → `all_sections_blocked`。摘要同理:schema 允许 `summary` 2000 字符中文,1536 预算同样可截断。
|
||||
(这正是 `TASK-report-sectioned-generation-20260830.md` 红线"输出上限取值要用数据决定,不许拍脑袋"要防的事故。)
|
||||
|
||||
**次嫌 · evidenceRefs 精确回显失败。**
|
||||
`assertWriterOutput` 要求 `candidate.evidenceRefs` 与 `entry.evidenceRefs` **集合相等**。按章过滤后的 bundle `evidenceRefs` = `entry.evidenceRefs ∪ 全局解读层 refs`(yoga/functional 回执随章下发),模型若照抄 bundle 的 refs 而不是 plan entry 的 refs → mismatch → 失败。修复重试的提示词只说"未通过校验",不说差在哪。
|
||||
|
||||
## 决策记录
|
||||
|
||||
- 产品方向不变:writer 输出 schema 不放宽;`assertWriterOutput` 的 id/theme/evidenceRefs 精确匹配语义不改(服务器主权);章节串行不变。
|
||||
- 授权:按任务 0 实测数据调整输出 token 预算公式与 repair 提示词;在报告失败记录/详情中透出失败细节(仅错误码级别,不含内容)。
|
||||
|
||||
## 硬红线
|
||||
|
||||
1. **任务 0 是门控。** 必须先从 staging 拿到 21:19 这次失败的实证(telemetry `finishReason` 分布 + `personal_report_sections.last_error_code`),确认主嫌/次嫌,再改数值。**预算新值必须由数据 + 中文 token 口径推导,在 PROGRESS 写出推导过程**,不得再拍脑袋。
|
||||
2. writer 输出 schema 只可收紧;`assertWriterOutput` 语义不改(允许把失败原因喂给 repair 提示词,不允许放松校验)。
|
||||
3. 隐私红线延续:日志/PROGRESS 只引用错误码、token 数、finishReason;不得摘录 prompt、bundle、模型输出、用户资料。
|
||||
4. 不改数据库结构(`personal_report_sections` 已有 `last_error_code`,透出走应用层)。
|
||||
5. 上两轮任务书的其余红线继续有效(隔离、assertionLevel、最低证据组、串行、`./node_modules/.bin/tsc` 而非 `npx tsc`、不改 `.gitea/workflows/**`、不提升 main)。
|
||||
|
||||
## 开工前置
|
||||
|
||||
```bash
|
||||
git fetch origin --prune
|
||||
git worktree add -b codex/report-writer-failure-20260902 \
|
||||
../.worktrees/report-writer-failure-20260902 origin/staging
|
||||
```
|
||||
|
||||
读 `pre_work_error_ledger.md`、`frontend/AGENTS.md`;`docs/BUG_HISTORY.md` 检索(本轮与 8-30 sectioned 轮的任务 0 观测、BUG-486 系列相邻)。
|
||||
|
||||
**先读**:`personal-report-generation.ts` 的 `generateSectionedPersonalReport`(预算、重试、blocked 判定)、`src/mastra/personal-report.ts` 的 `runStructured` / `REPAIR_PROMPT_SUFFIX` / telemetry、`personal-report-worker-core.ts` 的失败映射、`PROGRESS-report-sectioned-20260830.md` 的任务 0 数据。
|
||||
|
||||
---
|
||||
|
||||
## 任务 0(P0,门控)· 用 staging 实证定案
|
||||
|
||||
### 做法
|
||||
|
||||
执行环境有 staging 部署访问权,取证 requestId `77b79067-bd70-465e-ac23-adc4500d4738`:
|
||||
|
||||
1. 服务器日志中该时段的 `[personal-report-agent]` telemetry 行:每次尝试的 `outcome` / `finishReason` / `outputTokens`,与 `[personal-report] generation_failed` 的 `innerReason`。
|
||||
2. `personal_report_sections` 表该 requestId 的行:`section_id` / `status` / `attempt_count` / `last_error_code`。
|
||||
3. 汇总成表写进 PROGRESS(只有错误码与数值)。
|
||||
|
||||
### 判定
|
||||
|
||||
- `finishReason: "length"` 占多数 / `last_error_code = section_output_truncated` → 主嫌成立,任务 1 为主修。
|
||||
- `section_output_invalid` 且 telemetry `finishReason: "stop"` → 次嫌成立,任务 2 为主修。
|
||||
- 两者混合 → 两任务都做(大概率如此,都在本轮范围内)。
|
||||
- 若实证指向第三种原因(如 summary 失败、worker 层指纹错配),停下,把数据写进 PROGRESS/BLOCKED 再定方案,不得按本任务书猜测硬改。
|
||||
|
||||
---
|
||||
|
||||
## 任务 1(P0)· 输出预算按中文口径重算
|
||||
|
||||
### 做法
|
||||
|
||||
- 每章预算改为以中文口径推导:narrative 上限 `targetCharacters.max` 字 ≈ `max × 1.3` tokens,再加 title/actions/caveats/refs 与 JSON 结构余量(按任务 0 实测 outputTokens 校准),向上取整后 cap 在 `SECTION_OUTPUT_TOKEN_BUDGET`(3072;若实测 research 深度需要更高,单独说明再调)。**禁止再出现"字符 ÷ 2"式换算**。
|
||||
- 摘要预算同口径重算(schema 允许 headline 200 + summary 2000 + priorities,1536 明显不足)。
|
||||
- 修复重试遇 `finishReason: "length"` 时,repair 提示词明确要求压缩篇幅至 `targetCharacters.min` 附近,且重试预算不得低于首次。
|
||||
- telemetry 已记 `finishReason` 与 tokens,保持。
|
||||
|
||||
### 验收
|
||||
|
||||
- 单元测试锁新预算公式(各深度的每章/摘要预算值表)。
|
||||
- staging 部署后真实生成一份 standard personal_full:四章有正文;telemetry 无 `length`;PROGRESS 给每章实测 `outputTokens` 与预算余量。
|
||||
|
||||
---
|
||||
|
||||
## 任务 2(P1)· evidenceRefs 回显加固
|
||||
|
||||
### 做法
|
||||
|
||||
- `sectionPrompt` 明确一句:`evidenceRefs 必须逐字复制 plan.evidenceRefs 数组,不得增删`(模型已能看到 plan entry)。
|
||||
- 校验失败时把**失败类别**(identity mismatch / refs mismatch / schema invalid)拼进 repair 提示词(只有类别词,不带内容),让第二次尝试有的放矢。
|
||||
- `assertWriterOutput` 语义不变。
|
||||
|
||||
### 验收
|
||||
|
||||
- 单元测试:refs mismatch 的 repair 提示词包含类别提示;断言校验语义未变(原有 mismatch 用例全保留)。
|
||||
|
||||
---
|
||||
|
||||
## 任务 3(P1)· 失败可观测透出
|
||||
|
||||
### 做法
|
||||
|
||||
- 报告失败时把 `innerReason`(如 `all_sections_blocked`)与各章 `last_error_code` 汇总透出到报告详情 API(错误码级别;`personal_report_sections` 已有数据,应用层聚合即可,不改表)。
|
||||
- 报告中心失败卡片显示可读的失败摘要(如"4 个主题中 4 个写作失败:输出被截断"),替代裸码 `report_schema_invalid`。
|
||||
|
||||
### 验收
|
||||
|
||||
- 单元测试 + 一次人为构造的失败在页面上可读。
|
||||
|
||||
---
|
||||
|
||||
## 任务 4(P1)· 真实验证与上两轮欠账
|
||||
|
||||
staging 部署后真实生成 standard personal_full(四主题):
|
||||
|
||||
1. 报告 ready、四章有正文、摘要非降级文案。
|
||||
2. writer 输出回溯抽查 ≥3 处(叙事中的 yoga/大运/强弱表述能对回 bundle 条目)。
|
||||
3. 每章实测 `inputTokens` / `outputTokens` / 墙钟,对照 `PROGRESS-report-skill-parity-20260901.md` 的 2 倍线裁决(产品已接受估算 2.28×,以本次实测为准;实测超线把数据摆进 PROGRESS,不自行裁决)。
|
||||
|
||||
## 不在本轮范围
|
||||
|
||||
- writer schema、assert 语义、最低证据组、bundle 内容的任何改动。
|
||||
- 章节并发、模型更换、提示缓存策略调整。
|
||||
- 数据库结构。
|
||||
|
||||
## 收尾
|
||||
|
||||
- `PROGRESS-report-writer-failure-20260902.md`;`docs/BUG_HISTORY.md` 条目(编号对远端确认;与 8-30 任务 0 观测、`final_parse_rejected` 历史关联)。
|
||||
- 建议推送顺序:任务 0 取证先写 PROGRESS(可不推代码);任务 1–3 一次推送;任务 4 部署后补记。
|
||||
- 推送后核对 `https://staging.jyotisha.chat/api/health` 的 `.deployment.gitCommit`;流水线约 20 分钟。
|
||||
- 不自行提升 main。
|
||||
|
||||
## 交付物清单
|
||||
|
||||
1. 任务 0 实证表(telemetry finishReason/outputTokens + 各章 last_error_code)与定案结论
|
||||
2. 新预算公式 + 推导过程 + 各深度预算表 + 单元测试
|
||||
3. length 重试的压缩提示 + repair 类别提示 + 测试
|
||||
4. 失败详情透出(API + 报告中心文案)+ 测试
|
||||
5. 真实报告验收:四章正文、telemetry 无 length、回溯抽查 ≥3 处、实测 tokens/墙钟与 2 倍线对照
|
||||
6. 全套质量门实际输出、BUG_HISTORY、BLOCKED(如有止损)、PROGRESS
|
||||
Reference in New Issue
Block a user