fix(report): keep personal-report leases alive through serial chapter writes
Staging verification wrote four ready sections then lost the 120s job lease mid-chapter. Lengthen the worker lease and cap summary tokens with the same 3072 ceiling the section budget already uses. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -66,7 +66,7 @@
|
||||
|
||||
摘要结构余量 `580` **已经包含** headline / priorities / JSON,不再把 headline 算第二次:
|
||||
|
||||
`ceil(2000 × 1.3) + 580 = 2600 + 580 = 3180`
|
||||
`ceil(2000 × 1.3) + 580 = 2600 + 580 = 3180`,再与章节同一 cap **3072** 取 min(08-30 整份 `outputTokens=3069` / `stop`)。落地 **3072**。
|
||||
|
||||
章节:`min(3072, max(1024, ceil(maxChars × 1.3) + 568))`
|
||||
|
||||
@@ -76,7 +76,7 @@
|
||||
| standard | 1200 | 2128 | **2128** |
|
||||
| deep | 2000 | 3168 | **3072**(cap,缺口 96 token) |
|
||||
| research | 2800 | 4208 | **3072**(cap;约合 narrative 2504 字,低于 2800 上限) |
|
||||
| summary | 2000 字段 | 3180 | **3180**(cap 4096) |
|
||||
| summary | 2000 字段 | 3180 | **3072**(与章节同一 cap) |
|
||||
|
||||
08-30 整份 writer 一次调用 `outputTokens=3069` 且 `stop`,说明 3072 能装下一份大 JSON。本事故未证实截断,因此不把 cap 自行抬高。length 修复重试:同一 `outputBudget`(不得低于首次),提示词要求压到 `targetCharacters.min` 并闭合 JSON。
|
||||
|
||||
@@ -105,7 +105,50 @@
|
||||
|
||||
## 任务 4 · 真实验证
|
||||
|
||||
**未做。** 等任务 1–3 进入 staging 且 `GET https://staging.jyotisha.chat/api/health` 的 `.deployment.gitCommit` 对齐后再跑:standard `personal_full` 四章正文、telemetry 无 `length`、≥3 处 writer→bundle 回溯、每章 token/墙钟对照 `PROGRESS-report-skill-parity-20260901.md` 的 2 倍线。不提升 main。
|
||||
`GET https://staging.jyotisha.chat/api/health` → `.deployment.gitCommit` = `eda37c1523341898850e6d71d63c8dd40563973b`。Web 容器 `2026-09-02T17:31:48Z` recreate。未提升 main。
|
||||
|
||||
Web `docker logs` 在 Ready 之后几乎不再收录 `console.info`。本轮拿不到 `[personal-report-agent]` 的 `finishReason` / 每章 token。`usage_ledger` 对未 ready 的请求无 complete 行。2 倍线的真实 `inputTokens` 仍缺测;墙钟如下,不自行裁决。
|
||||
|
||||
### Run A · requestId `54be49f1-a657-43fa-88ce-7d7e8d575831`
|
||||
|
||||
standard `personal_full`,主题 career / marriage / wealth / timing。started 17:44:07 UTC,终态 17:54:30 UTC(约 10.4 分钟)。
|
||||
|
||||
| 章 | status | attempts | narrative 字数 | last_error_code |
|
||||
|---|---|---:|---:|---|
|
||||
| theme-career | ready | 1 | 1557 | (空) |
|
||||
| theme-marriage | ready | 2 | 1395 | (空) |
|
||||
| theme-timing | ready | 2 | 842 | (空) |
|
||||
| theme-wealth | ready | 1 | 470 | (空) |
|
||||
|
||||
事故那一章(career)这次 **1 次成功**,没有 `section_output_truncated` / `section_refs_mismatch`。四章都有正文。
|
||||
|
||||
Job 在写作过程中 `lease_expired` 两次(默认租约 120s,单章可超过 2 分钟;heartbeat 未能保住租约),第三次 attempt 写完 wealth 后约 39ms 整份 `report_schema_invalid`。进度停在 71%(timing 完成后的值),wealth 的 85% `onProgress` 未落库。四个 section 行仍是 ready。摘要/装配没有产出 `report_document`。reservation `released`。
|
||||
|
||||
默认 `leaseSeconds=120` 不够串行四章写作。本轮补丁改为 **600s 租约、20s heartbeat**。摘要预算与章节同一 cap **3072**(原公式 3180)。
|
||||
|
||||
### 回溯抽查(只记是否对得上 ref,不摘正文)
|
||||
|
||||
| # | 章 | 叙事层(布尔) | 对回的 evidenceRef |
|
||||
|---|---|---|---|
|
||||
| 1 | wealth | 含 yoga 词 | `ev-tech-yoga` |
|
||||
| 2 | 四章皆含 | 含 大运/主运/副运 | `ev-tech-vimshottari` |
|
||||
| 3 | marriage | 含 yoga 词 + 大运词 | `ev-tech-d9` + `ev-tech-dk` |
|
||||
| 4 | career | 含 yoga 词 + 大运词 | `ev-tech-amk` + `ev-tech-vimshottari` |
|
||||
| 5 | wealth | 含 yoga 词 + 大运词 | `ev-tech-d2` + `ev-tech-d11` |
|
||||
|
||||
未在四章叙事中扫到强弱/shadbala 用词,不记为回溯命中。
|
||||
|
||||
### Run B · requestId `8e75299b-58ac-43c2-8bf4-59f9bd394734`
|
||||
|
||||
为区分租约与摘要失败另开一次。四次 `POST /api/consultation_workflow` 均为 200,随后 worker 在 `buildReportEvidenceBundleV2` 处非重试失败,`calculation_unavailable`,无 section 行。未再消耗第三次生成。
|
||||
|
||||
### 相对 2 倍线
|
||||
|
||||
无每章 `inputTokens`/`outputTokens`。Run A 墙钟约 10.4 分钟(含两次租约回收与 marriage/timing 的 section repair)。数据摆出,不裁决。
|
||||
|
||||
### 任务 4 结论
|
||||
|
||||
Writer 修正在章节层成立:四章有正文、career 一次通过、无截断码。整份 ready 仍被 120s 租约和最后一步 `report_schema_invalid` 挡住。租约与摘要 cap 的补丁随本轮提交;整份 ready 要等该补丁部署后再验一次。
|
||||
|
||||
---
|
||||
|
||||
@@ -120,3 +163,5 @@
|
||||
| `personal-report-generation.test.ts` + `personal-report-worker.test.ts` | 51 pass / 0 fail |
|
||||
|
||||
未跑全量 `tests/*.test.ts`,不声称全套绿。未改 `.gitea/workflows/**`,未提升 main。
|
||||
|
||||
任务 4 补丁(租约 600s、摘要 cap 3072):`tsc --noEmit` 通过;plan / generation-v2 / worker 43 pass。
|
||||
|
||||
+1
-1
@@ -7643,7 +7643,7 @@
|
||||
- 触发条件:章节 writer 输出未通过 `assertWriterOutput`(常见为 `evidenceRefs` 与 plan 集合不等),或输出在 token 预算内被截断;随后整份报告失败。
|
||||
- 根因:三层叠加。旧错误分类把任何含 `evidence` 的消息打成 `section_evidence_insufficient`,`report_writer_evidence_refs_mismatch` 被误标;章节输出预算用英文「字符 ÷ 2」,standard 上限 1200 字中文只分到 1024 token;一章 blocked 或未捕获抛错可以中止整份,job 停在约 43%。失败详情只有聚合码,页面无法区分截断、引用不齐或未开始。staging 事故窗口的 writer telemetry 在容器 recreate 后丢失,本事故不能写成 `finishReason=length`。
|
||||
- 修复:中文口径重算每章/摘要 `maxOutputTokens`,去掉 ÷2;length 修复要求压到字数下限且预算不低于首次。Prompt 要求逐字复制 `plan.evidenceRefs`;repair 只加失败类别。refs/identity 先于泛化 `evidence` 分类。单章失败后继续其余主题;`all_sections_blocked` 走统一失败日志。详情与列表聚合已有 section 行,展示可读摘要与错误码,不改表、不放宽 writer schema。
|
||||
- 验证:预算表锁定 concise 1218 / standard 2128 / deep 3072 / research 3072 / summary 3180。refs mismatch 分类为 `section_refs_mismatch` 且其余主题仍交付。repair 提示含类别词、不含内容。失败详情从 section 行汇总。`tsc --noEmit`、改动文件 ESLint、个人报告聚焦测试。
|
||||
- 验证:预算表锁定 concise 1218 / standard 2128 / deep 3072 / research 3072 / summary 3072。refs mismatch 分类为 `section_refs_mismatch` 且其余主题仍交付。repair 提示含类别词、不含内容。失败详情从 section 行汇总。`tsc --noEmit`、改动文件 ESLint、个人报告聚焦测试。staging `eda37c15` 上真实 standard 四章均 ready(career 1 次成功,无截断码),整份仍因 120s 租约与最后一步 `report_schema_invalid` 未落 `report_document`;租约改为 600s。
|
||||
- 防复发:输出预算不得再用字符 ÷ 2。`assertWriterOutput` 保持 id/theme 全等与 refs 集合相等。section 错误码不得把 refs mismatch 归进 evidence insufficient。一章失败不得中止其余 write 主题。用户可见失败必须有错误码级摘要,不得只展示 `report_schema_invalid`。日志与 PROGRESS 不得写入 prompt、bundle、模型原文或用户资料。
|
||||
- 相关记录:BUG-352、BUG-451、BUG-486、BUG-489
|
||||
- 复发自:BUG-451(分章后仍把写作失败压成整份 schema 码,截断预算与失败分类未按中文口径收口)
|
||||
|
||||
@@ -46,7 +46,8 @@ export const SECTION_OUTPUT_TOKEN_BUDGET_CAP = 3072;
|
||||
export const SECTION_OUTPUT_TOKEN_BUDGET_FLOOR = 1024;
|
||||
/** title/actions/caveats/refs JSON envelope on top of narrative. */
|
||||
export const SECTION_STRUCTURE_TOKEN_ALLOWANCE = 568;
|
||||
export const SUMMARY_OUTPUT_TOKEN_BUDGET_CAP = 4096;
|
||||
/** Same cap as sections: 08-30 whole-report stop was 3069 output tokens. */
|
||||
export const SUMMARY_OUTPUT_TOKEN_BUDGET_CAP = 3072;
|
||||
export const SUMMARY_OUTPUT_TOKEN_BUDGET_FLOOR = 1536;
|
||||
/** headline + priorities + JSON envelope on top of the 2000-char summary field. */
|
||||
export const SUMMARY_STRUCTURE_TOKEN_ALLOWANCE = 580;
|
||||
|
||||
@@ -197,6 +197,8 @@ function createProductionWorker(workerId: string) {
|
||||
};
|
||||
return createPersonalReportWorker({
|
||||
workerId,
|
||||
leaseSeconds: 600,
|
||||
heartbeatIntervalMs: 20_000,
|
||||
jobs: createSupabasePersonalReportJobService(admin),
|
||||
reports: createSupabasePersonalReportService(admin),
|
||||
sectionService: createPersonalReportSectionService(admin as never),
|
||||
|
||||
@@ -420,6 +420,8 @@ test("production writer and worker keep the same signal on initial and repair mo
|
||||
assert.match(agentSource, /runOnce\(`\$\{prompt\}\$\{repairSuffix\}`\)/);
|
||||
assert.match(agentSource, /const signal = options\?\.signal/);
|
||||
assert.match(workerSource, /generatePersonalReport\(\{[\s\S]*signal: context\.signal,[\s\S]*\}\)/);
|
||||
assert.match(workerSource, /leaseSeconds: 600/);
|
||||
assert.match(workerSource, /heartbeatIntervalMs: 20_000/);
|
||||
});
|
||||
|
||||
test("an all-blocked plan still produces a valid v2 document with honest disclosures and no invented thematic section", async () => {
|
||||
|
||||
@@ -92,7 +92,7 @@ test("CJK section and summary output token budgets are derived from character ta
|
||||
(["concise", "standard", "deep", "research"] as const).map(sectionOutputTokenBudgetForDepth),
|
||||
[1_218, 2_128, 3_072, 3_072],
|
||||
);
|
||||
assert.equal(summaryOutputTokenBudget(), 3_180);
|
||||
assert.equal(summaryOutputTokenBudget(), 3_072);
|
||||
assert.ok(sectionOutputTokenBudget(1_200) > Math.ceil(1_200 / 2));
|
||||
assert.ok(summaryOutputTokenBudget() > 1_536);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user