merge: sync origin/main (22bbf8ab) into docs/correct-report-smoke
Resolve conflicts: - globals.css: keep origin/main disabled opacity 1 (BUG-140 parity) - consultation-entrypoint.test.ts: keep origin/main sticky-hover + disabled rules - BUG_HISTORY.md: keep origin/main BUG-139 (staging admin), renumber local Python prework fix to BUG-158
This commit is contained in:
@@ -1,383 +0,0 @@
|
||||
# 生时校正 V4 重构设计 — 2026-07-26
|
||||
|
||||
## 1. 结论
|
||||
|
||||
生时校正不应继续作为“聊天接口里顺便跑一次模型和分钟扫描”的功能维护。V4 将它重构为独立、可恢复、可审计的证据工作流:
|
||||
|
||||
1. 先收集跨领域、带日期精度的人生事件;
|
||||
2. 再对已经存在但日期较粗的事件做定向修订;
|
||||
3. 后台 Worker 对冻结的计算口径和证据集合评分;
|
||||
4. 只输出通过稳定性门槛的候选时间范围;
|
||||
5. 用户主动保存后,才把该范围交给原咨询问题继续使用。
|
||||
|
||||
产品永久边界:**V4 不确认单分钟,不把峰值分钟显示为真实出生时间,不修改 `profiles.active_birth_time`。**
|
||||
|
||||
本文记录已经落地到本地可测试工作树的目标架构,而不是对旧实现的小修补。
|
||||
|
||||
## 2. 交互记录暴露的旧架构问题
|
||||
|
||||
用户提供的 Agent 交互记录说明,专业校时需要的不只是一个聊天框,而是一套持续数十轮仍保持一致的证据系统。旧 Web 流程无法稳定复现该过程,主要原因不是文案不够像 Agent,而是职责没有拆开。
|
||||
|
||||
### 2.1 预设年份问卷替代了真实证据
|
||||
|
||||
旧流程先给出宽年份选项,再不断要求把同一事件从年份段缩到年份、季度、月份。这样会产生三个问题:
|
||||
|
||||
- 问题本身暗示事件应该发生在哪个窗口,带来确认偏差;
|
||||
- 用户已经给过事件后,系统仍可能把它当成下一条新事件;
|
||||
- 为了进入评分而取区间中点,会制造不存在的日期精度。
|
||||
|
||||
V4 改为先接收用户自己声明的事件和日期,再把后续日期补充写成同一事件的新 revision。
|
||||
|
||||
### 2.2 对话、抽取、评分和结果表达在同一请求中耦合
|
||||
|
||||
旧流程把事件抽取、技术计算、叙事生成和下一题规划放在一个请求生命周期里。任一模型超时、跨语言 schema 漂移或计算耗时,都可能让已提交的经历看起来没有保存。
|
||||
|
||||
V4 的同步请求只负责持久化答案并创建 Job;Worker 异步完成抽取、评分、稳定性检查和下一题规划。用户提交成功后可以离开页面,稍后继续。
|
||||
|
||||
### 2.3 “第一名分钟”被误当成产品答案
|
||||
|
||||
分钟扫描必然会产生一个最高分,但最高分不等于真实出生分钟。旧交互即使同时声明低置信度,仍会把一个具体分钟写成“建议暂用时间”,视觉上压过候选范围和不确定性说明。
|
||||
|
||||
V4 将 `representativeTime` 限定为内部聚类数据。公开快照中的 `canConfirmExactMinute` 是字面量 `false`;UI 只显示 `startTime–endTime`,不显示峰值分钟。
|
||||
|
||||
### 2.4 没有稳定的事件身份和修订链
|
||||
|
||||
用户可能先说“某年发生”,后续补成“某年某月”,也可能纠正原先年份。覆盖旧记录会丢失审计信息;新建一条记录又会重复计分。
|
||||
|
||||
V4 使用 `eventId + revision`:
|
||||
|
||||
- `eventId` 表示同一人生事件;
|
||||
- 每次补充创建 append-only revision;
|
||||
- `supersedesRevisionId` 指向上一版本;
|
||||
- 评分只读取每个 `eventId` 的最新 revision。
|
||||
|
||||
### 2.5 会话状态不足以承担业务状态
|
||||
|
||||
浏览器会话可能被删除、刷新或从另一设备继续。旧流程把关键进度附着在聊天消息上,难以保证幂等、恢复、并发提交、扣费和原问题 handoff。
|
||||
|
||||
V4 将 Case、Turn、Event Revision、Snapshot、Job 和 Handoff 独立持久化;聊天会话只是入口和展示容器,不再是生时校正业务真源。
|
||||
|
||||
## 3. 产品合同
|
||||
|
||||
### 3.1 输入
|
||||
|
||||
- 已保存出生日期;
|
||||
- 用户声明的候选时间和不确定范围;
|
||||
- 出生地点坐标与时区;
|
||||
- 固定计算口径:Lahiri、Mean Node、一分钟步长;
|
||||
- 用户主动提供的人生事件。
|
||||
|
||||
### 3.2 证据领域
|
||||
|
||||
| 领域 | 典型事件 | 评分状态 |
|
||||
| --- | --- | --- |
|
||||
| `education` | 升学、复读、毕业、转学 | scoreable |
|
||||
| `relocation` | 搬家、长期迁居、离乡 | scoreable |
|
||||
| `relationship` | 重要关系开始或结束 | scoreable |
|
||||
| `career` | 入职、离职、转行、职责突变 | scoreable |
|
||||
| `finance` | 收入、负债、资产明显变化 | scoreable |
|
||||
| `health_pressure` | 疾病、手术、事故、长期压力起点 | scoreable |
|
||||
| `family` | 家庭结构或亲属重大事件 | context-only |
|
||||
| `other` | 其他明确、重要、可核对事件 | 按领域能力决定 |
|
||||
|
||||
家庭事件先保留为上下文,不应为了“凑够领域”接入没有可靠 scorer 的技术层。
|
||||
|
||||
### 3.3 输出
|
||||
|
||||
- 一个主要候选范围;
|
||||
- 可选的次级候选范围;
|
||||
- 支持该范围的事件;
|
||||
- 冲突或区分力不足的事件;
|
||||
- 稳定性门结果;
|
||||
- 用户是否已主动保存该范围。
|
||||
|
||||
### 3.4 永久禁止
|
||||
|
||||
- 不确认单分钟;
|
||||
- 不把 `representativeTime` 作为公开结论;
|
||||
- 不自动保存候选范围;
|
||||
- 不修改 `profiles.active_birth_time`;
|
||||
- 不因聊天文案或模型失败丢弃已经持久化的答案;
|
||||
- 不用区间中点伪造事件日期;
|
||||
- 不把同一事件的日期补充重复计分。
|
||||
|
||||
## 4. 两阶段问题规划
|
||||
|
||||
### 4.1 阶段一:领域覆盖
|
||||
|
||||
按低回忆成本优先收集七个领域。每次只问一个开放问题,要求用户自己提供事件与尽可能准确的年月,不先展示系统猜测的年份窗口。
|
||||
|
||||
当前确定性顺序为:
|
||||
|
||||
```text
|
||||
education → relocation → relationship → career → finance → health_pressure → family
|
||||
```
|
||||
|
||||
规划器已经保留 `candidateSplitByDomain` 输入;未来只有当评分引擎能给出可解释的领域区分力时,才允许在不增加模型自由度的前提下动态排序。
|
||||
|
||||
### 4.2 阶段二:日期精度修订
|
||||
|
||||
完成领域覆盖后,对最新 revision 仍不是 `day` 精度的 scoreable 事件定向追问:
|
||||
|
||||
- 问题保存 `targetEventId`;
|
||||
- Turn 保存 `questionTargetEventId`;
|
||||
- Worker 用该 ID 将回答追加到原事件;
|
||||
- 如果回答没有可解析日期,不创建伪 revision;
|
||||
- 已经追问过或用户跳过的事件写入尝试集合,不循环追问。
|
||||
|
||||
如果所有可修订事件都已处理但仍未通过稳定性门,则询问新的、日期明确的重要事件;用户可以暂停或结束。
|
||||
|
||||
## 5. 状态机
|
||||
|
||||
### 5.1 Case 状态
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> awaiting_answer: create case
|
||||
awaiting_answer --> processing: submit answer
|
||||
processing --> awaiting_answer: worker plans next question
|
||||
processing --> range_ready: range passes gate
|
||||
awaiting_answer --> paused: pause
|
||||
paused --> awaiting_answer: resume
|
||||
awaiting_answer --> abandoned: abandon
|
||||
range_ready --> abandoned: abandon without save
|
||||
range_ready --> range_ready: explicitly save accepted range
|
||||
```
|
||||
|
||||
| 状态 | 含义 | `currentQuestion` |
|
||||
| --- | --- | --- |
|
||||
| `awaiting_answer` | 等待用户回答 | 必须非空 |
|
||||
| `processing` | 答案已保存,后台处理中 | 空 |
|
||||
| `range_ready` | 有候选范围;可能尚未保存 | 可空;稳定范围就绪时为空 |
|
||||
| `paused` | 用户主动暂停 | 保留可恢复进度 |
|
||||
| `abandoned` | 用户结束本次校正 | 空 |
|
||||
|
||||
关键不变量:证据不足时必须生成下一题,不能出现 `status=awaiting_answer` 且 `currentQuestion=null`。
|
||||
|
||||
### 5.2 Worker 阶段
|
||||
|
||||
```text
|
||||
extracting_evidence
|
||||
→ scoring_candidates
|
||||
→ checking_robustness
|
||||
→ planning_question
|
||||
→ collecting_evidence | complete
|
||||
```
|
||||
|
||||
Case 状态描述用户能做什么,Phase 描述系统正在做什么。两者不能混用。
|
||||
|
||||
### 5.3 Job 状态
|
||||
|
||||
```text
|
||||
pending → processing → completed
|
||||
└→ failed
|
||||
pending/expired processing → processing by another worker
|
||||
obsolete input → stale
|
||||
```
|
||||
|
||||
Job 使用十分钟 lease。只有持有有效 lease 且输入 case version、证据 hash、计算口径 hash 仍匹配的 Worker 可以完成任务。
|
||||
|
||||
### 5.4 Handoff 状态
|
||||
|
||||
```text
|
||||
pending → claimed → executing → consumed
|
||||
```
|
||||
|
||||
过期的 `claimed/executing` lease 可恢复为 `pending`。Handoff 使用 `requestId`、`claimActionId` 和 settlement receipt 保证多设备重试不会重复执行或重复扣费。
|
||||
|
||||
## 6. 数据模型
|
||||
|
||||
### 6.1 Case
|
||||
|
||||
Case 冻结一次校正的业务口径:
|
||||
|
||||
- `calculationSpec`;
|
||||
- `calculationSpecHash`;
|
||||
- `evidenceSetHash`;
|
||||
- `version`;
|
||||
- `currentQuestion`;
|
||||
- `latestSnapshot`;
|
||||
- `acceptedRange`。
|
||||
|
||||
`acceptedRange` 在用户点击“保存这个范围”前始终为 `null`。
|
||||
|
||||
### 6.2 Turn
|
||||
|
||||
Turn 是用户回答的不可变输入记录:
|
||||
|
||||
- 本轮问题 ID、领域和目标事件 ID;
|
||||
- 可见问题文本;
|
||||
- 用户原始回答;
|
||||
- `actionId`;
|
||||
- 提交时的 case version。
|
||||
|
||||
失败重试时可以恢复同一个问题,不需要从聊天文本反向猜测当时问了什么。
|
||||
|
||||
### 6.3 Event 与 Event Revision
|
||||
|
||||
Event 提供稳定身份;Revision 保存领域、事件类型、摘要、原文、日期范围、精度和评分资格。
|
||||
|
||||
评分前执行 `latestEventRevisions()`,确保同一事件只使用最新版本。原始回答保留,技术评分不依赖经过润色的叙事文案。
|
||||
|
||||
### 6.4 Candidate Snapshot
|
||||
|
||||
每次评分都生成不可变 Snapshot:
|
||||
|
||||
- 输入证据 hash;
|
||||
- 计算口径 hash;
|
||||
- 算法版本;
|
||||
- 全部分钟候选分数;
|
||||
- 候选聚类;
|
||||
- 稳定性结果;
|
||||
- 决策门原因。
|
||||
|
||||
Snapshot 使刷新、复算和算法升级可审计,不用覆盖上一轮结果。
|
||||
|
||||
## 7. 候选聚类与稳定性门
|
||||
|
||||
### 7.1 聚类
|
||||
|
||||
当前算法取峰值分数的相对 `0.97` 以上候选,将相邻分钟合并为 cluster,并按峰值和 score mass 排序。
|
||||
|
||||
`representativeTime` 仅用于内部描述 cluster 峰值。公开结果使用 `startTime` 和 `endTime`。
|
||||
|
||||
### 7.2 可保存范围门槛
|
||||
|
||||
主要范围必须同时满足:
|
||||
|
||||
- 至少 5 个 scoreable 事件;
|
||||
- 至少 3 个 scoreable 领域;
|
||||
- cluster 宽度至少 2 分钟,拒绝单分钟结果;
|
||||
- cluster 宽度不超过 15 分钟;
|
||||
- 邻近分钟支持至少 2 分钟;
|
||||
- leave-one-out 保留率至少 0.8;
|
||||
- 日期敏感性保留率至少 0.8;
|
||||
- 计算口径 hash 与建案时一致。
|
||||
|
||||
无论是否通过,`canConfirmExactMinute` 永远为 `false`。
|
||||
|
||||
## 8. 请求与后台执行边界
|
||||
|
||||
### 8.1 同步 API 负责
|
||||
|
||||
- 认证和输入校验;
|
||||
- 幂等 action;
|
||||
- optimistic case version 检查;
|
||||
- 保存 Turn;
|
||||
- 创建 pending Job;
|
||||
- 返回 `202` 和可轮询 Job。
|
||||
|
||||
### 8.2 Worker 负责
|
||||
|
||||
- 抽取新事件或定向修订;
|
||||
- 计算 evidence hash;
|
||||
- 调用 Python 候选引擎;
|
||||
- 聚类与稳定性门;
|
||||
- 生成 Snapshot;
|
||||
- 规划下一题;
|
||||
- 原子完成 Job 和 Case 状态迁移。
|
||||
|
||||
非关键叙事模型不在 V4 完成链路上。确定性问题和业务状态在模型不可用时仍可继续。
|
||||
|
||||
## 9. 数据库不变量
|
||||
|
||||
迁移 `20260726020000_birth_time_rectification_v4.sql` 将关键规则下沉到 PostgreSQL:
|
||||
|
||||
1. 同一用户最多一个未结束、未接受范围的活动 Case;
|
||||
2. `actionId` 幂等,同一 action 不能绑定不同问题;
|
||||
3. `expectedCaseVersion` 不匹配时拒绝陈旧写入;
|
||||
4. 未通过快照门或范围与最新主要 cluster 不一致时不能保存;
|
||||
5. 有效 Worker lease 不能被抢占,过期 lease 可以接管;
|
||||
6. Worker 只能完成仍匹配输入 hash 和 version 的 Job;
|
||||
7. Handoff claim、begin、refund 和 settlement 可重试;
|
||||
8. 一次 Handoff 最多产生一次成功扣费;
|
||||
9. 取消的请求可以重新生成 request key;
|
||||
10. 整个 V4 migration 不更新 `profiles.active_birth_time`。
|
||||
|
||||
## 10. UI 设计
|
||||
|
||||
### 10.1 首屏
|
||||
|
||||
必须同时告诉用户:
|
||||
|
||||
- 正在比较的声明候选边界;
|
||||
- 当前流程先核对经历;
|
||||
- 结果只会是候选范围,不是已确认分钟;
|
||||
- 原咨询问题已保留。
|
||||
|
||||
### 10.2 处理中
|
||||
|
||||
提交后立即显示“回答已经保存,计算在后台继续”。允许用户离开页面,不用让浏览器请求一直等待。
|
||||
|
||||
### 10.3 日期修订
|
||||
|
||||
日期修订问题必须有可见 label 和输入框,不得只显示空卡片。问题明确允许“不记得/跳过”,避免为了通过流程而编造日期。
|
||||
|
||||
### 10.4 结果
|
||||
|
||||
只显示:
|
||||
|
||||
- 主要/次级候选范围;
|
||||
- 支持经历;
|
||||
- 冲突或区分力不足;
|
||||
- 不确定性说明;
|
||||
- “保存这个范围”操作。
|
||||
|
||||
不显示内部事件 ID、hash、技术 packet、模型错误、评分明细或峰值分钟。
|
||||
|
||||
### 10.5 保存与继续咨询
|
||||
|
||||
点击“保存这个范围”只写 `acceptedRange`。之后用户可以把该范围带回原问题;咨询服务将其作为“未验证候选范围”使用,而不是已确认出生时间。
|
||||
|
||||
## 11. 错误与恢复策略
|
||||
|
||||
- 输入不合法:同步返回稳定中文错误,不创建 Job;
|
||||
- 陈旧版本:返回冲突,客户端刷新 Case;
|
||||
- Worker 失败:Job 标记 failed,恢复原问题;
|
||||
- Worker 崩溃:lease 到期后其他 Worker 接管;
|
||||
- 证据不足:生成下一题,不伪装成技术异常;
|
||||
- 用户暂停:保留 Case、事件和问题;
|
||||
- 用户结束:Case 进入 abandoned,出生资料不改写;
|
||||
- 页面刷新:从活动 Case、Job 和事件台账恢复;
|
||||
- 多设备 handoff:由数据库 lease 和 settlement receipt 仲裁。
|
||||
|
||||
## 12. 已落地代码边界
|
||||
|
||||
```text
|
||||
frontend/src/lib/rectification-v4/ 领域模型、规划、评分适配、Store、Worker
|
||||
frontend/src/app/api/rectification/v4/ HTTP API
|
||||
frontend/src/components/rectification-v4-panel.tsx
|
||||
frontend/src/hooks/use-rectification-v4.ts
|
||||
frontend/scripts/rectification-v4-worker.mts
|
||||
scripts/active_rectification_events_v4.py
|
||||
frontend/supabase/migrations/20260726020000_birth_time_rectification_v4.sql
|
||||
frontend/tests/rectification-v4-*.test.ts
|
||||
tests/test_active_rectification_events_v4.py
|
||||
```
|
||||
|
||||
首页旧聊天入口继续保留外壳和历史兼容,但新的活动流程由 `RectificationV4Panel` 和 V4 API 驱动。
|
||||
|
||||
## 13. 本地验收结果
|
||||
|
||||
截至 2026-07-26:
|
||||
|
||||
- 前端 V4、handoff、replay、consultation continuation:33 个测试通过;
|
||||
- Python 引擎:2 个测试通过;
|
||||
- 目标 ESLint:通过;
|
||||
- `npm run build -- --webpack`:通过,27 个页面生成成功;
|
||||
- PostgreSQL:12 项迁移和并发/扣费不变量通过;
|
||||
- 真实 PostgreSQL + Python Engine E2E:最终进入 `range_ready`,主要范围 `05:26–05:30`;
|
||||
- E2E 后 `active_birth_time` 保持原值,`acceptedRange` 保持空值;
|
||||
- 静态浏览器预览:日期修订输入框、范围保存按钮、不确认分钟文案和 390px 无横向溢出均通过。
|
||||
|
||||
本轮没有提交、推送或部署。由于当前本地认证浏览器与隔离 V4 数据库/服务环境没有安全地连在一起,尚未声称“真实登录态端到端 UI”已验收;发布前仍需在正确的本地或 staging 认证环境执行一次完整用户操作 smoke。
|
||||
|
||||
## 14. 发布前必须补齐
|
||||
|
||||
1. 应用迁移并核对 migration ledger;
|
||||
2. 启动独立 Worker,确认部署环境包含数据库和 Python API 配置;
|
||||
3. 用测试账户完成:建案 → 七领域 → 日期修订 → 范围就绪 → 主动保存 → 原问题 handoff;
|
||||
4. 证明刷新和另一设备可恢复;
|
||||
5. 证明重复提交、过期版本和 Worker 接管不重复事件、不重复扣费;
|
||||
6. 再次核对 `profiles.active_birth_time` 未被 V4 路径更新;
|
||||
7. 将验收绑定到精确部署 Git SHA,而不是只看 HTTP 200。
|
||||
@@ -1,131 +0,0 @@
|
||||
# 对话式生时纠正修改方案与真实回放门槛
|
||||
|
||||
日期:2026-07-22
|
||||
|
||||
## 结论先行
|
||||
|
||||
当前系统可以改成普通 session 式的一问一答,但不能把“交互更顺”描述成“已经能准确到分钟”。现有公开开发案例的分钟评分仍不稳定;产品入口还会丢弃健康、事故、家庭类证据,并且实际只把最近 6 条事件送入评分。
|
||||
|
||||
本方案把修改拆成两个互不混淆的目标:
|
||||
|
||||
1. 让用户通过自然语言持续补充经历,系统每轮只问一个最有区分力的问题。
|
||||
2. 用开发集和独立冻结盲测证明分钟排序是否真的收敛;没有通过门槛时只返回候选范围和证据缺口。
|
||||
|
||||
## 已确认的问题
|
||||
|
||||
### 1. 首轮正文被技术回执合同绑架
|
||||
|
||||
`narrative-agent.ts` 当前要求首轮正文复述所有稳定层、敏感层、具体值和领域映射。模型未满足时,fallback 又会输出同一份 D 层清单。技术包本身可以保留,但不应该成为用户正文。
|
||||
|
||||
### 2. 自然语言证据域不完整
|
||||
|
||||
`evidence-extractor.ts` 只显式识别事业、学业、迁移、关系、家庭、财富。疾病、手术、事故和丧亲会落到 `other`;`route.ts` 又会排除 `family` 和 `other`,所以 D30、家庭与部分重大人生事件无法参与评分。
|
||||
|
||||
日期正则还被限制为 1900–2099 年。公开历史人物案例中的 18xx 年事件会被保存成“日期未知”,无法评分;这也意味着现有历史 AA 开发案例并没有真正通过当前产品的自然语言入口。
|
||||
|
||||
### 3. 事件数量合同互相矛盾
|
||||
|
||||
- 会话收敛层:3 条开始排序,8 条停止继续取证。
|
||||
- 路由评分层:只保留最近 6 条。
|
||||
- Jyotish Skill:至少 5 条 dated events 才适合定框;高置信度通常需要 10–15 条多领域事件。
|
||||
|
||||
3 条可以产生早期候选反馈,但不得升级成准确分钟。停止条件也不能仅由事件数量触发。
|
||||
|
||||
### 4. 当前问题选择仍偏“让用户交材料”
|
||||
|
||||
系统应从候选差异中选择一个信息增益最高、用户容易回忆的问题,例如“第一次正式工作是哪年哪月”,而不是一次要求用户提交多个领域的事件。
|
||||
|
||||
## 最小实施方案
|
||||
|
||||
### 阶段 A:修正文合同,不改评分器
|
||||
|
||||
建议改动:
|
||||
|
||||
- `frontend/src/lib/conversational-rectification/narrative-agent.ts`
|
||||
- 首轮正文只要求:当前范围、尚未确认边界、证据进度、一条具体问题。
|
||||
- 取消首轮必须公开全部 stable/sensitive layer values 的校验。
|
||||
- fallback 同样只输出一条问题。
|
||||
- 技术层继续完整保存在 technical packet / receipt 中,在 UI 中放入可折叠“计算依据”。
|
||||
- 用户消息区、输入框、滚动和留白完全复用普通 session 布局,不保留卡片式问卷。
|
||||
|
||||
验收:首轮正文不出现 D1/D9/D10 技术 dump;每轮只有一个问号和一个待回答主题。
|
||||
|
||||
### 阶段 B:补齐事件入口
|
||||
|
||||
建议改动:
|
||||
|
||||
- 在持久化和技术包域中新增 `health_pressure`,不要把它降级成 `other`。
|
||||
- 抽取器识别:确诊、住院、手术、事故、受伤、创伤、重大疾病、康复。
|
||||
- 日期解析支持出生之后的合法公历年份,而不是写死 19xx/20xx;继续用 `asOfDate` 排除未来事件。
|
||||
- 家庭事件不要整体丢弃;按父母、子女、生育、丧亲等映射到后端支持的主题证据,若评分器尚无对应合同则明确存储为 context-only,而不是静默消失。
|
||||
- 移除 `.slice(-6)` 的隐式截断。建立一个共享常量;建议开发回放先测 5、8、10、12 条,再决定产品上限。
|
||||
- UI 显示“已记录 / 可评分 / 需补日期 / 仅作背景”的数量,避免用户以为回答已评分。
|
||||
|
||||
验收:真实测试集中所有标记为可评分的自然语言回答都能保留正确日期、精度和领域;任何被排除的事件都有可见原因。
|
||||
|
||||
### 阶段 C:一次问一个高信息量问题
|
||||
|
||||
复用现有 `candidateDifferences`、`suggestedDomains` 和 fact difference opportunities:
|
||||
|
||||
1. 过滤已经问过、用户表示不记得、或缺少后端支持的主题。
|
||||
2. 优先选择能把当前候选分成最均衡分区的事件领域。
|
||||
3. 将领域转成生活化问题,并要求“年份/月份 + 发生了什么”。
|
||||
4. 用户回答后重新排序,再选择下一题。
|
||||
|
||||
问题模板必须具体,例如:
|
||||
|
||||
> 你第一次明显的工作转折发生在什么时候?可以是入职、离职、换行业、升职或创业。直接告诉我大约哪一年哪一月和发生了什么;记不清月份也可以先说年份。
|
||||
|
||||
### 阶段 D:安全收敛
|
||||
|
||||
每轮都可以展示“当前领先范围”,但只有同时满足以下条件才允许提示确认:
|
||||
|
||||
- 至少 5 条可评分事件,且至少 3 个领域;高置信度目标使用 10–15 条。
|
||||
- 真实候选形成唯一分钟或预先规定的窄范围。
|
||||
- ±1/2/5 分钟邻近稳定性通过。
|
||||
- leave-one-event-out 通过,不能由单一事件决定结果。
|
||||
- 没有缺失的强制计算层。
|
||||
- 独立冻结盲测达到发布指标。
|
||||
|
||||
未满足时输出:候选范围、支持事件、冲突事件、未评分事件和下一条问题,不允许写“准确时间为”。
|
||||
|
||||
## 测试集分层
|
||||
|
||||
### 对话开发回放集
|
||||
|
||||
文件:`references/real_case_calibration/conversational_rectification_development_v1.json`
|
||||
|
||||
当前 v1 是入口 smoke fixture,不是分钟收敛 benchmark:TypeScript 测试验证 fixture 中自然语言在现有抽取器下的日期、精度和领域;Python 回放使用同一 fixture 的人工 `expected_route_scoreable` 标签模拟现有路由过滤,再测试逐轮排名。它不是自然语言到评分的端到端回放,且每例不足 5 条可评分事件,不能用于判断 Skill 级收敛。
|
||||
|
||||
下一版 development benchmark 必须把每例扩充至至少 5 条、目标 10–15 条多领域事件,并直接调用抽取器和路由投影,覆盖记不清、拒答、否定、纠正、重复和一条消息多事件。它引用仓库已有公开 AA 开发案例,永久排除在 holdout 之外,可以用于修 bug 和调问题排序。
|
||||
|
||||
### 独立冻结 holdout v4
|
||||
|
||||
开发集不能证明发布准确率。正式验证需要另一位 reviewer 收集至少 20 个未参与调参的公开 AA 案例,在评分前完成:出生来源复核、事件来源复核、假分钟承诺、manifest hash、实现 hash 和真值密封。只允许盲回放一次;失败后整批降为 development,下一版重新收集 fresh holdout。
|
||||
|
||||
## 指标
|
||||
|
||||
对话入口:
|
||||
|
||||
- 日期和精度提取准确率。
|
||||
- 领域提取准确率。
|
||||
- 应评分事件保留率。
|
||||
- 每轮问题数必须等于 1。
|
||||
- 不能回答、记不清、纠正旧答案时仍能继续。
|
||||
|
||||
分钟能力:
|
||||
|
||||
- 3、5、8、10 条事件时的 Top-1 / Top-3。
|
||||
- 逐轮真实分钟排名和误差轨迹。
|
||||
- 唯一分钟率、邻近稳定性、leave-one-out。并列范围未破时 `predicted_time` 必须为空;如保留确定性 tie-break,只能单列为诊断 MAE,不能当成分钟能力。
|
||||
- false confirmation rate 和 confirmation coverage 必须同时报告。
|
||||
|
||||
建议继续沿用当前发布门槛:Top-1 ≥ 60%,Top-3 ≥ 85%,MAE ≤ 2 分钟,false confirmation ≤ 5%,insufficient-evidence rejection ≥ 90%。confirmation coverage 不能为 0,否则只是“永不确认”而非可用收敛。
|
||||
|
||||
## 实施顺序
|
||||
|
||||
1. 先合并阶段 A 与入口可观测性,不改变“准确分钟”声明。
|
||||
2. 用本开发回放集修阶段 B,确保事件没有在入口丢失。
|
||||
3. 接入阶段 C 的单问题排序并做多轮 replay。
|
||||
4. 当开发集达到稳定目标后,冻结实现并收集 fresh holdout v4。
|
||||
5. 只有 holdout 通过后才开放分钟确认;否则产品始终停在候选范围。
|
||||
@@ -251,3 +251,15 @@ Prevention: keep evidence-request, life-event, private-candidate, public-recap,
|
||||
After accumulated historical evidence produced a very narrow winning segment, that segment could contain fewer than two linked samples or discriminating divisional themes. Packet construction treated this valid “not enough distinction yet” state as a dependency failure, so a later answer returned 503 even though scoring and the astrology service were healthy.
|
||||
|
||||
Prevention: classify insufficient candidate-range discrimination explicitly; when a newly narrowed segment cannot support the technical evidence contract, retain the prior candidate range, preserve scored evidence, clear the unconfirmed result, and continue conversational collection.
|
||||
|
||||
## ERR-102 | Gitea returned HTTP 502 during final remote synchronization check | active 2026-08-09
|
||||
|
||||
Two final `git fetch origin --prune` attempts against the configured primary Gitea remote failed before ref exchange with HTTP `502`. The last locally verified refs remain available, but this run cannot prove that they are still current and must not claim a completed remote synchronization or push.
|
||||
|
||||
Prevention: retry fetch and `git ls-remote` before any push or release action, compare the full `main`, `staging`, and migration-branch SHAs, and stop if Gitea remains unavailable. Do not substitute cached refs, the GitHub mirror, or a successful local commit for current Gitea synchronization evidence.
|
||||
|
||||
## ERR-103 | Release Gate runner 缺少 Docker Compose v2 导致 17 个集成测试级联失败 | mitigated 2026-08-09
|
||||
|
||||
手工 Release Gate Run `1638` 在 `xiaoxin` 上通过 Docker Engine 检查后进入完整测试,但该 runner 不支持 `docker compose`,`--project-name` 与 `--env-file` 被 Docker 顶层 CLI 判为 unknown flag,导致 17 个 PostgreSQL、备份、身份和权限集成测试级联失败。候选 staging SHA、旧生产、Supabase 与 DNS 未被改变。
|
||||
|
||||
Prevention: 将该门禁运行在已验证 Docker Compose v2 的 `manman-linux`,并在安装依赖前以 `docker compose version --short` 强制 v2;不得把 `docker version` 当作 Compose 能力证明。新 SHA 必须重新完成 staging gate、公网 staging 身份和手工 Release Gate,失败 run 不得授权生产 migration、deploy 或维护停写。
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
# User-reported birth-time flow issues — 2026-07-20
|
||||
|
||||
This ledger records the five reported product failures and the evidence needed
|
||||
to close them. It contains no copied authentication header, cookie, token,
|
||||
email, user UUID, or real birth record. The original plan described this file as
|
||||
an existing modification target; it did not exist in this checkout, so Task 12
|
||||
created it.
|
||||
|
||||
`verified-local` means deterministic contract tests and the equivalent local
|
||||
PostgreSQL 14 workflow pass. It is deliberately not `closed`: closure also
|
||||
requires an authenticated synthetic production smoke whose health Git SHA is
|
||||
the tested deployment SHA.
|
||||
|
||||
| Issue | Reported failure | Current status | Local evidence | Production closure artifact |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| ISSUE-BT-001 | A chat appeared impossible to delete, or a late response could recreate it. | verified-local | `20260720000000_chat_delete_and_dynamic_candidate_confirmation.sql`; `frontend/tests/chat-session-delete-contract.test.ts`; the PG14 full-flow test deletes a real RLS-owned chat as `authenticated` and proves the account case remains. | Authenticated synthetic delete plus account-case reload, tied to `/api/health` deployment SHA. |
|
||||
| ISSUE-BT-002 | A new chat could not establish a fresh rectification interaction and unfinished progress was coupled to chat state. | verified-local | `20260720010000_conversational_rectification_schema.sql`; account-level resume in `frontend/tests/conversational-rectification-e2e.test.ts` across two route clients; Task 9 current-chat consent tests. | Authenticated new-device/new-chat resume smoke tied to the deployed SHA. |
|
||||
| ISSUE-BT-003 | Confirming a candidate such as `17:15` surfaced `The string did not match the expected pattern`. | verified-local | Atomic v3 confirmation in `20260720030000_conversational_rectification_transitions.sql`; client retry/fallback and mismatched-then-exact confirmation in `frontend/tests/conversational-rectification-e2e.test.ts`; the PG14 full-flow test rejects `05:20`, confirms exact `05:21`, and proves the old time survives until commit. | Authenticated production exact-candidate confirmation with old-time preservation, plus transient deployment-error probe. |
|
||||
| ISSUE-BT-004 | Choosing `都不符合` surfaced the same raw English pattern error. | verified-local | The actual orchestrator treats `都不符合` as a normal direction change; Task 12 E2E advances the durable turn and preserves the single fee; client maps terminal 502/non-JSON failures to stable Chinese copy. | Authenticated production `都不符合` action followed by reload/resume, tied to the deployed SHA. |
|
||||
| ISSUE-BT-005 | Initialization used generic broad-year choices and lost the rich card/chat rectification analysis. | verified-local | Task 9 onboarding soft gate; v3 narrative grounding rejects broad-year questionnaires; Task 12 asserts candidate boundary, D1/D9/D10 layers, three domain rationales, free text, and year/month event request; PG14 proves future background persists without scoring and the legacy suite imports old unfinished work once with `migration_waived`. | Authenticated synthetic first-turn snapshot and one legacy import smoke tied to the deployed SHA. |
|
||||
|
||||
## Initial production diagnosis (preserved)
|
||||
|
||||
The original issue record captured these causes before implementation:
|
||||
|
||||
- Chat deletion lacked both an authenticated `DELETE` grant and an owner-only
|
||||
RLS policy.
|
||||
- `startNewChat()` created only a chat session; the birth profile and
|
||||
rectification case were account-scoped, so a new chat could not start an
|
||||
explicit re-rectification while preserving the active chart.
|
||||
- `dynamic-choice-v2` emitted `request_candidate_confirmation`, but the client
|
||||
sent the legacy `confirm_guided_candidate` mutation, which rejected dynamic
|
||||
cases and left them in `confirming` without applying the candidate.
|
||||
- The reported `都不符合` failure coincided with a deployment replacement in
|
||||
which Caddy returned HTTP 502 while Docker could not resolve the temporarily
|
||||
unavailable `web` service.
|
||||
- WebKit can represent a non-JSON parse failure as a `DOMException` named
|
||||
`SyntaxError`; the old transport recognized JavaScript `SyntaxError` only and
|
||||
could expose `The string did not match the expected pattern.` instead of
|
||||
stable Chinese product copy.
|
||||
|
||||
The original closure checklist required owner-only deletion, explicit
|
||||
account-level re-rectification, atomic idempotent candidate confirmation,
|
||||
deployment availability or bounded retry behavior, stable localized WebKit
|
||||
errors, and authenticated end-to-end coverage for `都不符合`, confirmation,
|
||||
re-rectification, and deletion. The table above maps each requirement to the
|
||||
implemented local evidence and the remaining production proof.
|
||||
|
||||
## Release decision
|
||||
|
||||
All five issues remain `verified-local` until the production closure artifacts
|
||||
above are attached. A public 200 response, an unverified browser session, or a
|
||||
local in-memory test cannot change them to `closed`. The deployment sequence and
|
||||
non-destructive rollback are defined in `deploy/README.md`. The executable
|
||||
creation policy keeps rollout `smoke_only` for one unlogged synthetic account
|
||||
until the smoke SHA matches the exact deployed revision; ordinary users cannot
|
||||
incur a new rectification charge during that canary window.
|
||||
@@ -1,205 +0,0 @@
|
||||
# Web 生时纠正 vs 本地 Claude Code:差异诊断与改进方案
|
||||
|
||||
> 日期:2026-08-01
|
||||
> 状态:诊断完成;改进方案第 4 节"完整 MVP"已实施(见第 7 节)
|
||||
> 范围:`jyotish-vedic-astrology` skill 方法论层 vs Web `skills/birth-time-rectification` 受限产品层
|
||||
|
||||
## 结论摘要
|
||||
|
||||
Web(staging)上用户感受到的"生时纠正交互僵硬、问题像硬编码模板",**不是 bug,而是两种刻意不同的架构**:
|
||||
|
||||
- **本地 Claude Code**:LLM 是**主分析师**,走 `jyotish-vedic-astrology` skill 的完整方法论,可自由多轮提问、运行脚本、交叉验证,最终产出精确出生分钟。
|
||||
- **Web(Mastra)**:LLM 是**被约束的叙述者**,走 `skills/birth-time-rectification/SKILL.md`(36 行受限证据工作流)。服务端 Python 引擎 + TS 状态机拥有全部计算(候选扫描/评分/诊断/事件 ID/策略门控),LLM 只能在服务端预建的问题机会里选一个、再渲染短中文回复,永不确认单一分钟。
|
||||
|
||||
staging 前端实际挂载的是 **v4 rectification** 入口(`RectificationV4Panel` → `/api/rectification/v4/*`)。"硬编码感"主要来自服务端模板问题生成器 `opportunity-builder.ts`,与 v4/v5 模式切换无关。
|
||||
|
||||
---
|
||||
|
||||
## 1. 两个系统的架构对比
|
||||
|
||||
| 维度 | 本地 Claude Code | Web(Mastra v4 rectification) |
|
||||
|---|---|---|
|
||||
| 使用的 skill | `jyotish-vedic-astrology`(仓库根 `SKILL.md`,712 行,版本 6.9.14) | `skills/birth-time-rectification/SKILL.md`(36 行) |
|
||||
| skill 目录结构 | symlink 指向根目录 `SKILL.md` + `references/`(100+ 方法论文档)+ `scripts/`(`jyotish_engine.py` 37 子命令)+ `assets/` | 36 行 SKILL.md + 6 个契约文件在 `skills/birth-time-rectification/references/` + `assets/rectification-capability-matrix.json` |
|
||||
| 方法论 | 8 大方法(Dasha+Transit、D9 Navamsa、D10 Dasamsa、六亲、外表体质、身体缺陷、职业判断、卜卦【AI 暂不支持】);五阶段流程(收集→±30min→±15min→事件验证→D9/D10 收口到 ±5min→报告);决策树权重 Dasha 40% / D9+D10 35% / 专题层 15% / Nakshatra Pada 10% | 受限证据工作流:服务端扫描候选时间簇→评分→生成高信息量机会;agent 每轮只问一个自然问题;输出候选区间而非确定时间 |
|
||||
| LLM 角色 | 主分析师,自由推理 | 被约束的叙述者(reasoner 选机会 / renderer 渲染) |
|
||||
| 计算归属 | LLM 驱动 + `scripts/` 脚本 + 外部 oracle(PyJHora/VedAstro/jyotishganit) | 服务端 Python 引擎 + TS 状态机全拥有 |
|
||||
| 输出 | 验证后的出生分钟(±5min) | 候选区间(`profiles.active_birth_time` 永不直接写入) |
|
||||
| 错误处理 | 交互式纠错 | 幂等重放(action receipt + fingerprint)、确定性回退 |
|
||||
|
||||
**两者的关系**:`jyotish-vedic-astrology` skill 内部同时定义了这两层——方法论层(`references/birth-time-rectification-advanced.md`)和受限产品工作流层(独立的 `skills/birth-time-rectification/`)。Web 端刻意只暴露受限产品层。
|
||||
|
||||
---
|
||||
|
||||
## 2. 为什么 Web 无法复刻本地交互(6 个根源)
|
||||
|
||||
### 2.1 权威模型相反(设计边界,不是 bug)
|
||||
|
||||
`skills/birth-time-rectification/SKILL.md` 硬边界原文:
|
||||
|
||||
> - The server owns candidate scanning, scores, diagnostics, event IDs, and policy gates.
|
||||
> - The agent may select one server-provided opportunity or request one server-provided diagnostic.
|
||||
> - Never invent candidate times, scores, event IDs, dates, techniques, or tool inputs.
|
||||
> - Never confirm a single minute or write `profiles.active_birth_time`.
|
||||
|
||||
这是一整套产品决策:**计费**(`billing.ts` reserve/complete/release)、**不暴露内部分数**(用户只能看到"候选区间"而非权重/评分)、**可靠性**(服务端计算确定性可审计,LLM 只做叙述)、**truth-overlay 合规**(`references/oracle/rectification_technique_usage_audit_2026_07_19.json` 把 D9/D10/D60 等标为"敏感度证据不是证明")。本地 Claude Code 没有这些约束,所以能做完整方法论。
|
||||
|
||||
### 2.2 问题是服务端模板("硬编码感"最强处)
|
||||
|
||||
`frontend/src/lib/rectification-agent/opportunity-builder.ts`:
|
||||
|
||||
- **固定模板文案**:`prompt` 字段全部是写死的句子,例如——
|
||||
- `clarify_event_subject`:"你刚才提到"X",这件事主要发生在你本人,还是家人或伴侣身上?"
|
||||
- `refine_event_date`:"关于"X",你还记得更具体的月份或日期吗?不确定也可以只说大概范围。"
|
||||
- `ask_new_event` 各领域:career/relationship/health_pressure 等各一句。
|
||||
- **硬编码 utility 公式**(L24-30):`.35*expectedInformationGain + .20*dateSensitivity + .15*candidateSplitRelevance + .10*domainCoverageGain + .10*recallEase + .10*novelty + routingValue[kind] - repetitionPenalty - privacyCost`,其中 `routingValue` 也是写死的(L14-22)。
|
||||
- reasoner(`reasoner-agent.ts`)只按 `opportunityId` 选一个机会,**从不用自己的话提问**。
|
||||
|
||||
> v3 对话式(`/api/birth-time-conversation`)的 `narrative-agent.ts` 已带 `freeConversation` 设置、允许 agent 自由措辞——但 v3 后端未接入当前 UI 面板。
|
||||
|
||||
### 2.3 每轮只问一个问题
|
||||
|
||||
skill turn strategy:"Ask one natural question only"。`reasoner-agent.ts` 的决策被 `rectificationDecisionSchema` 严格约束,`maxToolCalls` 默认 1、最多一次 `run_rectification_diagnostics` 工具调用,然后必须返回终态动作。本地 Claude Code 是自由多轮对话。
|
||||
|
||||
### 2.4 Mastra skill 懒加载
|
||||
|
||||
`@mastra/core`(v1.50.1)的 skill 机制:`skills: [skillPath]` 只把 skill **元数据**(name/description,`<available_skills>` 块)注入系统提示;完整 `SKILL.md` 要模型主动调 `skill` 工具才在对话中加载(`node_modules/@mastra/core/dist/` 的 `SkillsProcessor`)。deepseek 走 `structuredOutput` 路径时未必稳定触发 `skill` 工具 → LLM 实际可用的指令比预期少。
|
||||
|
||||
### 2.5 硬编码业务规则
|
||||
|
||||
- `references/rectification_policy.v1.json`:`minScoringEvents=1`、`minConfirmationEvents=4`、`minConfirmationDomains=3`、`maxExternalValidationWidthMinutes=15`、`maxConfirmationWidthMinutes=5`、`minConfirmationMarginPercent=20`、`maxPlateauRounds=2`。→ 必须凑够 ≥4 个事件、≥3 个领域,否则一直追问,造成"问卷感"。
|
||||
- 时段区间(`orchestrator.ts` L565-594、`handler.ts` L426-451):early_morning/morning/afternoon/evening/late_night;不确定性(医院 ±2min、家庭 5/10/15、约估 15/30/60)。
|
||||
- 正则模式(`orchestrator.ts` L132-139):方向切换词/不确定词/肯定否定词/相对日期词。
|
||||
- 领域分类关键词表(`evidence-extractor.ts` L101-146)。
|
||||
- 回退文案(`narrative-agent.ts` L639-651)。
|
||||
- 模型 ID(`handler.ts` L831-832):`deepseek-v4-pro` / `deepseek-v4-flash`。
|
||||
|
||||
### 2.6 渲染约束
|
||||
|
||||
- reasoner/renderer 都强制 `structuredOutput` JSON(`reasoner-agent.ts` L124、`renderer-agent.ts` L62)。
|
||||
- renderer 还要 `enforceServerQuestion`(L38-40、L63)把服务端预建的 `exactQuestion` 强制覆盖进输出——LLM 措辞被服务端文案顶替。
|
||||
- 模型为 deepseek 系列(非 Claude),对话自然度与指令遵循不同。
|
||||
|
||||
---
|
||||
|
||||
## 3. staging 入口确认
|
||||
|
||||
| 项 | 结论 | 证据 |
|
||||
|---|---|---|
|
||||
| 前端 UI | **v4 rectification**:`ConversationalBirthTimeRectification` 只是 `RectificationV4Panel` 的别名 | `components/conversational-birth-time-rectification.tsx:20` |
|
||||
| API 入口 | `/api/rectification/v4/*` | `lib/rectification-v4/client.ts`(cases / active / answer / revise / accept-range / pause/resume/abandon) |
|
||||
| v4 流程内部 | `runBoundedReasoner`(reasoner-agent.ts)+ `renderPublicTurn`(renderer-agent.ts),两者 `skills: [rectificationSkillPath]`(受限 36 行 skill) | `reasoner-agent.ts:115`、`renderer-agent.ts:16` |
|
||||
| 模型 | `RECTIFICATION_ORCHESTRATION_MODEL_ID` / `RECTIFICATION_NARRATION_MODEL_ID`(未设则默认目录) | `case-service.ts:46-47` |
|
||||
| v5 agent vs v4 legacy | 由部署宿主 `.env.staging` 的 `RECTIFICATION_AGENT_V5_ENABLED` / `RECTIFICATION_AGENT_V5_CANARY_PERCENT` / `RECTIFICATION_AGENT_V5_SHADOW` 决定(`feature-policy.ts`),仓库不可见;**两种模式都走同一套受限 skill + 模板问题** | `lib/rectification-agent/feature-policy.ts:26-39` |
|
||||
| v3 对话式 | 按 rollout audience(paused/smoke_only/public)门控,**未接入当前 UI 面板** | `deploy/configure-staging-rectification-rollout.sh`、`components/rectification-v4-panel.tsx`(无 v3 引用) |
|
||||
| 部署副本 | Dockerfile 把 `SKILL.md`/`assets`/`references`/`scripts`/`skills` 拷进 `/app/`,symlink 保留 | `deploy/railway-web.Dockerfile:18-22` |
|
||||
|
||||
> 注:`/api/health` 只上报 v3 的 rollout 状态(`rollout.conversationalRectificationV3.creationAudience`),不包含 v5 agent 的开关值,因此 v5 模式是否在 staging 开启需查部署宿主的 `.env.staging`。
|
||||
|
||||
---
|
||||
|
||||
## 4. 改进方案(在"服务端拥有计算"护栏内)
|
||||
|
||||
按侵入性从低到高排列,均为**建议**(本次未实施)。任何方案都不得把内部分数/权重/事件 ID 暴露给用户,不得确认单一分钟。
|
||||
|
||||
### 4.1 即时注入 skill 指令(低侵入,收益高)
|
||||
|
||||
- **改动**:把 36 行 `skills/birth-time-rectification/SKILL.md` 直接内联进 reasoner/renderer 的 `instructions`(`reasoner-agent.ts:117`、`renderer-agent.ts:17`),保留 `skills: [skillPath]` 作为能力来源。
|
||||
- **效果**:消除 Mastra skill 懒加载不确定性——模型每轮都确定拥有"turn strategy + public language + 硬边界"指令。
|
||||
- **风险**:低。指令与 skill 内容一致,只是从懒加载改为常驻。
|
||||
|
||||
### 4.2 LLM 起草问题 + 服务端 grounded 校验(中侵入,消除"模板感"核心)
|
||||
|
||||
- **改动**:`opportunity-builder.ts` 保留"选哪个机会"的服务端决策(kind/targetEventId/domain/utility),但把 `prompt` 从"必须原样使用"改为"话题约束";reasoner 用自然语言起草问题文本;新增一个 grounding 校验(复用 `narrative-agent.ts` 的 grounding 思路)确认草稿:① 命中目标事件/领域 ② 不含内部分数/权重/事件 ID ③ 是单问。
|
||||
- **效果**:问题随上下文自适应,消灭"你刚才提到X…"的模板感。
|
||||
- **风险**:中。需要新增校验层与测试;reasoner 输出 schema 从"选 opportunityId"扩展为"选 opportunityId + 起草文本"。
|
||||
|
||||
### 4.3 自由对话回合(中侵入)
|
||||
|
||||
- **改动**:服务端没有待处理机会(`opportunities` 为空或全部低效用)时,允许 agent 走"自然回应"而非强制提问。可复用 v3 `narrative-agent.ts` 的 `freeConversation` / `questionsAreOptional` 提示词模式,让 renderer 生成 1-3 句自然中文 + 可选开放收尾。
|
||||
- **效果**:不再每轮都是"选择题",更像本地对话。
|
||||
- **风险**:中。需防止发散、防止确认未验证分钟;收敛判定仍由服务端掌控。
|
||||
|
||||
### 4.4 渲染放宽(中侵入)
|
||||
|
||||
- **改动**:renderer 从 `structuredOutput` JSON 改为自然中文文本输出 + 事后校验(`enforceServerQuestion` 保留为兜底,仅当需要明确问题时强制服务端文案)。
|
||||
- **效果**:回复更自然,减少 JSON 式僵硬措辞。
|
||||
- **风险**:中。需新的文本校验(主题、长度、泄密扫描)。
|
||||
|
||||
### 4.5 模型目录加入 Claude(低侵入,可选)
|
||||
|
||||
- **改动**:`frontend/src/mastra/model.ts` 的模型目录加入 Claude(如 `claude-sonnet-5`),`RECTIFICATION_NARRATION_MODEL_ID` 指向它。
|
||||
- **效果**:叙事/对话质量显著提升(deepseek 在结构化约束下更易模板化)。
|
||||
- **风险**:低,纯配置;需确认供应商密钥与成本。
|
||||
|
||||
---
|
||||
|
||||
## 5. 不应改动(设计边界)
|
||||
|
||||
以下为 `birth-time-rectification` skill 与产品契约的硬性约束,**任何改进都不得触碰**:
|
||||
|
||||
1. 服务端拥有候选扫描、评分、诊断、事件 ID、策略门控。
|
||||
2. 永不确认单一分钟;永不直接写 `profiles.active_birth_time`。
|
||||
3. 候选区间只有确定性稳定门通过才对用户可见(`canAcceptRange`)。
|
||||
4. 计费幂等(billing reserve/complete/release + action receipt 指纹重放)。
|
||||
5. truth-overlay 强制降级:`reference_only`/`blocked`/`partial` 技法不得作为确定性结论(`references/oracle/skill_truth_overlay_2026_07_19.json`)。
|
||||
6. 不暴露内部分数、权重、领域标签、工具载荷、agent 轨迹。
|
||||
|
||||
改进目标是让**叙述/提问的自然度**贴近本地,而不是让 Web 复刻本地的方法论深度——那需要把整条计算链路搬进 LLM 上下文,与现有产品架构冲突。
|
||||
|
||||
---
|
||||
|
||||
## 6. 附:关键文件索引
|
||||
|
||||
| 文件 | 作用 |
|
||||
|---|---|
|
||||
| `skills/birth-time-rectification/SKILL.md` | Web 端受限 skill(36 行硬边界) |
|
||||
| `SKILL.md`(仓库根) | 本地完整 skill(712 行方法论,symlink 到 skill 目录) |
|
||||
| `frontend/src/lib/rectification-agent/opportunity-builder.ts` | 服务端模板问题生成器(硬编码根源) |
|
||||
| `frontend/src/lib/rectification-agent/reasoner-agent.ts` | v4/v5 reasoner(选机会 + diagnostic 工具) |
|
||||
| `frontend/src/lib/rectification-agent/renderer-agent.ts` | v4/v5 renderer(渲染公开回合 + enforceServerQuestion) |
|
||||
| `frontend/src/lib/rectification-agent/feature-policy.ts` | v4_legacy / v5_shadow / v5_agent 选择 |
|
||||
| `frontend/src/lib/rectification-v4/case-service.ts` | 建 case、deployment_mode、模型 ID |
|
||||
| `frontend/src/lib/rectification-v4/supabase-store.ts` | 持久化 deployment_mode/agent_mode |
|
||||
| `frontend/src/lib/conversational-rectification/narrative-agent.ts` | v3 叙事 agent(freeConversation 参考实现) |
|
||||
| `frontend/src/app/api/birth-time-conversation/handler.ts` | v3 handler(deepseek 模型 ID、流式) |
|
||||
| `references/rectification_policy.v1.json` | 收敛门槛硬编码 |
|
||||
| `deploy/configure-staging-rectification-rollout.sh` | staging rollout(paused/smoke_only/public) |
|
||||
| `frontend/supabase/migrations/20260728020000_*.sql` | v5 列(deployment_mode/agent_mode/model id/version) |
|
||||
|
||||
---
|
||||
|
||||
## 7. 已实施:Agentic 生时纠正 MVP(2026-08-01)
|
||||
|
||||
按用户决策"完全复刻本地方法论",实现了一个新的 **agentic 生时纠正**聊天流:LLM 挂载完整 `jyotish-vedic-astrology` skill,像本地 Claude Code 一样驱动方法论,通过引擎工具请求计算(而不是自己瞎算),自由多轮对话,最终经高 rigor 确认门 + 用户明确同意后写回 `profiles.active_birth_time`。
|
||||
|
||||
### 7.1 新增文件
|
||||
|
||||
| 文件 | 作用 |
|
||||
|---|---|
|
||||
| `frontend/src/mastra/rectification-tools.ts` | 7 个工具包 Python 引擎端点:`rectification-gate`(精度门)、`rectification-scan`(分钟敏感度扫描)、`rectification-score`(V5 矩阵评分)、`rectification-diagnostics`(鲁棒性诊断)、`rectification-candidate-features`(候选静态特征)、`rectification-confirm`(高 rigor 三引擎 parity 确认门)、`rectification-save-birth-time`(服务端双重校验后写 profile) |
|
||||
| `frontend/src/mastra/agentic-rectification.ts` | agent 工厂:完整 skill + 工具 + 中文指令(方法论流程、truth overlay、保存门控) |
|
||||
| `frontend/src/lib/rectification-agentic/session.ts` | 会话支持:加载 profile 出生字段、`applyConfirmedBirthTime` 调 service-role RPC 写回 |
|
||||
| `frontend/src/app/api/rectification/agent/route.ts` | NDJSON 流式端点:认证 → profile → 计费 reserve → agent.stream → delta/done 事件 → settle |
|
||||
| `frontend/src/components/rectification-agentic-chat.tsx` | 聊天面板:流式渲染、隐藏块解析(suggestions/title/保存哨兵)、错误处理 |
|
||||
| `frontend/src/components/conversational-birth-time-rectification.tsx` | 入口智能切换:有进行中的 v4 case → v4 面板恢复;否则 → agentic 聊天 |
|
||||
| `frontend/supabase/migrations/20260801000000_agentic_rectification_profile_write.sql` | `apply_agentic_rectification_birth_time` RPC(security definer,仅 service_role,含基线并发保护) |
|
||||
| `frontend/tests/rectification-agentic-tools.test.ts` / `rectification-agentic-session.test.ts` | 12 个测试 |
|
||||
|
||||
### 7.2 安全门控(核心)
|
||||
|
||||
LLM 绝不能写任意分钟。`rectification-confirm` 只有在引擎高 rigor 门全过(≥4 事件、≥3 领域、宽度/边际阈值、三引擎 parity、外部 VedAstro 校验)返回 `confirmation_allowed=true` + 确认分钟时,才在会话闭包中设置 `confirmedGate`;`rectification-save-birth-time` 要求请求的时间**恰好等于**该确认分钟,才调用 RPC 写库。RPC 还带 `p_baseline_time` 并发保护(当前 active 时间必须仍是会话开始时的基线)。
|
||||
|
||||
### 7.3 验证
|
||||
|
||||
- `npx tsx --test tests/*.test.ts`:**1076 全通过**(含 12 个新测试)。
|
||||
- `npx tsc --noEmit`:新文件零错误(仓库剩余 5 个为预先存在)。
|
||||
- `npx eslint`:新文件零错误零警告。
|
||||
|
||||
### 7.4 待办/注意
|
||||
|
||||
- **引擎端点鉴权**:`rectification-save-birth-time` 走的 RPC 仅 service_role;引擎各 rectification 端点无需 token(与 `runConsultationWorkflow` 一致)。
|
||||
- **计费**:按消息 reserve/complete/cancel 咨询点数(复用 `begin/complete/cancel_consultation_credit`)。
|
||||
- **v4 保留**:有进行中 v4 case 时仍走 v4 面板恢复,不丢数据。
|
||||
- **模型**:默认走当前模型目录;若想让叙事用 Claude,在 `LLM_MODELS_JSON` 加 Claude 项并把 `LLM_DEFAULT_MODEL_ID` 指过去即可。
|
||||
- **部署**:新路由无需新环境变量(复用 `JYOTISH_API_BASE`、Supabase 密钥、模型目录);新迁移需在 staging 执行 `db:migrate`。
|
||||
Reference in New Issue
Block a user