Files
DevFlow/docs/02-架构设计/专项设计/消息级溯源设计-2026-06-19.md
绝尘 44d1c6a00c 优化: 文档路径同步 + 审查登记 + 消息级溯源设计文档
- PROGRESS/URGENT 文档分类后子目录路径修正
- 待审查.md 登记 CR-260619-08 intent + CR-260619-09 PhaseB+C(均  PASS)
- 新增消息级溯源设计(消息拆分存储 + 知识/审计/灵感全链路消息级定位)
2026-06-19 18:58:49 +08:00

8.4 KiB

消息级溯源设计

创建:2026-06-19 | 编号:F-260619-04(消息级溯源) | 状态:📐 设计待实施 关联任务:消息级溯源:知识库/审计/灵感全链路从对话级升级到消息级(DevFlow 项目) 关联灵感:b2e61a21 消息拆分存储 + 消息级溯源 + 知识脉络网络 关联设计:消息拆分存储设计-2026-06-19.md(本设计的持久化基础) 上级索引:../INDEX.md


一、目标

将 DevFlow 所有溯源场景从「对话级」升级为「消息级」,实现四个场景的精确定位:

场景 升级后溯源字段 粒度
知识库提炼 source_ref = "conv_msg:{message_id}" 消息级
知识生命线·提炼/引用 source_ref = "conv_msg:{message_id}" 消息级
工具执行审计 message_id: Option<String> 独立列 消息级
灵感来源 source = "conv_msg:{message_id}" 消息级

依赖关系:本设计依赖消息拆分存储提供持久化消息 ID,但中间态可不依赖拆表先上线(见 §五)。


二、现状(4 个溯源场景全部只到对话级)

场景 当前溯源字段 粒度 位置
知识库提炼 source_ref = "conv:{conversation_id}" 对话级 knowledge_inject.rs:434
知识生命线·提炼/引用 source_ref = "conv:{conversation_id}" 对话级 knowledge_timeline.rs:87,99
工具执行审计 conversation_id(无 message_id 列) 对话级 audit/mod.rs(14 处写入)
灵感来源 source(自由文本,无结构) 无结构 idea.rs:27

痛点

  • 知识溯源:打开整个对话,不知道是哪一轮产出的
  • 工具审计(最痛):tool_call_id 埋在整个对话 JSON 里,排查"AI 为什么删这个文件"需反序列化全量 JSON 逐条搜
  • 知识引用追踪:不知道知识在对话哪一轮被使用

三、前置:ChatMessage 加 id 字段

3.1 字段定义

crates/df-ai-core/src/types.rs:72ChatMessage 加:

/// 消息全局唯一 ID(ULID)。用于消息级溯源(source_ref / audit message_id / idea source)。
/// 构造时生成;老 JSON 反序列化为 None(向前兼容)。
/// 消息拆分存储(F-260619-03)后,此 ID 即 ai_messages.id 列主键。
#[serde(default, skip_serializing_if = "Option::is_none")]
pub id: Option<String>,

3.2 ID 生成时机

构造路径 ID 生成
ChatMessage::new_user() 构造时 Some(new_id())
ChatMessage::new_assistant() 构造时 Some(new_id())
ChatMessage::new_tool() 构造时 Some(new_id())
ChatMessage::new_system() 构造时 Some(new_id())
老消息反序列化 None(serde default)

new_id() 来自 df-types::types::new_id()(ULID)。df-ai-core 已依赖 df-types。

3.3 向前兼容

  • #[serde(default)]:老 JSON 无 id 字段 → 反序列化为 None
  • skip_serializing_if = "Option::is_none":id = None 时不序列化(不污染老格式)
  • round-trip 测试:序列化 → 反序列化 → 字段一致

四、溯源字段升级

4.1 知识库提炼

文件:src-tauri/src/commands/ai/knowledge_inject.rs:434

// 旧
source_ref: Some(format!("conv:{}", conv_id)),

// 新
source_ref: Some(format!("conv_msg:{}", message_id)),

message_id 来源:提炼触发时,取当前 assistant 消息的 id

4.2 知识生命线

文件:src-tauri/src/commands/knowledge_timeline.rs:87,99

// 旧(timeline 提炼事件 + 引用事件)
source_ref: Some(format!("conv:{}", conv_id)),

// 新
source_ref: Some(format!("conv_msg:{}", message_id)),

4.3 工具执行审计

文件:crates/df-storage/src/models.rs + crud/conversation_repo.rs + src-tauri/src/commands/ai/audit/mod.rs

4.3.1 Model + 迁移

AiToolExecutionRecordmessage_id: Option<String>

V21 迁移(与消息拆分同一版本,或拆为 V21a/V21b):

