Files
Jyotisha/docs/tasks/TASK-consultation-session-capacity-20260915.md
T
Jesse_ChenandClaude Opus 5 11893c7f97 docs(tasks): 普通聊天审计三单(外网缓存 / 记忆三缺口 / 对话上限)
只读审计 origin/staging @ 6b3248bf 后出的三份任务书,含产品 2026-09-15
拍板的三项口径:

- external-evidence-cache(BUG-727/728,Python,串行在 api-server-decomposition
  之后):每轮每域同步等 api.vedastro.org,cProfile 前三名全是 TLS 往返,本地
  swisseph 只占 0.022 s;前台 1.5 s / 后台 8 s / 2 个 worker 且超时不 cancel。
  定案按「出生数据+岁差+交点+UTC 日期」缓存,与引擎内部同键;不许「干脆不调」,
  那会重开 BUG-301。另 western_evidence_packet 122 KB 无人读。
- context-memory(BUG-729/730/731,TS,可并行):droppedCount 算了没人读、
  摘要阈值与预算脱钩(窗口 < 70,667 即每轮静默丢)、写满时不继承摘要。
  定案静默继承,摘要文本永不由客户端提供。
- session-capacity(BUG-732,一份迁移,可并行):200,000 额度一半被思考文本
  吃掉,约 19 轮即写满。定案思考文本不计入,另设按整条 JSON 计的物理上限。

纯文档推送,不触发门禁、不发布镜像、不部署。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JUei7K13cYxLHE3Axe4A45
2026-09-15 22:47:55 +00:00

9.5 KiB
Raw Blame History

TASK · 对话上限一半被思考文本吃掉,十几轮就「已写满」

  • 日期:2026-09-15
  • 基线 commitorigin/staging @ 6b3248bf
  • 执行分支:codex/consultation-session-capacity-20260915
  • 主要落点:新增一份迁移(CREATE OR REPLACE FUNCTION public.append_consultation_question)、frontend/tests/ 下的合同测试
  • 不改 frontend/src/app/api/consult/route.ts:两档上限都继续返回同一个 session_full,路由与客户端一行都不用动
  • 与 external-evidence-cache 单、context-memory 单无文件重叠,可并行

1. 事故实证

问下一个问题之前,append_consultation_questionsupabase/migrations/20260901010000_append_consultation_question.sql)先数这段对话:

select coalesce(sum(
  length(coalesce(elem->>'text', ''))
  + length(coalesce(elem->>'thinkingText', ''))
  + case when elem ? 'thinkingSections'
      then length((elem->'thinkingSections')::text) else 0 end
), 0) into v_chars from jsonb_array_elements(...) as elem;

if v_count >= 200 or (v_chars + v_new_chars) > 200000 then
  return query select false, 'session_full'::text;

超了就 409,文案「这段对话已写满,开个新对话继续吧」。

每一轮一问一答实际吃掉多少:

计入的内容 每轮字符 用户读得到吗
助手正文 text 约 3,000 5,000 是,这才是对话
思考文本 thinkingText 最多 4,000route.tsslice(0, 4_000) 默认折叠
思考分节 thinkingSections 1,521 / 2,243 / 2,977(实测 1 / 2 / 3 个问题域) 只是步骤标签
用户提问 约 20 200

分节那三个数是跑 natalConsultationThinkingPlan 实测的 JSON 长度(3 / 4 / 5 个 section19 / 29 / 39 个 step)。合计每轮约 9,000 12,000 字符,一半以上是模型内部状态

200,000 ÷ 每轮约 10,500 ≈ 19 轮。另一档上限 200 条消息(100 轮)永远碰不到——真正卡住用户的是字符数,而字符数一半是思考。

口径本来就不一致:同一条助手消息里还存了 techniqueTruthworkflowReceiptagentExecutionReceipt,这三个字段照样入库却完全不计入上限。

2. 根因

这条上限身兼二职却一职没做好:

  • 想当「这段对话有多长」→ 不该数用户读不到、也无法控制的思考文本。
  • 想当「护住数据库那一行」→ 那就该把 techniqueTruth / workflowReceipt / agentExecutionReceipt 一起数,可它没数。

两个目标挤在一个数字里,结果是两边都不成立:用户的对话额度被模型内部状态吃掉一半,而数据库行的真实大小从来没被这条上限约束过。

3. 决策记录

产品 2026-09-15 拍板:思考文本不计入对话上限,并把两个目标拆成两条上限。

  1. 对话额度只数用户读得到的东西:助手正文 text + 用户提问 textthinkingTextthinkingSections 不计入。额度数值 200,000 不变——变的只是数什么。预期从约 19 轮提到约 50 轮。
  2. 另设一条物理上限护住数据库行,把全部字段(含三个 receipt)都算进去。它的作用是防止单行异常膨胀,正常使用下不应该先于对话额度触发。
  3. 两档都返回同一个 session_full。用户看到的处置一样(开新对话),没必要让前端分两种文案,也就不用动路由和客户端。
  4. 不删、不截断任何已存字段。 「老轮次的思考文本要不要继续留着」是另一个产品问题,见 §9 观察项,本单不碰。
  5. 200 条消息那档上限保留不动。

