- 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
171 lines
17 KiB
Markdown
171 lines
17 KiB
Markdown
# 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`、部署文件、提示词
|