feat(research): consult biography backtest harness and pre-change baseline
Independent Staging Quality Gate / validate (push) Successful in 16m20s
Independent Staging Quality Gate / publish (push) Successful in 3m54s

TASK-consult-no-presupposition-and-backtest-20261001 T4 infrastructure only
(T1-T3 untouched). New files only, so it merges cleanly with the parallel
evidence-card brief.

- capture_consult_biography_backtest_golden.py: same handler/body/trim as the
  evidence-card golden; nine public AA charts x parents/marriage/health/career.
- consult-biography-backtest-golden.json: real engine output, byte-reproducible.
- consult_biography_backtest_rubric.json: facts with sources, must_not,
  expected_signals per figure x domain.
- consult-biography-backtest.mts: runs the product's real agent, tools, card,
  methodology, user-turn shape and streamAgentResponse; deterministic checks only.
- Baseline on 9b937c4a: 39/72 severe biography conflicts; all five audit cases
  reproduce in both runs.

BUG-1170 is registered when brief 3 lands.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N4f2nya58RoRu4yEmJgRGE
This commit is contained in:
Jesse_Chen
2026-10-02 00:10:06 +08:00
co-authored by Claude Opus 5.5
parent e5199106a7
commit 691ee440e3
6 changed files with 2011 additions and 0 deletions
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,41 @@
# PROGRESS · 名人生平回测基础设施与基线(2026-10-01)
- 任务书:`docs/tasks/TASK-consult-no-presupposition-and-backtest-20261001.md`,本轮只做 **T4 的基础设施 + 改动前基线**;T1~T3(形状、示例、追问轮)不在本轮。
- 执行方式:产品负责人要求子代理直接执行。
- 基线:`origin/staging` `9b937c4a`
- 分支 / worktree:`codex/consult-biography-backtest-20261001` / `.worktrees/consult-biography-backtest-20261001`(未推送)
- 并行:另一会话在改证据卡(`TASK-consult-card-affliction-data-20261001`)。本轮**只新增文件、不改任何已有文件**,两边可以干净合并;`docs/tasks/README.md` 索引也没动,由第三单执行方登记。
## 交付
| 文件 | 内容 |
| --- | --- |
| `scripts/research/capture_consult_biography_backtest_golden.py` | 复用 `capture_consult_evidence_card_golden.py` 的 `trim()` 与 `consult_evidence_card_run` 的 handler、请求体;9 位 × 4 条引擎路由(parents 走 family);路由间相同的 chart 字段存一份 `shared_chart`,不同的(今天是 `modules.yogas`、`modules.chara_dasha`)留在各路由下。`PYTHONHASHSEED=0 JYOTISH_API_CHART_CACHE_TTL_SECONDS=0` 下两次输出逐字节相同 |
| `frontend/tests/fixtures/consult-biography-backtest-golden.json` | 真实引擎输出,2.87 MB;不并入现有合同 golden |
| `docs/research/consult_biography_backtest_rubric.json` | 9 位 × 父母 / 婚姻 / 健康 / 事业:生平事实(带来源)、`must_not`(含原文筛网)、`expected_signals`(引用卡字段)、评分口径 |
| `frontend/scripts/research/consult-biography-backtest.mts` | 用产品真实模块组装(见测试文档「与真实路由的差异」);`--dump-cards` / `--recheck`;确定性检查只做禁句、性别未知代词、must_not 原文 |
| `docs/testing/consult-affliction-backtest-20261001.md` | 名单、保真度缺口、基线表与合计、代表句、成本 |
## 基线结论(详见测试文档)
72 份里严重冲突 39 份;排查记录的 5 份严重冲突 10 / 10 次复现;新增 4 位严重冲突 20 / 32(通过线 ≤ 1);对照组误报 1 份(轻)。新发现:事业题 16 / 18 份把人写成「专业人 / 顾问 / 幕后」,与盘无关,第三单不覆盖,建议另开单。
## 门禁
| 项 | 结果 |
| --- | --- |
| `tsc --noEmit`(`tsconfig.json` 的 include 含 `**/*.mts`,脚本在范围内) | 0 错 |
| `eslint`(`npm run lint` 的配置覆盖 `.mts`) | 0 error |
| 既有测试 | 未改任何测试文件 |
| `npm test`(Node 22) | 4868 项,pass 4803,fail 24,cancelled 0;24 项全是需要 Docker / PostgreSQL / 部署环境的套件(`startPostgresFixture` 等),本机无 Docker,属已知环境缺口,与本轮新增文件无关 |
| Python 快速门 `run_quality_gate.py --profile quick`(本机无 `.venv`,用系统 python3) | pytest 1036 passed / 1 skipped;门禁内的 `npm test` 被系统 Node 20 跑,70 项因 Node 20 不支持模块 mock 失败(环境),用 Node 22 单跑结果见上一行 |
| `tests/test_repo_privacy_markers.py` | 通过 |
## BUG 编号
本轮是基础设施,不登记 BUG。BUG-1170(T4 名人生平回测)在第三单(`TASK-consult-no-presupposition-and-backtest-20261001`)合入时登记,引用本进度记录与测试文档的基线。
## 环境备注
- 模型 key 只从环境变量读,没有写入任何文件、日志或提交。
- 模型输出在 `scratch/consult-biography-backtest/baseline-20261001/`(gitignore),未提交。
@@ -0,0 +1,119 @@
# 普通对话名人生平回测(2026-10-01)
- 任务书:`docs/tasks/TASK-consult-no-presupposition-and-backtest-20261001.md` T4(D5:普通对话提示词或清单改动的固定验收)
- 排查记录:`docs/research/consult_affliction_audit_2026_10_01.md`
- 名单与评分表:`docs/research/consult_biography_backtest_rubric.json`
- golden:`frontend/tests/fixtures/consult-biography-backtest-golden.json`(`scripts/research/capture_consult_biography_backtest_golden.py` 生成,与 `capture_consult_evidence_card_golden.py` 同一 handler、同一请求体)
- 脚本:`frontend/scripts/research/consult-biography-backtest.mts`
- 模型输出不入库,默认写到 `scratch/consult-biography-backtest/<时间戳>/`(已 gitignore);本文只摘短句。
## 怎么跑
```bash
cd frontend
DS_KEY=<临时 key> ./node_modules/.bin/tsx scripts/research/consult-biography-backtest.mts --repeats 2 --concurrency 6
# 只看模型拿到的卡,不调模型:
./node_modules/.bin/tsx scripts/research/consult-biography-backtest.mts --dump-cards --out /tmp/cards
# 改了评分表后,对旧输出重算确定性检查:
./node_modules/.bin/tsx scripts/research/consult-biography-backtest.mts --recheck <输出目录>
```
需要 Node 22(本机 `/exec-daemon/node`)。脚本不进 CI,需要外网与模型 key。key 只从环境变量 `DS_KEY` / `BACKTEST_MODEL_KEY` 读。
## 名单
| 名人 | 测什么 | 出生资料 |
| --- | --- | --- |
| Steve Jobs | 出生即送养;晚年重病(胰腺肿瘤,56 岁去世);顶级企业家 | AA,`minute_rectification_development_v1.json` |
| Barack Obama | 父亲两岁离开、外祖父母带大;婚姻稳定、健康良好(这两项作对照) | AA,`minute_rectification_holdout_v4.json` |
| Elizabeth Taylor | 八次婚姻、丧偶;终身多病、数十次手术;童星 | AA,同上 |
| Marilyn Monroe | 生父不明、母亲精神病住院、寄养家庭与孤儿院;三次离婚;慢性病与药物依赖 | AA,`minute_rectification_holdout_v5.json` |
| Judy Garland | 父亲在她 13 岁去世、强势母亲;五次婚姻;终身药物依赖;童星 | AA,同上 |
| Édith Piaf | 被母亲遗弃、祖母带大;两次婚姻、挚爱空难;车祸与吗啡依赖 | AA,同上 |
| Frida Kahlo | 小儿麻痹、车祸、约 30 次手术、截肢;与同一人离婚又复婚;与父亲亲近、与母亲冷淡 | AA,同上 |
| George W. Bush(对照) | 父母都在且亲近、1977 年至今一段婚姻、健康总体良好 | AA,同上 |
| Zinedine Zidane(对照) | 父母都在且亲近、1994 年至今一段婚姻、运动员健康 | AA,同上 |
Bill Clinton 不在仓库案例库里,没有加;「父亲缺席」由 Obama、Monroe、Garland、Jobs 覆盖。
## 与真实路由的差异(保真度缺口)
脚本直接用产品的 `getJyotishAgent`(系统提示、绑定的技能方法块、工具)、Mastra `Agent.stream` + `consultationNatalPrepareStep` + `consultationGenerationSettings`、`createConsultationTools`(卡片、方法论清单、`read-consultation-evidence` 查卡)、`consultationUserTurnContent` + `natalUserTurnShape`,以及 `streamAgentResponse`(按 step 取答案、Pass 4、合同重试、空答重试、长度续写)。差异:
1. 引擎调用换成 golden:同 capture 路径、raman 岁差(产品默认也是 raman)、mean 交点、参考日 2026-09-27,外部 VedAstro 不调用。
2. 用户回合不带「用户称呼」:真实路由会带档案名;这里故意不给名人名字,免得模型凭记忆答生平。
3. `natalToolInstruction` 一句和 `currentTimeContext` 是 `route.ts` 内部函数、未导出,脚本按原文复刻(两处字符串)。改了这两处要同步改脚本。
4. 没有内容审核、计费、标题旁路事件、历史摘要、请求时钟(真实路由有工具期与 5 分钟答题钟;本轮最长一份 87 s,不触发);单轮、无历史。
5. 性别一律按档案未填(性别未知)。
6. 模型经 Mastra 的 OpenAI 兼容通道直连 DeepSeek(`deepseek-flash`),不经产品数据库的模型目录;产品的 staging 模型是 `deepseek-v4-flash`,两者是否同一模型以供应商为准。
7. 计划用 `createConsultationPlan({ userIntent, theme })`;真实路由用预留阶段的计划(含点数与深度)。
8. 模型若自己多选一个领域(本轮 1 次:Obama 父母 run1 多选了 `family`),`family` / `children` 一律回放 `parents` 的 golden(引擎同为 family 路由,只差问题文本)。
## 基线(改动前)
- 代码:`origin/staging` `9b937c4a`(第一、二、三单都未合入)
- 模型:`deepseek-flash`,thinking 开,输出上限 24576(产品设置)
- 9 位 × 4 领域 × 2 次 = 72 份,失败 0 份
- 评分:Claude 逐份阅读,按评分表 `how_to_score`:通过 / 漏读 / 读反 / 严重冲突 / 误报
| 名人 | 父母 r1 / r2 | 婚姻 r1 / r2 | 健康 r1 / r2 | 事业 r1 / r2 |
| --- | --- | --- | --- | --- |
| Steve Jobs | 严重冲突 / 严重冲突 | 通过 / 通过 | 读反 / 严重冲突 | 严重冲突 / 严重冲突 |
| Barack Obama | 严重冲突 / 严重冲突 | 通过 / 通过(预设未婚) | 通过 / 通过 | 严重冲突 / 严重冲突 |
| Elizabeth Taylor | 通过 / 通过 | 严重冲突 / 严重冲突 | 严重冲突 / 严重冲突 | 严重冲突 / 严重冲突 |
| Marilyn Monroe | 严重冲突 / 严重冲突 | 严重冲突 / 严重冲突 | 读反 / 读反 | 严重冲突 / 严重冲突 |
| Judy Garland | 严重冲突 / 严重冲突 | 通过(弱) / 严重冲突 | 严重冲突 / 严重冲突 | 严重冲突 / 严重冲突 |
| Édith Piaf | 严重冲突 / 严重冲突 | 通过(弱) / 严重冲突 | 读反 / 读反 | 通过 / 严重冲突 |
| Frida Kahlo | 漏读 / 漏读 | 读反 / 读反 | 严重冲突 / 严重冲突 | 漏读 / 严重冲突 |
| George W. Bush(对照) | 通过 / 通过 | 通过 / 通过 | 通过 / 通过 | 严重冲突 / 严重冲突 |
| Zinedine Zidane(对照) | 通过 / 通过 | 误报(轻) / 通过 | 通过 / 通过 | 严重冲突 / 严重冲突 |
### 合计
| 项 | 数 |
| --- | --- |
| 严重冲突 | 39 / 72 份 |
| 读反 | 7 |
| 漏读 | 3 |
| 通过 | 22 |
| 对照组误报 | 1(轻,Zidane 婚姻 run1「旧联系重新变近、分开又见面」) |
| 两次都通过的格子 | 9 / 36 |
| 排查记录里的 5 份严重冲突(Obama 父母、Jobs 父母、Taylor 婚姻 / 健康 / 事业) | 10 / 10 份仍冲突,原样复现 |
| 新增 4 位(Monroe、Garland、Piaf、Kahlo)严重冲突 | 20 / 32 份(通过线 ≤ 1) |
| 确定性检查:禁句(逆行…反复/来回) | 7 处,6 份 |
| 确定性检查:性别未知时婚恋题用他/她 | 5 处(3 处指伴侣,2 处是把金星称「她」) |
| 确定性检查:must_not 原文 | 27 处(是筛网,最终以阅读为准;对照组 Bush 健康「慢性病」一处属正常描述) |
按领域:父母 10 份严重冲突(缺席的父亲一律写成「话少、分量重、靠做事表达」);婚姻 6 份(多次婚姻写成「定下来之后很稳」);健康 7 份严重 + 5 份读反(终身重病写成「底子不差、恢复力强」);事业 16 / 18 份严重冲突,连两位对照组也冲突。
### 代表句(每条只摘短句)
- 父母·缺席的父亲:Obama「他话不多,存在感却稳……你做事的方式里有一部分就是照着他的样子长出来的」;Monroe「爸爸话少、分量重,是你衡量自己的那把尺」;Garland「父亲这条线表面上不显眼,但分量不轻」;Jobs「他们一直在,而且投入得很深」「挑一件具体的事去问爸爸的意见」。
- 父母·被遗弃的母亲:Piaf「她是你家里管事最多的那个人」。
- 婚姻:Taylor「你的婚姻是有的,而且底子偏硬……定下来之后反而稳」;Monroe「最后落到的那段关系……事情交给他你放心」;Garland「婚姻是往后走、慢慢稳下来的类型」。
- 健康:Taylor「身体底子属于中上:扛得住,也恢复得回来」;Kahlo「你不是一张虚弱的盘」;Garland「这不是有严重疾病迹象」;Jobs「你不是被病拖垮的类型」。
- 事业:Taylor「偏技术、分析和系统……纯靠嘴皮子和人缘往前推的事,你做起来会别扭」;Obama「顶是深度,不是高度……不是管一个大组织那种顶」;Zidane「真正的手艺在嘴和脑子上:写、讲、分析、算、谈判」;Monroe「靠一门具体的手艺慢慢被人认出来」。
- 杂项:Taylor 事业 run1 正文出现「加上海王……更正一下」。
### 基线读出来的东西(给第三单和验收)
1. 排查记录的 C 类(形状预设对象在场)在 4 位新名人上全部复现:只要问父母,缺席的一方就被写成「话少、靠做事」。第三单 T1 的新句正对这一条。
2. 事业题出现排查记录没有单列的一类:16 / 18 份把人写成「专业人 / 顾问 / 幕后」,与盘无关(总统、球星、电影明星都一样)。来源看起来是同一形状要求加上「接触层 / 结构层 / 落地层」三层清单,模型把「不预设上班族」理解成了「默认专业服务」。AL 落 10 宫、10 宫 SAV 高这些成名信号几乎没被读出来。第三单不覆盖这一条,需要另开单或在第三单决策记录里补。
3. 健康题默认开头「底子不差」,8 宫、6 宫主落 8 宫等受冲只降级成「慢性消耗」。第一单(健康卡加 1 / 12 宫)和第二单(受冲读法)是对口的修法。
4. 婚姻题有清单,表现最好(9 / 18 通过),但多次婚姻的三位仍有 6 份写成稳定。
5. 对照组只有 1 份轻度误报:改动后要重点看对照组有没有被「先说受冲、请确认」带出新的误报。
## 改动后
(第三单 T1~T3 合入后,用同一命令重跑,按上表格式填;通过线见任务书 T4 第 5 条。)
## 成本(一次全量)
| 项 | 数 |
| --- | --- |
| 份数 | 72(9 × 4 × 2) |
| 墙钟 | 664 s,并发 6;单份中位 54 s,最长 87 s |
| 输入 token | 3,480,792,其中命中缓存 3,187,584(约 92%) |
| 输出 token | 761,012,其中推理 664,649 |
| 单份平均 | 输入约 48k、输出约 10.6k |
| 查卡工具 | 72 份里 19 次调用了 `read-consultation-evidence` |