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

151 lines
9.5 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 · 对话上限一半被思考文本吃掉,十几轮就「已写满」
- 日期:2026-09-15
- 基线 commit`origin/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_question``supabase/migrations/20260901010000_append_consultation_question.sql`)先数这段对话:
```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,000`route.ts``slice(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 轮)**永远碰不到**——真正卡住用户的是字符数,而字符数一半是思考。
口径本来就不一致:同一条助手消息里还存了 `techniqueTruth``workflowReceipt``agentExecutionReceipt`,这三个字段**照样入库却完全不计入**上限。
## 2. 根因
这条上限身兼二职却一职没做好:
- 想当「这段对话有多长」→ 不该数用户读不到、也无法控制的思考文本。
- 想当「护住数据库那一行」→ 那就该把 `techniqueTruth` / `workflowReceipt` / `agentExecutionReceipt` 一起数,可它没数。
两个目标挤在一个数字里,结果是两边都不成立:用户的对话额度被模型内部状态吃掉一半,而数据库行的真实大小从来没被这条上限约束过。
## 3. 决策记录
产品 2026-09-15 拍板:**思考文本不计入对话上限**,并把两个目标拆成两条上限。
1. **对话额度只数用户读得到的东西**:助手正文 `text` + 用户提问 `text``thinkingText``thinkingSections` 不计入。额度数值 **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'``thinkingText``thinkingSections` 不再进这个和。上限常数 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 提到约 50`GET /api/sessions/[id]` 返回的整份 `messages` 也会按比例变大(该接口的 `sessionSelect``messages`,打开一段长会话是整份取回)。本单不改这个接口,但必须把数字量出来:
- 构造一段跑到新额度边界的会话(测试夹具即可,不需要真实用户数据),记录详情接口 JSON 的体积。
- 结论写进进度记录。如果在 2 vCPU 上明显偏大,追加到 §9 观察项并写进 `BLOCKED.md`**不得默默略过**。
- 验收:进度记录里有改前(约 19 轮)与改后(约 50 轮)两组体积数字。
### 5.4 Bug 历史
同一变更内写进 `docs/BUG_HISTORY.md`,预占 **BUG-732**。必须写明:**关联 BUG-464**200 / 200,000 两档上限与 `append_consultation_question` 都是那一单立的),以及这不是回归,是那条上限从一开始就身兼二职。防复发写成:
> 会话上限必须分成两条各司其职的口径:面向用户的对话额度只数用户读得到的正文;面向存储的物理上限必须把整条消息 JSON 算全。新增会存进 `messages` 的字段时,必须明确它进哪一条,不得默认落进对话额度。
## 6. 让步顺序
1. 5.1 必须做,它是本单的全部意义。
2. 5.2 必须和 5.1 同轮——只放宽不设物理上限,等于把行大小的护栏整个拆掉。
3. 5.3 可以退化成「写成环境缺口」,但不得跳过不提。
4. 5.4 不得砍。
## 7. 开工前置命令
```bash
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 自动迁移必须向后兼容的那一段。
验收命令:
```bash
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**;校正四单预占 **721726**external-evidence-cache 单预占 **727 / 728**context-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`