doctorAI/docs/SPEC_md_first_persistence_v1.md

300 lines
13 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.

# SPEC: Markdown-First 物理落盘与来源分级架构
> **版本**: v1.0
> **前置依赖**:
> - [SPEC_deepview_metadata_scoping_v3.md](./SPEC_deepview_metadata_scoping_v3.md) (Prosumer-First 沙箱隔离)
> - [SPEC_tri_domain_knowledge_architecture.md](./SPEC_tri_domain_knowledge_architecture.md) (三元知识域)
> - [DESIGN_PHILOSOPHY_AI_NATIVE.md](./DESIGN_PHILOSOPHY_AI_NATIVE.md) (AI-Native 产品心法)
> **状态**: 草案 (Proposal)
---
## 1. 问题陈述
当前管线中存在三个结构性隐患,它们共享同一根源——**AI 产出物与原始素材在文件系统中无法区分**。
### 1.1 Ghost Response 问题
Hermes Agent 在执行"分析素材并生成报告"类任务时,天然地会通过 `write_file` 工具将分析结果写入物理文件,而 `final_response` 仅返回"操作完成"的告知性消息。如果管线代码从 `final_response` 取值,拿到的是一段无意义的摘要而非真正的分析内容。
这不是 Agent 的 bug——这是 Agent 作为**自主工作者**的正确行为。人类分析师也不会口述 2000 字报告给你,他会"写完放这里,告诉你一声"。
| 管线 | 当前取值方式 | 风险 |
|------|-------------|------|
| 录音笔记 Stage 1 | `result.get("final_response")` | 碰巧可靠Agent 恰好未写文件),但不稳定 |
| 客户档案 Stage 1 | `result.get("final_response")` | **已验证失效** — Agent 将内容写入 `profile.md`response 仅 401 字符 |
### 1.2 幻觉复利效应
当前 `profile.md` 既是 Agent 的**输入素材**(读取既往档案),又是 Agent 的**输出目标**(生成新档案后覆盖写入)。文件系统中没有任何标记区分:
- `asr.md`L0 原始转写,可信度最高)
- `profile.md`L2 AI 聚合产出,可能包含错误推断)
当 Agent 下次执行时,它 `read_file("profile.md")`——无法判断这是人类录入的事实,还是上一轮 AI 的推测。如果上一次的 profile.md 含有错误推断(如"客户属于价格不敏感型"Agent 会将其当作已有事实继续叠加推理。
**错误被放大而非纠正——这是幻觉的复利。**
### 1.3 Token 消耗悖论
一位客户积累 50 次面诊后:
- **暴力拼接所有 asr.md**(每条 1-3 万字)→ 100 万字 → 上下文直接爆炸
- **只读 report.md 摘要**(每条 3000 字)→ 15 万字 → 可控
但前提是:**Agent 必须知道 report.md 是 AI 生成的一阶摘要有损asr.md 才是无损证据。当需要考据时,应回溯 asr.md 原文,而非引用 report.md 中的话当作原声。**
可靠的中间产出物反而减少 Token 消耗;不可靠的中间产出物则制造幻觉。
---
## 2. 核心原则Markdown-First 物理落盘
### 2.1 总则
> **管线各阶段之间的接口契约,必须是物理文件,不是内存态变量。**
| 接口方式 | 韧性 | 可审计性 | Agent 兼容性 |
|----------|------|---------|-------------|
| `final_response`(内存态) | ❌ 进程崩溃即丢 | ❌ 需翻 session 日志 | ❌ 与 Agent 的 `write_file` 行为冲突 |
| 物理 `.md` 文件 | ✅ 落盘即不丢 | ✅ `cat` 即可审 | ✅ 顺应 Agent 自然行为 |
### 2.2 两段式管线的物理接口
```
Stage 0 (ASR/上传) Stage 1 (Hermes Agent) Stage 2 (Qwen JSON)
│ │ │
▼ ▼ ▼
asr.md ──read───► Agent ──write──► report_draft.md ──read──► LLM ──► DB JSON
(L0 原始) (L1 AI 产出)
```
- **Stage 0 → Stage 1**Agent 通过 `read_file` 自主读取 `asr.md`(及其他素材)
- **Stage 1 → Stage 2**:管线代码从 Agent 写入的物理文件读取内容,传入 Stage 2
- **Stage 2 → DB**JSON 写入数据库(已实现,无变更)
### 2.3 Agent 指令约定
Agent 的 user_message 必须明确指定**输出目标路径**,让 Agent 将分析结果写入该路径。管线代码执行完 Agent 后,从该路径读取内容。
```python
# ✅ 正确明确输出路径Agent 写入后管线读取
result = agent.run_conversation(
user_message=f"分析 {historyDir}/ 下的素材,将全景档案写入 {outputPath}",
system_message=systemPrompt,
)
with open(outputPath, "r") as f:
mdReport = f.read()
# ❌ 错误:依赖 final_responseGhost Response 风险)
mdReport = result.get("final_response", "")
```
---
## 3. 来源分级标记 (Provenance Levels)
### 3.1 三级分类
| 级别 | 标识 | 含义 | 可信度 | 文件命名约定 | 示例 |
|------|------|------|--------|-------------|------|
| **L0** | `source: human` | 物理世界直接采集的原始素材 | 最高——可回溯到原声/原件 | `asr.md`, `asr_raw.m4a`, 用户上传照片 | 录音转写文本ASR 原始输出) |
| **L1** | `source: ai-single` | AI 基于**单个** L0 素材生成的一阶分析 | 高——有损摘要,但来源明确 | `report_draft.md` | 单条录音的面诊 X 光片报告 |
| **L2** | `source: ai-aggregate` | AI 基于**多个** L0/L1 素材聚合生成的产物 | 中——叠加推理,幻觉风险最大 | `profile.md` | 客户全景档案(跨录音合成) |
### 3.2 文件头元数据标注
所有 AI 生成的 `.md` 文件,必须在文件头以 HTML 注释形式嵌入来源元数据:
```markdown
<!-- provenance: ai-single | level: L1 | generated_at: 2026-04-12T19:42:30+08:00 | model: gemini-pro-vertex | sources: [rep_bc439351/asr.md] -->
# 面诊沟通X光片深度分析报告
...
```
```markdown
<!-- provenance: ai-aggregate | level: L2 | generated_at: 2026-04-12T19:54:39+08:00 | model: gemini-pro-vertex | sources: [rep_bc439351/asr.md, rep_abc123/asr.md] -->
# 👤 客户全景档案:测女士
...
```
L0 文件asr.md**不需要**标注——没有标注即为 L0。这是"无标记即原始"的默认原则。
### 3.3 Agent 行为约束(注入 SKILL.md
在 Agent 的 SKILL 指令中,追加来源分级感知规则:
```markdown
## 📋 来源分级阅读规则
1. **L0 文件**asr.md, asr_raw.m4a是不可变的原始证据所有核心论断必须以 L0 文件为最终考据依据。
2. **L1 文件**report_draft.md是单次录音的 AI 摘要,可用于快速浏览历史,但引用原声时必须回溯 L0 验证。
3. **L2 文件**profile.md是跨录音的 AI 聚合分析,优先级最低。如果 L2 中的结论与 L0 原文冲突,以 L0 为准。
4. **引用格式**:所有洞察必须标注来源文件名和大致行号,并注明来源级别。
-`(来源: rep_bc439351/asr.md L0, 125 行)`
-`(来源: profile.md)` ← 禁止引用 L2 作为唯一证据
```
---
## 4. 文件系统约定
### 4.1 录音笔记管线(单条录音 → 报告)
**当前**:
```
inbox/{asrId}/
├── asr.md ← L0 原始转写
└── asr_raw.m4a ← L0 原始音频
```
**改造后**(归档前):
```
inbox/{asrId}/
├── asr.md ← L0 原始转写
├── asr_raw.m4a ← L0 原始音频
└── report_draft.md ← L1 AI 单次分析Agent 写入,管线读取后传入 Stage 2
```
**改造后**(归档后):
```
clients/{clientId}/history/{reportId}/
├── asr.md ← L0 不可变
├── asr_raw.m4a ← L0 不可变
└── report_draft.md ← L1 随迁(审计留痕)
```
### 4.2 客户档案管线(多条录音 → 全景档案)
```
clients/{clientId}/
├── profile.md ← L2 AI 聚合产出(每次生成时覆盖)
└── history/
├── {reportId_A}/
│ ├── asr.md ← L0
│ ├── asr_raw.m4a ← L0
│ └── report_draft.md ← L1
├── {reportId_B}/
│ ├── asr.md ← L0
│ └── report_draft.md ← L1
└── ...
```
### 4.3 不可变性规则
| 文件 | 可读 | 可写/覆盖 | 可删除 |
|------|------|----------|--------|
| `asr.md` (L0) | ✅ Agent + 管线 | ❌ 上传后不可变 | ❌ |
| `asr_raw.m4a` (L0) | ✅ 审计用 | ❌ | ❌ |
| `report_draft.md` (L1) | ✅ Agent + 管线 | ✅ 重新生成时覆盖 | ❌ |
| `profile.md` (L2) | ✅ Agent + 管线 | ✅ 每次归档后重新生成 | ❌ |
---
## 5. 代码改造清单
### 5.1 录音笔记管线 (`_handleReportGenerate`)
| 步骤 | 现状 | 改造 |
|------|------|------|
| Agent user_message | 未指定输出路径 | 新增 `report_draft.md` 输出路径指令 |
| Stage 1 取值 | `result.get("final_response")` | 优先读取 `inbox/{asrId}/report_draft.md`,兜底 `final_response` |
| report_draft.md 写入 | 无 | Agent 自行写入prompt 引导),或管线兜底写入 |
| 文件头标注 | 无 | Agent prompt 中要求写入 `<!-- provenance -->` 标注 |
### 5.2 客户档案管线 (`_generateClientProfile`)
| 步骤 | 现状 | 改造 |
|------|------|------|
| Agent user_message | 含 `profileMdPath` 路径 | 保持Agent 已自然写入此路径) |
| Stage 1 取值 | ~~`result.get("final_response")`~~ **已修复** → 读取 `profile.md` | ✅ 已完成 |
| 文件头标注 | 无 | 需追加 `<!-- provenance: ai-aggregate ... -->` |
### 5.3 SKILL.md 追加
| 文件 | 变更 |
|------|------|
| `skills/deepview_assistant/SKILL.md` | 追加"来源分级阅读规则"章节 |
| `skills/deepview_profile/PROMPT_stage1.md` | 要求输出文件头带 `<!-- provenance -->` 标注 |
---
## 6. Agent 信息获取策略(规模化场景)
当客户积累大量面诊录音后Agent 应自主规划信息获取策略,而非被管线代码预先读取所有文件:
### 6.1 分层阅读策略
```
Agent 收到任务: "生成 p_b1232417 的全景档案"
├─ Step 1: search_files → 扫描 history/ 目录结构,确认有 N 条录音
├─ Step 2: read_file(profile.md) → 读取 L2 既有档案(如果存在)
│ └── Agent 识别 provenance: ai-aggregate → 知道这是上一轮 AI 自己的产出,仅供参考
├─ Step 3: 逐条读取 report_draft.md (L1) → 快速掌握每条录音的核心结论
│ └── Token 消耗: N × ~3000 字(远小于 N × ~20000 字的 asr.md
├─ Step 4 (按需): 对关键论断回溯 asr.md (L0) 验证
│ └── 仅在需要考据原声时才读取完整 ASR避免全量加载
└─ Step 5: write_file(profile.md) → 生成新的 L2 档案,覆盖旧版
```
### 6.2 Token 经济模型
| 录音数量 | 暴力全读 (asr.md) | 分层阅读 (report_draft.md + 按需 asr.md) | 节省比 |
|----------|-------------------|----------------------------------------|--------|
| 5 条 | ~10 万字 | ~1.5 万字 + 按需 ~2 万字 | ~65% |
| 20 条 | ~40 万字 | ~6 万字 + 按需 ~4 万字 | ~75% |
| 50 条 | ~100 万字 (上下文爆炸) | ~15 万字 + 按需 ~6 万字 | **可行 vs 不可行** |
---
## 7. 与已有 SPEC 的关系
```mermaid
graph TB
A["DESIGN_PHILOSOPHY_AI_NATIVE<br/>先有价值,后有治理"] --> B["SPEC_metadata_scoping_v3<br/>Prosumer-First 沙箱隔离"]
B --> C["SPEC_tri_domain_knowledge<br/>三元知识域"]
B --> D["SPEC_md_first_persistence<br/>📄 本规范"]
D --> E["来源分级标记"]
D --> F["管线物理接口"]
D --> G["Agent 分层阅读策略"]
```
- **V3 SPEC** 定义了"数据在哪"`users/{userId}/` 沙箱)
- **三元域 SPEC** 定义了"知识从哪来"(平台/机构/个人三级注入)
- **本 SPEC** 定义了"文件怎么流"物理落盘、来源标记、Agent 感知证据层级)
三者合力构成了完整的数据治理体系:**沙箱隔离 × 知识分域 × 来源追溯**。
---
## 8. 验证计划
### 8.1 自动化测试
```bash
# 1. 录音笔记管线:验证 report_draft.md 是否被生成
test -f inbox/{asrId}/report_draft.md && echo "✅ L1 文件存在"
head -1 inbox/{asrId}/report_draft.md | grep "provenance" && echo "✅ 来源标记存在"
# 2. 客户档案管线:验证 profile.md 来源标记
head -1 clients/{clientId}/profile.md | grep "ai-aggregate" && echo "✅ L2 标记正确"
# 3. 归档后文件完整性
ls clients/{clientId}/history/{reportId}/ | grep -c ".md" # 应 >= 2 (asr.md + report_draft.md)
```
### 8.2 人工审计
- 对比 `profile.md` 中的引述与 `asr.md` 原文,确认无二次幻觉放大
- 检查 Agent session 日志,确认分层阅读行为(先读 L1 摘要,按需回溯 L0
---
> *本规范定义了深维面诊助理的 Markdown-First 物理落盘与来源分级架构。*
> *核心信念物理文件是管线各阶段之间的唯一接口契约。AI 产出必须标注来源级别,原始素材不可变。*
> *审阅确认后,将指导两条管线的统一改造。*