feat(consult): 进度、思考、正文三通道在生成时分开(BUG-942/943/944)
This commit is contained in:
@@ -12317,3 +12317,51 @@
|
||||
- 相关记录:BUG-924、BUG-927、BUG-933、BUG-935、BUG-939
|
||||
- 复发自:BUG-933
|
||||
- 修复版本:`0378d9e0`
|
||||
|
||||
## BUG-942 | 思维链过滤器在句内删词,连词和孤儿标点留给用户
|
||||
|
||||
- 状态:resolved
|
||||
- 首次发现:2026-09-18
|
||||
- 最近更新:2026-09-18
|
||||
- 影响面:`public-thinking.ts`(已删)、`stream-agent-response.ts`、思考面板
|
||||
- 用户现象:思考区出现 `"这周哪些事最好先放一放" — are put .` 这类残句。
|
||||
- 触发条件:模型 reasoning 含中英夹杂,旧 sanitizer 按句切、从第一个汉字截断、再删 ≥4 字母英文词。
|
||||
- 根因:一个文本通道当三个用,下游用正则当刀。
|
||||
- 修复:删除就地删词。provider reasoning 只打截断日志,不进事件。公开思考改为完整 `think.step`,校验不过整条丢弃。
|
||||
- 验证:`public-thinking.test.ts` 5 条;agentic-runtime 两条 reasoning 合同改为「reasoning 不进公开通道」。
|
||||
- 防复发:新增正则必须能回答「门还是刀」;刀不许进。
|
||||
- 相关记录:BUG-943、BUG-944
|
||||
- 复发自:无
|
||||
- 修复版本:待发布
|
||||
|
||||
## BUG-943 | 本命正文首节是参数罗列、尾节是技法审计表
|
||||
|
||||
- 状态:resolved
|
||||
- 首次发现:2026-09-18
|
||||
- 最近更新:2026-09-18
|
||||
- 影响面:`consultation-thinking-plan.ts`、`product-voice.ts`、咨询正文
|
||||
- 用户现象:回答先堆岁差/宫位,文末再贴技法审计表,问题被挤到后面。
|
||||
- 触发条件:本命咨询走旧 `REPORT_HEADING`(统一参数 / 领域 / 审计表 / 现代生活)。
|
||||
- 根因:方法学记账被当成用户读物。
|
||||
- 修复:正文章节改为「先回答你的问题 / 盘里支持这个判断的地方 / 时间怎么看 / 这周可以做的一件事」。审计表继续走回执折叠面板,不写进正文。
|
||||
- 验证:thinking-plan / voice / timeline 合同。
|
||||
- 防复发:正文出现「统一参数」「技法审计表」算 Pass 4 检测命中。
|
||||
- 相关记录:BUG-298、BUG-942、BUG-944
|
||||
- 复发自:BUG-298
|
||||
- 修复版本:待发布
|
||||
|
||||
## BUG-944 | 整轮共用 110 秒闸门,分段写作被静默掐断后变成空回答
|
||||
|
||||
- 状态:resolved
|
||||
- 首次发现:2026-09-18
|
||||
- 最近更新:2026-09-18
|
||||
- 影响面:`consult/route.ts` `composeSection`、`stream-agent-response.ts` `composeByHeadings`、领域时长常数
|
||||
- 用户现象:等约 110 秒后「本次没有生成回答」。回执分不清模型没写和流被掐。
|
||||
- 触发条件:工具耗时长(实测约 31s/领域),后续逐节写作共用同一个 `AbortSignal.timeout(110_000)`。
|
||||
- 根因:分段写作 + 共享闸刀;abort 没有独立运行步。
|
||||
- 修复:取消逐节 `composeSection` / `section-empty-retry`,一次成文。领域时长按 31s 重算。abort 记 `kind: "abort"`。已有正文片段不得再以 `empty_answer` 结束。
|
||||
- 验证:workflow-contract 与 agentic-runtime composeAnswer 合同。后台 job / 断线重放本轮未做,见进度记录让步。
|
||||
- 防复发:每个 stream 自己的预算;abort 必须留痕。
|
||||
- 相关记录:BUG-942、BUG-943
|
||||
- 复发自:无
|
||||
- 修复版本:待发布
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
# PROGRESS · 咨询三通道重做(2026-09-18)
|
||||
|
||||
工作树:`.worktrees/consult-three-channels-20260918`
|
||||
分支:`codex/consult-three-channels-20260918`
|
||||
基线:`origin/staging` @ `742ffbc6`
|
||||
任务书来源:`origin/docs/three-channel-redesign-20260918`(该分支误把产品三 bug 写成 939–941,staging 上这两号已是门禁合同,落地改号 **BUG-942 / 943 / 944**)
|
||||
|
||||
## 任务状态
|
||||
|
||||
| 任务 | 状态 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| BUG-942 删词刀 | 完成 | 删除 `public-thinking.ts`;reasoning 只打截断日志;`think.step` 整条接受或丢弃 |
|
||||
| BUG-943 正文记账 | 完成 | 本命四标题;审计表不进正文 |
|
||||
| BUG-944 110s 闸刀 | 完成 | 取消 `composeSection` / `section-empty-retry`;一次成文;abort 记 `kind:"abort"`;领域时长 31s |
|
||||
| 事件 v2 | 完成 | `think.plan` / `think.step` / `phase.*`;客户端仍能渲染 v1 `thinking.delta` / `thinking.section` |
|
||||
| 后台 job + 断线重放 | 让步 | 任务书「彻底版建议本单一起做」。本轮未加表、未把 run 搬出 HTTP。预算账本模块已落地,route 仍用总超时 |
|
||||
|
||||
## 让步
|
||||
|
||||
1. Pass 2 未再开一轮模型 JSON:`interpretFindings` 目前只发布计划 id,思考面板先用标题,完整判断句等下一轮接上。
|
||||
2. Pass 4 检测命中只记 `pass4-reject` 运行步,不在已经流出的正文上就地改写,也不回放第二轮(避免闪两次正文)。
|
||||
3. 未做后台 job / 事件日志表 / `runId` 重放。
|
||||
|
||||
## 测试
|
||||
|
||||
| 项 | 结果 |
|
||||
| --- | --- |
|
||||
| `tsc --noEmit` | 0 错 |
|
||||
| 定向 tsx | thinking-plan / workflow-contract / timeline / voice / birth-accuracy / public-thinking / timing-guard / chat-answer-split 等 **56+ 绿** |
|
||||
| `consultation-agentic-runtime.test.ts` | 本机 Windows `EPERM` skill symlink 无法加载,逻辑已按 v2 改写,以 Linux 门禁为准 |
|
||||
| 全量 `npm test` | 以门禁为准 |
|
||||
@@ -135,6 +135,7 @@
|
||||
| `TASK-session-list-single-source-20260917.md` | `PROGRESS-session-list-single-source-20260917.md` | 会话列表一处数据源:本地 PG 兼容层 `order()` 只保留最后一键,`/api/sessions` 实际按 `id` 排、与游标不一致;`/` 与次级页两份数据源、`/` 每次回来重启动(产品拍板:首页与三个次级页进同一路由组,侧栏外壳与列表 provider 常驻 layout,不做 store);空「新对话」落库堆积(首页 50 条里 28 条);标题类别在后、同名靠墙钟 HH:MM。串行在 composer-guard 单之后。BUG 段 926 起 | 验收未通过 | `e4e73f56`(实现按任务书落地:多键排序 / `(app)` 路由组常驻外壳 / provider / 空会话不入列 + draft 复用 / 标题类别在前;两条让步任务书允许。**P1:4 条针对 `page.tsx` 的源码合同没跟着外壳搬家,`npm test` 在门禁里 `set -e` 下必红,staging 至今停在 `dc2f2a16`**。另:进度记录报「tsc 0 错」实为 2 错,只跑了定向测试。修复单 `TASK-session-list-single-source-fix-20260917.md`) |
|
||||
| `TASK-session-list-single-source-fix-20260917.md` | `PROGRESS-session-list-single-source-fix-20260917.md` | 验收修复单:F1 四条源码合同搬到 `(app)/layout.tsx` 两端对断(BUG-933);F2 两条陈旧 Python 入口断言(BUG-934,既有欠账);F3 无活跃会话时输入框静默吞发送(BUG-935);交付前必须跑全量测试 | 待验收 | `codex/session-list-single-source-fix-20260917` |
|
||||
| — | `PROGRESS-birth-time-journey-page-path-20260918.md` | **P0 门禁**:`test_birth_time_journey_contract` 仍读已搬走的 `app/page.tsx`(BUG-939);修好后 run 2764 又露出 5 条校正守卫后的前端源码合同(BUG-940)。均复发自 BUG-933 | 已合入 | `39a0d7a9` / `0378d9e0` |
|
||||
| `TASK-consult-three-channels-20260918.md` | `PROGRESS-consult-three-channels-20260918.md` | 咨询运行时三通道:进度 / 思考 / 正文生成时分离。设计分支误用 BUG-939/940/941,落地改号 942–944 | 执行中 | `codex/consult-three-channels-20260918` |
|
||||
| `TASK-first-paint-dead-screen-fallback-20260917.md` | — | 真机:首页永远停在「正在载入账户」,兜底全在没跑起来的 bundle 里(BUG-936 investigating)。根 layout 加与 bundle 无关的内联兜底 + 去掉本仓正则后行断言 | 待领取 | — |
|
||||
| `TASK-consultation-answer-start-anchor-20260917.md` | `PROGRESS-consultation-answer-start-anchor-20260917.md` | 主会话回答落在结尾:`useConversationScrollAnchor` 是贴底跟随,流式期间视口钉在最后一个字,回答开头滚出视口;改为发送后问题钉顶、回答向下长、长出视口显示「跳到最新」、末尾动态留白;产品追加拍板:校正面同一语义(推翻 BUG-041/048 贴底),本轮开头 = 用户行或新助手行。BUG 段 930 起 | 已验收(经修复单) | `worktree/green-harbor-5be3` |
|
||||
| `TASK-consultation-answer-start-anchor-fix-20260917.md` | `PROGRESS-consultation-answer-start-anchor-fix-20260917.md` | 验收修复单:F1 头就是留白行时留白按整视口算(BUG-931);F2 留白只在钉住期间存在(BUG-932);前置:先修 e4e73f56 的两处 TS 错否则门禁不过 | 已验收 | `cc1a8980`(Claude 验收:tsc 0 / lint 0 error / npm test 3457 条 39 红与 11c0028d 逐条一致、新增 2 条绿 / `next build --webpack` 通过、`/` Static、首屏 gzip 591,242(较 09-16 基线 582,800 +1.45%,含会话列表单)/ Chrome 真实布局 S1–S6 全部通过,S6 新助手行距顶 16px 且增高不动,S5 不再写留白);真机六条欠 |
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
# TASK · 咨询运行时重做:进度 / 思考 / 正文三通道在生成时分离(2026-09-18)
|
||||
|
||||
> 落地分支:codex/consult-three-channels-20260918,基线 origin/staging @ 742ffbc6。
|
||||
> 设计稿把三则产品 bug 写成 BUG-939/940/941,与 staging 上门禁合同编号冲突,实现改号 **BUG-942(删词)/ BUG-943(正文记账)/ BUG-944(110s 闸刀)**。
|
||||
|
||||
|
||||
分支:`docs/three-channel-redesign-20260918`。基线 `origin/staging` = `5a1dcbd2`。
|
||||
目标:**干净的思考内容可展示、进度可见、正文只剩结论,三者互不混流,且回答质量不下降。**
|
||||
代价不设限(可以多跑模型轮次、可以落库、可以把运行搬出 HTTP 请求),但不得靠事后正则修补。
|
||||
|
||||
## 0. 病根一句话
|
||||
|
||||
产品把**一个文本通道**当三个用,然后在下游用正则把它们劈开。凡是「先混流、再过滤」的设计,过滤器迟早在错误的时刻删掉正确的东西——这正是现在同时产生「脏思维链」「正文里有思考」「空回答」的同一个根。
|
||||
|
||||
实证(`origin/staging` 行号,会漂):
|
||||
|
||||
- `frontend/src/lib/public-thinking.ts:27-33`:一句话通过 CJK 门之后,还要 `.replace(/[A-Za-z]{4,}/g, "")` 删掉所有 ≥4 字母的英文词。模型原句 `The user asked "这周哪些事最好先放一放" — are put off.` 先被 `buffer.slice(cjk)` 从第一个汉字截断(留下孤儿引号和破折号),再被删词(`asked`/`user` 没了,`are`/`put`/`the` 三字母以内全留),输出 `"这周哪些事最好先放一放" — are put .`。**不是模型乱,是过滤器把信息量最大的词删了、把连词留下了。**
|
||||
- `frontend/src/lib/consultation-thinking-plan.ts:12-16`:本命正文章节 = `统一参数与原始结构` + 各领域 + `技法审计表` + `现代生活`。正文第一节就是岁差/上升/宫位罗列,倒数第二节是技法审计表——方法学记账被当成用户读物。同文件 `DAILY_HEADING` 已经是干净口径,本命路线没跟上。
|
||||
- `frontend/src/app/api/consult/route.ts:898`:整轮只有一个 `AbortSignal.timeout(110_000)`,被首轮、工具、每一节分段写作、续写、重试**共用**。2026-09-18 的 run `1b81e263`:工具 61.7s(2 领域 ≈31s/个,而预算常数 `consultation-tools.ts:93` 写的是 21s),随后 5 节写作全部无 thinking、无 text、无 error chunk(`stream-agent-response.ts:408` 会把 error 记成 `model-stream-error`,回执里一条都没有)——**沉默流 = 被掐断的流**,于是 5 次 `section-empty-retry` + 1 次 `answer-retry` → `empty_answer`,用户等 110 秒拿到一句「本次没有生成回答」。
|
||||
|
||||
## 1. 四条设计原则
|
||||
|
||||
- **P1 三通道在生成时就分开。** 进度是服务端确定性事件;思考是模型**有意写给用户**的结构化条目;正文只有结论散文。任何通道都不靠下游切分得到。
|
||||
- **P2 provider 的 reasoning token 永不外发。** `thinking: "enabled"` 保留(它提升质量),但 `reasoning-delta` 只进服务端日志(截断 + 有保留期),不进事件流、不进数据库可见字段、不进 UI。展示给用户的「思考」是产品产物,不是 CoT 抓取。
|
||||
- **P3 正则只能当门,不能当刀。** 用正则判断「接受 / 拒绝 / 重来」可以;用正则**就地删词、挖空、替换半句**一律删除。这条同时判掉思维链的 `[A-Za-z]{4,}` 删词和正文的 `[具体时间已省略]` 就地替换。
|
||||
- **P4 时钟是预算,不是闸刀。** 每个阶段开工前向账本要时间;要不到就降级(少领域、少条目、单段成文),不允许静默 abort。任何 abort 必须在回执里留痕。
|
||||
|
||||
## 2. 新运行时:四个 pass + 一个预算账本
|
||||
|
||||
```
|
||||
Pass 0 服务端确定性 绑方法、定路由、定主题(入口钉死) 无模型
|
||||
Pass 1 读题与取证计划 结构化 JSON:reading + steps[] + domains[] 模型
|
||||
→ 计算开始前就把「思考」面板填满,用户在 60s 计算期有东西看
|
||||
计算 逐领域执行 确定性进度事件 progress{current,total} 无模型
|
||||
→ Pass 1 的 step 状态 pending → running → done
|
||||
Pass 2 证据解读 每个 step 产出 {stepId, finding, refs, confidence} 模型
|
||||
→ 这就是用户看到的「思考内容」:中文、成句、可校验;同时是写作输入
|
||||
Pass 3 成文 只写结论散文,输入 = 问题 + 证据契约 + findings 模型
|
||||
Pass 4 合规校验 检测越权断言;不通过则指名违规句退回 Pass 3 重写 模型/确定性
|
||||
```
|
||||
|
||||
要点:
|
||||
|
||||
1. **思考通道不再是碎片流。** `think.step` 一次发一条**完整条目**,不发 chunk。没有 chunk 就没有「按 chunk 过滤」这回事,`public-thinking.ts` 整个消失。要打字机效果,前端对完整条目做动画,不要靠网络分片。
|
||||
2. **正文一次成文。** 取消 `composeSection` 的逐节往返(`route.ts:1185-1203`):大输出预算 + 只有 `finishReason=length` 才续写。进度粒度由 think 通道提供,不再需要用分段来制造「正在写 X」。
|
||||
3. **Pass 2 是 grounding 步骤,不是装饰。** 强制模型先把证据显式转成带出处的结论候选,再写散文——这通常**提升**质量(少空话、少幻觉),也让「思考内容」天然可读。
|
||||
4. **诚实性红线:** Pass 2 是模型为用户重述的推理依据,**不等于** provider 内部 CoT。UI 文案只能叫「推理过程 / 判断依据」,不得宣称是模型的真实思考。
|
||||
|
||||
### 事件协议 v2
|
||||
|
||||
```jsonc
|
||||
run.started {runId, requestId}
|
||||
phase.started {phase, label, progress?: {current, total}} // 确定性,永不含模型文本
|
||||
phase.completed {phase, durationMs}
|
||||
think.plan {steps: [{id, title}]} // Pass 1
|
||||
think.step {id, status: "running"|"done", text?} // Pass 2,一条完整条目
|
||||
answer.delta {text} // Pass 3,只有散文
|
||||
run.completed {receipt} | run.failed {code, receipt}
|
||||
```
|
||||
|
||||
- `thinking.delta` 从协议里**删除**。`thinking.section` 由 `think.plan` + `think.step` 取代。
|
||||
- 每条 `think.step.text` 过 zod(长度、必须成句、必须含 CJK);**校验不过就丢掉这一条并按计划标签兜底,绝不改写这条的字**。这是 P3 的落点。
|
||||
- 客户端三个区域各认各的事件:进度条认 `phase.*`,思考面板认 `think.*`,正文认 `answer.*`。现有 `ConsultationRunTimeline` 的 method/calculate/think/write 四类行可以直接接 v2,不用重画。
|
||||
|
||||
## 3. 删除清单(不是改造,是删掉)
|
||||
|
||||
1. `frontend/src/lib/public-thinking.ts` —— 整个文件。
|
||||
2. `frontend/src/lib/stream-agent-response.ts` 的两处 `reasoning-delta → thinking.delta` 映射(约 277、431 行)。reasoning 改为只写服务端日志。
|
||||
3. `frontend/src/lib/consultation-agent-events.ts` 的 `thinkingDeltaSchema` 与 `thinkingSectionEventSchema`(v2 用新事件)。
|
||||
4. `frontend/src/lib/consultation-run-timeline.ts` 里 `thinking.delta` 的兜底分支(约 107 行)。
|
||||
5. `frontend/src/lib/timing-output-guard.ts` 的**就地替换**:`exactTimingPatterns` → `[具体时间已省略]`、`guaranteeConclusionPatterns` → `[保证性结论已省略]`、`guardGeneralNoBirthTimeOutput` 的整句替换。正则本身**保留为检测器**供 Pass 4 判定,但不得再写回文本。
|
||||
6. `route.ts` 的 `composeSection` / `stream-agent-response.ts` 的 `composeByHeadings` + `section-empty-retry`(一次成文后它们没有存在理由)。
|
||||
|
||||
## 4. 正文长什么样
|
||||
|
||||
- 章节改成面向问题的口径(照 `DAILY_HEADING` 的形状重做本命计划),例如:`先回答你的问题` / `盘里支持这个判断的地方` / `时间怎么看` / `这周可以做的一件事`。
|
||||
- `统一参数与原始结构`、`技法审计表` 退出正文,改由**证据面板**渲染——数据本来就是结构化的(`workflowReceipt`、`techniqueAuditTable`),不需要模型复述成散文。面板默认折叠。
|
||||
- 正文里出现 `##统一参数`、技法审计行、领域 id、英文术语堆砌,一律算 Pass 4 不通过。
|
||||
|
||||
## 5. 时钟与预算(治 `empty_answer`,顺带让「慢」变成可见进度)
|
||||
|
||||
- 一个 run 一本**预算账本**:`{总预算, 已用, 每阶段预留}`。每个阶段开工前申请,不足就降级而不是硬跑。
|
||||
- 每个 stream 自己的 AbortSignal = `min(阶段预算, 剩余总预算)`;**任何 abort 追加一条 `kind:"abort"` 运行步**,回执必须能区分「模型没写」和「流被掐」——现在两者长得一模一样,这是这次查不出来的直接原因。
|
||||
- 领域时长常数按实测重算(当前实测 ≈31s/领域,常数写的 21s),上限随之下调;`CONSULTATION_ANSWER_RESERVE_MS` 是给「一次成文」设计的,一次成文回归后它才重新成立。
|
||||
- 彻底版(建议本单一起做):**把 run 从 HTTP 请求里搬出来**——后台 job + 事件日志表 + 客户端按 `runId` 断线重放。函数 120s 上限不再是产品上限;同时 evidence packet 随 job 落库,**追问直接复用**,不必重跑 60s 计算(这也顺手修掉声明窗口路线「existing packet 根本不存在」的那条自相矛盾)。
|
||||
|
||||
## 6. 怎么证明「质量没下降」
|
||||
|
||||
改前改后各跑同一组 20 条真实问题(覆盖本命 / 声明窗口 / 无分钟 / 今日入口),记录:
|
||||
|
||||
| 口径 | 方法 | 要求 |
|
||||
|---|---|---|
|
||||
| 结论具体性、证据引用、可读性 | 人工双盲打分 | 不低于基线 |
|
||||
| 越权断言条数 | Pass 4 检测器统计 | ≤ 基线,且**改写次数为 0**(现在是每次都在改写) |
|
||||
| 正文里方法学记账段落数 | 关键词扫描 | 0 |
|
||||
| 思考面板可读性 | 人工 | 每条成句、中文、无孤儿标点 |
|
||||
| 首字延迟 / 总时长 / 失败率 | 埋点 | 失败率下降;总时长不劣于基线 |
|
||||
|
||||
## 7. 硬红线
|
||||
|
||||
1. 不得再出现「先混流再过滤」的新代码。新增正则必须能回答「它是门还是刀」,是刀就不许进。
|
||||
2. `tsc --noEmit` 0 错、`npm run lint` 0 error;测试总数不得低于基线。删掉的 `public-thinking` 相关断言必须被 v2 的「校验不过整条丢弃」断言替代,不能净减。
|
||||
3. provider reasoning 不得出现在任何对外事件、数据库可见字段或日志的完整原文里(日志只留截断 + 保留期)。
|
||||
4. 事件协议 v2 与 v1 并行一个发布周期,客户端两者都能渲染;回执 schema 仍是 strict,新增字段走白名单。
|
||||
5. 任何一次 run 只要已经产出过正文片段,就不得以 `empty_answer` 结束。
|
||||
Reference in New Issue
Block a user