// migrations.rs migrate_v21
if !column_exists(conn, "ai_tool_executions", "message_id") {
    conn.execute(
        "ALTER TABLE ai_tool_executions ADD COLUMN message_id TEXT",
        [],
    )?;
    tracing::info!("v21: 补建 ai_tool_executions.message_id 列(消息级溯源)");
}

V9 建表 SQL 同步补 message_id TEXT 列(新库直接有)。

4.3.2 CRUD 层

  • ai_tool_execution_from_row:加 message_id: row.get("message_id")?
  • insert/update SQL:加 message_id 列 + params 占位

4.3.3 审计写入(14 处)

audit/mod.rs 中 14 处写入 AiToolExecutionRecord 的位置,在 conversation_id 旁加 message_id:

// audit 记录构造
let record = AiToolExecutionRecord {
    // ... 既有字段 ...
    conversation_id: Some(conv_id.clone()),
    message_id: current_message_id.clone(),  // 新增
    // ...
};

current_message_id 来源:audit 写入时,从 ContextManager 取当前 assistant 消息的 id

4.4 灵感来源

文件:src-tauri/src/commands/idea.rs:27

// 旧(AI 路径,自由文本)
source: Some(format!("AI 对话 {}", conv_id)),

// 新(AI 路径,结构化)
source: Some(format!("conv_msg:{}", message_id)),

人工创建的灵感 source 保持自由文本不变。


五、中间态(不依赖消息拆表)

本设计可先于消息拆分存储上线:

5.1 内存 ID 已足够写入溯源

  • ChatMessage 加 id 字段后,所有新消息有 ULID
  • 溯源写入时带上 message_id —— 信息已存下

5.2 查询体验(中间态)

操作 中间态(消息未拆表) 最终态(消息拆表后)
知识溯源点击 反序列化对话 JSON 搜 id SELECT * FROM ai_messages WHERE id = ?(O(1))
工具审计查消息 反序列化对话 JSON 搜 id SELECT * FROM ai_messages WHERE id = ?(O(1))

中间态查询不快,但溯源信息已完整存下,消息拆表后自然升级为 O(1)。

5.3 source_ref 格式约定

格式 含义 示例
conv:{conversation_id} 对话级(旧格式,向前兼容) conv:abc123
conv_msg:{message_id} 消息级(新格式) conv_msg:01J...

解析逻辑:前缀匹配 conv_msg: → 消息级;conv: → 对话级(兼容)。


六、涉及文件

文件 改动
crates/df-ai-core/src/types.rs ChatMessage 加 id: Option<String> + 构造器生成
crates/df-storage/src/migrations.rs V21 迁移 ai_tool_executions 加 message_id 列 + V9 建表 SQL 同步
crates/df-storage/src/models.rs AiToolExecutionRecord 加 message_id: Option<String>
crates/df-storage/src/crud/conversation_repo.rs ai_tool_execution_from_row + insert/update SQL 补 message_id
src-tauri/src/commands/ai/knowledge_inject.rs source_ref 升级为 conv_msg:{id}
src-tauri/src/commands/knowledge_timeline.rs source_ref 升级(2 处)
src-tauri/src/commands/ai/audit/mod.rs 14 处 conversation_id 写入旁加 message_id
src-tauri/src/commands/idea.rs source 字段结构化(AI 路径)

七、验收标准

  1. ChatMessage 有稳定 id:构造时生成 ULID,序列化/反序列化 round-trip 正确
  2. 知识库提炼的 source_ref 为 conv_msg:{id} 格式
  3. 工具审计记录携带 message_id(14 处全覆盖)
  4. 老数据(无 id)无回归:source_ref 解析兼容 conv: 旧格式;ChatMessage id = None 不报错
  5. cargo check --workspace EXIT 0

八、与消息拆分存储的协作

消息级溯源(F-260619-04)              消息拆分存储(F-260619-03)
┌─────────────────────────┐         ┌─────────────────────────┐
│ ChatMessage.id 字段     │◄────────│ ai_messages 表          │
│ (内存 ULID,中间态)     │  提供   │ (持久化 id,最终态)    │
│                         │         │                         │
│ source_ref = conv_msg:X │ 查询时  │ SELECT WHERE id = X     │
│ audit.message_id = X    │───────►│ (O(1) 查询)            │
└─────────────────────────┘         └─────────────────────────┘

实施顺序建议:

  1. 先上消息级溯源(本设计):ChatMessage 加 id + 溯源字段升级 —— 中间态可上线
  2. 再上消息拆分存储:ai_messages 表 + 全量迁移 + 切读 —— 溯源查询升级为 O(1)