4. 硬红线

  1. append_consultation_question 必须保持 advisory lock、request_id 幂等、满员拒绝 三件事(BUG-464 防复发原文)。本单只改「数什么」和「加一档」,不得动这三条。
  2. 迁移必须对当前已部署的那一版代码向后兼容AGENTS §7.6):CREATE OR REPLACE 保持函数签名与返回列不变,error_code 仍只用既有的取值。staging 迁移在部署之前自动应用,旧代码必须能继续正常调用。
  3. 不得新增列、不得删列、不得改类型(本轮不动表结构)。
  4. 不得放宽 char_length(v_text) > 16000 那条单条提问长度校验。
  5. 物理上限的数值必须由算术推出来并写进迁移注释,不得拍脑袋写一个整数。
  6. 不得顺手升级依赖、不得顺手修不在本单里的 warning。

5. 任务分解

5.1 对话额度只数正文

新迁移里 CREATE OR REPLACE 该函数,把 v_chars 的求和改成只累加 elem->>'text'thinkingTextthinkingSections 不再进这个和。上限常数 200,000 不变。

  • 验收:npm run test:db --prefix frontend(需 Docker)新增用例——同一段会话,助手消息带 4,000 字 thinkingText 与 3,000 字 thinkingSections 时,不再影响还能问几轮;只有正文长度影响。
  • 验收:无 Docker 时写成环境缺口进 BLOCKED.md,并用 SQL 静态合同断言(求和表达式里不含 thinkingText / thinkingSections)替代,不得写成「通过」
  • 验收:幂等分支(同 request_id 重入返回 true, null)、advisory lock、session_missing 三条既有行为的测试一条不改仍全绿。

5.2 加一条把全部字段算上的物理上限

同一个函数里加第二次求和:对每条消息取 length(elem::text)(整条 JSON 的长度,字段增减都自动覆盖),超过物理上限同样返回 session_full

数值按算术定,并把算式写进迁移注释:50 轮 × 每轮(正文约 4,000 + 思考 4,000 + 分节 3,000 + 三个 receipt 约 3,000)≈ 700,000,取 1,000,000 作为留有余量的物理上限。执行方若测得 receipt 实际更大,按实测调整并在进度记录里写明新算式。

  • 验收:新增用例——正文很短但 receipt 极大的会话会被物理上限拒绝,且 error_code 仍是 session_full
  • 验收:正常形态的会话在触发对话额度之前不会先撞上物理上限(用 5.1 的夹具跑到额度边界,断言物理上限未触发)。
  • 验收:迁移注释里有那行算式。

5.3 量一次真实的详情接口体积

对话轮数上限从约 19 提到约 50GET /api/sessions/[id] 返回的整份 messages 也会按比例变大(该接口的 sessionSelectmessages,打开一段长会话是整份取回)。本单不改这个接口,但必须把数字量出来:

  • 构造一段跑到新额度边界的会话(测试夹具即可,不需要真实用户数据),记录详情接口 JSON 的体积。

  • 结论写进进度记录。如果在 2 vCPU 上明显偏大,追加到 §9 观察项并写进 BLOCKED.md不得默默略过

  • 验收:进度记录里有改前(约 19 轮)与改后(约 50 轮)两组体积数字。

5.4 Bug 历史

同一变更内写进 docs/BUG_HISTORY.md,预占 BUG-732。必须写明:关联 BUG-464200 / 200,000 两档上限与 append_consultation_question 都是那一单立的),以及这不是回归,是那条上限从一开始就身兼二职。防复发写成:

会话上限必须分成两条各司其职的口径:面向用户的对话额度只数用户读得到的正文;面向存储的物理上限必须把整条消息 JSON 算全。新增会存进 messages 的字段时,必须明确它进哪一条,不得默认落进对话额度。

6. 让步顺序

  1. 5.1 必须做,它是本单的全部意义。
  2. 5.2 必须和 5.1 同轮——只放宽不设物理上限,等于把行大小的护栏整个拆掉。
  3. 5.3 可以退化成「写成环境缺口」,但不得跳过不提。
  4. 5.4 不得砍。

7. 开工前置命令

git fetch origin --prune
git worktree add -b codex/consultation-session-capacity-20260915 \
  .worktrees/consultation-session-capacity-20260915 origin/staging
cd .worktrees/consultation-session-capacity-20260915
git status -sb | head -1
cd frontend && npm ci

开工前必读:docs/BUG_HISTORY.md 里的 BUG-464(这两档上限的来历与三条防复发),以及 AGENTS §7.6 关于 staging 自动迁移必须向后兼容的那一段。

验收命令:

cd frontend
./node_modules/.bin/tsc --noEmit
npm run lint
npm run db:migrate:check            # 迁移可应用性
npm run test:db                     # 需 Docker;无 Docker 写 BLOCKED.md
npx tsx --test tests/consultation-*.test.ts tests/chat-session-*.test.ts
npx tsx --test tests/*.test.ts       # 与基线逐条比对失败清单

8. BUG 编号起点

基线 6b3248bf 上最大号 BUG-720;校正四单预占 721726external-evidence-cache 单预占 727 / 728context-memory 单预占 729 / 730 / 731。本单预占 BUG-732。开工时核对实际最大号,冲突顺延并写进进度记录。

9. 观察项与不在本单范围

  • 老轮次的思考文本要不要一直留着thinkingText 会在历史消息里渲染(chat-message-row.tsx 对已结算消息也判断 message.thinkingText?.trim()),所以它是个真功能,不是纯浪费。但它的价值随时间衰减,只保留最近 N 轮是个可选方向——这是产品决策,本单不做,也不得顺手做。
  • 详情接口分页 / 按需加载历史(5.3 只负责量数据)
  • 200 条消息那档上限
  • 上下文记忆三条(见 TASK-consultation-context-memory-20260915.md
  • 外网证据缓存(见 TASK-consultation-external-evidence-cache-20260915.md