Files
Jyotisha/docs/tasks/TASK-answer-effort-ab-20261013.md
T
Jesse_ChenandClaude Opus 5.5 2994588ad4 docs(tasks): fold product decisions into audit briefs (BUG-1344..1346)
- dasha-unify: fix Ashtottari birth balance (BUG-1344)
- rect-scoring-perf: server-side candidate cap 241 (BUG-1345)
- node-runtime-resources: enable user-side session cookie cache, 300 s (BUG-1346)
- consult-prep-parallel: classifier/reserve concurrency declined
- answer-effort-ab: model key supplied by product at acceptance
- drop stricter-than-test line-count red lines (AGENTS.md section 6)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Ad9rqAB8pXXvyzh7f4X8v
2026-10-14 00:41:02 +08:00

171 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# TASK · 普通对话答题步推理强度:开关 + A/B(issue #待定)
- 日期:2026-10-13
- 来源:「2026-10-13 全仓屎山与性能排查」P1 · 答题步推理强度
- 执行方:**Grok(Windows + Docker)**,分支 `codex/answer-effort-ab-20261013`,只推这个分支
- 验收:Claude(Linux)
- BUG 编号:**无 BUG 编号**(研究 + 开关,记录写 PROGRESS 与 A/B 记录)。若 T0 证实「既有的 `reasoningEffort` 管道从未送到供应商」需要立 Bug,开工时核对 `docs/BUG_HISTORY.md` 最大号(写作时最大号 BUG-1317,1335~1339 已分配)顺延取号,并在 PROGRESS 写明。(另:`TASK-upstream-sync7-20261014` 占 BUG-1318~1322,本批号段整体从 BUG-1323 起)
- 开工前置:**`consult-prep-parallel` 已合入 `origin/staging`**(两单都改 `frontend/src/mastra/consultation-tools.ts`)。
## 1. 基线
- 基线 commit:`origin/staging` `77057a819`(开工时以合入 `consult-prep-parallel` 之后的 staging 为准)
- 涉及文件:
- `frontend/src/mastra/consultation-tools.ts::consultationGenerationSettings`(第 224 行)、`consultationSliceGenerationSettings`(第 232 行)、`consultationNatalPrepareStep`
- `frontend/src/lib/agent-generation-settings.ts::agentGenerationSettings`
- `frontend/src/app/api/consult/route.ts`(第 1214 行 `...consultationGenerationSettings(selectedModel.model)`,`natalStreamOptions` / `windowStreamOptions`)
- `frontend/scripts/research/consult-biography-backtest.mts`(AGENTS.md §10 指定的名人生平回测,A/B 的底座)
## 2. 事故实证
### 2.1 推理占答题时间的大头(既有实测,非本轮)
10-04 咨询耗时审计(Claude 记忆与 `docs/testing/consult-plain-answer-20261001-model-runs.md`、`docs/testing/prompt-slim-20261007-model-runs.md`):答题步推理 token 中位 9.5k–11.9k,约占答题步输出 90%,约 200 tok/s,答题步 45–60 s,占整轮 75–85%。10-07 精简提示词后:deepseek-flash 10 题平均思考 5,683 token、30.3 s;deepseek-v4-pro 3 题平均思考 3,588 token、33.6 s。这些数字本轮未重测。
### 2.2 普通对话答题从不传推理强度
`consultationGenerationSettings(model)` = `agentGenerationSettings(model, { thinking: "enabled" })`,没有 `reasoningEffort`。route 第 1214 行把它摊进同一轮的 `streamOptions`,第 0 步(决定调工具)与第 1 步(写答案)用同一份设置。
### 2.3 既有的 `reasoningEffort: "low"` 管道很可能从未到达供应商(起草时读依赖源码得出,需 T0 用真实请求证实)
1. `consultationSliceGenerationSettings`(唯一传 `reasoningEffort: "low"` 的地方)**在产品代码里没有调用方**:`grep -rn consultationSliceGenerationSettings frontend/src` 只有定义;只有 `frontend/tests/consultation-agentic-runtime.test.ts:2460` 与 `consultation-workflow-contract.test.ts:210` 断言它的形状。
2. 形状放错了位置:`agentGenerationSettings` 产出 `providerOptions[providerId] = { thinking: { type: "enabled", reasoningEffort: "low" } }`,强度嵌在 `thinking` 里。
3. 产品的模型走哪条通道:`model-catalog.ts::resolveRow` 对非 openai / anthropic 的供应商给 `{ providerId: <供应商 code>, modelId, url, apiKey }`;Mastra 1.50.1(`package-lock.json`)在 `config.url` 存在时一律 `createOpenAICompatible({ name: providerId })`(`@mastra/core/dist/chunk-AR6WPTSV.js` 约 518 行)。
4. OpenAI 兼容通道的取参(`@mastra/core/dist/chunk-DRULWNQ6.js` 约 3225–3290 行):只认 `providerOptions[name]` **顶层**的 `reasoningEffort`(字符串)→ 请求体 `reasoning_effort`;其余键原样摊进请求体。所以今天的形状会发出 `"thinking": {"type": "enabled", "reasoningEffort": "low"}`、**没有** `reasoning_effort` 字段——DeepSeek 大概率忽略这个嵌套键。
5. 同版本 Mastra 内置的 DeepSeek 原生通道(`chunk-YJB7RPO7.js` `deepseekChatOptions`,约 1515 行)的注释是「Controls the thinking strength for DeepSeek V4 reasoning models」,取值只有 `"high" | "max"`。也就是说 **DeepSeek V4 可能根本不接受 `low` / `medium`**,产品要的「low / medium / 不设」三组可能有一组或两组发不出去。
上面 3~5 是读打包后源码的推断,必须 T0 用真实请求证实,不得直接当结论。
### 2.4 以前的 A/B 做法
`docs/testing/prompt-slim-20261007-model-runs.md`:同一套拼法(系统提示 + 用户轮指令 + 工具调用 + 工具结果)直连 DeepSeek,公开名人 golden 盘造工具结果,`max_tokens` 与线上一致(24,576;8,000 会让 4/10 空答),thinking 模式下 assistant 的 tool_call 消息必须带 `reasoning_content`。那次脚本没入库;现在仓库里已有等价且更完整的 `frontend/scripts/research/consult-biography-backtest.mts`(真实 `getJyotishAgent`、真实 `consultationGenerationSettings`、真实 `streamAgentResponse`,引擎调用换成 `frontend/tests/fixtures/consult-biography-backtest-golden.json`,9 张公开 AA 盘 × 4 领域,确定性检查对照 `docs/research/consult_biography_backtest_rubric.json`),本单在它上面加参数,不另写一套拼提示词的脚本。
## 3. 根因
- 2.2:从未设计。BUG-1142 把答题钟从 70 s 放到 300 s 时记录了「推理 token 也从同一只钟里扣」,但没有人去调强度。
- 2.3:`consultationSliceGenerationSettings` 加入时只写了形状测试(断言 `providerOptions.deepseek` 等于嵌套形状),没有对真实请求体做断言,所以「传了 low」看上去成立。这是测试只验源码/对象形状、不验线上请求的老问题(同 BUG-148 防复发「不得用只匹配源码字符串的断言替代该回归」)。
- 关联:BUG-1142(答题钟只防卡死)、BUG-1051(截断不扣点)、BUG-1232(`modelSteps`、`answer.reasoning_ms` 埋点,本单 A/B 用它)、BUG-1256(提示词精简 A/B)、BUG-282 / BUG-937(第 0 步关思考的风险,本单不碰第 0 步)。
## 4. 决策记录
- **产品负责人 2026-10-14:模型 key 在本单交付验收时由产品临时提供给 Claude**。Grok 无 key 时只交付开关、请求体拦截测试与 A/B 脚本,T0 第 2 步与 T2 的真实运行由 Claude 验收时执行;回报里写「A/B 未跑:等产品提供 key」,不得写通过。
- 产品授权:2026-10-13 产品负责人同意按排查结论全部立单交 Grok 执行,并**已同意做答题步推理强度 A/B**;默认值等 A/B 结论交产品拍板。
- 本单默认行为**不变**:开关不设时,请求体与 `77057a819` 逐字节相同。
- `consultationSliceGenerationSettings` 的嵌套形状与它的两条断言**本单不改**(没有调用方,改它不影响线上;是否删除或修正交产品,写进 PROGRESS「需拍板」)。
- 「答题步关思考」(`thinking: disabled`)**不作为候选默认**:只在 T0 证实 DeepSeek 不接受降档时,作为对照组 D 跑数据供产品参考;若要采用需另行拍板(BUG-282 / 937 记录过关思考的质量风险)。
- 不推翻任何既有红线。
## 5. 硬红线
1. 开关未设置时:发给供应商的请求体逐字节不变(T1 用拦截 `fetch` 的测试证明)。
2. 只影响**答题步**(拿到计算结果之后写答案的那一步,及其续写/重试——续写本来就是 `thinking: disabled`,不动)。第 0 步(决定调工具)的请求体不变。若 Mastra 的 `prepareStep` 返回 `providerOptions` 不是按步生效,则在 PROGRESS 写明并改为整轮生效,A/B 照常,但要把第 0 步耗时单列。
3. 不改答题钟(300 s)、工具钟(110 s)、`AGENT_ANSWER_OUTPUT_TOKENS` / `AGENT_THINKING_OUTPUT_TOKENS`、`max_tokens`。
4. 不改系统提示、Skill、证据卡、回答形状(改了就要另跑生平回测,超出本单)。
5. 模型 key 只从环境变量读(`DS_KEY` / `BACKTEST_MODEL_KEY`),不得写进任何文件、日志、提交、A/B 记录;回答原文不入库,A/B 记录只写数字、条数与短评。
6. 不动表结构与迁移;不改 `scripts/jyotish_api_server.py`;不碰 `frontend/src/app/(app)/page.tsx`;不改 `.gitea/workflows/**`;不升级依赖(包括 `@mastra/core`);不顺手修 warning。
7. 改既有断言写「原值 / 新值 / 原因」三栏。开工先 grep:`grep -rn "consultationGenerationSettings\|agentGenerationSettings\|reasoningEffort\|thinking: { type" frontend/tests`。
## 6. 任务分解
### T0 · 先证实:供应商收到了什么、接受什么(需 key;Grok 无 key 则只交付脚本,由 Claude 跑)
- 写 `frontend/scripts/research/answer-effort-probe.mts`:
1. **请求体拦截**(不需要 key):用 Mastra 的 `createOpenAICompatible` 通道(与产品同一构造方式:`{ providerId: "deepseek", modelId, url, apiKey: "dummy" }`)配一个假 `fetch`,分别以 (a) 今天的 `consultationGenerationSettings`、(b) `consultationSliceGenerationSettings`、(c) T1 的新形状 `low` / `medium` / `high` 发一次 `agent.stream`,打印实际请求体里的 `thinking`、`reasoning_effort`、`max_tokens`。预期 (b) 只有嵌套键、无 `reasoning_effort`——证实或推翻 §2.3。
2. **供应商接受度**(需 key):对 `deepseek-flash` 与 `deepseek-v4-pro`(以 `docs/testing/prompt-slim-20261007-model-runs.md` 用过的模型 id 为准;若产品库里的 id 不同,以产品负责人提供的为准),各发一个短问题,`reasoning_effort` 依次 不设 / `low` / `medium` / `high` / `max`,记录 HTTP 状态、错误码、`completion_tokens_details.reasoning_tokens`。每档 3 次。
- 判定:
- 某档 HTTP 4xx → 该档「不支持」,A/B 去掉这档;
- 某档 200 但推理 token 与「不设」差异 < 10%(中位数)→ 视为「被忽略」,同样去掉;
- `low` 与 `medium` 都不支持或被忽略 → 推理强度这条路不通:只跑对照组 D(答题步 `thinking: disabled`)供参考,结论写「供应商不支持降档」,交产品。
- 产出写进 `docs/testing/answer-effort-ab-20261013.md` 第一节。
### T1 · 环境变量开关(Grok 自跑 / Claude 验收)
- 新开关 `CONSULT_ANSWER_REASONING_EFFORT`:取值 `low` / `medium` / `high` / `max` / 空;空或非法 = 不设(今天的行为),非法值 `console.warn` 一次。
- `agent-generation-settings.ts::agentGenerationSettings` 增加一个**新**选项(例如 `providerReasoningEffort`),把强度写到 `providerOptions[providerId].reasoningEffort` **顶层**(OpenAI 兼容通道据此发 `reasoning_effort`;`providerOptions.openai` 同样顶层),不改现有 `reasoningEffort`(嵌套)选项的行为——slice 的断言因此不需要改。
- 生效位置:`consultationNatalPrepareStep` / `consultationWindowPrepareStep` 在答题步(`stepNumber ≥ 1` 且计算结果已在手——与 `createConsultationRunClock` 的 `answerReady` 同一判据)返回带强度的 `providerOptions`;第 0 步不带。无工具的公开日历路线(`usesPublicDailyGeneralAgent`,`answerFromStart`)从第一步起就是答题步,同样带。
- 验收:
- 新测试 `frontend/tests/answer-effort-switch-20261013.test.ts`:拦截 `fetch`,开关不设时第 0、1 步请求体与改前逐字节一致(快照取自改前代码运行结果,`fixtureProvenance` 写明命令);设 `low` 时只有答题步出现 `"reasoning_effort":"low"`、第 0 步没有;续写请求仍是 `thinking: disabled` 且无 `reasoning_effort`。
- `tsc --noEmit` 0 错、`npm run lint` 0 error。
### T2 · A/B 脚本(Grok 写;有 key 才跑)
- 在 `consult-biography-backtest.mts` 上加最少的参数,不复制提示词:
- `--cases figure:domain,…`:固定题集(取代 `--figures × --domains` 的笛卡尔积);
- 强度由环境变量 `CONSULT_ANSWER_REASONING_EFFORT` 传入(脚本走的就是产品的 `consultationGenerationSettings` 与 prepareStep),summary 里记下当次取值;
- 每行额外记录:端到端秒数、第 0 / 1 步秒数与推理 token(复用 BUG-1232 的 `modelStepTimings` / `answerReasoningMs`)、输出 token、`finish`、是否空答、是否截断、正文字数。
- 固定 10 题(9 张公开 AA 盘均在 golden 里):
| # | 盘 | 领域 |
| --- | --- | --- |
| 1 | steve_jobs | career |
| 2 | barack_obama | parents |
| 3 | elizabeth_taylor | marriage |
| 4 | marilyn_monroe | parents |
| 5 | judy_garland | health |
| 6 | edith_piaf | marriage |
| 7 | frida_kahlo | health |
| 8 | george_w_bush | career |
| 9 | zinedine_zidane | career |
| 10 | marilyn_monroe | marriage |
- 组别:A = 不设;B1 = `low`;B2 = `medium`(T0 去掉的档不跑);D = 答题步关思考(只在 T0 判定降档不通时跑)。每组 10 题 × 3 次,模型先 `deepseek-flash`;`deepseek-v4-pro` 抽 1、4、6 三题 × 3 次。
- 批量跑时 `--concurrency` 不超过 4,组与组交错跑(同一题 A、B1、B2 相邻),避免供应商时段波动全落在某一组。
### T3 · 质量对比与判定(Claude 验收时做;产品拍板)
- 自动项(脚本出):rubric `must_not` 命中数、空答数、截断数、`finish` 非正常数、英文术语/后台词等 `recheck` 已有检查。
- 人工项(Claude 读,同题 A/B 正文并排):每题记 缺事实(证据卡里有、与问题直接相关却没说)/ 跑题 / 重复 / 空答 的条数,三次运行都读。
- 判定门槛(某档 B 可提议为默认,须**全部**满足):
1. 答题步推理 token 中位数降 ≥ 30%,且端到端耗时中位数降 ≥ 20%;
2. 空答、截断均为 0(A 若有,B 不得多于 A);
3. rubric `must_not` 命中总数 ≤ A;
4. 人工四项每一项的总条数 ≤ A(30 份回答合计);
5. `deepseek-v4-pro` 抽样不出现 A 没有的空答或截断。
满足者取降幅最大的一档提议为默认;都不满足 → 默认维持「不设」,开关保留。结论只是**提议**,由产品负责人拍板后另一个小提交改默认值(`CONSULT_ANSWER_REASONING_EFFORT` 的部署默认写在哪里也由那次决定,本单不改部署文件)。
### T4 · 记录(Grok)
- `docs/testing/answer-effort-ab-20261013.md`:T0 结果(请求体实拍、各档接受度)、T2/T3 的表(耗时、推理 token、质量条数),不贴回答原文。
- `docs/tasks/PROGRESS-answer-effort-ab-20261013.md`:开关实现、三栏改断言表(若有)、「需拍板」清单(默认值;`consultationSliceGenerationSettings` 删除或修正)。
- 若 T0 证实 §2.3,按 §头部规则取一个 BUG 号,记录「既有低强度设置从未送达供应商」,状态 `investigating`(slice 无调用方,线上无影响)。
- `CHANGELOG.md`:本单默认不变,不写;产品拍板改默认那次再写。
- `frontend/DESIGN.md`:不改。
- 无 key 时在 `BLOCKED.md` 记「A/B 未跑:缺模型 key」。
## 7. 让步顺序
T0 第 1 步(请求体拦截)与 T1 不可砍。时间不够先砍:`deepseek-v4-pro` 抽样 → 组 D → B2(`medium`)。
## 8. 验收命令分工
| 谁 | 命令 |
| --- | --- |
| Grok 自跑 | `cd frontend && ./node_modules/.bin/tsc --noEmit && npm run lint`;`./node_modules/.bin/tsx --test tests/answer-effort-switch-20261013.test.ts tests/consultation-agentic-runtime.test.ts tests/consultation-workflow-contract.test.ts tests/consult-answer-clock-20261001.test.ts tests/consult-latency-timing-20261005.test.ts`(`# fail` 与 `# cancelled` 都为 0);`./node_modules/.bin/tsx scripts/research/answer-effort-probe.mts --dry-run`(只做请求体拦截)。有 key 才跑 T0 第 2 步与 T2;**没有 key 就回报「A/B 未跑:缺模型 key」,不得写通过** |
| Claude 验收 | 上述全部 + 前端全量 `npm test` 与基线逐名比对 + `next build`(`/` 仍 Static,首屏 gzip ±2%)+ 用产品负责人提供的临时 key 跑 T0、T2,按 T3 读稿判定 |
| 不需要 | `test:db`、Python 快速门(不动 Python) |
## 9. 开工前置命令
```bash
git fetch origin --prune
# 前置单必须已在 staging 上:
git log origin/staging --oneline | grep -i "consult-prep-parallel\|BUG-1337" | head -3 # 为空则停,回报「前置未合入」
git worktree add -b codex/answer-effort-ab-20261013 .worktrees/answer-effort-ab-20261013 origin/staging
cd .worktrees/answer-effort-ab-20261013
grep -n "^## BUG-" docs/BUG_HISTORY.md | tail -3
grep -rn "consultationSliceGenerationSettings" frontend/src # 起草时只有定义,没有调用方
grep -rn "consultationGenerationSettings\|agentGenerationSettings\|reasoningEffort\|thinking: { type" frontend/tests
```
## 10. BUG 编号起点
无(研究 + 开关)。T0 若证实 §2.3 需立 Bug,开工时取当前最大号 + 1。
## 11. 交付物清单
- 代码:`agent-generation-settings.ts`(新选项)、`consultation-tools.ts`(开关读取 + prepareStep)
- 测试:`frontend/tests/answer-effort-switch-20261013.test.ts`(新)
- 脚本:`frontend/scripts/research/answer-effort-probe.mts`(新)、`consult-biography-backtest.mts`(加 `--cases` 与计时字段)
- 文档:`docs/testing/answer-effort-ab-20261013.md`、`docs/tasks/PROGRESS-answer-effort-ab-20261013.md`、视 T0 结果 `docs/BUG_HISTORY.md`、无 key 时 `BLOCKED.md`
- 不改:`CHANGELOG.md`、`frontend/DESIGN.md`、部署文件、提示词