# ZCLAW OpenViking 深度集成方案 ## Context **背景**:ZCLAW 项目基于 ZCLAW 定制开发,目标是结合 ZCLAW、NanoClaw、ZeroClaw 等系统的优点。当前 Agent 智能层已超前完成(Phase 1-3 完成, Phase 4 部分完成),但 OpenViking 集成依赖外部 Python 服务,用户安装繁琐。 **问题**:如何深度集成 OpenViking,避免 Python 依赖,实现无感安装体验? **目标**:以 OpenViking Rust CLI (`ov`) 为核心,通过 Tauri sidecar 集成,让记忆系统成为原生组件。CLI 缺失的功能再自行开发补充。 --- ## 关键决策总结 基于头脑风暴讨论,确定以下技术决策: | 决策点 | 选择 | 理由 | |--------|------|------| | **集成方式** | OpenViking Rust CLI + 自建补充 | 利用成熟工具,减少开发量,缺失功能自行补充 | | **记忆存储** | CLI 内置 SQLite + sqlite-vec | CLI 已实现,无需重复开发 | | **Embedding 模型** | doubao-embedding-vision | 中文效果优秀,火山引擎生态 | | **记忆提取** | LLM 提取 | 对话结束后调用 LLM 分析并提取 | | **部署方式** | Tauri Sidecar | CLI 作为可执行文件随应用分发 | --- ## Architecture Design ``` ┌─────────────────────────────────────────────────────────────────┐ │ ZCLAW Desktop (Tauri + React) │ │ │ │ ┌────────────────────────────────────────────────────────────┐ │ │ │ React UI Layer │ │ │ │ ┌──────────┐ ┌──────────┐ ┌───────────┐ ┌──────────────┐ │ │ │ │ │ ChatArea │ │MemoryPanel│ │SwarmPanel│ │ SkillMarket │ │ │ │ │ └────┬─────┘ └────┬─────┘ └─────┬─────┘└──────┬───────┘ │ │ │ └───────────┼────────────┼─────────────┼───────────────┼─────┘ │ │ ▼ ▼ ▼ ▼ │ │ ┌────────────────────────────────────────────────────────────┐ │ │ │ TypeScript Integration Layer │ │ │ │ ┌──────────────────────────────────────────────────────┐ │ │ │ │ │ VikingAdapter (已存在,保持兼容) │ │ │ │ │ └──────────────────────────────────────────────────────┘ │ │ │ └──────────────────────────┬─────────────────────────────────┘ │ │ │ │ │ ┌──────────────────────────▼─────────────────────────────────┐ │ │ │ Tauri Command Layer (Rust) │ │ │ │ ┌────────────────────────────────────────────────────────┐│ │ │ │ │ SidecarWrapper: 调用 `ov` CLI ││ │ │ │ │ - invoke('viking_add', ...) → ov add ││ │ │ │ │ - invoke('viking_find', ...) → ov find ││ │ │ │ │ - invoke('viking_grep', ...) → ov grep ││ │ │ │ └────────────────────────────────────────────────────────┘│ │ │ │ ┌────────────────────────────────────────────────────────┐│ │ │ │ │ SupplementalModule: CLI 缺失功能补充 ││ │ │ │ │ - SessionExtractor (LLM 记忆提取) ││ │ │ │ │ - EmbeddingService (doubao API 封装) ││ │ │ │ │ - ContextBuilder (L0/L1/L2 分层加载) ││ │ │ │ └────────────────────────────────────────────────────────┘│ │ │ └─────────────────────────────────────────────────────────────┘ │ │ │ │ │ ┌──────────────────────────▼─────────────────────────────────┐ │ │ │ Storage Layer │ │ │ │ ┌──────────────────┐ ┌──────────────────────────────────┐ │ │ │ │ │ OpenViking CLI │ │ AppData (配置) │ │ │ │ │ │ ~/.viking/ │ │ ~/.zclaw/config.toml │ │ │ │ │ │ - SQLite + vec │ │ │ │ │ │ │ │ - 向量索引 │ │ │ │ │ │ │ └──────────────────┘ └──────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────────┘ ``` --- ## OpenViking Rust CLI 能力分析 ### CLI 已实现功能 (直接使用) | 命令 | 功能 | 状态 | |------|------|------| | `ov add ` | 添加资源到索引 | ✅ 可用 | | `ov find ` | 语义搜索 | ✅ 可用 | | `ov grep ` | 正则搜索 | ✅ 可用 | | `ov ls ` | 列出资源 | ✅ 可用 | | `ov tree ` | 目录树 | ✅ 可用 | | `ov chat` | 交互式对话 | ✅ 可用 | ### CLI 缺失功能 (需要自建) | 功能 | 说明 | 优先级 | |------|------|--------| | Session Extraction | 对话后 LLM 提取记忆 | 高 | | L0/L1/L2 分层加载 | Token 优化上下文构建 | 高 | | Embedding 批量生成 | doubao API 封装 | 中 | | 记忆老化/清理 | 低重要性记忆自动清理 | 低 | | 多 Agent 隔离 | agent_id 维度隔离 | 中 | --- ## Implementation Phases ### Phase 1: Sidecar 集成 (Week 1) **Goal**: 将 OpenViking CLI 集成为 Tauri sidecar #### Steps 1. **下载并嵌入 CLI** ```bash # 将 ov 二进制放入 src-tauri/binaries/ # Windows: ov-x86_64-pc-windows-msvc.exe # macOS: ov-x86_64-apple-darwin # Linux: ov-x86_64-unknown-linux-gnu ``` 2. **配置 tauri.conf.json** ```json { "tauri": { "bundle": { "externalBin": ["binaries/ov"] } } } ``` 3. **创建 Tauri Commands** ```rust // src-tauri/src/viking_commands.rs #[tauri::command] pub async fn viking_add(uri: String, content: String) -> Result { let sidecar = Command::new_sidecar("ov") .map_err(|e| e.to_string())?; let output = sidecar .args(["add", &uri]) .output() .await .map_err(|e| e.to_string())?; Ok(String::from_utf8_lossy(&output.stdout).to_string()) } #[tauri::command] pub async fn viking_find(query: String, limit: usize) -> Result, String> { let sidecar = Command::new_sidecar("ov") .map_err(|e| e.to_string())?; let output = sidecar .args(["find", "--json", &query, "--limit", &limit.to_string()]) .output() .await .map_err(|e| e.to_string())?; serde_json::from_slice(&output.stdout) .map_err(|e| e.to_string()) } ``` #### Files to Create | File | Purpose | |------|---------| | `src-tauri/src/viking_commands.rs` | Tauri 命令封装 | | `src-tauri/binaries/ov-*` | Sidecar 二进制 | #### Files to Modify | File | Changes | |------|---------| | `src-tauri/src/lib.rs` | 注册 viking 模块 | | `src-tauri/tauri.conf.json` | 添加 externalBin 配置 | | `desktop/src/lib/viking-adapter.ts` | 添加 `invoke()` 调用 | ### Phase 2: TypeScript 适配层 (Week 1-2) **Goal**: 更新 VikingAdapter 使用 Tauri 命令 #### Key Changes ```typescript // desktop/src/lib/viking-adapter.ts import { invoke } from '@tauri-apps/api/tauri'; export class VikingAdapter { private mode: 'sidecar' | 'remote' = 'sidecar'; async addResource(uri: string, content: string): Promise { if (this.mode === 'sidecar') { await invoke('viking_add', { uri, content }); } else { // Remote fallback await this.httpClient.addResource(uri, content); } } async find(query: string, options?: FindOptions): Promise { if (this.mode === 'sidecar') { return await invoke('viking_find', { query, limit: options?.limit || 10 }); } else { return this.httpClient.find(query, options); } } } ``` ### Phase 3: 补充模块开发 (Week 2-3) **Goal**: 实现 CLI 缺失的功能 #### 3.1 Session Extractor ```rust // src-tauri/src/memory/extractor.rs pub struct SessionExtractor { llm_client: LlmClient, } impl SessionExtractor { /// Extract memories from conversation pub async fn extract( &self, messages: Vec, agent_id: &str, ) -> Result, Error> { let prompt = self.build_extraction_prompt(&messages); let response = self.llm_client.complete(&prompt).await?; self.parse_extraction(&response, agent_id) } } ``` #### 3.2 Context Builder (L0/L1/L2) ```rust // src-tauri/src/memory/context_builder.rs pub struct ContextBuilder { viking: VikingSidecar, } impl ContextBuilder { /// Build layered context for token efficiency pub async fn build_context( &self, query: &str, agent_id: &str, max_tokens: usize, ) -> Result { // L0: Quick scan - top 50 by similarity let l0_results = self.viking.find(query, 50).await?; // L1: Load overview for top 10 let l1_items = self.load_overviews(&l0_results[..10]).await?; // L2: Full content for top 3 let l2_items = self.load_full_content(&l0_results[..3]).await?; Ok(EnhancedContext { l1_items, l2_items }) } } ``` #### Files to Create | File | Purpose | |------|---------| | `src-tauri/src/memory/mod.rs` | 模块入口 | | `src-tauri/src/memory/extractor.rs` | LLM 记忆提取 | | `src-tauri/src/memory/context_builder.rs` | L0/L1/L2 分层加载 | | `src-tauri/src/llm/client.rs` | doubao API 客户端 | ### Phase 4: UI 集成 (Week 3-4) **Goal**: 完善记忆面板 UI #### Files to Modify | File | Changes | |------|---------| | `desktop/src/components/MemoryPanel.tsx` | 集成 sidecar 模式 | | `desktop/src/components/RetrievalTrace.tsx` | 显示 L0/L1/L2 检索轨迹 | | `desktop/src/store/chatStore.ts` | 使用新的 VikingAdapter | --- ## Critical Files ### Existing Files (Reuse) | File | Path | Purpose | |------|------|---------| | VikingAdapter | `desktop/src/lib/viking-adapter.ts` | 保持兼容,添加 sidecar 模式 | | VikingHttpClient | `desktop/src/lib/viking-client.ts` | 远程模式时使用 | | AgentMemory | `desktop/src/lib/agent-memory.ts` | 现有记忆接口 | | MemoryPanel | `desktop/src/components/MemoryPanel.tsx` | 现有 UI | ### New Files (Create) | File | Path | Purpose | |------|------|---------| | VikingCommands | `src-tauri/src/viking_commands.rs` | Sidecar 命令封装 | | SessionExtractor | `src-tauri/src/memory/extractor.rs` | LLM 提取 (CLI 缺失) | | ContextBuilder | `src-tauri/src/memory/context_builder.rs` | 分层加载 (CLI 缺失) | | LlmClient | `src-tauri/src/llm/client.rs` | doubao API 封装 | --- ## Dependencies to Add ```toml # src-tauri/Cargo.toml [dependencies] tauri = { version = "2", features = ["process-command-api"] } tokio = { version = "1", features = ["full"] } reqwest = { version = "0.11" } # For LLM API calls serde = { version = "1", features = ["derive"] } serde_json = "1" ``` --- ## Verification Plan ### Unit Tests ```bash # Run Rust tests cargo test --manifest-path=src-tauri/Cargo.toml # Run TypeScript tests pnpm vitest run tests/desktop/memory*.test.ts ``` ### Integration Tests 1. **Sidecar 启动**: CLI 能正确作为 sidecar 运行 2. **Memory Save/Load**: 通过 Tauri 命令保存和检索记忆 3. **Vector Search**: 语义搜索返回相关结果 4. **Session Extraction**: 对话结束后正确提取记忆 5. **Context Building**: L0/L1/L2 分层加载正常工作 6. **UI Integration**: MemoryPanel 正确显示数据 ### Manual Testing 1. 启动应用,验证 CLI sidecar 自动启动 2. 发送消息,检查记忆是否保存 3. 发送新消息,验证 Agent 能回忆之前的信息 4. 测试记忆搜索功能 5. 验证无 Python 依赖 --- ## Migration Path ### From Current State ``` Current: Target: viking-client.ts ─────────────► Tauri Command (HTTP to Python server) (Sidecar wrapper) │ ▼ viking-adapter.ts ──────────────► Dual mode: sidecar + remote fallback ``` ### Data Compatibility - OpenViking CLI 使用 `~/.viking/` 目录存储数据 - 与 Python Server 版本数据格式兼容 - 可无缝迁移现有数据 --- ## Success Criteria - [ ] OpenViking CLI 作为 sidecar 正确运行 - [ ] Tauri 命令可调用 CLI 功能 - [ ] 记忆保存和检索功能正常 - [ ] 语义搜索返回相关结果 - [ ] LLM 记忆提取正常工作 (自建模块) - [ ] L0/L1/L2 分层加载正常工作 (自建模块) - [ ] MemoryPanel UI 正确显示 - [ ] 所有测试通过 - [ ] **无需 Python 依赖** --- ## Risks and Mitigations | Risk | Mitigation | |------|------------| | CLI 二进制兼容性 | 提供多平台预编译版本 | | CLI 功能不足 | 自建补充模块填补空白 | | Embedding API 限流 | 实现本地缓存 | | LLM 提取失败 | 保留规则提取作为 fallback | | Sidecar 启动失败 | 优雅降级到远程模式 | --- ## Timeline | Week | Phase | Deliverables | |------|-------|--------------| | 1 | Sidecar 集成 | CLI 嵌入 + Tauri 命令 + TypeScript 适配 | | 2 | 补充模块 | SessionExtractor + ContextBuilder | | 3 | LLM 集成 | doubao API 客户端 + 提取逻辑 | | 4 | UI 集成 | MemoryPanel + RetrievalTrace | | 5 | 测试完善 | 集成测试 + 文档 | --- ## 开发策略总结 1. **Phase 1**: 直接集成 OpenViking Rust CLI 作为 sidecar 2. **Phase 2**: 检测 CLI 功能覆盖度 3. **Phase 3**: 自行开发 CLI 缺失的功能 (Session Extraction, Context Builder) 4. **Phase 4**: UI 完善和测试 **核心原则**: 最大化利用现有成熟工具,最小化自建代码量,只在必要时补充缺失功能。