feat(hermes): implement intelligence pipeline — 4 chunks, 684 tests passing
Hermes Intelligence Pipeline closes breakpoints in ZCLAW's existing intelligence components with 4 self-contained modules: Chunk 1 — Self-improvement Loop: - ExperienceStore (zclaw-growth): FTS5+TF-IDF wrapper with scope prefix - ExperienceExtractor (desktop/intelligence): template-based extraction from successful proposals with implicit keyword detection Chunk 2 — User Modeling: - UserProfileStore (zclaw-memory): SQLite-backed structured profiles with industry/role/expertise/comm_style/recent_topics/pain_points - UserProfiler (desktop/intelligence): fact classification by category (Preference/Knowledge/Behavior) with profile summary formatting Chunk 3 — NL Cron Chinese Time Parser: - NlScheduleParser (zclaw-runtime): 6 pattern matchers for Chinese time expressions (每天/每周/工作日/间隔/每月/一次性) producing cron expressions - Period-aware hour adjustment (下午3点→15, 晚上8点→20) - Schedule intent detection + task description extraction Chunk 4 — Trajectory Compression: - TrajectoryStore (zclaw-memory): trajectory_events + compressed_trajectories - TrajectoryRecorderMiddleware (zclaw-runtime/middleware): priority 650, async non-blocking event recording via tokio::spawn - TrajectoryCompressor (desktop/intelligence): dedup, request classification, satisfaction detection, execution chain JSON Schema migrations: v2→v3 (user_profiles), v3→v4 (trajectory tables)
This commit is contained in:
@@ -0,0 +1,742 @@
|
||||
# Hermes Intelligence Pipeline Design
|
||||
|
||||
> 基于 Hermes Agent (Nous Research) 竞品分析,吸收 4 个核心理念到 ZCLAW 的详细设计方案。
|
||||
> 架构方案:Pipeline Closure — 闭合现有管线断点,不引入新架构层。
|
||||
|
||||
## Context
|
||||
|
||||
Hermes Agent 验证了"一个管家 + 记忆飞轮"的方向,其 4 个核心创新对 ZCLAW 发布后迭代有直接参考价值:
|
||||
|
||||
1. **自我改进闭环** — 执行 → 评估 → 提取技能 → 改进 → 复用
|
||||
2. **用户建模** — 三层记忆栈 + 统一用户画像
|
||||
3. **自然语言 Cron** — LLM 解析自然语言为定时任务
|
||||
4. **轨迹压缩** — 工具调用链 → 结构化 JSON → RL 基础
|
||||
|
||||
**关键诊断:** ZCLAW 缺的不是模块,是管线没接通。现有 PainAggregator、SolutionGenerator、Reflection、Heartbeat、MemoryExtractor 等组件已就位,但彼此断开。本设计闭合这些断点。
|
||||
|
||||
**范围约束:**
|
||||
- 管家路由器(ButlerRouterMiddleware + SemanticSkillRouter 接通)由另一个会话推进,本设计标注为外部依赖
|
||||
- 发布后迭代,不影响当前发布计划
|
||||
- 4 个理念全部设计,按优先级排序:自我改进闭环 > 用户建模 > NL Cron > 轨迹压缩
|
||||
|
||||
**总代码量估算:** ~2200 行新增/修改(~1700 新增 + ~500 修改)
|
||||
|
||||
### 类型约定
|
||||
|
||||
本设计使用以下 ID 类型约定:
|
||||
|
||||
```rust
|
||||
// 所有 Rust 原生结构体使用强类型
|
||||
use uuid::Uuid;
|
||||
use zclaw_types::{AgentId, SessionId};
|
||||
|
||||
// 为新实体定义类型别名(newtype wrapper 在 Tauri 命令层解包为 String)
|
||||
type ExperienceId = String; // Uuid::new_v4().to_string()
|
||||
type ProposalId = String; // 与现有 Proposal.id 一致
|
||||
type TrajectoryId = String; // Uuid::new_v4().to_string()
|
||||
```
|
||||
|
||||
Rust 内部结构体使用 `AgentId`、`SessionId`;Tauri 命令边界使用 `String`(Tauri serialize 要求)。
|
||||
|
||||
### 统一完成状态枚举
|
||||
|
||||
跨 Section 1/4 使用统一的完成状态:
|
||||
|
||||
```rust
|
||||
/// 通用完成状态,所有 Outcome 枚举的基础
|
||||
enum CompletionStatus {
|
||||
Success,
|
||||
Partial,
|
||||
Failed,
|
||||
Abandoned, // Section 1 不使用此变体(运行时约定,非编译时约束)
|
||||
}
|
||||
```
|
||||
|
||||
Section 1 的 Experience 使用 `CompletionStatus`(不含 Abandoned),Section 4 的 CompressedTrajectory 使用完整版。
|
||||
|
||||
---
|
||||
|
||||
## Section 1: 自我改进闭环
|
||||
|
||||
### 目标
|
||||
|
||||
用户反馈痛点 → 自动识别 → 自动生成方案 → 方案成功后提取为可复用经验 → 下次类似问题直接复用。
|
||||
|
||||
### 数据流
|
||||
|
||||
```
|
||||
用户消息 → PainAggregator(已有)
|
||||
↓ confidence >= 0.7
|
||||
SolutionGenerator(已有,改为自动触发)
|
||||
↓ 生成 Proposal
|
||||
等待用户反馈(成功/失败)
|
||||
↓ 成功
|
||||
ExperienceExtractor(新增)
|
||||
↓ 生成结构化经验
|
||||
ExperienceStore(新增,SQLite)
|
||||
↓ 下次对话
|
||||
MemoryMiddleware(已有)注入相关经验
|
||||
```
|
||||
|
||||
### 关键断点修复
|
||||
|
||||
**断点 1:PainAggregator → SolutionGenerator(未自动触发)**
|
||||
|
||||
- 文件:`desktop/src-tauri/src/intelligence/pain_aggregator.rs`
|
||||
- 当 `confidence >= 0.7` 时,通过 Tauri event 自动调用 `butler_generate_solution`
|
||||
- 新增 `PainConfirmedEvent` 事件结构体
|
||||
|
||||
**断点 2:方案结果反馈(无反馈机制)**
|
||||
|
||||
- 新增 `ProposalFeedback` 结构体
|
||||
- 在聊天流中检测用户隐式反馈关键词("好了""解决了""没用")
|
||||
- 新增 Tauri 命令 `butler_submit_proposal_feedback`
|
||||
|
||||
**断点 3:成功方案 → 结构化经验(完全缺失)**
|
||||
|
||||
- 新增 `ExperienceExtractor`:从成功方案中提取经验
|
||||
- LLM 辅助提取(复用现有 LlmDriver),fallback 到模板提取
|
||||
- 存入 VikingStorage(使用 scope 前缀 `experience://{agent_id}/`)
|
||||
|
||||
**断点 4:经验复用(完全缺失)**
|
||||
|
||||
- 扩展 `MemoryMiddleware`:用户新消息时,通过 VikingStorage 检索相关经验
|
||||
- 使用 scope 过滤 `experience://` 前缀 + TF-IDF 相关性匹配
|
||||
- 相似度 > 阈值时,注入"过往经验"到 system prompt
|
||||
- 格式:`[过往经验] 类似情况 X 做过 Y,结果是 Z`
|
||||
|
||||
### 数据结构
|
||||
|
||||
```rust
|
||||
// 新增文件:desktop/src-tauri/src/intelligence/experience.rs
|
||||
|
||||
use zclaw_types::AgentId;
|
||||
use uuid::Uuid;
|
||||
|
||||
struct Experience {
|
||||
id: ExperienceId,
|
||||
agent_id: AgentId,
|
||||
pain_pattern: String, // 触发模式(关键词摘要)
|
||||
context: String, // 问题上下文
|
||||
solution_steps: Vec<String>, // 解决步骤
|
||||
outcome: CompletionStatus, // Success | Partial(经验只记录成功的)
|
||||
source_proposal_id: Option<ProposalId>,
|
||||
reuse_count: usize,
|
||||
created_at: DateTime,
|
||||
}
|
||||
|
||||
struct ProposalFeedback {
|
||||
proposal_id: ProposalId,
|
||||
outcome: CompletionStatus, // Success | Failed | Partial
|
||||
user_comment: Option<String>,
|
||||
detected_at: DateTime,
|
||||
}
|
||||
|
||||
struct PainConfirmedEvent {
|
||||
pain_point_id: String, // PainPoint.id (Uuid String)
|
||||
pattern: String,
|
||||
confidence: f32,
|
||||
}
|
||||
```
|
||||
|
||||
### 存储策略
|
||||
|
||||
经验存储在现有 VikingStorage 中,使用 scope 前缀区分:
|
||||
|
||||
```rust
|
||||
// Experience 存储为 VikingStorage memory entry
|
||||
scope: "agent://{agent_id}/experience/{pattern_hash}" // 遵循 OpenViking URI 约定
|
||||
content: JSON(Experience) // 序列化的完整 Experience 结构体
|
||||
```
|
||||
|
||||
**为什么不用独立的 experiences + FTS5 表:**
|
||||
- VikingStorage 已有成熟的 FTS5 + TF-IDF + embedding 检索管道
|
||||
- MemoryMiddleware 已与 VikingStorage 集成,增加 scope 前缀即可区分
|
||||
- 避免维护两套独立的 FTS5 索引
|
||||
|
||||
独立的 `experience_store.rs` 文件负责 VikingStorage CRUD + scope 过滤,不创建新表。
|
||||
|
||||
### 迁移策略
|
||||
|
||||
不需要新数据库表或 schema 变更。经验数据通过 VikingStorage 的现有 memory 表存储,使用 scope 前缀区分。
|
||||
|
||||
### 错误处理
|
||||
|
||||
- ExperienceExtractor LLM 调用失败 → fallback 到模板提取(固定格式提取 solution_steps)
|
||||
- ProposalFeedback 检测失败 → 不阻塞对话,静默跳过
|
||||
- 经验注入失败 → MemoryMiddleware 记录 warn 日志,不注入,不影响正常对话
|
||||
- 所有错误遵循代码库约定:非关键路径使用 `log::warn!` / `log::error!`,不阻塞主流程
|
||||
|
||||
### 测试计划
|
||||
|
||||
| 测试目标 | 文件位置 | 覆盖场景 |
|
||||
|----------|---------|---------|
|
||||
| ExperienceExtractor | `experience.rs` 内联 `#[cfg(test)]` | LLM 提取成功/failure fallback、模板提取 |
|
||||
| ExperienceStore | `experience_store.rs` 内联 | CRUD 往返、scope 过滤、VikingStorage 集成 |
|
||||
| PainConfirmedEvent 触发 | `pain_aggregator.rs` 测试扩展 | confidence >= 0.7 触发事件 |
|
||||
| 经验注入 | MemoryMiddleware 测试 | 相关性过滤、token 限制、空结果处理 |
|
||||
| ProposalFeedback 检测 | `solution_generator.rs` 测试扩展 | 隐式反馈关键词匹配 |
|
||||
|
||||
### 文件清单
|
||||
|
||||
| 文件 | 用途 | 预估行数 |
|
||||
|------|------|---------|
|
||||
| `desktop/src-tauri/src/intelligence/experience.rs` | ExperienceExtractor + 逻辑 | ~250 |
|
||||
| `crates/zclaw-growth/src/experience_store.rs` | VikingStorage scope CRUD | ~120 |
|
||||
| 改动 `pain_aggregator.rs` | 自动触发 SolutionGenerator | ~30 |
|
||||
| 改动 `solution_generator.rs` | Proposal feedback 槽位 | ~40 |
|
||||
| 改动 `intelligence_hooks.rs` | 新增 post-proposal-evaluation hook | ~50 |
|
||||
| 改动 MemoryMiddleware | 经验注入逻辑(scope 过滤) | ~60 |
|
||||
| 改动 `crates/zclaw-memory/src/lib.rs` | 导出新模块 | ~5 |
|
||||
|
||||
**预估:~555 行新增/修改**
|
||||
|
||||
---
|
||||
|
||||
## Section 2: 用户建模
|
||||
|
||||
### 目标
|
||||
|
||||
从每次对话中持续提取用户特征,聚合为结构化画像,注入到路由和生成环节。
|
||||
|
||||
### 数据流
|
||||
|
||||
```
|
||||
对话消息 → MemoryExtractor(已有)
|
||||
↓
|
||||
UserProfiler(新增)
|
||||
↓ 聚合到 UserProfile
|
||||
UserProfileStore(新增,SQLite)
|
||||
↓
|
||||
├→ ButlerRouter(外部依赖,另一个会话)
|
||||
│ → 路由决策考虑用户偏好
|
||||
└→ MemoryMiddleware(已有)
|
||||
→ system prompt 注入用户画像摘要
|
||||
```
|
||||
|
||||
### 设计决策
|
||||
|
||||
**为什么新建 UserProfile 而不沿用 IdentityManager.user_profile?**
|
||||
|
||||
现有 user_profile 是非结构化 markdown,无法做条件查询。Profile injection 已被有意禁用(`identity.rs:291-298`),因为它导致模型过度关注旧话题。需要结构化画像做相关性过滤后注入。
|
||||
|
||||
**单用户桌面场景:** 桌面版使用 `"default_user"` 作为 user_id(与 PainAggregator 一致),仅维护一条 UserProfile 记录。
|
||||
|
||||
### 数据结构
|
||||
|
||||
```rust
|
||||
// 新增文件:crates/zclaw-memory/src/user_profile_store.rs
|
||||
|
||||
struct UserProfile {
|
||||
user_id: String, // "default_user"(桌面版单用户)
|
||||
|
||||
// 静态属性(低频更新)
|
||||
industry: Option<String>, // "医疗" "制造业"
|
||||
role: Option<String>, // "行政主任" "厂长"
|
||||
expertise_level: Option<Level>, // Beginner / Intermediate / Expert
|
||||
communication_style: Option<CommStyle>, // Concise / Detailed / Formal / Casual
|
||||
preferred_language: String, // "zh-CN"
|
||||
|
||||
// 动态属性(高频更新)
|
||||
recent_topics: Vec<String>, // 最近 7 天的话题
|
||||
active_pain_points: Vec<String>, // 当前未解决痛点
|
||||
preferred_tools: Vec<String>, // 常用技能/工具
|
||||
|
||||
// 元数据
|
||||
updated_at: DateTime,
|
||||
confidence: f32, // 画像置信度
|
||||
}
|
||||
|
||||
enum Level { Beginner, Intermediate, Expert }
|
||||
enum CommStyle { Concise, Detailed, Formal, Casual }
|
||||
```
|
||||
|
||||
### 聚合逻辑(UserProfiler)
|
||||
|
||||
1. **MemoryExtractor 输出 → 分类**:已提取的记忆按 `UserPreference` / `UserFact` / `AgentLesson` 分类
|
||||
2. **分类后聚合**:
|
||||
- `UserPreference` → 更新 `communication_style`, `preferred_tools`
|
||||
- `UserFact` → 更新 `industry`, `role`, `expertise_level`
|
||||
- `AgentLesson` → 更新 `recent_topics`
|
||||
- PainAggregator 的活跃痛点 → 更新 `active_pain_points`
|
||||
3. **去重 + 衰减**:相似属性合并,超过 30 天无佐证的属性降低 confidence
|
||||
4. **存储**:单用户单条记录(upsert),SQLite `user_profiles` 表
|
||||
|
||||
### 注入逻辑
|
||||
|
||||
```rust
|
||||
// 在 MemoryMiddleware 中新增
|
||||
fn inject_user_profile(&self, ctx: &mut MiddlewareContext, profile: &UserProfile) {
|
||||
// 只注入与当前话题相关的属性
|
||||
let relevant = self.filter_by_relevance(profile, &ctx.user_input);
|
||||
if relevant.is_empty() { return; }
|
||||
|
||||
// 格式化为简洁摘要,不超过 100 tokens
|
||||
let summary = format_user_profile_summary(&relevant);
|
||||
ctx.system_prompt.push_str(&summary);
|
||||
}
|
||||
```
|
||||
|
||||
**关键约束:** 注入内容不超过 100 tokens,只注入与当前话题相关的属性。
|
||||
|
||||
### 与管家路由器的协作(外部依赖)
|
||||
|
||||
当管家路由器接通后:
|
||||
- ButlerRouterMiddleware 可读取 UserProfile.industry 和 role
|
||||
- 路由时考虑用户背景
|
||||
- 本设计只提供数据接口,路由逻辑由另一个会话处理
|
||||
|
||||
### 迁移策略
|
||||
|
||||
新增 `user_profiles` 表,通过 `schema.rs` 的 `MIGRATIONS` 数组递增版本。初始版本包含 CREATE TABLE + 默认 "default_user" 行。
|
||||
|
||||
```rust
|
||||
// 在 schema.rs MIGRATIONS 数组新增
|
||||
("CREATE TABLE IF NOT EXISTS user_profiles (...)", "DROP TABLE IF EXISTS user_profiles")
|
||||
```
|
||||
|
||||
### 错误处理
|
||||
|
||||
- UserProfileStore 读写失败 → `log::warn!` + 返回 None,不阻塞对话
|
||||
- UserProfiler 聚合失败 → 保留上次有效画像,不覆盖
|
||||
- Profile 注入失败 → MemoryMiddleware 降级到无 profile 注入模式
|
||||
- 所有操作遵循:非关键路径错误不阻塞主流程
|
||||
|
||||
### 测试计划
|
||||
|
||||
| 测试目标 | 文件位置 | 覆盖场景 |
|
||||
|----------|---------|---------|
|
||||
| UserProfileStore | `user_profile_store.rs` 内联 | CRUD 往返、upsert 去重、JSON 字段序列化 |
|
||||
| UserProfiler 聚合 | `user_profiler.rs` 内联 | 分类正确性、去重、衰减、空输入 |
|
||||
| Profile 注入 | MemoryMiddleware 测试扩展 | 相关性过滤、100 token 限制、空 profile |
|
||||
| 迁移 | schema 测试 | 新建 + 升级路径 |
|
||||
|
||||
### 数据库 Schema
|
||||
|
||||
```sql
|
||||
CREATE TABLE IF NOT EXISTS user_profiles (
|
||||
user_id TEXT PRIMARY KEY,
|
||||
industry TEXT,
|
||||
role TEXT,
|
||||
expertise_level TEXT, -- 'Beginner' | 'Intermediate' | 'Expert'
|
||||
communication_style TEXT, -- 'Concise' | 'Detailed' | 'Formal' | 'Casual'
|
||||
preferred_language TEXT DEFAULT 'zh-CN',
|
||||
recent_topics TEXT, -- JSON array
|
||||
active_pain_points TEXT, -- JSON array
|
||||
preferred_tools TEXT, -- JSON array
|
||||
confidence REAL DEFAULT 0.0,
|
||||
updated_at TEXT NOT NULL
|
||||
);
|
||||
```
|
||||
|
||||
### 文件清单
|
||||
|
||||
| 文件 | 用途 | 预估行数 |
|
||||
|------|------|---------|
|
||||
| `crates/zclaw-memory/src/user_profile_store.rs` | UserProfile 结构体 + SQLite CRUD | ~200 |
|
||||
| `desktop/src-tauri/src/intelligence/user_profiler.rs` | 聚合逻辑 | ~180 |
|
||||
| 改动 `MemoryMiddleware` | profile 注入(相关性过滤) | ~80 |
|
||||
| 改动 `intelligence_hooks.rs` | post-extraction 触发 UserProfiler | ~30 |
|
||||
| 改动 `crates/zclaw-memory/src/lib.rs` | 导出新模块 | ~5 |
|
||||
|
||||
**预估:~495 行新增/修改**
|
||||
|
||||
---
|
||||
|
||||
## Section 3: 自然语言 Cron
|
||||
|
||||
### 目标
|
||||
|
||||
用户说"每天早上9点提醒我查房" → 系统解析为 `0 9 * * *` → 自动创建定时任务。
|
||||
|
||||
### 数据流
|
||||
|
||||
```
|
||||
用户消息(含时间意图)
|
||||
↓
|
||||
意图分类(ButlerRouter / 正则预检)
|
||||
↓ 检测到"定时/提醒"意图
|
||||
NlScheduleParser(新增,位于 zclaw-runtime)
|
||||
↓ 解析为 ParsedSchedule
|
||||
ScheduleConfirmDialog(新增)
|
||||
↓ 用户确认 "每天早上9点 → 0 9 * * *"
|
||||
SchedulerService(已有,位于 zclaw-kernel)
|
||||
↓ 创建定时任务
|
||||
TriggerManager(已有)
|
||||
↓ 到时触发
|
||||
Hand 执行(已有)
|
||||
```
|
||||
|
||||
### 解析策略(三层 fallback)
|
||||
|
||||
**Layer 1: 正则模式匹配(覆盖 80% 常见场景)**
|
||||
|
||||
| 模式 | 示例 | Cron |
|
||||
|------|------|------|
|
||||
| 每天 + 时间 | 每天早上9点 | `0 9 * * *` |
|
||||
| 每周N + 时间 | 每周一上午10点 | `0 10 * * 1` |
|
||||
| 工作日 + 时间 | 工作日下午3点 | `0 15 * * 1-5` |
|
||||
| 每N小时 | 每2小时 | `0 */2 * * *` |
|
||||
| 每月N号 | 每月1号 | `0 0 1 * *` |
|
||||
| 相对时间 | 明天下午3点 | 一次性 ISO |
|
||||
|
||||
**Layer 2: LLM 辅助解析(覆盖模糊/复杂表述)**
|
||||
|
||||
- 使用 Haiku(~50 tokens 输入,~20 tokens 输出)
|
||||
- 处理如"下个月开始每周二和周四提醒我"
|
||||
|
||||
**Layer 3: 交互澄清(无法确定时)**
|
||||
|
||||
- "我理解您想设置定时任务,请确认:..."
|
||||
|
||||
### 数据结构
|
||||
|
||||
```rust
|
||||
// 新增文件:crates/zclaw-runtime/src/nl_schedule.rs
|
||||
// 放在 runtime 层因为这是纯文本→cron工具,不依赖 kernel 协调
|
||||
|
||||
use zclaw_types::AgentId;
|
||||
|
||||
struct ParsedSchedule {
|
||||
cron_expression: String, // "0 9 * * *"
|
||||
natural_description: String, // "每天早上9点"
|
||||
confidence: f32,
|
||||
task_description: String, // "查房提醒"
|
||||
task_target: TaskTarget,
|
||||
}
|
||||
|
||||
/// 定时任务目标
|
||||
enum TaskTarget {
|
||||
Agent(AgentId), // 触发指定 agent
|
||||
Hand(String), // 触发指定 hand(工具名)
|
||||
Workflow(String), // 触发指定 workflow(名称)
|
||||
}
|
||||
|
||||
enum ScheduleParseResult {
|
||||
Exact(ParsedSchedule), // 高置信度,直接确认
|
||||
Ambiguous(Vec<ParsedSchedule>), // 多种理解,需选择
|
||||
Unclear, // 需要澄清
|
||||
}
|
||||
```
|
||||
|
||||
### 确认流程
|
||||
|
||||
1. 用户说"每天早上9点提醒我查房"
|
||||
2. 解析为 `{ cron: "0 9 * * *", desc: "查房提醒" }`
|
||||
3. 系统回复:"好的,我为您设置了:**每天早上 9:00** 提醒查房。确认吗?"
|
||||
4. 用户确认 → 调用已有 `SchedulerService.create_trigger()`
|
||||
5. 用户修正 → 重新解析或手动编辑
|
||||
|
||||
### 迁移策略
|
||||
|
||||
不需要新数据库表。NlScheduleParser 是纯计算工具,输出通过现有 `SchedulerService` + `TriggerManager` 存储。
|
||||
|
||||
### 错误处理
|
||||
|
||||
- 正则匹配失败 → 尝试 Layer 2 LLM 解析
|
||||
- LLM 解析失败 → 返回 `ScheduleParseResult::Unclear`,触发交互澄清
|
||||
- 定时任务创建失败 → 向用户报告错误,建议手动设置
|
||||
- 所有错误不阻塞对话流程
|
||||
|
||||
### 测试计划
|
||||
|
||||
| 测试目标 | 文件位置 | 覆盖场景 |
|
||||
|----------|---------|---------|
|
||||
| 正则解析 | `nl_schedule.rs` 内联 | 10+ 中文时间表述模式、边界值、无效输入 |
|
||||
| LLM fallback | mock 测试 | LLM 返回无效 cron 时的容错 |
|
||||
| ParsedSchedule | 单元测试 | 序列化、字段完整性 |
|
||||
| TaskTarget 枚举 | 单元测试 | 各变体匹配现有类型 |
|
||||
| 确认流程 | 集成测试 | 完整 parse → confirm → create 链路 |
|
||||
|
||||
### 文件清单
|
||||
|
||||
| 文件 | 用途 | 预估行数 |
|
||||
|------|------|---------|
|
||||
| `crates/zclaw-runtime/src/nl_schedule.rs` | NlScheduleParser + 中文模式库 | ~300 |
|
||||
| 改动 `intelligence_hooks.rs` | 检测定时意图并触发解析 | ~40 |
|
||||
| 改动 desktop store + component | 确认对话框交互 | ~150 |
|
||||
| 改动 `crates/zclaw-kernel/src/scheduler.rs` | 接受 cron 字符串输入 | ~20 |
|
||||
|
||||
**预估:~510 行新增/修改**
|
||||
|
||||
---
|
||||
|
||||
## Section 4: 轨迹压缩
|
||||
|
||||
### 目标
|
||||
|
||||
记录完整的工具调用链(用户请求 → 意图分类 → 技能选择 → 执行步骤 → 结果 → 用户满意度),压缩为结构化 JSON,作为未来 RL/改进的基础数据。
|
||||
|
||||
### 数据流
|
||||
|
||||
```
|
||||
用户请求
|
||||
↓
|
||||
AgentLoop(已有)
|
||||
↓ 每步通过中间件记录
|
||||
TrajectoryRecorderMiddleware(新增,实现 AgentMiddleware trait)
|
||||
↓ 异步写入 trajectory_events 表
|
||||
↓ 会话结束时
|
||||
TrajectoryCompressor(新增)
|
||||
↓ 压缩为结构化 JSON
|
||||
compressed_trajectories 表
|
||||
↓ 可选
|
||||
导出为 RL 训练数据格式
|
||||
```
|
||||
|
||||
### 关键设计决策:TrajectoryRecorder 作为中间件
|
||||
|
||||
TrajectoryRecorder 实现 `AgentMiddleware` trait(来自 `zclaw-runtime`),利用现有中间件钩子:
|
||||
|
||||
- `before_completion` → 记录 `UserRequest` 步骤
|
||||
- `after_tool_call` → 记录 `ToolExecution` 步骤
|
||||
- `after_completion` → 记录 `LlmGeneration` 步骤 + 会话结束时触发压缩
|
||||
|
||||
**为什么不用自定义 AgentLoop hook:**
|
||||
- 现有中间件系统已提供所有需要的钩子点
|
||||
- `MiddlewareContext` 已暴露 `agent_id`、`session_id`、`user_input`、`input_tokens`、`output_tokens`
|
||||
- 符合 Pipeline Closure 原则:不引入新架构层
|
||||
|
||||
优先级设置:600-799 范围(遥测类别),确保在业务中间件之后运行。注意现有 `token_calibration` 中间件已占用优先级 700,推荐使用 650。
|
||||
|
||||
### 数据结构
|
||||
|
||||
```rust
|
||||
// 新增文件:crates/zclaw-memory/src/trajectory_store.rs
|
||||
|
||||
use zclaw_types::{AgentId, SessionId};
|
||||
use uuid::Uuid;
|
||||
|
||||
/// 单条轨迹事件(细粒度,按步骤记录)
|
||||
struct TrajectoryEvent {
|
||||
id: TrajectoryId,
|
||||
session_id: SessionId,
|
||||
agent_id: AgentId,
|
||||
step_index: usize,
|
||||
step_type: TrajectoryStepType,
|
||||
input_summary: String, // ≤200 字
|
||||
output_summary: String, // ≤200 字
|
||||
duration_ms: u64,
|
||||
timestamp: DateTime,
|
||||
}
|
||||
|
||||
enum TrajectoryStepType {
|
||||
UserRequest, // 用户原始请求
|
||||
IntentClassification, // 意图分类结果
|
||||
SkillSelection, // 选择了哪个技能
|
||||
ToolExecution, // 工具调用
|
||||
LlmGeneration, // LLM 生成
|
||||
UserFeedback, // 用户反馈
|
||||
}
|
||||
|
||||
/// 压缩后的完整轨迹(会话结束时生成)
|
||||
struct CompressedTrajectory {
|
||||
id: TrajectoryId,
|
||||
session_id: SessionId,
|
||||
agent_id: AgentId,
|
||||
|
||||
request_type: String, // "data_report" "policy_query"
|
||||
tools_used: Vec<String>, // ["researcher", "collector"]
|
||||
outcome: CompletionStatus, // Success | Partial | Failed | Abandoned
|
||||
total_steps: usize,
|
||||
total_duration_ms: u64,
|
||||
total_tokens: u32,
|
||||
|
||||
execution_chain: String, // JSON: [{step, tool, result_summary}]
|
||||
|
||||
satisfaction_signal: Option<SatisfactionSignal>,
|
||||
|
||||
created_at: DateTime,
|
||||
}
|
||||
|
||||
enum SatisfactionSignal {
|
||||
Positive, // "谢谢""很好""解决了"
|
||||
Negative, // "不对""没用""还是不行"
|
||||
Neutral, // 无明显信号
|
||||
}
|
||||
```
|
||||
|
||||
### 记录策略
|
||||
|
||||
**低开销原则:** 轨迹记录不能影响正常对话性能。
|
||||
|
||||
1. **事件记录:** 每步只存 `step_type + input_summary(≤200字) + output_summary(≤200字)`
|
||||
2. **异步写入:** 通过 `tokio::spawn` 异步写入 SQLite,不阻塞主流程
|
||||
3. **压缩触发:** 会话结束时(compactor flush 或 session close),异步压缩
|
||||
4. **保留策略:** 压缩后删除原始事件(保留 7 天),压缩轨迹保留 90 天
|
||||
|
||||
### 压缩算法
|
||||
|
||||
```rust
|
||||
fn compress(events: Vec<TrajectoryEvent>) -> CompressedTrajectory {
|
||||
// 1. 提取关键步骤(跳过中间重试/错误恢复)
|
||||
// 2. 合并连续相同类型的步骤
|
||||
// 3. 生成 execution_chain JSON
|
||||
// 4. 推断 outcome(最后一步是否成功 + 用户反馈信号)
|
||||
// 5. 统计 token 用量和耗时
|
||||
}
|
||||
```
|
||||
|
||||
### 与自我改进闭环的协作
|
||||
|
||||
当 ExperienceExtractor 运行时:
|
||||
- 查询 `compressed_trajectories` 找到类似场景的历史轨迹
|
||||
- 评估"这个方案上次用了几步?成功率多少?"
|
||||
- 为经验提取提供数据支撑
|
||||
|
||||
### 未来 RL 扩展(本次不实施)
|
||||
|
||||
- `execution_chain` 可直接转换为 Atropos/GEPA 训练格式
|
||||
- `satisfaction_signal` 可作为 reward signal
|
||||
- RL 训练管道不在本次范围内
|
||||
|
||||
### 迁移策略
|
||||
|
||||
通过 `schema.rs` 的 `MIGRATIONS` 数组递增版本(使用 `&[&str]` 扁平数组格式,与现有代码一致),新增 `trajectory_events` 和 `compressed_trajectories` 两张表。
|
||||
|
||||
```rust
|
||||
// 在 schema.rs MIGRATIONS 数组新增(扁平 &str 数组,无 down migration)
|
||||
&[
|
||||
"CREATE TABLE IF NOT EXISTS trajectory_events (
|
||||
id TEXT PRIMARY KEY,
|
||||
session_id TEXT NOT NULL,
|
||||
...
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS compressed_trajectories (
|
||||
...
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_trajectory_session ON trajectory_events(session_id);",
|
||||
]
|
||||
```
|
||||
|
||||
### 错误处理
|
||||
|
||||
- TrajectoryRecorder 异步写入失败 → `log::warn!`,不重试,丢弃单条事件
|
||||
- TrajectoryCompressor 压缩失败 → `log::warn!`,原始事件保留 7 天后自动清理
|
||||
- 压缩轨迹查询失败 → ExperienceExtractor 降级到无历史数据模式
|
||||
- 所有操作:非关键路径错误不阻塞对话
|
||||
|
||||
### 测试计划
|
||||
|
||||
| 测试目标 | 文件位置 | 覆盖场景 |
|
||||
|----------|---------|---------|
|
||||
| TrajectoryStore CRUD | `trajectory_store.rs` 内联 | 插入/查询/删除、session 过滤 |
|
||||
| 压缩算法 | `trajectory_compressor.rs` 内联 | 正常压缩、空事件、单步事件、合并去重 |
|
||||
| TrajectoryRecorderMiddleware | 中间件测试 | before/after 钩子记录、空输入跳过 |
|
||||
| 保留策略 | 集成测试 | 7 天清理、90 天清理 |
|
||||
| 满意度检测 | 单元测试 | 正/负/中性关键词匹配 |
|
||||
|
||||
### 数据库 Schema
|
||||
|
||||
```sql
|
||||
CREATE TABLE IF NOT EXISTS trajectory_events (
|
||||
id TEXT PRIMARY KEY,
|
||||
session_id TEXT NOT NULL,
|
||||
agent_id TEXT NOT NULL,
|
||||
step_index INTEGER NOT NULL,
|
||||
step_type TEXT NOT NULL,
|
||||
input_summary TEXT,
|
||||
output_summary TEXT,
|
||||
duration_ms INTEGER DEFAULT 0,
|
||||
timestamp TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX idx_trajectory_session ON trajectory_events(session_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS compressed_trajectories (
|
||||
id TEXT PRIMARY KEY,
|
||||
session_id TEXT NOT NULL,
|
||||
agent_id TEXT NOT NULL,
|
||||
request_type TEXT NOT NULL,
|
||||
tools_used TEXT, -- JSON array
|
||||
outcome TEXT NOT NULL, -- 'Success'|'Partial'|'Failed'|'Abandoned'
|
||||
total_steps INTEGER DEFAULT 0,
|
||||
total_duration_ms INTEGER DEFAULT 0,
|
||||
total_tokens INTEGER DEFAULT 0,
|
||||
execution_chain TEXT NOT NULL, -- JSON
|
||||
satisfaction_signal TEXT, -- 'Positive'|'Negative'|'Neutral'|NULL
|
||||
created_at TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX idx_ct_request_type ON compressed_trajectories(request_type);
|
||||
CREATE INDEX idx_ct_outcome ON compressed_trajectories(outcome);
|
||||
```
|
||||
|
||||
### 文件清单
|
||||
|
||||
| 文件 | 用途 | 预估行数 |
|
||||
|------|------|---------|
|
||||
| `crates/zclaw-memory/src/trajectory_store.rs` | TrajectoryEvent + CompressedTrajectory + SQLite CRUD | ~250 |
|
||||
| `crates/zclaw-runtime/src/middleware/trajectory_recorder.rs` | AgentMiddleware 实现 | ~150 |
|
||||
| `desktop/src-tauri/src/intelligence/trajectory_compressor.rs` | 压缩算法 | ~120 |
|
||||
| 改动 `crates/zclaw-memory/src/lib.rs` | 导出新模块 | ~5 |
|
||||
| 改动 `crates/zclaw-kernel/src/kernel/mod.rs` | 注册中间件(priority 650) | ~10 |
|
||||
|
||||
**预估:~535 行新增/修改**
|
||||
|
||||
---
|
||||
|
||||
## 总览
|
||||
|
||||
### 代码量汇总
|
||||
|
||||
| 理念 | 新增 | 修改 | 总计 | 优先级 |
|
||||
|------|------|------|------|--------|
|
||||
| 自我改进闭环 | ~400 | ~155 | ~555 | P1 |
|
||||
| 用户建模 | ~380 | ~115 | ~495 | P2 |
|
||||
| 自然语言 Cron | ~320 | ~190 | ~510 | P3 |
|
||||
| 轨迹压缩 | ~525 | ~15 | ~540 | P4 |
|
||||
| **总计** | **~1625** | **~475** | **~2100** | — |
|
||||
|
||||
### 实施顺序和依赖关系
|
||||
|
||||
```
|
||||
Section 1 (自我改进闭环) ← 立即开始
|
||||
↓
|
||||
Section 2 (用户建模) ← 可与 Section 1 并行,无强依赖
|
||||
↓
|
||||
Section 3 (NL Cron) ← 依赖 Section 2 的 UserProfile(可选)+ 管家路由器(外部)
|
||||
↓
|
||||
Section 4 (轨迹压缩) ← 可与 Section 1-3 并行,无依赖
|
||||
```
|
||||
|
||||
Section 1 和 2 可以并行开发。Section 3 建议在管家路由器接通后实施。Section 4 完全独立。
|
||||
|
||||
### 外部依赖
|
||||
|
||||
- 管家路由器(ButlerRouterMiddleware + SemanticSkillRouter 接通)— 另一个会话推进
|
||||
- 痛点数据持久化(内存 → SQLite)— 已在 pre-release strategy 中规划
|
||||
|
||||
### intelligence_hooks.rs 管理
|
||||
|
||||
当前 `intelligence_hooks.rs` 约 281 行。本设计新增约 120 行钩子代码(Section 1: ~50, Section 2: ~30, Section 3: ~40)。
|
||||
如果文件超过 400 行,应拆分为 `hooks/` 子模块:
|
||||
- `hooks/pain.rs` — 痛点相关钩子
|
||||
- `hooks/profile.rs` — 用户画像钩子
|
||||
- `hooks/schedule.rs` — 定时任务意图检测
|
||||
- `hooks/mod.rs` — 统一注册
|
||||
|
||||
### 验证方式
|
||||
|
||||
每个 Section 完成后的验证步骤:
|
||||
|
||||
1. **自我改进闭环:** 人工模拟痛点对话 → 验证自动生成方案 → 验证经验提取 → 验证经验复用注入
|
||||
2. **用户建模:** 多轮对话 → 检查 UserProfile 各字段是否正确聚合 → 验证注入内容相关性
|
||||
3. **NL Cron:** 测试 10+ 种中文时间表述 → 验证 cron 输出 → 验证定时任务创建
|
||||
4. **轨迹压缩:** 完整对话流程 → 检查 trajectory_events 记录 → 验证压缩结果 → 检查异步写入无阻塞
|
||||
|
||||
### 验证命令
|
||||
|
||||
```bash
|
||||
# Rust 编译检查
|
||||
cargo check --workspace --exclude zclaw-saas
|
||||
|
||||
# Rust 测试
|
||||
cargo test --workspace --exclude zclaw-saas
|
||||
|
||||
# TypeScript 类型检查
|
||||
cd desktop && pnpm tsc --noEmit
|
||||
|
||||
# 前端测试
|
||||
cd desktop && pnpm vitest run
|
||||
```
|
||||
Reference in New Issue
Block a user