From 00ebf18f231739947b4f4e607f60659dfc3eb77e Mon Sep 17 00:00:00 2001 From: iven Date: Thu, 23 Apr 2026 17:16:25 +0800 Subject: [PATCH] =?UTF-8?q?docs(spec):=20=E5=8A=A8=E6=80=81=E5=BB=BA?= =?UTF-8?q?=E8=AE=AE=E6=99=BA=E8=83=BD=E5=8C=96=E8=AE=BE=E8=AE=A1=20?= =?UTF-8?q?=E2=80=94=20=E6=8E=A5=E9=80=9A=E6=99=BA=E8=83=BD=E5=B1=82?= =?UTF-8?q?=E7=9A=84=20Prompt=20=E5=A2=9E=E5=BC=BA=E6=96=B9=E6=A1=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 发散式探讨确定方案A: 在现有建议生成流程中并行拉取4个智能上下文 (UserProfiler + 痛点 + 经验 + 技能路由),注入增强prompt。 新增1个只读Tauri命令(experience_find_relevant),消除2s人为延迟。 --- ...-dynamic-suggestion-intelligence-design.md | 255 ++++++++++++++++++ 1 file changed, 255 insertions(+) create mode 100644 docs/superpowers/specs/2026-04-23-dynamic-suggestion-intelligence-design.md diff --git a/docs/superpowers/specs/2026-04-23-dynamic-suggestion-intelligence-design.md b/docs/superpowers/specs/2026-04-23-dynamic-suggestion-intelligence-design.md new file mode 100644 index 0000000..ff26ead --- /dev/null +++ b/docs/superpowers/specs/2026-04-23-dynamic-suggestion-intelligence-design.md @@ -0,0 +1,255 @@ +# 动态建议智能化设计 + +> 日期: 2026-04-23 | 状态: Draft | 方案: Prompt 增强法 + +## 1. 问题与目标 + +### 现状 + +ZCLAW 的 SuggestionChips 系统能工作,但建议内容是"有引擎没燃料"的状态: + +- 建议由 LLM 基于最近 6 条对话文本生成,纯通用续问 +- Hermes 管线(ExperienceStore、PainAggregator、UserProfiler)已实现但未接入 +- ButlerRouter 的行业检测 + SemanticSkillRouter 的技能匹配未用于建议 +- SaaS 模式有 2s 人为延迟(`setTimeout(2000)` 避免与记忆提取并发) +- 冷启动的行业检测与动态建议完全断开 + +### 目标 + +接通 UserProfiler + 痛点/经验 + 行业/技能路由,让建议从"通用续问"变"个性化混合建议"(2 条续问 + 1 条管家关怀),不改 UI 形态。 + +### 约束 + +- 稳定化功能冻结:不新增 SaaS 端点、不新增 SKILL.md、不新增 admin 页面 +- 允许小幅扩展:可新增 1-2 个只读 Tauri 命令 +- 复用 @reserved 命令:5 个 Butler 命令已注册未接通,优先复用 + +## 2. 方案选择 + +评估了 3 种方案: + +| 方案 | 描述 | 改动量 | 风险 | +|------|------|--------|------| +| **A. Prompt 增强(选定)** | 拉取智能上下文注入建议 prompt | 小 | 低 | +| B. 双轨建议引擎 | LLM 续问 + 规则引擎管家关怀分离 | 中 | 中 | +| C. 中间件注入 | 在中间件链中生成建议上下文 | 大 | 高 | + +选择 A 的理由:改动最小、增量安全(上下文是可选增强)、复用现有 @reserved 命令、可并行化消除人为延迟。 + +## 3. 架构设计 + +### 3.1 改造后流程 + +``` +[Stream 完成] + ↓ +createCompleteHandler() + ↓ Promise.all (并行) + ├── extractFromConversation() ← 记忆提取(已有) + ├── reflection.record() ← 反思记录(已有) + └── fetchSuggestionContext() ← 🆕 智能上下文拉取 + ├── 检查 __TAURI_INTERNALS__ 是否存在(SaaS 模式下不存在则跳过全部) + ├── identity_get_file("userprofile") + ├── butler_list_pain_points() + ├── experience_find_relevant() ← 🆕 新 Tauri 命令 + └── route_intent() ← 技能/流水线匹配 + ↓ +generateLLMSuggestions(对话文本 + 智能上下文) ← 增强 prompt + ↓ +SuggestionChips 渲染(UI 不变) +``` + +**SaaS 模式处理**: `fetchSuggestionContext()` 首先检查 `window.__TAURI_INTERNALS__` 是否存在。SaaS 模式下浏览器环境无 Tauri 运行时,此检查失败后直接返回空上下文——建议生成回退到纯对话续问,与改造前行为一致。无需新增 SaaS API 端点。 + +### 3.2 上下文源详细设计 + +#### 源 1: 用户画像 + +- **命令**: `identity_get_file(agent_id, "userprofile")` (已有 @connected) +- **注意**: 参数用 `"userprofile"`(identity.rs 641 行的规范键名,`"user_profile"` 也兼容) +- **返回**: `String` — 用户画像文本(行业、角色、专长、沟通风格) +- **前端处理**: 截取前 200 字符,格式化为 `用户是{行业}{角色},{偏好}。最近关注{话题}。` +- **降级**: 为空时跳过该段落 + +#### 源 2: 痛点列表 + +- **命令**: `butler_list_pain_points(agent_id)` (已在 invoke_handler 注册,@reserved 仅表示无前端 UI,前端可直接 `invoke('butler_list_pain_points', { agentId })` 调用) +- **返回**: `Vec` — 含 summary, category, confidence, status, occurrence_count +- **前端处理**: + - 过滤: `confidence >= 0.5 && status ∉ {Solved, Dismissed}` + - 排序: 按 confidence 降序 + - 取前 3 条,格式化为 `1. [{category}] {summary}(出现{n}次)` +- **降级**: 为空时跳过管家关怀指令,全部 3 条生成对话续问 + +#### 源 3: 相关经验 + +- **命令**: `experience_find_relevant(agent_id, query)` (**新增 1 个只读命令**) +- **Rust 实现**: 封装 `ExperienceExtractor::find_relevant_experiences()` +- **返回**: `Vec` — `{ pain_pattern: String, solution_summary: String, reuse_count: u32 }` +- **前端处理**: 取前 2 条,格式化为 `上次解决"{pain}"的方法:{solution}(已复用{n}次)` +- **超时**: 500ms,超时后跳过 + +#### 源 4: 技能/流水线匹配 + +- **命令**: `route_intent({ userInput })` (已有 @connected,Tauri 自动注入 `PipelineState` + `KernelState`) +- **返回**: `RouteResultResponse::NoMatch { suggestions: Vec }` +- **前端处理**: 取 confidence 最高的 1 条,格式化为 `你可能需要:{display_name} — {description}` +- **降级**: 无匹配时跳过 + +### 3.3 新增 Tauri 命令 + +只需 1 个新的只读命令。遵循 `butler_list_pain_points` 的无状态单例模式(不使用 `tauri::State`): + +```rust +// desktop/src-tauri/src/intelligence/experience.rs + +static EXPERIENCE_EXTRACTOR: OnceLock> = OnceLock::new(); + +fn get_extractor() -> Option> { + EXPERIENCE_EXTRACTOR.get().cloned() +} + +/// Initialize the global ExperienceExtractor with a VikingAdapter-backed store. +/// Called once during app startup (alongside init_pain_storage). +pub async fn init_experience_extractor(pool: sqlx::SqlitePool) -> Result<()> { + let sqlite_storage = crate::viking_commands::get_storage().await + .map_err(|e| anyhow::anyhow!("viking storage: {}", e))?; + let viking = Arc::new(zclaw_growth::VikingAdapter::from_sqlite_storage(sqlite_storage)); + let store = Arc::new(ExperienceStore::new(viking)); + let extractor = Arc::new(ExperienceExtractor::new(store)); + EXPERIENCE_EXTRACTOR.set(extractor) + .map_err(|_| anyhow::anyhow!("ExperienceExtractor already initialized"))?; + Ok(()) +} + +#[tauri::command] +pub async fn experience_find_relevant( + agent_id: String, + query: String, +) -> Result, String> { + let extractor = get_extractor() + .ok_or("ExperienceExtractor not initialized".to_string())?; + let experiences = extractor.find_relevant_experiences(&agent_id, &query).await; + // Map full Experience → brief (in command, not in extractor) + Ok(experiences.into_iter().take(3).map(|e| ExperienceBrief { + pain_pattern: e.pain_pattern, + solution_summary: e.solution_steps.join(";") + .chars().take(100).collect(), + reuse_count: e.reuse_count, + }).collect()) +} +``` + +`ExperienceBrief` 结构(定义在同一文件): + +```rust +#[derive(Serialize, Deserialize)] +pub struct ExperienceBrief { + pub pain_pattern: String, + pub solution_summary: String, + pub reuse_count: u32, +} +``` + +**关键设计决策**: +- 使用 `OnceLock>` 单例,与 `PAIN_AGGREGATOR` 模式一致 +- 通过 `viking_commands::get_storage()` → `VikingAdapter::from_sqlite_storage()` → `ExperienceStore` 获取持久化后端 +- `Experience → ExperienceBrief` 映射在命令内完成,`ExperienceExtractor` 保持原样不变 +- 启动时在 `init_pain_storage()` 旁调用 `init_experience_extractor()` + +## 4. 增强 Prompt 模板 + +### 4.1 双层 Prompt 结构 + +**System prompt(静态,OTA 可缓存)**:保持 `HARDCODED_PROMPTS.suggestions` 作为基础 system prompt,只修改生成规则部分: + +``` +根据对话上下文和用户画像,生成恰好 3 个个性化建议。 + +## 生成规则 +1. 2 条对话续问(深入当前话题,帮助用户继续探索) +2. 1 条管家关怀(基于用户消息中提供的痛点、经验或技能信息) + - 如果有未解决痛点 → 回访建议 + - 如果有相关经验 → 引导复用 + - 如果有匹配技能 → 推荐使用 + - 无特殊信号时 → 也生成对话续问 +3. 每个不超过 30 个中文字符 +4. 返回 JSON 数组 ["建议1", "建议2", "建议3"] +5. 使用与用户相同的语言 +6. 不要重复已经讨论过的内容 +``` + +**User message(动态,每次请求拼装)**:由 `fetchSuggestionContext()` 生成的上下文段落拼入 user message,与对话历史一起发送: + +``` +以下是用户的背景信息,请在生成建议时参考: + +{user_profile_section} +{pain_points_section} +{experiences_section} +{skill_match_section} + +最近对话: +{conversation_text} +``` + +**OTA 兼容**:System prompt 仍走 SaaS OTA 缓存(`getSystemPrompt('suggestions')`),动态上下文只在 user message 中注入,不影响缓存机制。 + +### 4.2 全部为空时的回退 + +当所有上下文段落均为空时,user message 不注入背景信息,直接使用对话文本——行为与改造前完全一致。 + +## 5. 降级策略 + +| 故障场景 | 降级行为 | 用户感知 | +|----------|---------|---------| +| 用户画像为空 | 跳过该段落 | 无 | +| 痛点列表为空 | 跳过管家关怀指令 | 无——3 条都是对话续问 | +| 经验查询超时(500ms) | 跳过该段落 | 无 | +| 技能无匹配 | 跳过该段落 | 无 | +| 所有上下文全部失败 | 使用原始 prompt(纯对话续问) | 无——与改造前完全一致 | +| LLM 建议生成失败 | 触发现有关键词 fallback | 无变化 | + +**核心原则**: 上下文是可选增强,任何失败都静默降级,不破坏现有体验。 + +**错误日志**: 所有降级通过 `createLogger('StreamStore')` 在 `warn` 级别记录,与现有记忆提取失败的处理方式一致。不在用户界面显示错误。 + +## 6. 延迟优化 + +| 对比项 | 改造前 | 改造后 | +|--------|--------|--------| +| 上下文拉取 | 无 | Promise.all 并行 ~100-300ms | +| 人为延迟 | setTimeout(2000) | **消除** | +| LLM 调用时机 | +2000ms 后 | +max(记忆, 上下文) 后 | +| 建议出现时间 | ~2s + LLM | ~0.3s + LLM | +| **净提升** | — | **~1.7s 更快(估算值,需实测验证)** | + +> **注意**: 表中"~100-300ms"为估算值。实际延迟取决于 SQLite 冷读、`PainAggregator` 的 `RwLock` 竞争、以及 `ExperienceExtractor` 的 FTS5 查询性能。建议在实现后用 `performance.now()` 埋点实测。 + +## 7. 关键文件清单 + +### 新增 +- `desktop/src/lib/suggestion-context.ts` — `fetchSuggestionContext()` 聚合函数 + 类型定义 + +### 修改 +- `desktop/src-tauri/src/intelligence/experience.rs` — 新增 `experience_find_relevant` Tauri 命令 +- `desktop/src-tauri/src/lib.rs` — 注册新命令到 invoke_handler +- `desktop/src/store/chat/streamStore.ts` — 改造 `createCompleteHandler()` 和 `generateLLMSuggestions()` +- `desktop/src/lib/llm-service.ts` — 更新 suggestion prompt 模板 + +### 复用(已有,不修改) +- `desktop/src-tauri/src/intelligence/pain_aggregator.rs` — `butler_list_pain_points` 命令 +- `desktop/src-tauri/src/intelligence/identity.rs` — `identity_get_file` 命令 +- `desktop/src-tauri/src/pipeline_commands/intent_router.rs` — `route_intent` 命令 + +## 8. 验证方式 + +1. **Rust 编译**: `cargo check --workspace --exclude zclaw-saas` +2. **Rust 测试**: `cargo test -p zclaw-kernel -- experience` +3. **TypeScript 类型**: `cd desktop && pnpm tsc --noEmit` +4. **前端测试**: `cd desktop && pnpm vitest run` +5. **手动验证**: + - 启动 `pnpm start:dev` + - 进行 2-3 轮对话,观察建议内容是否个性化 + - 检查开发者工具 console 无上下文拉取错误 + - 对比改造前后建议相关性和出现速度