docs: audit reports + feature docs + skills + admin-v2 + config sync
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
Update audit tracker, roadmap, architecture docs, add admin-v2 Roles page + Billing tests, sync CLAUDE.md, Cargo.toml, docker-compose.yml, add deep-research / frontend-design / chart-visualization skills Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,275 @@
|
||||
# ZCLAW 上线前双轨并行改进设计
|
||||
|
||||
> **日期**: 2026-03-31
|
||||
> **阶段**: 上线前准备
|
||||
> **策略**: 功能+质量并行
|
||||
> **基于**: 三维系统分析 + 代码审查验证(已排除已完成项)
|
||||
|
||||
---
|
||||
|
||||
## 1. 背景与动机
|
||||
|
||||
ZCLAW 系统经过多轮迭代(Sprint 1-8 完成,10 个 Batch 完成),核心功能完成度达 87-95%。系统当前处于 **B+ 健康度**,存在 41 个已知问题(0 Critical, 6 High, 20 Medium, 15 Low)。
|
||||
|
||||
**已完成的关键项(经代码审查确认)**:
|
||||
- Token 刷新竞态: 已修复 (request.ts `onTokenRefreshFailed`)
|
||||
- Dockerfile: 已存在 (多阶段构建, rust:1.85-bookworm)
|
||||
- saas-env.example: 已存在
|
||||
- 生产部署指南: 已存在 (docs/deployment/saas-production.md, 430 行)
|
||||
- /api/health: 已在公开路由组
|
||||
- Agent Template 详情: 已展示所有扩展字段
|
||||
- AgentOnboardingWizard: 已使用 React hook 模式
|
||||
|
||||
**实际剩余上线阻塞项**:
|
||||
- 反向代理场景下限流失效(ConnectInfo 返回代理 IP 而非客户端 IP)
|
||||
- 前端测试覆盖率 0-15%,回归风险极高
|
||||
- 调度任务执行器为 STUB,违反"不展示假数据"原则
|
||||
- Desktop 废弃代码 + TS 遗留错误
|
||||
|
||||
**目标**: 在上线前完成所有阻塞项,同时推进 1-2 个高价值功能以提升产品竞争力。
|
||||
|
||||
---
|
||||
|
||||
## 2. 系统分析摘要
|
||||
|
||||
### 2.1 规模
|
||||
|
||||
| 组件 | 规模 |
|
||||
|------|------|
|
||||
| Rust Crates | 10 个 (types/memory/runtime/kernel/skills/hands/protocols/pipeline/growth/saas) |
|
||||
| SaaS API | 93 个端点 (5 公开 + 88 受保护) |
|
||||
| Tauri Commands | 106 个 |
|
||||
| Zustand Store | 14 个 (desktop) + 2 个 (admin-v2) |
|
||||
| 技能 | 71 个 SKILL.md |
|
||||
| Hands | 9 个已启用 (7 完整 + 2 demo) + 2 个已禁用 (Predictor/Lead) |
|
||||
| 数据库 | PostgreSQL, Schema v11, 25+ 表 |
|
||||
|
||||
### 2.2 实际系统性挑战(经验证)
|
||||
|
||||
1. **测试覆盖率缺口** — Admin V2 0%, Desktop ~15%, Rust 34%
|
||||
2. **类型安全裂缝** — Desktop 50+ `as any`, Mixin 模式无编译时保障
|
||||
3. **反向代理限流失效** — ConnectInfo 返回代理 IP,需 trusted_proxies 配置
|
||||
4. **功能完整度断层** — 调度任务 STUB, Quiz 占位符, 5 个 404 API
|
||||
5. **废弃代码残留** — gatewayStore.ts 废弃未清理, TS 遗留错误
|
||||
|
||||
### 2.3 关键洞察
|
||||
|
||||
- **技能系统是隐藏的宝石**: 71 个 SKILL.md 是差异化资产,应优先展示
|
||||
- **测试是最大风险**: 0-15% 的覆盖率意味着任何改动都可能回归
|
||||
- **部署基本就绪**: Dockerfile + 部署指南已存在,需验证而非创建
|
||||
- **STUB 是隐性技术债**: 违反 CLAUDE.md "不允许展示假数据" 原则
|
||||
|
||||
---
|
||||
|
||||
## 3. 设计:双轨并行方案
|
||||
|
||||
### 3.1 轨道 1 — 质量轨道 (Quality Track)
|
||||
|
||||
> 必须在上线前全部完成,阻塞发布。
|
||||
|
||||
#### Q1. 安全加固 (~2h)
|
||||
|
||||
**Q1.1 反向代理场景限流修复** (唯一实际安全项)
|
||||
- **文件**: `crates/zclaw-saas/src/middleware.rs:133-140`
|
||||
- **问题**: 代码正确地不信任 X-Forwarded-For(使用 ConnectInfo),但当部署在 Nginx 反向代理后,ConnectInfo 返回的是代理 IP(如 127.0.0.1),导致所有客户端共享同一 IP 限流桶,限流失效
|
||||
- **方案**:
|
||||
- 在 `saas-config.toml` 添加 `trusted_proxies = ["127.0.0.1", "::1"]` 配置
|
||||
- 当请求来自可信代理 IP 时,解析 `X-Forwarded-For` 头获取真实客户端 IP
|
||||
- 非可信来源继续使用 ConnectInfo(保持当前安全行为)
|
||||
- **验证**: 单元测试验证:可信代理 + 有效头 → 使用头中 IP;可信代理 + 无头 → 使用代理 IP;非可信来源 → 忽略头
|
||||
|
||||
**Q1.2 adminRouting 解析验证**
|
||||
- **文件**: `desktop/src/store/connectionStore.ts:358-372`
|
||||
- **问题**: 从 localStorage 读取 `adminRouting` 时无类型校验,第 376 行 catch 块静默吞错误
|
||||
- **方案**: 添加 Zod schema 验证解析结果,无效值 fallback 到默认模式
|
||||
- **验证**: 测试各种非法输入(null, undefined, 畸形 JSON)
|
||||
|
||||
#### Q2. 部署验证 (~2h)
|
||||
|
||||
> 部署基础设施已存在,此任务为验证和完善。
|
||||
|
||||
**验证项**:
|
||||
- `docker compose up` 端到端测试(PostgreSQL + SaaS 启动 → 健康检查通过)
|
||||
- Nginx 配置引用的 `deploy/nginx.conf` 是否存在
|
||||
- saas-env.example 环境变量是否与实际代码一致
|
||||
- 生产部署指南步骤是否可执行
|
||||
|
||||
**完善项**(如验证中发现缺失):
|
||||
- 补充 deploy/nginx.conf(如果不存在)
|
||||
- 更新 saas-env.example 中的遗漏项
|
||||
|
||||
#### Q3. 测试基础 (~10h)
|
||||
|
||||
**Q3.1 Admin V2 测试基础设施** (~2h)
|
||||
- 安装: `vitest`, `@testing-library/react`, `@testing-library/jest-dom`, `msw`
|
||||
- 配置: `admin-v2/vitest.config.ts` + setup 文件
|
||||
- MSW handlers 模拟 SaaS API 响应
|
||||
|
||||
**Q3.2 Admin V2 核心测试** (~4h)
|
||||
- `request.ts`: 已有 Token 刷新修复的回归测试、网络错误包装、401 自动重定向
|
||||
- `authStore`: 登录/登出/Token 刷新状态管理
|
||||
- 核心页面冒烟测试: Accounts, Providers, AgentTemplates 渲染验证
|
||||
|
||||
**Q3.3 Desktop 关键 Store 测试** (~4h)
|
||||
- `connectionStore`: 三种连接模式切换逻辑
|
||||
- `chatStore`: 消息发送/接收/流式响应
|
||||
- `saasStore`: 认证流程 + 心跳降级
|
||||
|
||||
#### Q4. 功能补全 (~6h)
|
||||
|
||||
**Q4.1 调度任务执行器真实实现** (~4h)
|
||||
- **文件**: `crates/zclaw-saas/src/scheduler.rs:132,166`
|
||||
- **方案**: 在 scheduler loop 中实际触发任务执行:
|
||||
- Agent/Hand/Workflow 类型任务通过内部 HTTP 调用触发(而非外部 API)
|
||||
- 记录执行结果到 scheduled_tasks 表
|
||||
- 支持失败重试 (指数退避, 最多 3 次)
|
||||
- **验证**: 集成测试验证任务创建 → 执行 → 结果记录全流程
|
||||
|
||||
**Q4.2 Admin V2 表格搜索/筛选** (~2h)
|
||||
- **方案**: 使用 ProTable 内置搜索能力,为 Accounts/Models/Providers/ApiKeys/Prompts 表格添加搜索栏
|
||||
- **范围**: 至少覆盖 Accounts 和 Models 两个最常用的表格
|
||||
|
||||
#### Q5. 代码清理 (~4h)
|
||||
|
||||
**Q5.1 废弃 gatewayStore.ts 清理** (~2h)
|
||||
- **文件**: `desktop/src/store/gatewayStore.ts` (358 行)
|
||||
- **方案**:
|
||||
1. 审查所有 gatewayStore 导入(agentStore, configStore, handStore, workflowStore, sessionStore 等可能引用)
|
||||
2. 验证 facade 模式是否被组件间接使用
|
||||
3. 移除整个文件,更新所有引用改用 connectionStore
|
||||
4. 运行全量测试验证无回归
|
||||
- **风险**: facade 模式可能被组件间接依赖,需要充分验证
|
||||
|
||||
**Q5.2 TS 遗留错误修复** (~1h)
|
||||
- **文件**: `desktop/src/lib/gateway-api.ts`, `desktop/src/lib/kernel-hands.ts`
|
||||
- **方案**: 修复之前重构引入的类型错误
|
||||
|
||||
**Q5.3 未使用依赖清理** (~1h)
|
||||
- **文件**: `admin-v2/package.json`
|
||||
- **方案**: 移除 `@ant-design/charts` 未使用依赖
|
||||
|
||||
**质量轨道实际总计**: ~24h
|
||||
|
||||
### 3.2 轨道 2 — 功能轨道 (Feature Track)
|
||||
|
||||
> 不阻塞上线,但提升产品竞争力。可与质量轨道并行。
|
||||
|
||||
#### F1. 技能市场 UI (~16h)
|
||||
|
||||
**目标**: 展示 71 个 SKILL.md 技能,让用户浏览和发现可用能力
|
||||
|
||||
**组件设计**:
|
||||
- `SkillMarket.tsx` — 主页面,网格布局展示技能卡片
|
||||
- `SkillCard.tsx` — 单个技能卡片 (名称、描述、标签、激活状态)
|
||||
- `SkillDetail.tsx` — 技能详情弹窗 (完整 SKILL.md 渲染 + 使用示例)
|
||||
- `SkillSearch.tsx` — 搜索栏 + 分类筛选
|
||||
|
||||
**数据流**:
|
||||
- 从 Kernel IPC `skill_list` 命令获取技能完整列表
|
||||
- **搜索方案**: 客户端过滤(71 个技能规模小,前端过滤足够高效)
|
||||
- 名称/描述模糊匹配 (Fuse.js 或手写 filter)
|
||||
- 按分类标签筛选
|
||||
- 无需后端搜索命令
|
||||
- 激活/停用通过现有 `skill_activate` 命令
|
||||
|
||||
**分类方案**(基于 SKILL.md metadata):
|
||||
- 开发与工程 / 商业与营销 / 研究与内容 / 运营 / 专业技能
|
||||
|
||||
**依赖**: 需确认 `skill_list` 返回的数据结构包含分类信息
|
||||
|
||||
#### F2. 上下文压缩 + 记忆系统 (~12h)
|
||||
|
||||
**上下文压缩 Kernel 集成**:
|
||||
- 将 zclaw-growth 的 Compactor 接入 Kernel 中间件链
|
||||
- 触发条件: 上下文长度超过模型窗口的 80%
|
||||
- 压缩策略: 保留最近 N 条完整消息 + 早期消息生成摘要
|
||||
- **用户可见性**: 压缩后在聊天界面显示"上下文已压缩"系统消息
|
||||
- **存储**: 摘要作为系统消息存储,原始消息保留但标记为已压缩
|
||||
|
||||
**记忆系统升级**:
|
||||
- 向量化检索优化 (FTS5 → 混合 FTS5 + Embedding)
|
||||
- MemoryMiddleware 30s 防抖实现
|
||||
- 记忆提取后自动关联到 Agent Soul
|
||||
|
||||
#### F3. Quiz Hand 真实化 (~6h)
|
||||
|
||||
**当前问题**: Quiz Hand 使用占位符生成器,生成假数据
|
||||
|
||||
**方案**:
|
||||
- 替换为 LLM 驱动的真实题目生成
|
||||
- 使用 Pipeline 模板系统 (已有基础设施)
|
||||
- 支持多题型: 选择题、填空题、问答题
|
||||
- 难度自适应: 基于用户表现动态调整
|
||||
|
||||
**功能轨道总计**: ~34h
|
||||
|
||||
---
|
||||
|
||||
## 4. 时间线
|
||||
|
||||
```
|
||||
Week 1-2: 质量轨道冲刺 (Q1-Q3) — 阻塞上线
|
||||
├── Q1: 安全加固 (2h)
|
||||
├── Q2: 部署验证 (2h)
|
||||
└── Q3: 测试基础 (10h)
|
||||
┃ 可并行 ──→ F1: 技能市场 UI (16h)
|
||||
|
||||
Week 3-4: 质量收尾 + 功能启动
|
||||
├── Q4: 功能补全 (6h)
|
||||
├── Q5: 代码清理 (4h)
|
||||
├── F2: 上下文压缩 + 记忆 (12h)
|
||||
└── F3: Quiz Hand 真实化 (6h)
|
||||
|
||||
Week 5+: 功能深化
|
||||
├── Semantic Router 成熟化 (8h)
|
||||
├── Pipeline 编辑器增强 (12h)
|
||||
└── i18n 框架 (12h)
|
||||
|
||||
=== 上线 Gate ===
|
||||
质量轨道 Q1-Q5 全部完成 = 可发布
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 成功标准
|
||||
|
||||
### 上线 Gate (必须全部通过)
|
||||
|
||||
- [ ] `docker compose up` 可成功启动 SaaS 后端(验证现有基础设施)
|
||||
- [ ] 反向代理场景下限流正确区分不同客户端 IP
|
||||
- [ ] Admin V2 测试套件 >10 个测试且全部通过
|
||||
- [ ] Desktop 关键 Store 测试 >5 个且全部通过
|
||||
- [ ] 调度任务可创建并可执行(非 STUB)
|
||||
- [ ] `tsc --noEmit` 和 `cargo check` 零错误
|
||||
- [ ] gatewayStore.ts 已完全移除
|
||||
|
||||
### 质量指标 (目标)
|
||||
|
||||
- Admin V2 测试覆盖率 >30%(后续迭代提升至 80%)
|
||||
- Desktop 关键 Store 测试覆盖率 >40%
|
||||
- TypeScript 严格模式 `any` 数量 Desktop <10 处
|
||||
- Rust 测试覆盖率 >40%
|
||||
|
||||
---
|
||||
|
||||
## 6. 风险与缓解
|
||||
|
||||
| 风险 | 概率 | 影响 | 缓解 |
|
||||
|------|------|------|------|
|
||||
| 调度任务执行器集成复杂度超预期 | 中 | 高 | 先实现最简单的 HTTP 触发,后续迭代增加重试 |
|
||||
| 测试编写发现更多 bug | 高 | 中 | 这是好事,发现的 bug 加入修复列表 |
|
||||
| gatewayStore 移除导致间接依赖断裂 | 中 | 高 | 先审查所有导入,添加弃用警告阶段 |
|
||||
| 技能市场 skill_list 返回数据不包含分类 | 中 | 低 | 前端基于 SKILL.md 路径或关键词自动分类 |
|
||||
|
||||
---
|
||||
|
||||
## 7. 不包含的内容
|
||||
|
||||
以下功能明确不在此设计范围内:
|
||||
|
||||
- Predictor/Lead Hand 实现(需产品定义)
|
||||
- 插件系统(需安全沙盒设计)
|
||||
- 多用户协作(需架构设计)
|
||||
- Token Pool 计费系统(需商业化设计)
|
||||
- 实时配置推送 WebSocket(当前拉取模式足够)
|
||||
- Redis 持久化限流(单实例部署暂不需要)
|
||||
@@ -0,0 +1,429 @@
|
||||
# DeerFlow 系统架构分析文档 — 增强版评估报告
|
||||
|
||||
> **版本**: v2.0(多专家组头脑风暴增强版)
|
||||
> **日期**: 2026-04-01
|
||||
> **评估对象**: `G:\deerflow\.trae\documents\deerflow-system-architecture-analysis.md`(1065 行)
|
||||
> **专家组**: 后端架构师、前端架构师、AI Agent 专家、安全工程师、DevOps 工程师
|
||||
|
||||
---
|
||||
|
||||
## Context
|
||||
|
||||
**目的**: 评估 DeerFlow 系统架构分析文档的深度和完整性,通过源代码交叉验证识别信息缺失,并为 ZCLAW 项目复刻 DeerFlow 优秀设计提供准确的技术参考和迁移策略。
|
||||
|
||||
**结论**: 原文档整体框架完整,覆盖了系统概述、分层架构、后端/前端详解、数据流、IM 集成、配置和部署等核心章节。但通过 5 位专家与源代码交叉验证,发现 **8 项关键缺失(CRITICAL)、12 项显著不足(SIGNIFICANT)、10 项安全发现、18 项细节差距(MINOR)**。文档对中间件系统、提示词工程、安全架构的描述存在事实性错误或严重遗漏。
|
||||
|
||||
---
|
||||
|
||||
## Part 1: 原报告事实准确性修正
|
||||
|
||||
### 1.1 中间件数量与排序错误
|
||||
|
||||
**原报告声称**: 14 个中间件,按固定编号 0-13 排列。
|
||||
|
||||
**实际代码** (`agent.py:208-270` + `tool_error_handling_middleware.py:68-131`):
|
||||
|
||||
中间件通过 `build_lead_runtime_middlewares()` + `_build_middlewares()` **动态条件组合**,分为两层:
|
||||
|
||||
**层 1: 运行时基础中间件** (`_build_runtime_middlewares`):
|
||||
| # | 中间件 | 条件 |
|
||||
|---|--------|------|
|
||||
| 1 | ThreadDataMiddleware | 始终 |
|
||||
| 2 | UploadsMiddleware | 仅 lead agent |
|
||||
| 3 | SandboxMiddleware | 始终 |
|
||||
| 4 | DanglingToolCallMiddleware | 仅 lead agent |
|
||||
| 5 | GuardrailMiddleware | 需要 guardrails.enabled + provider |
|
||||
| 6 | SandboxAuditMiddleware | 条件性 |
|
||||
| 7 | ToolErrorHandlingMiddleware | 始终 |
|
||||
|
||||
**层 2: Lead Agent 专用中间件** (`_build_middlewares`):
|
||||
| # | 中间件 | 条件 |
|
||||
|---|--------|------|
|
||||
| 8 | SummarizationMiddleware | 需要 summarization.enabled |
|
||||
| 9 | TodoMiddleware | 需要 is_plan_mode=true |
|
||||
| 10 | TokenUsageMiddleware | 需要 token_usage.enabled |
|
||||
| 11 | TitleMiddleware | 始终 |
|
||||
| 12 | MemoryMiddleware | 始终 |
|
||||
| 13 | ViewImageMiddleware | 需要模型 supports_vision |
|
||||
| 14 | DeferredToolFilterMiddleware | 需要 tool_search.enabled |
|
||||
| 15 | SubagentLimitMiddleware | 需要 subagent_enabled |
|
||||
| 16 | LoopDetectionMiddleware | 始终 |
|
||||
| 17 | ClarificationMiddleware | 始终(**永远最后**) |
|
||||
|
||||
**原报告遗漏**: DanglingToolCallMiddleware (#4)、SandboxAuditMiddleware (#6)、TokenUsageMiddleware (#10)、DeferredToolFilterMiddleware (#14)
|
||||
|
||||
> **文件数 vs. 运行时实例数说明**: 磁盘上有 14 个中间件 `.py` 文件(不含 `__init__.py`),但运行时通过条件组合可产生最多 17 个实例。差异来源:`GuardrailMiddleware` 来自 `guardrails/middleware.py`(非 `middlewares/` 目录),`SummarizationMiddleware` 来自 `langchain` 外部包,`ToolErrorHandlingMiddleware` 自身就是构建器。
|
||||
|
||||
**关键设计发现**: 中间件链是**条件组合**而非固定列表。`_build_middlewares` 通过条件判断动态组装。
|
||||
|
||||
**源码参考**:
|
||||
- `G:\deerflow\backend\packages\harness\deerflow\agents\lead_agent\agent.py:208-270`
|
||||
- `G:\deerflow\backend\packages\harness\deerflow\agents\middlewares\tool_error_handling_middleware.py:68-131`
|
||||
- `G:\deerflow\backend\packages\harness\deerflow\agents\factory.py:299-372`(@Next/@Prev 声明式排序)
|
||||
|
||||
### 1.2 make_lead_agent 伪代码差异
|
||||
|
||||
原报告第 3.1.1 节伪代码省略了:
|
||||
- `reasoning_effort` 参数处理
|
||||
- `is_bootstrap` 引导代理路径(使用 `setup_agent` 工具 + 仅 `bootstrap` 技能)
|
||||
- `agent_name` → `load_agent_config()` 自定义代理配置加载
|
||||
- LangSmith trace metadata 注入
|
||||
- 3 级模型回退逻辑(request > agent config > global default)
|
||||
|
||||
**源码参考**: `G:\deerflow\backend\packages\harness\deerflow\agents\lead_agent\agent.py:273-348`
|
||||
|
||||
---
|
||||
|
||||
## Part 2: 关键缺失(CRITICAL — 缺少核心概念)
|
||||
|
||||
### C1. Harness/App 架构分层与 CI 强制边界
|
||||
|
||||
**缺失内容**: DeerFlow 后端分为两个严格隔离的层:
|
||||
- `packages/harness/deerflow/` — 可发布的 Python 包(`deerflow-harness`),包含代理框架核心
|
||||
- `app/` — 不可发布的应用层,包含 FastAPI Gateway 和 IM Channels
|
||||
|
||||
**关键设计**: 单向依赖规则 enforced by CI — `app` 可以导入 `deerflow`,但 `deerflow` 绝不能导入 `app`。由 `tests/test_harness_boundary.py` 在 CI 中验证。
|
||||
|
||||
**ZCLAW 相关性**: ZCLAW 的 crate 依赖链需要类似的 CI 边界强制机制。
|
||||
|
||||
### C2. DeerFlowClient 嵌入式模式(双模式运行时)
|
||||
|
||||
**缺失内容**: `client.py`(931 行)提供完整进程内客户端,可在不启动 HTTP 服务的情况下使用所有 DeerFlow 功能。包含 77 个单元测试进行 Gateway 一致性验证。
|
||||
|
||||
**三种部署模式**: 完整服务器 | 嵌入式客户端 | CLI
|
||||
|
||||
**ZCLAW 相关性**: ZCLAW 的 Tauri 命令层 vs. SaaS HTTP API 双轨制可以直接借鉴 Gateway 一致性测试模式。应定义 `KernelClient` trait,实现 Tauri IPC 和 HTTP 两种后端。
|
||||
|
||||
**源码参考**: `G:\deerflow\backend\packages\harness\deerflow\client.py`
|
||||
|
||||
### C3. ACP Agent-to-Agent 通信协议
|
||||
|
||||
**缺失内容**: `invoke_acp_agent_tool.py`(9.5KB)实现跨进程 Agent 间通信。每个 ACP 代理获得独立工作空间 `/mnt/acp-workspace/`。
|
||||
|
||||
**ZCLAW 相关性**: ZCLAW 的 `zclaw-protocols` crate 已包含 A2A 支持,DeerFlow 的 ACP 提供具体沙箱隔离参考。
|
||||
|
||||
### C4. DanglingToolCallMiddleware 状态修复
|
||||
|
||||
**缺失内容**: 用户中断代理执行时,AIMessage 可能有 tool_calls 但缺少 ToolMessages。此中间件在第 4 位执行,注入合成错误 ToolMessage 修复状态。
|
||||
|
||||
**ZCLAW 相关性**: 任何允许用户中断代理执行的系统都需要此模式。
|
||||
|
||||
### C5. 反射系统(Reflection System)
|
||||
|
||||
**缺失内容**: `reflection/` 模块(`resolve_variable`、`resolve_class`)是动态加载基础,提供可操作的错误信息(如 "run `uv add langchain-google-genai`")。
|
||||
|
||||
### C6. StreamBridge 生产者/消费者背压机制(专家新增)
|
||||
|
||||
**缺失内容**: 核心的 Agent Worker 与 SSE 端点解耦机制:
|
||||
- 每次运行的独立队列 `asyncio.Queue(maxsize=256)`
|
||||
- 单调递增事件 ID 用于 SSE 重连
|
||||
- 背压处理:`wait_for(timeout=30s)` 超时丢弃
|
||||
- 15 秒心跳哨兵
|
||||
- 60 秒延迟清理
|
||||
|
||||
**ZCLAW 相关性**: ZCLAW 的 LLM 中继 SSE 流需要同样的背压机制。映射为 `tokio::sync::broadcast` + `tokio::time::timeout`。
|
||||
|
||||
**源码参考**: `G:\deerflow\backend\packages\harness\deerflow\runtime\stream_bridge\memory.py`
|
||||
|
||||
### C7. 提示词模板动态组装系统(专家新增)
|
||||
|
||||
**缺失内容**: `agents/lead_agent/prompt.py`(528 行)实现了条件段落组装:
|
||||
- 10+ 个条件段落:`agent_name`、`soul`、`memory_context`、`thinking_style`、`clarification_system`、`skills_section`、`deferred_tools_section`、`subagent_section`、`acp_section`、`citations`、`critical_reminders`
|
||||
- 每个段落根据运行时配置条件性包含
|
||||
- 这是整个系统最复杂的单一文件之一
|
||||
|
||||
**ZCLAW 相关性**: **P0 优先级迁移项**。ZCLAW 目前使用扁平 `system_prompt` 字符串,这是最大的架构差距。
|
||||
|
||||
**源码参考**: `G:\deerflow\backend\packages\harness\deerflow\agents\lead_agent\prompt.py`
|
||||
|
||||
### C8. 中间件-提示词协调契约(专家新增)
|
||||
|
||||
**缺失内容**: DeerFlow 的中间件和提示词段落是**统一设计**的协调系统:
|
||||
- `SubagentLimitMiddleware` 强制执行提示词段落描述的并发限制
|
||||
- `ClarificationMiddleware` 拦截提示词段落定义的 `ask_clarification` 工具
|
||||
- `DeferredToolFilterMiddleware` 隐藏提示词段落仅列出名称的工具 schema
|
||||
- `LoopDetectionMiddleware` 作为幻觉信号检测器
|
||||
|
||||
**ZCLAW 相关性**: 迁移时必须保持中间件-提示词的协调关系,不能独立迁移。
|
||||
|
||||
---
|
||||
|
||||
## Part 3: 显著不足(SIGNIFICANT — 覆盖不充分)
|
||||
|
||||
### S1. 中间件链排序原理与位置约束
|
||||
|
||||
中间件排序编码了关键设计决策:
|
||||
- ClarificationMiddleware **必须最后**:通过 `Command(goto=END)` 中断执行
|
||||
- GuardrailMiddleware 在 SummarizationMiddleware **之前**:安全检查在上下文压缩前执行
|
||||
- DanglingToolCallMiddleware 修复在 GuardrailMiddleware **之前**:确保安全检查时状态一致
|
||||
- ThreadDataMiddleware 在 SandboxMiddleware **之前**:确保 thread_id 可用
|
||||
|
||||
### S2. Memory 管道实现细节
|
||||
|
||||
缺少关键实现细节:
|
||||
- **原子文件 I/O**: temp 文件 + rename
|
||||
- **空白字符规范化去重**: trim 后比较避免格式差异
|
||||
- **注入格式**: `<memory>` XML 标签 + top 15 facts + 上下文摘要
|
||||
- **队列去重**: 同一线程更新替换策略
|
||||
- **批量延迟**: 0.5s 间隔处理
|
||||
|
||||
### S3. IM Channel 架构细节
|
||||
|
||||
缺少:
|
||||
- **MessageBus**: 异步 pub/sub 架构
|
||||
- **复合键映射**: `channel_name:chat_id[:topic_id]` → `thread_id`
|
||||
- **流式差异**: 飞书 `runs.stream()` + 增量卡片 vs. Slack/Telegram `runs.wait()`
|
||||
|
||||
### S4. 前端组件架构深度
|
||||
|
||||
缺少:
|
||||
- **ai-elements 组件库**: 28 文件的 AI 交互原语层(Context-Provider 模式)
|
||||
- **Streamdown**: 自定义流式 Markdown 渲染器,`Intl.Segmenter` 中文逐词动画
|
||||
- **Landing Page**: WebGL (OGL) + GSAP
|
||||
- **Better Auth + i18n + Mock 模式**
|
||||
|
||||
### S5. 安全架构深度(仅表面覆盖)
|
||||
|
||||
**原报告遗漏**,详见 Part 4 安全审计发现。
|
||||
|
||||
### S6. 自定义代理系统
|
||||
|
||||
文件系统存储(`config.yaml` + `SOUL.md` + 可选 `memory.json`),每个代理独立配置模型、工具组、个性。Bootstrap Agent 路径使用最小工具集。
|
||||
|
||||
### S7. Gateway API 路由完整性
|
||||
|
||||
遗漏路由:threads CRUD、agents CRUD、suggestions、thread_runs、runs、health。
|
||||
|
||||
### S8. 配置系统深度
|
||||
|
||||
20+ Pydantic 子配置模块、版本检查、4 级路径解析优先级、`config-upgrade.sh` 自动迁移。
|
||||
|
||||
### S9. Checkpointer 抽象持久化层(专家新增)
|
||||
|
||||
- 抽象 `Checkpointer` 协议 + memory/sqlite/postgres 策略
|
||||
- 异步上下文管理器生命周期
|
||||
- 回滚支持:每次运行前捕获 `pre_run_checkpoint_id`
|
||||
- `AsyncExitStack` 生命周期管理
|
||||
|
||||
**源码参考**: `G:\deerflow\backend\packages\harness\deerflow\agents\checkpointer\async_provider.py`
|
||||
|
||||
### S10. 前端 3 阶段乐观消息合并管道(专家新增)
|
||||
|
||||
`useThreadStream` 实现三阶段乐观渲染:
|
||||
1. **即时本地回声**: 创建合成消息 `opt-human-${Date.now()}`
|
||||
2. **服务器确认**: useEffect 监听真实消息到达,清除乐观消息
|
||||
3. **文件上传状态转换**: 乐观消息中的 `status: "uploading"` → `status: "uploaded"`
|
||||
|
||||
**源码参考**: `G:\deerflow\frontend\src\core\threads\hooks.ts:186-410`
|
||||
|
||||
### S11. 子代理提示词隔离与执行模型(专家新增)
|
||||
|
||||
- 子代理获得**全新 ThreadState**,仅含单个 `HumanMessage(task)`
|
||||
- 简化中间件链(无 uploads、无 dangling-tool patch、无嵌套子代理)
|
||||
- `disallowed_tools` 默认 `["task"]` 防止递归生成
|
||||
- `thinking_enabled=False`、`timeout_seconds=900`、`max_turns=50`
|
||||
- 独立线程池(`_scheduler_pool` + `_execution_pool` 各 3 worker)
|
||||
- Trace ID 链接父子代理日志
|
||||
|
||||
**源码参考**: `G:\deerflow\backend\packages\harness\deerflow\subagents\executor.py:71-75, 391-453`
|
||||
|
||||
### S12. CI/CD 与部署管道(DevOps 专家新增)
|
||||
|
||||
- GitHub Actions: `backend-unit-tests.yml` + `lint-check.yml`(并发组控制)
|
||||
- **无**集成/E2E 测试阶段
|
||||
- **无**安全扫描(pip audit、npm audit、SAST)
|
||||
- **无**自动化镜像标记或容器注册表推送
|
||||
- 前端多阶段构建 vs. 后端单阶段构建(后者包含完整 langgraph 依赖树)
|
||||
- DooD 模式安全隐患(Docker socket 挂载)
|
||||
|
||||
---
|
||||
|
||||
## Part 4: 安全审计发现(安全工程师新增)
|
||||
|
||||
### 关键洞察: "Toxic Output Loop"
|
||||
|
||||
DeerFlow 将所有安全投入放在**预执行检查**(GuardrailMiddleware #5),但**零后执行输出消毒**。工具输出(web 搜索结果、文件内容、子代理响应)未经检查直接流入 LLM 上下文,构成最大的提示注入攻击面。
|
||||
|
||||
### 安全发现清单
|
||||
|
||||
| ID | 级别 | 发现 | 影响 | 源码 |
|
||||
|----|------|------|------|------|
|
||||
| S-01 | **CRITICAL** | LocalSandbox 使用 `shell=True` 执行任意命令 | 主机完全暴露 | `local_sandbox.py:217` |
|
||||
| S-02 | **HIGH** | Docker 容器禁用 seccomp | 容器逃逸 = 主机文件系统访问 | `local_backend.py:230` |
|
||||
| S-03 | **HIGH** | `resolve_class` 从 config 加载任意 Python 模块 | 配置被篡改 = 代码执行 | `resolvers.py` |
|
||||
| S-04 | **CRITICAL** | MCP 配置端点零认证 | 任意命令注入 | `routers/mcp.py:98` |
|
||||
| S-05 | **HIGH** | ACP `auto_approve_permissions` 绕过安全检查 | 禁用人工审批 | `acp_config.py:19` |
|
||||
| S-06 | **MEDIUM** | Agent 执行无速率限制 | LLM API 配额耗尽 | 全局 |
|
||||
| S-07 | **HIGH** | 所有线程端点零认证 | 任意用户读取他人数据 | 线程路由 |
|
||||
| S-08 | **HIGH** | 工具输出零消毒(提示注入向量) | Toxic Output Loop | 中间件链 |
|
||||
| S-09 | **MEDIUM** | MCP 配置端点泄露密钥 | API key 暴露 | `routers/mcp.py` GET |
|
||||
| S-10 | **MEDIUM** | 文件上传 TOCTOU 竞态条件 | 符号链接攻击 | 上传处理 |
|
||||
|
||||
### ZCLAW 安全迁移建议
|
||||
|
||||
1. **替换 LocalSandbox**: 使用 WASM 或受限进程,**绝不**使用 `shell=True`
|
||||
2. **双向 Guardrails**: 在工具执行前后都添加检查
|
||||
3. **OS Keychain**: API 密钥存储使用系统密钥链
|
||||
4. **Tauri IPC Allowlisting**: 比 HTTP 端点天然更受限
|
||||
5. **保留 ZCLAW 现有 SaaS 认证**: 比 DeerFlow 的零认证 API 层显著更强
|
||||
|
||||
---
|
||||
|
||||
## Part 5: 细节差距(MINOR)
|
||||
|
||||
| # | 差距 | 说明 | 来源 |
|
||||
|---|------|------|------|
|
||||
| M1 | Provisioner 服务 | 端口 8002,仅 K8s 模式启用 | DevOps |
|
||||
| M2 | ModelConfig 新字段 | `use_responses_api`、`output_version` | 后端 |
|
||||
| M3 | Skills 安装 API | `POST /api/skills/install` 从 ZIP 安装 | 后端 |
|
||||
| M4 | 文件上传转换 | markitdown 自动转换 PDF/PPT/Excel/Word | 后端 |
|
||||
| M5 | 上传全有或全无语义 | 任一文件失败则整批拒绝 | 后端 |
|
||||
| M6 | MCP OAuth 支持 | client_credentials、refresh_token + 自动刷新 | 后端 |
|
||||
| M7 | credential_loader.py | 7KB 凭据加载模块 | 后端 |
|
||||
| M8 | Stream Bridge | SSE 格式化 + 流桥接 | 后端 |
|
||||
| M9 | Run Manager | 运行生命周期管理 | 后端 |
|
||||
| M10 | 配置版本迁移 | `config-upgrade.sh` 文本替换+递归合并+自动备份 | DevOps |
|
||||
| M11 | 条件服务激活 | config.yaml 解析 → Docker profile | DevOps |
|
||||
| M12 | Nginx 流式头 | `proxy_buffering off` + 600s timeout | DevOps |
|
||||
| M13 | 3 层并发模型 | asyncio + threading + sync 单例 → Rust tokio 统一 | 后端 |
|
||||
| M14 | @Next/@Prev 声明式排序 | 冲突检测 + 不变式强制 | 后端 |
|
||||
| M15 | Ref 回调稳定化模式 | useStream hook 中的 listeners.current | 前端 |
|
||||
| M16 | 双模式 PromptInput | Provider 模式 vs 独立模式 | 前端 |
|
||||
| M17 | 语义消息分组 | human/assistant/clarification/present-files/subagent | 前端 |
|
||||
| M18 | Thinking Mode 控制 | per-model supports_thinking + reasoning_effort | AI Agent |
|
||||
|
||||
---
|
||||
|
||||
## Part 6: ZCLAW 迁移策略
|
||||
|
||||
### 6.1 迁移优先级矩阵
|
||||
|
||||
#### P0: 基础(必须首先实现)
|
||||
|
||||
| DeerFlow 模式 | ZCLAW 目标 | 实现路径 | 相对工作量 |
|
||||
|--------------|-----------|---------|-----------|
|
||||
| 提示词模板动态组装 ★最大差距 | `zclaw-kernel` | 候选方案: `tera`(模板引擎) / `minijinja`(Jinja2兼容) / 自定义 `StringBuilder` + 条件段落 + `tokenizers` crate 预算截断 | **L** (3-5天) |
|
||||
| 双模式运行时 (Client + Gateway) | `zclaw-kernel` | `KernelClient` trait + Tauri IPC impl + HTTP impl + 一致性测试 | **M** (2-3天) |
|
||||
| 工具错误恢复消息 | `zclaw-runtime` | `Result<ToolMessage, GraphBubbleUp>` + 引导性错误描述 | **S** (1天) |
|
||||
| 模型能力标志 | `zclaw-runtime` | `supports_thinking`、`supports_reasoning_effort`、`supports_vision` | **S** (0.5天) |
|
||||
|
||||
#### P1: 核心链
|
||||
|
||||
| DeerFlow 模式 | ZCLAW 目标 | 实现路径 | 相对工作量 |
|
||||
|--------------|-----------|---------|-----------|
|
||||
| StreamBridge 背压机制 | `zclaw-saas` relay | `tokio::sync::broadcast` + timeout + heartbeat | **M** (2天) |
|
||||
| DanglingToolCallMiddleware | `zclaw-kernel` | 中断后状态修复 | **S** (1天) |
|
||||
| MemoryMiddleware + 防抖 | `zclaw-memory` | `tokio::sync::mpsc` + debounce timer | **M** (2天) |
|
||||
| GuardrailMiddleware | `zclaw-kernel` | `trait GuardrailProvider` + `async fn evaluate()` | **M** (2天) |
|
||||
| ClarificationMiddleware | `zclaw-runtime` | 拦截 clarification 工具调用 | **S** (1天) |
|
||||
| 乐观消息合并管道 | `desktop/src` | `useTauriThreadStream` 3 阶段合并 | **L** (3天) |
|
||||
| Thinking Mode 控制 | `zclaw-runtime` | per-model 动态参数注入 | **S** (1天) |
|
||||
| **MCP 端点认证** | `zclaw-saas` | 复用现有 auth middleware 保护 MCP 配置端点 | **S** (0.5天) |
|
||||
| **工具输出消毒** | `zclaw-kernel` | post-execution guardrail + 输出长度/模式检查 | **M** (2天) |
|
||||
|
||||
#### P2: 高级特性
|
||||
|
||||
| DeerFlow 模式 | ZCLAW 目标 | 实现路径 |
|
||||
|--------------|-----------|---------|
|
||||
| Checkpointer trait | `zclaw-memory` | SQLite impl + PostgreSQL impl |
|
||||
| Subagent 执行器 | `zclaw-kernel` | `tokio::spawn` + timeout + 简化中间件链 |
|
||||
| ai-elements 组件库 | `desktop/src/components/ai/` | 直接移植 28 文件 + Context-Provider 模式 |
|
||||
| Context Engineering | `zclaw-kernel` | `tokenizers` crate + 校准估算 |
|
||||
| IM Channel MessageBus | `zclaw-protocols` | pub/sub + 复合键线程映射 |
|
||||
| 配置版本迁移 | `saas-config.toml` | `config_version` + 迁移脚本 |
|
||||
|
||||
#### P3: 打磨
|
||||
|
||||
| DeerFlow 模式 | ZCLAW 目标 | 实现路径 |
|
||||
|--------------|-----------|---------|
|
||||
| LangSmith 追踪 | `zclaw-runtime` | OpenTelemetry + Jaeger |
|
||||
| 配置热重载 | `zclaw-saas` | file mtime watch + 通知 |
|
||||
| ACP 协议 | `zclaw-protocols` | 跨进程 Agent 通信 |
|
||||
| 延迟工具注册表 | `zclaw-runtime` | 按需工具发现 |
|
||||
| 条件服务激活 | Docker Compose | config → profile 映射 |
|
||||
|
||||
### 6.2 Python async → Rust tokio 映射表
|
||||
|
||||
| Python | Rust 等价 | 说明 |
|
||||
|--------|----------|------|
|
||||
| `asyncio.Lock` | `tokio::sync::Mutex` | 仅关键区用 |
|
||||
| `asyncio.Queue` | `tokio::sync::broadcast` | SSE 多消费者 |
|
||||
| `asyncio.Task` | `JoinHandle` | 提供 `abort()` |
|
||||
| `asyncio.Event` | `CancellationToken` | `tokio_util` |
|
||||
| `threading.Lock` | 消除 | tokio 统一 async |
|
||||
| `ThreadPoolExecutor` | `tokio::spawn` | 无需线程池 |
|
||||
| `threading.Timer` | `tokio::time::sleep` | via `tokio::select!` |
|
||||
| `AsyncExitStack` | RAII `Drop` | Rust 自动 |
|
||||
|
||||
### 6.3 前端迁移关键决策
|
||||
|
||||
| 决策 | 推荐 | 理由 |
|
||||
|------|------|------|
|
||||
| ai-elements 组件库 | **直接移植** | 无后端依赖,纯 React + Radix + Tailwind |
|
||||
| 状态管理 | Zustand + TanStack Query | Zustand 替换 Context,Query 保持 server state |
|
||||
| 流式 Markdown | **保留 streamdown** | 框架无关,直接可用 |
|
||||
| 乐观消息合并 | **新建 useTauriThreadStream** | 替换 useStream,保持相同返回类型 |
|
||||
| Layout 嵌套 | **复制层级结构** | QueryClientProvider → Sidebar → SubtasksProvider → ArtifactsProvider → PromptInputProvider |
|
||||
|
||||
---
|
||||
|
||||
## Part 7: 完全缺失的章节
|
||||
|
||||
以下主题在原文档中**零覆盖**:
|
||||
|
||||
1. **测试策略** — 无测试组织、TDD 要求、边界测试、一致性测试
|
||||
2. **错误恢复哲学** — 无中间件链错误传播、工具错误恢复消息、状态修复
|
||||
3. **部署模式对比** — 无完整服务器 vs. 嵌入式客户端 vs. CLI 对比
|
||||
4. **性能特征** — 无背压、并发限制、StreamBridge 性能分析
|
||||
5. **Harness/App 边界强制** — 无架构分层和 CI 验证
|
||||
6. **提示词工程** — 无动态模板组装、条件段落、中间件协调
|
||||
7. **配置版本迁移** — 无版本化配置、自动升级、向后兼容
|
||||
8. **可观测性** — 无结构化日志、追踪、指标端点
|
||||
|
||||
---
|
||||
|
||||
## Part 8: 建议修正与补充
|
||||
|
||||
### 需要修正的内容
|
||||
|
||||
1. **第 3.2 节 中间件系统**: 修正为动态条件组合模式(非固定 14 个),补充 4 个遗漏中间件,解释排序约束原理
|
||||
2. **第 3.1.1 节 伪代码**: 补充 is_bootstrap 路径、agent_name 配置、3 级模型回退
|
||||
3. **第 3.3.2 节 工具加载**: 补充 ACP Agent 工具、tool_search 延迟加载
|
||||
|
||||
### 需要新增的章节
|
||||
|
||||
4. **Harness/App 架构分层** — publishable/unpublishable 分离 + CI 边界强制
|
||||
5. **DeerFlowClient 嵌入式模式** — 三种部署模式 + Gateway 一致性测试
|
||||
6. **安全架构深度** — 沙箱审计、Artifact XSS、上传验证、Toxic Output Loop
|
||||
7. **自定义代理系统** — SOUL.md、Bootstrap 路径、Agent Gallery
|
||||
8. **提示词模板系统** — 10+ 条件段落 + 中间件协调契约
|
||||
9. **StreamBridge 背压机制** — 生产者/消费者解耦 + SSE 重连 + 心跳
|
||||
|
||||
### 需要扩展的章节
|
||||
|
||||
10. **第 3.7 节 Memory**: 原子 I/O、去重算法、注入格式、队列去重
|
||||
11. **第 4 节 前端**: ai-elements 组件模式、Streamdown 管道、乐观消息合并
|
||||
12. **第 6 节 IM Channel**: MessageBus 架构、复合键映射、流式差异
|
||||
13. **第 7 节 配置**: 版本检查、路径解析优先级、配置升级
|
||||
|
||||
### 安全加固建议
|
||||
|
||||
14. **双向工具检查**: pre-execution guardrails + post-execution 输出消毒
|
||||
15. **MCP 端点认证**: 对配置写入端点添加认证中间件
|
||||
16. **容器安全加固**: 启用 seccomp、移除 Docker socket 挂载
|
||||
17. **API 密钥保护**: 端点响应中脱敏密钥字段
|
||||
18. **文件上传原子化**: 修复 TOCTOU 竞态 + 添加文件大小限制
|
||||
|
||||
---
|
||||
|
||||
## Part 9: 验证方式
|
||||
|
||||
验证本评估的准确性和文档修正的完整性:
|
||||
|
||||
1. **中间件验证**: `ls G:/deerflow/backend/packages/harness/deerflow/agents/middlewares/*.py` 确认 15 个文件
|
||||
2. **路由验证**: `grep "include_router" G:/deerflow/backend/app/gateway/app.py` 确认路由注册
|
||||
3. **提示词模板验证**: `wc -l G:/deerflow/backend/packages/harness/deerflow/agents/lead_agent/prompt.py` 确认 528 行
|
||||
4. **技能验证**: `ls G:/deerflow/skills/public/` 确认 17 个技能目录
|
||||
5. **前端组件验证**: `ls G:/deerflow/frontend/src/components/ai-elements/` 确认 27 个组件文件
|
||||
6. **安全验证**: 在 ZCLAW 中实现双向 guardrails 后,运行 `cargo test` + `pnpm vitest run`
|
||||
7. **集成验证**: ZCLAW `KernelClient` trait 的两种实现(Tauri IPC + HTTP)通过一致性测试
|
||||
680
docs/superpowers/specs/2026-04-01-knowledge-base-design.md
Normal file
680
docs/superpowers/specs/2026-04-01-knowledge-base-design.md
Normal file
@@ -0,0 +1,680 @@
|
||||
# 行业知识库功能设计
|
||||
|
||||
> 日期: 2026-04-01
|
||||
> 状态: 设计完成,待实施
|
||||
> 范围: SaaS 管理端 + AI Agent 集成
|
||||
|
||||
## 1. 背景与目标
|
||||
|
||||
ZCLAW 作为面向中文用户的 AI Agent 桌面端,当前对话能力依赖通用 LLM 知识。行业用户(制造业、医疗、教育、设计)在专业领域提问时,通用模型回答缺乏深度和准确性。
|
||||
|
||||
**目标**: 建立行业知识库系统,让 SaaS 管理员配置行业专业知识,通过 RAG + Agent Tool 混合方案提升 AI Agent 的行业回答精准度。
|
||||
|
||||
**核心价值**:
|
||||
- 管理员可系统化管理行业知识(分类、录入、版本控制)
|
||||
- AI Agent 自动检索并引用相关知识(RAG 注入)
|
||||
- Agent 可主动查询知识库(Tool 调用)
|
||||
- 全生命周期分析看板追踪知识使用效果
|
||||
|
||||
## 2. 设计决策
|
||||
|
||||
| 维度 | 决策 | 理由 |
|
||||
|------|------|------|
|
||||
| 使用者 | SaaS 管理员配置(平台级资源) | 当前无多租户架构,知识库作为平台级共享资源,通过角色权限控制访问 |
|
||||
| AI 集成 | RAG + Agent Tool 混合 | 覆盖自动注入和主动查询两个场景 |
|
||||
| 文档格式 | 仅 Markdown | 简化实现,Markdown 是知识的自然格式 |
|
||||
| 审核流程 | 免审核(直接生效) | 小团队高效运作 |
|
||||
| 分析看板 | 全生命周期分析 | 数据驱动知识库运营 |
|
||||
| 交付节奏 | 一次性完整实现 | 功能完整交付 |
|
||||
| 存储架构 | PostgreSQL + pgvector | 复用现有基础设施,零新增运维组件 |
|
||||
| Admin UI | 标签页表格(Ant Design 风格) | 与现有 Admin V2 一致 |
|
||||
| 主键类型 | TEXT(应用生成 UUID 字符串) | 匹配现有所有表的主键约定 |
|
||||
| 向量索引 | HNSW(pgvector >= 0.5.0) | 无最低行数要求,召回率优于 IVFFlat |
|
||||
| 中文检索 | 依赖向量语义搜索 + keywords 数组匹配 | 中文无空格分词,tsvector 不适用;向量搜索天然跨语言 |
|
||||
|
||||
## 3. 数据模型
|
||||
|
||||
### 3.1 约定
|
||||
|
||||
- 所有主键使用 `TEXT` 类型,由 Rust 端 `uuid::Uuid::new_v4().to_string()` 生成,匹配现有 25 张表的约定
|
||||
- 知识库为平台级资源(无 tenant_id),通过角色权限控制访问
|
||||
- 外键引用 `accounts(id)` 均为 TEXT 类型
|
||||
|
||||
### 3.2 新增表(5 张)
|
||||
|
||||
```sql
|
||||
-- 启用 pgvector 扩展
|
||||
CREATE EXTENSION IF NOT EXISTS vector;
|
||||
|
||||
-- 行业分类树
|
||||
CREATE TABLE knowledge_categories (
|
||||
id TEXT PRIMARY KEY,
|
||||
name VARCHAR(100) NOT NULL,
|
||||
description TEXT,
|
||||
parent_id TEXT REFERENCES knowledge_categories(id),
|
||||
icon VARCHAR(50),
|
||||
sort_order INT DEFAULT 0,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
CHECK (id != parent_id) -- 防止自引用
|
||||
);
|
||||
|
||||
-- 知识条目
|
||||
CREATE TABLE knowledge_items (
|
||||
id TEXT PRIMARY KEY,
|
||||
category_id TEXT NOT NULL REFERENCES knowledge_categories(id),
|
||||
title VARCHAR(255) NOT NULL,
|
||||
content TEXT NOT NULL,
|
||||
keywords TEXT[] DEFAULT '{}',
|
||||
related_questions TEXT[] DEFAULT '{}',
|
||||
priority INT DEFAULT 0,
|
||||
status VARCHAR(20) DEFAULT 'active' CHECK (status IN ('active', 'archived', 'deprecated')),
|
||||
version INT DEFAULT 1,
|
||||
source VARCHAR(50) DEFAULT 'manual',
|
||||
tags TEXT[] DEFAULT '{}',
|
||||
created_by TEXT NOT NULL REFERENCES accounts(id),
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
-- 内容长度约束:单条最大 100KB
|
||||
CHECK (length(content) <= 100000)
|
||||
);
|
||||
|
||||
-- 知识分块(RAG 检索核心)
|
||||
CREATE TABLE knowledge_chunks (
|
||||
id TEXT PRIMARY KEY,
|
||||
item_id TEXT NOT NULL REFERENCES knowledge_items(id) ON DELETE CASCADE,
|
||||
chunk_index INT NOT NULL,
|
||||
content TEXT NOT NULL,
|
||||
embedding vector(1536),
|
||||
keywords TEXT[] DEFAULT '{}',
|
||||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- 版本快照
|
||||
CREATE TABLE knowledge_versions (
|
||||
id TEXT PRIMARY KEY,
|
||||
item_id TEXT NOT NULL REFERENCES knowledge_items(id) ON DELETE CASCADE,
|
||||
version INT NOT NULL,
|
||||
title VARCHAR(255) NOT NULL,
|
||||
content TEXT NOT NULL,
|
||||
keywords TEXT[] DEFAULT '{}',
|
||||
related_questions TEXT[] DEFAULT '{}',
|
||||
change_summary TEXT,
|
||||
created_by TEXT NOT NULL REFERENCES accounts(id),
|
||||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- 使用追踪
|
||||
CREATE TABLE knowledge_usage (
|
||||
id TEXT PRIMARY KEY,
|
||||
item_id TEXT NOT NULL REFERENCES knowledge_items(id),
|
||||
chunk_id TEXT REFERENCES knowledge_chunks(id),
|
||||
session_id VARCHAR(100),
|
||||
query_text TEXT,
|
||||
relevance_score FLOAT,
|
||||
was_injected BOOLEAN DEFAULT FALSE,
|
||||
agent_feedback VARCHAR(20) CHECK (agent_feedback IN ('positive', 'negative')),
|
||||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
```
|
||||
|
||||
### 3.3 索引
|
||||
|
||||
```sql
|
||||
-- 关联索引
|
||||
CREATE INDEX idx_ki_category ON knowledge_items(category_id);
|
||||
CREATE INDEX idx_kchunks_item ON knowledge_chunks(item_id);
|
||||
CREATE INDEX idx_kv_item ON knowledge_versions(item_id);
|
||||
CREATE INDEX idx_ku_item ON knowledge_usage(item_id);
|
||||
|
||||
-- 分类树
|
||||
CREATE INDEX idx_kc_parent ON knowledge_categories(parent_id);
|
||||
|
||||
-- 使用追踪时间范围查询
|
||||
CREATE INDEX idx_ku_created ON knowledge_usage(created_at);
|
||||
|
||||
-- 向量相似度索引(HNSW,无需预填充数据,召回率优于 IVFFlat)
|
||||
CREATE INDEX idx_kchunks_embedding ON knowledge_chunks
|
||||
USING hnsw (embedding vector_cosine_ops)
|
||||
WITH (m = 16, ef_construction = 64);
|
||||
|
||||
-- 关键词数组索引(GIN,支持 && 重叠操作符)
|
||||
CREATE INDEX idx_ki_keywords ON knowledge_items USING GIN(keywords);
|
||||
CREATE INDEX idx_kchunks_keywords ON knowledge_chunks USING GIN(keywords);
|
||||
```
|
||||
|
||||
### 3.4 Embedding 维度说明
|
||||
|
||||
`vector(1536)` 对应 OpenAI text-embedding-ada-002。若切换到其他 embedding 提供商:
|
||||
- Zhipu embedding-3: 2048 维
|
||||
- Qwen text-embedding-v2: 1536 维
|
||||
- Doubao: 1024 维
|
||||
|
||||
**约束**: 同一知识库内所有条目必须使用相同维度的 embedding 模型。切换模型时需执行 re-embedding(见 5.4 节)。维度值存储在 `config_items` 表中(category: `knowledge_base`, key: `embedding_dimension`),迁移时据此动态创建列。
|
||||
|
||||
## 4. API 设计
|
||||
|
||||
### 4.1 核心请求/响应类型
|
||||
|
||||
```rust
|
||||
// === 分类 ===
|
||||
#[derive(Deserialize)]
|
||||
pub struct CreateCategoryRequest {
|
||||
pub name: String,
|
||||
pub description: Option<String>,
|
||||
pub parent_id: Option<String>,
|
||||
pub icon: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct UpdateCategoryRequest {
|
||||
pub name: Option<String>,
|
||||
pub description: Option<String>,
|
||||
pub parent_id: Option<String>,
|
||||
pub icon: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct CategoryResponse {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub description: Option<String>,
|
||||
pub parent_id: Option<String>,
|
||||
pub icon: Option<String>,
|
||||
pub sort_order: i32,
|
||||
pub item_count: i64, // 该分类下的条目数
|
||||
pub children: Vec<CategoryResponse>, // 树形嵌套
|
||||
pub created_at: String,
|
||||
pub updated_at: String,
|
||||
}
|
||||
|
||||
// === 知识条目 ===
|
||||
#[derive(Deserialize)]
|
||||
pub struct CreateItemRequest {
|
||||
pub category_id: String,
|
||||
pub title: String,
|
||||
pub content: String,
|
||||
pub keywords: Option<Vec<String>>,
|
||||
pub related_questions: Option<Vec<String>>,
|
||||
pub priority: Option<i32>,
|
||||
pub tags: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct UpdateItemRequest {
|
||||
pub category_id: Option<String>,
|
||||
pub title: Option<String>,
|
||||
pub content: Option<String>,
|
||||
pub keywords: Option<Vec<String>>,
|
||||
pub related_questions: Option<Vec<String>>,
|
||||
pub priority: Option<i32>,
|
||||
pub status: Option<String>,
|
||||
pub tags: Option<Vec<String>>,
|
||||
pub change_summary: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct ListItemsQuery {
|
||||
pub page: Option<i64>,
|
||||
pub page_size: Option<i64>,
|
||||
pub category_id: Option<String>,
|
||||
pub status: Option<String>,
|
||||
pub keyword: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct ItemResponse {
|
||||
pub id: String,
|
||||
pub category_id: String,
|
||||
pub category_name: String,
|
||||
pub title: String,
|
||||
pub content: String,
|
||||
pub keywords: Vec<String>,
|
||||
pub related_questions: Vec<String>,
|
||||
pub priority: i32,
|
||||
pub status: String,
|
||||
pub version: i32,
|
||||
pub source: String,
|
||||
pub tags: Vec<String>,
|
||||
pub created_by: String,
|
||||
pub reference_count: i64, // 引用次数(从 knowledge_usage 统计)
|
||||
pub created_at: String,
|
||||
pub updated_at: String,
|
||||
}
|
||||
|
||||
// === 搜索 ===
|
||||
#[derive(Deserialize)]
|
||||
pub struct SearchRequest {
|
||||
pub query: String,
|
||||
pub category_id: Option<String>,
|
||||
pub limit: Option<i64>, // 默认 5, 最大 10
|
||||
pub min_score: Option<f64>, // 最低相关性阈值,默认 0.5
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct SearchResult {
|
||||
pub chunk_id: String,
|
||||
pub item_id: String,
|
||||
pub item_title: String,
|
||||
pub category_name: String,
|
||||
pub content: String,
|
||||
pub score: f64,
|
||||
pub keywords: Vec<String>,
|
||||
}
|
||||
|
||||
// === 分析 ===
|
||||
#[derive(Serialize)]
|
||||
pub struct AnalyticsOverview {
|
||||
pub total_items: i64,
|
||||
pub active_items: i64,
|
||||
pub total_categories: i64,
|
||||
pub weekly_new_items: i64,
|
||||
pub total_references: i64,
|
||||
pub avg_reference_per_item: f64,
|
||||
pub hit_rate: f64, // 命中率
|
||||
pub injection_rate: f64, // 注入率
|
||||
pub positive_feedback_rate: f64,
|
||||
pub stale_items_count: i64, // 90天未引用
|
||||
}
|
||||
```
|
||||
|
||||
### 4.2 分类管理(6 个端点)
|
||||
|
||||
| Method | Path | 说明 |
|
||||
|--------|------|------|
|
||||
| GET | `/api/v1/knowledge/categories` | 树形列表(含每节点 item_count) |
|
||||
| POST | `/api/v1/knowledge/categories` | 创建分类 |
|
||||
| PUT | `/api/v1/knowledge/categories/:id` | 更新分类(含父级循环检测) |
|
||||
| DELETE | `/api/v1/knowledge/categories/:id` | 删除分类(有子分类或条目时拒绝) |
|
||||
| PATCH | `/api/v1/knowledge/categories/reorder` | 批量更新 sort_order |
|
||||
| GET | `/api/v1/knowledge/categories/:id/items` | 分类下条目分页列表 |
|
||||
|
||||
### 4.3 知识条目 CRUD(7 个端点)
|
||||
|
||||
| Method | Path | 说明 |
|
||||
|--------|------|------|
|
||||
| GET | `/api/v1/knowledge/items` | 分页列表(筛选/搜索) |
|
||||
| POST | `/api/v1/knowledge/items` | 创建条目(触发异步 embedding) |
|
||||
| GET | `/api/v1/knowledge/items/:id` | 条目详情 |
|
||||
| PUT | `/api/v1/knowledge/items/:id` | 更新条目(触发异步 re-embedding) |
|
||||
| DELETE | `/api/v1/knowledge/items/:id` | 删除条目(级联删除 chunks + versions) |
|
||||
| POST | `/api/v1/knowledge/items/batch` | 批量创建(单次最多 50 条) |
|
||||
| POST | `/api/v1/knowledge/items/import` | Markdown 文件导入(单次最多 20 个文件) |
|
||||
|
||||
### 4.4 版本控制(3 个端点)
|
||||
|
||||
| Method | Path | 说明 |
|
||||
|--------|------|------|
|
||||
| GET | `/api/v1/knowledge/items/:id/versions` | 版本历史列表 |
|
||||
| GET | `/api/v1/knowledge/items/:id/versions/:v` | 查看特定版本 |
|
||||
| POST | `/api/v1/knowledge/items/:id/rollback/:v` | 回滚到指定版本(创建新版本) |
|
||||
|
||||
### 4.5 检索(2 个端点,内部调用)
|
||||
|
||||
| Method | Path | 说明 |
|
||||
|--------|------|------|
|
||||
| POST | `/api/v1/knowledge/search` | 语义搜索(向量 + 关键词混合) |
|
||||
| POST | `/api/v1/knowledge/recommend` | 关联推荐(基于当前条目的关键词重叠) |
|
||||
|
||||
### 4.6 分析看板(5 个端点)
|
||||
|
||||
| Method | Path | 说明 |
|
||||
|--------|------|------|
|
||||
| GET | `/api/v1/knowledge/analytics/overview` | 总览统计(含命中率/注入率/反馈率) |
|
||||
| GET | `/api/v1/knowledge/analytics/trends` | 使用趋势(支持 day/week/month 粒度) |
|
||||
| GET | `/api/v1/knowledge/analytics/top-items` | 高频引用排行(支持分类筛选) |
|
||||
| GET | `/api/v1/knowledge/analytics/quality` | 质量指标(按分类分组) |
|
||||
| GET | `/api/v1/knowledge/analytics/gaps` | 知识缺口检测(低分查询聚类) |
|
||||
|
||||
### 4.7 权限模型
|
||||
|
||||
| 权限 | 说明 | 授予角色 |
|
||||
|------|------|----------|
|
||||
| `knowledge:read` | 查看分类、条目、版本、分析 | admin, super_admin |
|
||||
| `knowledge:write` | 创建/编辑/导入条目和分类 | admin, super_admin |
|
||||
| `knowledge:admin` | 删除、回滚 | super_admin |
|
||||
| `knowledge:search` | 内部检索(Agent/中间件) | 系统内部 |
|
||||
|
||||
## 5. RAG 管道
|
||||
|
||||
### 5.1 入库管道(写入路径)
|
||||
|
||||
```
|
||||
管理员创建/编辑 Markdown
|
||||
↓
|
||||
内容分块(Markdown 标题层级 + 500-1000 token 固定切分 + 50 token 重叠)
|
||||
↓
|
||||
Worker 异步生成 embedding(调用 models 表中 is_embedding=true 的模型)
|
||||
↓
|
||||
存入 knowledge_chunks(content + embedding + keywords)
|
||||
↓
|
||||
自动创建 knowledge_versions 快照
|
||||
↓
|
||||
更新 knowledge_items.version++
|
||||
```
|
||||
|
||||
**分块策略**:
|
||||
1. 优先按 Markdown 标题(`#`, `##`, `###`)自然分段
|
||||
2. 超长段落按 500-1000 token 切分
|
||||
3. 相邻块之间保留 50 token 重叠,避免语义断裂
|
||||
4. 每个块继承父级标题作为上下文前缀
|
||||
|
||||
**Embedding 生成**:
|
||||
- 复用现有 embedding 提供商配置(OpenAI, Zhipu, Doubao, Qwen, DeepSeek, Local/TF-IDF)
|
||||
- 通过 Worker 系统异步处理,不阻塞管理员操作
|
||||
- 模型选择: 从 `config_items` 读取 `knowledge_base.embedding_model_id`,默认使用第一个 `is_embedding=true` 的模型
|
||||
|
||||
### 5.2 检索管道(读取路径)
|
||||
|
||||
```
|
||||
用户提问
|
||||
↓
|
||||
relay 层知识注入(在 chat_completions handler 内调用)
|
||||
↓
|
||||
1. 生成查询 embedding
|
||||
2. 混合检索:
|
||||
a) HNSW 向量余弦相似度(权重 0.7)
|
||||
b) keywords 数组重叠匹配(权重 0.2)
|
||||
c) related_questions 文本包含匹配(权重 0.1)
|
||||
3. 合并排序,取 Top-K(默认 5 条)
|
||||
4. token 预算控制(不超过 context window 的 20%)
|
||||
5. 格式化注入 system prompt
|
||||
↓
|
||||
记录到 knowledge_usage(检索事件)
|
||||
↓
|
||||
LLM 生成回答
|
||||
```
|
||||
|
||||
**混合检索公式**:
|
||||
```
|
||||
final_score = 0.7 * cosine_similarity + 0.2 * keyword_overlap_count / max_keywords + 0.1 * related_question_match
|
||||
```
|
||||
|
||||
**token 预算控制**:
|
||||
- 最大注入 token 数 = min(context_window * 0.2, 2000)
|
||||
- 按相关性排序,截断超出预算的低分块
|
||||
- 注入格式: `[行业知识 #N] 标题\n内容`
|
||||
|
||||
**集成方式**: 在 `relay::handlers::chat_completions` 内部,转发到上游 LLM 之前调用 `knowledge::service::search_and_inject()`。不使用 Axum 中间件层,而是作为 handler 内的业务逻辑步骤,与现有的 stream 处理管道自然集成。
|
||||
|
||||
### 5.3 Agent Tool
|
||||
|
||||
```
|
||||
tool: knowledge_search
|
||||
params:
|
||||
query: string # 搜索查询
|
||||
category?: string # 限定分类
|
||||
limit?: number # 返回数量 (默认 3, 最大 10)
|
||||
返回:
|
||||
items: Array<{
|
||||
title: string
|
||||
content: string # 匹配的知识片段
|
||||
category: string
|
||||
relevance: number # 相关性分数
|
||||
}>
|
||||
```
|
||||
|
||||
Agent 在判断需要行业专业知识时主动调用此工具。通过 SaaS API 调用 `POST /api/v1/knowledge/search`。
|
||||
|
||||
### 5.4 Re-embedding 策略
|
||||
|
||||
当 embedding 模型切换时(维度或提供商变化):
|
||||
|
||||
1. **检测触发**: 管理员在分析看板页点击"重建索引"按钮
|
||||
2. **执行流程**:
|
||||
- 创建 re-embedding Worker 任务,按 batch(每批 100 条 item)分片
|
||||
- 每个 batch: 删除旧 chunks → 重新分块 → 生成新 embedding → 写入新 chunks
|
||||
- 通过 `SpawnLimiter` 控制并发,防止连接池耗尽
|
||||
3. **原子性**: 每个 item 的 re-embedding 在单个事务内完成(删旧 chunk + 写新 chunk)
|
||||
4. **状态追踪**: 在 `config_items` 中记录 `knowledge_base.reindex_status`(idle/running/completed/failed)
|
||||
5. **失败处理**: 单条 item 失败不影响其他 item,记录到 operation_logs,支持重试
|
||||
|
||||
## 6. Admin UI 设计
|
||||
|
||||
### 6.1 页面结构
|
||||
|
||||
在 Admin V2 侧边栏新增"知识库"菜单组,包含 3 个子页面:
|
||||
|
||||
**页面 1: 知识条目(默认页)**
|
||||
- 顶部 Tab: 知识条目 | 批量导入
|
||||
- 条目列表 Tab: Ant Design Table
|
||||
- 列: 标题、分类、关键词(Tag)、引用次数、状态(StatusTag)、更新时间、操作
|
||||
- 筛选: 分类下拉、状态筛选、关键词搜索输入框
|
||||
- 操作: 新增(Modal)、编辑(Modal)、删除(Popconfirm)、查看版本历史(Drawer)
|
||||
- 批量导入 Tab:
|
||||
- Markdown 文件上传(Ant Design Upload,支持多文件,单次最多 20 个)
|
||||
- 分类选择(下拉选择导入到哪个分类下)
|
||||
- 导入预览(文件列表 + 标题预览)+ 确认按钮
|
||||
|
||||
**页面 2: 分类管理**
|
||||
- 树形组件(Ant Design Tree)
|
||||
- 拖拽排序
|
||||
- 内联编辑(新增/重命名/删除)
|
||||
- 每个节点显示条目数量
|
||||
- 删除前检查是否有子分类或关联条目
|
||||
|
||||
**页面 3: 分析看板**
|
||||
- 总览卡片: 条目总数、本周新增、活跃率、平均引用次数
|
||||
- 使用趋势图: 折线图(检索/命中/注入三条线,日/周/月粒度切换)
|
||||
- 高频引用排行: 表格(支持按分类筛选)
|
||||
- 质量指标: 命中率、注入率、正向反馈率、过期知识标记(90天未引用)
|
||||
- 知识缺口: 缺失主题、查询频率、建议分类
|
||||
|
||||
### 6.2 新增文件
|
||||
|
||||
```
|
||||
admin-v2/src/
|
||||
├── pages/
|
||||
│ ├── KnowledgeItems.tsx # 知识条目页
|
||||
│ ├── KnowledgeCategories.tsx # 分类管理页
|
||||
│ └── KnowledgeAnalytics.tsx # 分析看板页
|
||||
├── services/
|
||||
│ └── knowledgeService.ts # API 调用封装
|
||||
├── types/
|
||||
│ └── knowledge.d.ts # 类型定义
|
||||
└── components/
|
||||
└── knowledge/
|
||||
├── ItemForm.tsx # 条目编辑表单(Modal)
|
||||
├── ItemDetail.tsx # 条目详情抽屉
|
||||
├── VersionHistory.tsx # 版本历史(Drawer)
|
||||
├── ImportPanel.tsx # 批量导入面板
|
||||
└── AnalyticsCharts.tsx # 分析图表组件
|
||||
```
|
||||
|
||||
### 6.3 路由注册
|
||||
|
||||
```typescript
|
||||
// router/index.tsx 新增(使用现有 lazy 加载模式)
|
||||
{ path: 'knowledge/items', lazy: () => import('@/pages/KnowledgeItems').then(m => ({ Component: m.default })) },
|
||||
{ path: 'knowledge/categories', lazy: () => import('@/pages/KnowledgeCategories').then(m => ({ Component: m.default })) },
|
||||
{ path: 'knowledge/analytics', lazy: () => import('@/pages/KnowledgeAnalytics').then(m => ({ Component: m.default })) },
|
||||
```
|
||||
|
||||
### 6.4 侧边栏导航
|
||||
|
||||
```typescript
|
||||
// AdminLayout.tsx navItems 新增
|
||||
{
|
||||
path: '/knowledge/items',
|
||||
name: '知识库',
|
||||
icon: BookOutlined,
|
||||
permission: 'knowledge:read',
|
||||
group: '资源管理',
|
||||
},
|
||||
{
|
||||
path: '/knowledge/categories',
|
||||
name: '分类管理',
|
||||
icon: FolderOutlined,
|
||||
permission: 'knowledge:read',
|
||||
group: '资源管理',
|
||||
},
|
||||
{
|
||||
path: '/knowledge/analytics',
|
||||
name: '知识分析',
|
||||
icon: BarChartOutlined,
|
||||
permission: 'knowledge:read',
|
||||
group: '资源管理',
|
||||
},
|
||||
```
|
||||
|
||||
## 7. SaaS 后端实现
|
||||
|
||||
### 7.1 新增模块
|
||||
|
||||
```
|
||||
crates/zclaw-saas/src/
|
||||
└── knowledge/
|
||||
├── mod.rs # 模块注册 + 路由定义(pub fn routes() -> Router<AppState>)
|
||||
├── types.rs # 请求/响应/DTO 类型(见 4.1 节)
|
||||
├── handlers.rs # 23 个 API handler
|
||||
├── service.rs # 业务逻辑(CRUD + 检索 + 分析)
|
||||
└── chunk.rs # 分块 + embedding 生成 + re-embedding
|
||||
```
|
||||
|
||||
### 7.2 模块注册
|
||||
|
||||
```rust
|
||||
// lib.rs 新增
|
||||
pub mod knowledge;
|
||||
|
||||
// main.rs build_router() 新增
|
||||
.merge(zclaw_saas::knowledge::routes())
|
||||
```
|
||||
|
||||
### 7.3 新增 Worker
|
||||
|
||||
```rust
|
||||
// workers/generate_embedding.rs
|
||||
use crate::state::AppState;
|
||||
use crate::workers::Worker;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::PgPool;
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct GenerateEmbeddingArgs {
|
||||
pub item_id: String,
|
||||
}
|
||||
|
||||
pub struct GenerateEmbedding;
|
||||
|
||||
impl Worker for GenerateEmbedding {
|
||||
type Args = GenerateEmbeddingArgs;
|
||||
|
||||
fn name(&self) -> &str {
|
||||
"generate_embedding"
|
||||
}
|
||||
|
||||
async fn perform(&self, db: &PgPool, args: Self::Args) -> crate::error::SaasResult<()> {
|
||||
// 1. 从 knowledge_items 读 content
|
||||
// 2. 调用 chunk.rs 分块
|
||||
// 3. 调用 embedding 提供商生成向量
|
||||
// (通过 relay 模块的 provider 客户端,复用现有 HTTP 客户端)
|
||||
// 4. 删除旧 chunks,写入新 knowledge_chunks
|
||||
// 5. 更新 knowledge_items.updated_at
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// main.rs Worker 注册新增
|
||||
state.dispatcher.register::<GenerateEmbedding>();
|
||||
```
|
||||
|
||||
### 7.4 Cargo 依赖
|
||||
|
||||
```toml
|
||||
# zclaw-saas/Cargo.toml 新增
|
||||
[dependencies]
|
||||
pgvector = { version = "0.4", features = ["sqlx"] }
|
||||
```
|
||||
|
||||
pgvector crate 提供 `pgvector::Vector` 类型,支持 sqlx 的 `Encode`/`Decode`,可直接用于读写 `vector(N)` 列。
|
||||
|
||||
### 7.5 迁移文件
|
||||
|
||||
```
|
||||
crates/zclaw-saas/migrations/20260402000002_knowledge_base.sql
|
||||
```
|
||||
|
||||
包含: pgvector 扩展启用 + 5 张表 + 所有索引(见第 3 节)。
|
||||
|
||||
## 8. Docker 变更
|
||||
|
||||
将 PostgreSQL 镜像切换为 pgvector 版本:
|
||||
|
||||
```yaml
|
||||
# docker-compose.yml
|
||||
services:
|
||||
db:
|
||||
image: pgvector/pgvector:pg16-alpine # 原 postgres:16-alpine
|
||||
```
|
||||
|
||||
使用 Alpine 变体保持与原有配置一致。
|
||||
|
||||
## 9. 权限种子数据
|
||||
|
||||
在迁移文件中通过应用层兼容的方式更新权限(`permissions` 列为 TEXT 类型存储 JSON 数组字符串):
|
||||
|
||||
```sql
|
||||
-- 以应用层可解析的格式追加权限
|
||||
-- super_admin: 追加 knowledge:read, knowledge:write, knowledge:admin, knowledge:search
|
||||
UPDATE roles
|
||||
SET permissions = REPLACE(
|
||||
permissions,
|
||||
']',
|
||||
', "knowledge:read", "knowledge:write", "knowledge:admin", "knowledge:search"]'
|
||||
)
|
||||
WHERE name = 'super_admin'
|
||||
AND permissions NOT LIKE '%knowledge:read%';
|
||||
|
||||
-- admin: 追加 knowledge:read, knowledge:write, knowledge:search
|
||||
UPDATE roles
|
||||
SET permissions = REPLACE(
|
||||
permissions,
|
||||
']',
|
||||
', "knowledge:read", "knowledge:write", "knowledge:search"]'
|
||||
)
|
||||
WHERE name = 'admin'
|
||||
AND permissions NOT LIKE '%knowledge:read%';
|
||||
```
|
||||
|
||||
## 10. 内容限制与防护
|
||||
|
||||
| 限制项 | 值 | 实现位置 |
|
||||
|--------|-----|----------|
|
||||
| 单条内容最大长度 | 100KB (100,000 字符) | 数据库 CHECK 约束 + API 验证 |
|
||||
| 批量创建最大条数 | 50 条/次 | API handler 验证 |
|
||||
| 文件导入最大文件数 | 20 个/次 | API handler 验证 |
|
||||
| 单文件最大大小 | 5MB | Upload 中间件限制 |
|
||||
| 搜索结果最大数量 | 10 条 | API 参数上限 |
|
||||
| 分类树最大深度 | 3 层 | API handler 递归检测 |
|
||||
| 分类名称最大长度 | 100 字符 | 数据库 VARCHAR 约束 |
|
||||
| 标题最大长度 | 255 字符 | 数据库 VARCHAR 约束 |
|
||||
|
||||
## 11. 验证方案
|
||||
|
||||
### 11.1 后端验证
|
||||
|
||||
1. **数据库迁移**: 启动 SaaS 服务,确认 pgvector 扩展和 5 张表创建成功
|
||||
2. **CRUD API**: 用 curl 测试分类和条目的完整 CRUD 流程
|
||||
3. **分块 + Embedding**: 创建条目后检查 knowledge_chunks 表有正确分块和向量
|
||||
4. **混合检索**: 调用 `/api/v1/knowledge/search` 验证向量+关键词混合结果
|
||||
5. **版本控制**: 编辑条目后检查 knowledge_versions 快照正确性,验证回滚
|
||||
6. **分析 API**: 注入测试数据后验证 5 个分析端点返回正确统计
|
||||
7. **分类循环检测**: 尝试设置循环父级关系,确认被拒绝
|
||||
8. **内容限制**: 尝试提交超长内容,确认被 CHECK 约束拒绝
|
||||
|
||||
### 11.2 前端验证
|
||||
|
||||
1. **分类管理**: 树形 CRUD + 拖拽排序
|
||||
2. **条目 CRUD**: 创建、编辑、删除、列表筛选
|
||||
3. **批量导入**: Markdown 文件上传导入
|
||||
4. **版本历史**: 查看历史版本 + 回滚
|
||||
5. **分析看板**: 图表渲染 + 数据联动
|
||||
|
||||
### 11.3 集成验证
|
||||
|
||||
1. **RAG 注入**: 在桌面端对话中提问行业相关问题,验证知识被检索和注入
|
||||
2. **Agent Tool**: 在对话中触发 Agent 主动查询知识库
|
||||
3. **使用追踪**: 对话后检查 knowledge_usage 表有检索记录
|
||||
4. **分析闭环**: 对话后查看分析看板数据更新
|
||||
5. **Re-embedding**: 切换 embedding 模型后触发重建,验证向量正确更新
|
||||
649
docs/superpowers/specs/2026-04-02-chatstore-refactor-design.md
Normal file
649
docs/superpowers/specs/2026-04-02-chatstore-refactor-design.md
Normal file
@@ -0,0 +1,649 @@
|
||||
# ChatStore 结构化重构设计
|
||||
|
||||
> 日期: 2026-04-02
|
||||
> 状态: Draft
|
||||
> 范围: desktop/src/store/chatStore.ts 及关联文件
|
||||
|
||||
## 1. 背景
|
||||
|
||||
ChatStore(908 行)是 ZCLAW 桌面端聊天的核心状态管理模块,承担了消息管理、流式处理、对话管理、Artifact 面板、离线队列、ChatMode 切换等职责。经过多轮功能迭代,存在以下问题:
|
||||
|
||||
### 1.1 功能断裂
|
||||
|
||||
| 问题 | 影响 |
|
||||
|------|------|
|
||||
| `cancelStream()` 是 no-op | 用户无法取消长时间运行的流式响应 |
|
||||
| GatewayClient 路径不支持 thinking delta | Web 版/远程连接用户无法使用推理模式 |
|
||||
| 双路径(Kernel/Gateway)逻辑重复且不一致 | 维护成本高,行为不可预测 |
|
||||
|
||||
### 1.2 数据可靠性
|
||||
|
||||
| 问题 | 影响 |
|
||||
|------|------|
|
||||
| 流式过程中刷新页面丢失未持久化内容 | 用户丢失已生成的回复 |
|
||||
| 无消息重试机制 | 网络波动后用户需手动重新输入 |
|
||||
| 对话持久化仅在 `onComplete` 时触发 | 长对话中断后数据丢失 |
|
||||
|
||||
### 1.3 架构债务
|
||||
|
||||
| 问题 | 影响 |
|
||||
|------|------|
|
||||
| ChatStore 908 行、职责过多 | 难以理解和修改 |
|
||||
| `Message` vs `SessionMessage` 两套类型体系 | 类型转换混乱 |
|
||||
| 未纳入 Store Coordinator | 不符合项目 store 注入模式 |
|
||||
| `Conversation.tsx` 有未使用的 Context | 死代码增加认知负担 |
|
||||
|
||||
### 1.4 性能风险
|
||||
|
||||
| 问题 | 影响 |
|
||||
|------|------|
|
||||
| 所有消息全量在内存 | 长对话占用过多内存 |
|
||||
| 每次 `onDelta` 触发全量 `set()` 映射 | 频繁渲染影响性能 |
|
||||
|
||||
## 2. 设计决策
|
||||
|
||||
### 2.1 方案选择
|
||||
|
||||
选择 **方案 B: 结构化多 Store 重构**,理由:
|
||||
|
||||
- 每个拆分后的 Store 可在一个上下文窗口内完整理解
|
||||
- 统一流式抽象层消除双路径重复
|
||||
- 定时批量持久化平衡性能与可靠性
|
||||
- 逐步迁移保证每步可验证
|
||||
|
||||
### 2.2 双路径统一策略
|
||||
|
||||
统一 KernelClient(Tauri 事件)和 GatewayClient(WebSocket)的流式体验,使两条路径具备相同能力:thinking delta 支持、5 分钟超时、取消机制。
|
||||
|
||||
### 2.3 持久化策略
|
||||
|
||||
采用定时批量保存(每 3 秒或每 50 条 delta),使用 `requestIdleCallback`(不可用时降级为 `setTimeout`)降低对 UI 的性能影响。存储目标为 IndexedDB(通过 `idb-keyval` 库),避免 localStorage 的 5-10 MB 大小限制。localStorage 仅保留对话元数据(id 列表、当前对话 ID、当前 agent)。
|
||||
|
||||
## 3. 架构设计
|
||||
|
||||
### 3.1 Store 拆分
|
||||
|
||||
```
|
||||
desktop/src/store/
|
||||
├── chat/ # 新建目录
|
||||
│ ├── conversationStore.ts # 对话列表管理(~200行)
|
||||
│ ├── messageStore.ts # 消息管理 + 检索(~250行)
|
||||
│ ├── streamStore.ts # 统一流式处理(~200行)
|
||||
│ └── artifactStore.ts # Artifact 面板(~80行)
|
||||
├── chatStore.ts # 保留为 facade,re-export 统一接口
|
||||
```
|
||||
|
||||
### 3.2 conversationStore
|
||||
|
||||
**职责**: 对话生命周期管理
|
||||
|
||||
**状态:**
|
||||
- `conversations: Conversation[]`
|
||||
- `currentConversationId: string | null`
|
||||
- `agents: Agent[]`(agent 列表,从现有 chatStore 迁入)
|
||||
- `currentAgent: Agent | null`
|
||||
- `sessionKey: string | null`
|
||||
- `currentModel: string`(当前模型名称,持久化)
|
||||
|
||||
**Actions:**
|
||||
- `newConversation()` — 保存当前对话,创建新的空对话
|
||||
- `switchConversation(id: string)` — 保存当前,加载目标对话
|
||||
- `deleteConversation(id: string)` — 删除对话
|
||||
- `upsertActiveConversation()` — 批量保存当前对话的 messages/sessionKey 到 conversations 数组
|
||||
- `getCurrentConversation()` — 获取当前活跃对话
|
||||
- `setCurrentAgent(agent: Agent)` — 切换 agent,保存/恢复对话
|
||||
- `syncAgents(profiles: AgentProfileLike[])` — 同步 agent 列表
|
||||
- `setCurrentModel(model: string)` — 切换模型
|
||||
|
||||
**Agent 绑定**: 每个 Conversation 关联一个 agentId,切换对话时恢复对应 agent。
|
||||
|
||||
**存储**: 对话列表和 agent 信息持久化到 IndexedDB(通过 zustand persist 的自定义 storage),localStorage 仅存元数据。包含存储配额检查:写入前估算数据大小,超过 4 MB 时自动清理最旧的已归档对话。
|
||||
|
||||
### 3.3 messageStore
|
||||
|
||||
**职责**: 当前对话的消息数据管理
|
||||
|
||||
**状态:**
|
||||
- `messages: ChatMessage[]`
|
||||
- `totalInputTokens: number`
|
||||
- `totalOutputTokens: number`
|
||||
|
||||
**Actions:**
|
||||
- `addMessage(message: ChatMessage)` — 追加消息
|
||||
- `updateMessage(id: string, updates: Partial<ChatMessage>)` — 合并更新
|
||||
- `getStreamingMessage()` — 获取当前流式消息(role=assistant 且 streaming=true)
|
||||
- `updateStreamingContent(id: string, delta: string)` — 高性能增量更新
|
||||
- `appendThinking(id: string, delta: string)` — 追加 thinking 内容
|
||||
- `addToolStep(id: string, step: ToolStep)` — 追加工具调用步骤
|
||||
- `completeMessage(id: string, tokens: TokenUsage)` — 标记消息完成,记录 token
|
||||
- `failMessage(id: string, error: string)` — 标记消息失败,保存原始内容用于重试
|
||||
- `retryMessage(id: string)` — 使用 originalContent 创建重试
|
||||
- `addTokenUsage(input: number, output: number)` — 累计 token
|
||||
- `resetMessages(messages: ChatMessage[])` — 切换对话时重载消息
|
||||
- `searchMessages(query: string)` — 消息内文本搜索
|
||||
|
||||
### 3.4 streamStore
|
||||
|
||||
**职责**: 统一流式处理、离线队列、完成后副作用
|
||||
|
||||
**状态:**
|
||||
- `isStreaming: boolean`
|
||||
- `isLoading: boolean`
|
||||
- `streamHandle: StreamHandle | null`
|
||||
- `chatMode: ChatModeType`
|
||||
- `suggestions: string[]`
|
||||
|
||||
**Actions:**
|
||||
- `sendMessage(content: string, context?: SendMessageContext)` — 核心发送逻辑
|
||||
1. **离线检查**:调用 `offlineStore.isOffline()`;若离线,委托 `offlineStore.queueMessage()` 并显示系统消息后返回
|
||||
2. **流式守卫**:若 `isStreaming === true`,拒绝发送(前端防重复)
|
||||
3. 选择活跃的 `StreamingAdapter`
|
||||
4. **原子消息创建**:一次性创建 optimistic 用户消息 + 流式 assistant 占位消息,通过单次 `set()` 写入 messageStore(避免部分状态被批量保存)
|
||||
5. 启动流式请求,注册 callbacks
|
||||
6. 管理 dirty 标志触发批量保存
|
||||
7. **完成后副作用**(onComplete 回调中):
|
||||
- `conversationStore.upsertActiveConversation()` — 立即保存
|
||||
- `memoryExtractor.extractFromConversation()` — 异步记忆提取(.catch 静默处理)
|
||||
- `intelligenceClient.reflection.recordConversation()` — 对话记录(.catch 静默处理)
|
||||
- `intelligenceClient.reflection.shouldReflect()` — 反射触发检查
|
||||
- `generateFollowUpSuggestions(content)` — 关键词建议生成 → `setSuggestions()`
|
||||
- 浏览器 TTS(如已启用)
|
||||
- `cancelStream()` — 取消当前流式响应
|
||||
- `setChatMode(mode: ChatModeType)` — 切换聊天模式
|
||||
- `getChatModeConfig()` — 获取当前模式配置
|
||||
- `setSuggestions(suggestions: string[])` — 设置建议列表
|
||||
|
||||
**批量保存机制:**
|
||||
```
|
||||
流式开始(用户消息 + assistant 占位已原子写入 messageStore)
|
||||
│
|
||||
├── dirty 标志管理
|
||||
│ └── 每次 delta/thinking/tool 更新后设置 dirty = true
|
||||
│
|
||||
├── 每 3 秒检查 dirty 标志
|
||||
│ └── dirty → conversationStore.upsertActiveConversation()
|
||||
│
|
||||
├── 每累积 50 条 delta 强制保存
|
||||
│
|
||||
├── onComplete → 立即保存 + 触发副作用
|
||||
│
|
||||
└── onError → 立即保存(保留已接收的部分内容)
|
||||
```
|
||||
|
||||
**跨 Store 同步契约**: streamStore 调用 messageStore 和 conversationStore 的方法均为同步 Zustand `set()` 调用。批量保存计时器(`setInterval`)在 Zustand 事务外运行,读取的 `messages` 始终是上一帧的完整快照——不存在部分写入的中间态。
|
||||
|
||||
**依赖:** streamStore → messageStore(更新流式消息)、conversationStore(保存对话)、offlineStore(离线队列)、connectionStore(选择 adapter)
|
||||
|
||||
### 3.5 artifactStore
|
||||
|
||||
**职责:** Artifact 面板管理(从现有 ChatStore 直接提取,无逻辑变更)
|
||||
|
||||
**状态:**
|
||||
- `artifacts: ArtifactFile[]`
|
||||
- `selectedArtifactId: string | null`
|
||||
- `artifactPanelOpen: boolean`
|
||||
|
||||
**Actions:**
|
||||
- `addArtifact(artifact)`, `selectArtifact(id)`, `setArtifactPanelOpen(open)`, `clearArtifacts()`
|
||||
|
||||
### 3.6 Facade 兼容层
|
||||
|
||||
保留 `chatStore.ts` 作为 re-export facade,确保渐进迁移:
|
||||
|
||||
```typescript
|
||||
// chatStore.ts (facade)
|
||||
export { useConversationStore } from './chat/conversationStore'
|
||||
export { useMessageStore } from './chat/messageStore'
|
||||
export { useStreamStore } from './chat/streamStore'
|
||||
export { useArtifactStore } from './chat/artifactStore'
|
||||
|
||||
// 兼容层 — 逐步迁移后删除
|
||||
export { useChatStore } from './chat/chatStoreCompat'
|
||||
```
|
||||
|
||||
`chatStoreCompat` 聚合所有子 Store 的状态和 actions 为统一的 `useChatStore` 接口,使现有组件无需修改即可继续工作。
|
||||
|
||||
## 4. 统一流式抽象层
|
||||
|
||||
### 4.1 StreamingAdapter 接口
|
||||
|
||||
文件: `desktop/src/lib/streaming-adapter.ts`
|
||||
|
||||
```typescript
|
||||
interface StreamCallbacks {
|
||||
onDelta: (delta: string) => void
|
||||
onThinkingDelta?: (delta: string) => void
|
||||
onToolStart?: (name: string, input: unknown) => void
|
||||
onToolEnd?: (name: string, output: unknown) => void
|
||||
onHandStart?: (name: string) => void
|
||||
onHandEnd?: (name: string, result: unknown) => void
|
||||
onWorkflowStart?: (workflowId: string) => void
|
||||
onWorkflowEnd?: (workflowId: string, result: unknown) => void
|
||||
onComplete: (tokens: TokenUsage) => void
|
||||
onError: (error: string) => void
|
||||
}
|
||||
|
||||
interface TokenUsage {
|
||||
inputTokens: number
|
||||
outputTokens: number
|
||||
}
|
||||
|
||||
interface StreamHandle {
|
||||
cancel(): void
|
||||
readonly active: boolean
|
||||
}
|
||||
|
||||
interface StreamingAdapter {
|
||||
start(
|
||||
agentId: string,
|
||||
message: string,
|
||||
sessionId: string,
|
||||
mode: ChatModeType,
|
||||
callbacks: StreamCallbacks
|
||||
): StreamHandle
|
||||
|
||||
isAvailable(): boolean
|
||||
}
|
||||
```
|
||||
|
||||
### 4.2 KernelStreamAdapter
|
||||
|
||||
封装现有 `kernel-chat.ts` 的 `chatStream` 方法:
|
||||
|
||||
- 监听 Tauri `stream:chunk` 事件
|
||||
- **TokenUsage 适配**: 现有 `kernel-types.ts` 的 `onComplete` 使用位置参数 `(inputTokens?: number, outputTokens?: number)`,KernelStreamAdapter 内部将其转换为 `TokenUsage` 对象 `{ inputTokens, outputTokens }`
|
||||
- 映射 `StreamChatEvent` 到 `StreamCallbacks`:
|
||||
| StreamChatEvent | StreamCallbacks |
|
||||
|----------------|-----------------|
|
||||
| `delta` | `onDelta(text)` |
|
||||
| `thinkingDelta` | `onThinkingDelta(thinking)` |
|
||||
| `tool_start` | `onToolStart(name, input)` |
|
||||
| `tool_end` | `onToolEnd(name, output)` |
|
||||
| `handStart` | `onHandStart(name)` |
|
||||
| `handEnd` | `onHandEnd(name, result)` |
|
||||
| `complete` | `onComplete({ inputTokens, outputTokens })` |
|
||||
| `error` | `onError(message)` |
|
||||
- 5 分钟超时(保持现有行为)
|
||||
- `cancel()` 调用新增的 Tauri command `cancel_stream(session_id)`
|
||||
- `iteration_start` 事件内部日志记录,不暴露到 callbacks
|
||||
|
||||
### 4.3 GatewayStreamAdapter
|
||||
|
||||
封装 GatewayClient 的 WebSocket 流式:
|
||||
|
||||
- 使用 GatewayClient 的 `chatStream` 方法
|
||||
- 映射 `AgentStreamDelta` 事件到 `StreamCallbacks`:
|
||||
| AgentStreamDelta | StreamCallbacks |
|
||||
|-----------------|-----------------|
|
||||
| `stream === 'assistant'` | `onDelta(content)` |
|
||||
| `stream === 'thinking'` | `onThinkingDelta(content)` |
|
||||
| `stream === 'tool'` + `step === 'start'` | `onToolStart(name, input)` |
|
||||
| `stream === 'tool'` + `step === 'end'` | `onToolEnd(name, output)` |
|
||||
| `stream === 'hand'` + `step === 'start'` | `onHandStart(name)` |
|
||||
| `stream === 'hand'` + `step === 'end'` | `onHandEnd(name, result)` |
|
||||
| `stream === 'workflow'` + `step === 'start'` | `onWorkflowStart(workflowId)` |
|
||||
| `stream === 'workflow'` + `step === 'end'` | `onWorkflowEnd(workflowId, result)` |
|
||||
| `stream === 'lifecycle'` + `phase === 'end'` | `onComplete(tokens)` |
|
||||
| `stream === 'error'` | `onError(message)` |
|
||||
- 新增 5 分钟超时(与 Kernel 统一)
|
||||
- `cancel()` 关闭 WebSocket 连接并发送取消消息
|
||||
- 统一 `onComplete` 签名,包含 TokenUsage 参数
|
||||
|
||||
### 4.4 适配器选择
|
||||
|
||||
`streamStore` 通过 `connectionStore.getClient()` 获取当前客户端实例,判断类型选择 adapter:
|
||||
|
||||
```typescript
|
||||
const client = getClient()
|
||||
const adapter = client instanceof KernelClient
|
||||
? kernelStreamAdapter
|
||||
: gatewayStreamAdapter
|
||||
```
|
||||
|
||||
## 5. 类型统一
|
||||
|
||||
### 5.1 统一 ChatMessage 类型
|
||||
|
||||
文件: `desktop/src/types/chat.ts`
|
||||
|
||||
```typescript
|
||||
interface ChatMessage {
|
||||
id: string
|
||||
role: 'user' | 'assistant' | 'tool' | 'hand' | 'workflow' | 'system'
|
||||
content: string
|
||||
timestamp: Date
|
||||
streaming?: boolean
|
||||
optimistic?: boolean
|
||||
// thinking
|
||||
thinkingContent?: string
|
||||
// error & retry
|
||||
error?: string
|
||||
originalContent?: string
|
||||
// tool/hand context
|
||||
toolSteps?: ToolStep[]
|
||||
handName?: string
|
||||
handStatus?: string
|
||||
handResult?: unknown
|
||||
// workflow
|
||||
workflowId?: string
|
||||
workflowStep?: number
|
||||
workflowStatus?: string
|
||||
workflowResult?: unknown
|
||||
// subtasks
|
||||
subtasks?: Subtask[]
|
||||
// attachments
|
||||
files?: MessageFile[]
|
||||
codeBlocks?: CodeBlock[]
|
||||
// metadata
|
||||
metadata?: {
|
||||
inputTokens?: number
|
||||
outputTokens?: number
|
||||
model?: string
|
||||
runId?: string
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5.2 辅助类型
|
||||
|
||||
```typescript
|
||||
// ToolStep 替代现有 ToolCallStep,统一命名
|
||||
interface ToolStep {
|
||||
id: string
|
||||
name: string
|
||||
status: 'running' | 'completed' | 'error'
|
||||
input?: unknown
|
||||
output?: unknown
|
||||
startTime: Date
|
||||
endTime?: Date
|
||||
}
|
||||
|
||||
interface Subtask {
|
||||
id: string
|
||||
title: string
|
||||
status: 'pending' | 'in_progress' | 'completed' | 'failed'
|
||||
result?: unknown
|
||||
}
|
||||
|
||||
interface SendMessageContext {
|
||||
files?: MessageFile[]
|
||||
parentMessageId?: string
|
||||
}
|
||||
```
|
||||
|
||||
### 5.3 SessionMessage → ChatMessage 映射
|
||||
|
||||
Gateway 路径的会话历史使用 `SessionMessage`(API 响应格式,字符串日期),需要映射函数:
|
||||
|
||||
```typescript
|
||||
// desktop/src/types/chat.ts
|
||||
function sessionToChatMessage(sm: SessionMessage): ChatMessage {
|
||||
return {
|
||||
id: sm.id,
|
||||
role: mapSessionRole(sm.role), // 'user'/'assistant'/'system' 直接映射
|
||||
content: sm.content,
|
||||
timestamp: new Date(sm.timestamp),
|
||||
metadata: {
|
||||
model: sm.metadata?.model,
|
||||
inputTokens: sm.metadata?.tokens?.input,
|
||||
outputTokens: sm.metadata?.tokens?.output,
|
||||
},
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`sessionStore` 内部继续使用 API 响应的 `SessionMessage` 类型(它是 API 契约),仅在展示层转换为 `ChatMessage`。不需要修改 `sessionStore` 的内部类型。
|
||||
|
||||
### 5.4 类型清理
|
||||
|
||||
| 类型 | 文件 | 动作 |
|
||||
|------|------|------|
|
||||
| `ChatStore.Message` | `store/chatStore.ts` | 迁移到 `types/chat.ts` 的 `ChatMessage` |
|
||||
| `ConversationContext` | `components/ai/Conversation.tsx` | 仅删除未使用的 Provider/Context/hook,保留滚动容器组件 |
|
||||
| `initStreamListener` | `store/chatStore.ts` | 被 `streamStore` + `StreamingAdapter` 替代 |
|
||||
|
||||
## 6. Cancel 机制
|
||||
|
||||
### 6.1 前端(Phase 5a — 纯前端取消)
|
||||
|
||||
前端 cancel 分两步实现,先纯前端方案(Phase 5a),后端配合后完善(Phase 5b)。
|
||||
|
||||
```typescript
|
||||
// streamStore
|
||||
cancelStream() {
|
||||
if (this.streamHandle?.active) {
|
||||
this.streamHandle.cancel()
|
||||
}
|
||||
// 标记当前流式消息为已完成
|
||||
const streamingMsg = messageStore.getStreamingMessage()
|
||||
if (streamingMsg) {
|
||||
messageStore.updateMessage(streamingMsg.id, {
|
||||
streaming: false,
|
||||
content: streamingMsg.content + '\n\n_(响应已取消)_'
|
||||
})
|
||||
}
|
||||
set({ isStreaming: false, streamHandle: null })
|
||||
conversationStore.upsertActiveConversation() // 立即保存
|
||||
}
|
||||
```
|
||||
|
||||
**KernelStreamAdapter.cancel()**(Phase 5a 纯前端):
|
||||
- 停止监听 Tauri `stream:chunk` 事件(移除 listener)
|
||||
- 不通知后端停止,后端继续运行直到自然完成或 5 分钟超时
|
||||
- 前端标记消息为已取消,用户可继续操作
|
||||
|
||||
**GatewayStreamAdapter.cancel()**(Phase 5a 纯前端):
|
||||
- 发送 WebSocket 取消消息 `{ type: "cancel", sessionId }`(如果 Gateway 服务端已支持则生效)
|
||||
- 关闭事件监听器
|
||||
|
||||
### 6.2 后端配合(Phase 5b — 需新基础设施)
|
||||
|
||||
Phase 5b 在后端基础设施就绪后实施。需要在 Tauri 端新增:
|
||||
|
||||
1. **SessionStreamGuards 状态**: 在 `lib.rs` 中注册 `DashMap<String, Arc<AtomicBool>>` 作为 Tauri managed state
|
||||
2. **cancel_stream command**: 读取 guards map,设置取消标志
|
||||
3. **流式循环检查**: `tokio::spawn` 内每轮迭代检查 `cancel_flag`
|
||||
|
||||
```rust
|
||||
// chat.rs — 取消标志写入
|
||||
#[tauri::command]
|
||||
async fn cancel_stream(
|
||||
session_id: String,
|
||||
guards: State<'_, SessionStreamGuards>,
|
||||
) -> Result<(), String> {
|
||||
if let Some(pair) = guards.0.get(&session_id) {
|
||||
pair.value().store(true, Ordering::SeqCst);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// agent_chat_stream — 在每轮接收循环中检查
|
||||
let cancelled = cancel_flag.load(Ordering::Relaxed);
|
||||
if cancelled {
|
||||
tx.send(LoopEvent::Complete(AgentLoopResult {
|
||||
response: "...".into(),
|
||||
input_tokens: 0,
|
||||
output_tokens: 0,
|
||||
iterations,
|
||||
})).ok();
|
||||
break;
|
||||
}
|
||||
```
|
||||
|
||||
Phase 5a 和 5b 可独立交付,Phase 5a 即可满足用户需求。
|
||||
|
||||
## 7. 迁移计划
|
||||
|
||||
### 7.1 迁移顺序
|
||||
|
||||
| Phase | 内容 | 影响文件数 | 风险 |
|
||||
|-------|------|-----------|------|
|
||||
| 0 | 创建 `types/chat.ts` + `store/chat/` 目录 | 新建 | 无 |
|
||||
| 1 | 提取 `artifactStore` | ~5 | 极低 |
|
||||
| 2 | 提取 `conversationStore`(含 agents、sessionKey) | ~8 | 低 |
|
||||
| 3 | 提取 `messageStore`(含 token 统计、search) | ~10 | 中 |
|
||||
| 4 | 提取 `streamStore` + `StreamingAdapter`(含离线检查、副作用、suggestions) | ~12 | 中高 |
|
||||
| 5a | 前端 cancel(纯前端,停止监听+标记已取消) | ~3 | 低 |
|
||||
| 5b | 后端 cancel(Rust SessionStreamGuards + cancel_stream command) | ~4 | 中 |
|
||||
| 6 | 实现定时批量持久化(IndexedDB + 配额检查) | ~4 | 中 |
|
||||
| 7 | 删除旧代码 + 清理 facade | ~6 | 低 |
|
||||
| 8 | 删除死代码(ConversationContext、旧类型映射) | ~4 | 低 |
|
||||
|
||||
### 7.2 每阶段验证
|
||||
|
||||
每个 Phase 完成后执行:
|
||||
|
||||
1. `pnpm tsc --noEmit` — 类型检查通过
|
||||
2. `pnpm vitest run` — 现有测试通过
|
||||
3. 手动验证: 发送消息 → 流式响应 → 切换对话 → 刷新页面数据保持
|
||||
4. 手动验证(Phase 5 后): cancel 流式响应 → 状态正确恢复
|
||||
|
||||
### 7.3 关键文件清单
|
||||
|
||||
| 文件 | 角色 |
|
||||
|------|------|
|
||||
| `desktop/src/store/chatStore.ts` | 重构对象,最终保留为 facade |
|
||||
| `desktop/src/store/chat/conversationStore.ts` | 新建 |
|
||||
| `desktop/src/store/chat/messageStore.ts` | 新建 |
|
||||
| `desktop/src/store/chat/streamStore.ts` | 新建 |
|
||||
| `desktop/src/store/chat/artifactStore.ts` | 新建 |
|
||||
| `desktop/src/store/chat/chatStoreCompat.ts` | 新建(兼容层,最终删除) |
|
||||
| `desktop/src/lib/streaming-adapter.ts` | 新建(StreamingAdapter 接口 + 双实现) |
|
||||
| `desktop/src/lib/kernel-chat.ts` | 修改(KernelStreamAdapter 封装) |
|
||||
| `desktop/src/types/chat.ts` | 新建(ChatMessage + ToolStep + Subtask) |
|
||||
| `desktop/src/types/session.ts` | 保留(API 契约类型),添加映射函数 |
|
||||
| `desktop/src/components/ChatArea.tsx` | 逐步迁移 import |
|
||||
| `desktop/src/components/ai/Conversation.tsx` | 清理死代码(仅 Context/Provider) |
|
||||
| `desktop/src/store/index.ts` | 注册新 Store |
|
||||
| `desktop/src/store/offlineStore.ts` | 不修改,streamStore 调用其 API |
|
||||
| `desktop/src-tauri/src/kernel_commands/chat.rs` | Phase 5b: 新增 cancel_stream + SessionStreamGuards |
|
||||
| `desktop/src-tauri/src/lib.rs` | Phase 5b: 注册 cancel_stream + guards state |
|
||||
|
||||
## 8. streamStore 完成后副作用
|
||||
|
||||
`streamStore.sendMessage` 的 `onComplete` 回调在流式响应完成后触发以下副作用。这些副作用在 `streamStore` 内部处理,不属于 `StreamingAdapter` 的职责。
|
||||
|
||||
```typescript
|
||||
// streamStore 内部 onComplete 处理
|
||||
async function handleComplete(tokens: TokenUsage) {
|
||||
// 1. 更新消息状态
|
||||
messageStore.completeMessage(streamingMsgId, tokens)
|
||||
|
||||
// 2. 立即持久化
|
||||
conversationStore.upsertActiveConversation()
|
||||
|
||||
// 3. 记忆提取(非阻塞,失败静默)
|
||||
try {
|
||||
const extractor = getMemoryExtractor()
|
||||
if (extractor) {
|
||||
await extractor.extractFromConversation(
|
||||
conversationStore.getCurrentConversation()
|
||||
)
|
||||
}
|
||||
} catch (e) {
|
||||
logger.warn('Memory extraction failed', e)
|
||||
}
|
||||
|
||||
// 4. 对话反思跟踪(非阻塞)
|
||||
try {
|
||||
const client = getIntelligenceClient()
|
||||
if (client?.reflection) {
|
||||
await client.reflection.recordConversation(...)
|
||||
const shouldReflect = await client.reflection.shouldReflect(...)
|
||||
if (shouldReflect) {
|
||||
// 触发反思流程
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
logger.warn('Reflection tracking failed', e)
|
||||
}
|
||||
|
||||
// 5. 后续建议生成
|
||||
const suggestions = generateFollowUpSuggestions(lastAssistantContent)
|
||||
set({ suggestions })
|
||||
|
||||
// 6. 语音朗读(如果用户开启)
|
||||
if (speechSettings.autoSpeak) {
|
||||
speechSynth.speak(lastAssistantContent)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 9. 离线队列集成
|
||||
|
||||
`streamStore.sendMessage` 在发起流式请求之前检查离线状态:
|
||||
|
||||
```typescript
|
||||
async sendMessage(content: string, context?: SendMessageContext) {
|
||||
// 1. 离线检查(优先级最高)
|
||||
// 注意:isOffline 是 boolean 属性(不是函数),queueMessage 使用位置参数
|
||||
const { isOffline, queueMessage } = useOfflineStore.getState()
|
||||
if (isOffline) {
|
||||
const userMsg = createUserMessage(content, context?.files)
|
||||
messageStore.addMessage(userMsg)
|
||||
messageStore.addMessage(createSystemMessage('消息已加入离线队列,网络恢复后将自动发送'))
|
||||
queueMessage(content, conversationStore.currentAgent?.id, conversationStore.sessionKey ?? undefined)
|
||||
conversationStore.upsertActiveConversation()
|
||||
return // 不继续流式请求
|
||||
}
|
||||
|
||||
// 2. 正常流式流程...
|
||||
}
|
||||
```
|
||||
|
||||
## 10. streamStore 状态补充
|
||||
|
||||
`suggestions` 和 `chatMode` 归属 streamStore:
|
||||
|
||||
**状态:**
|
||||
- `suggestions: string[]`
|
||||
- `chatMode: ChatModeType`
|
||||
|
||||
**Actions:**
|
||||
- `setSuggestions(suggestions: string[])` — 设置后续建议
|
||||
- `setChatMode(mode: ChatModeType)` — 切换聊天模式
|
||||
- `getChatModeConfig()` — 获取当前模式配置
|
||||
- `searchSkills(query: string)` — 委托给 `getSkillDiscovery().searchSkills()`
|
||||
|
||||
`totalInputTokens`/`totalOutputTokens` 为仅会话内累计(不持久化),刷新后重置为 0。
|
||||
|
||||
## 11. 兼容层迁移指南
|
||||
|
||||
`chatStoreCompat.ts` 聚合子 Store 为统一的 `useChatStore` 接口,确保现有 19 个消费者文件无需修改。
|
||||
|
||||
```typescript
|
||||
// chatStoreCompat.ts — 使用 Zustand subscribe 保持响应式
|
||||
// 注意:不能在 create() 中直接 .getState(),那样只会读取初始值不会响应变化
|
||||
import { subscribe } from 'zustand'
|
||||
|
||||
// 方案:直接 re-export 子 Store,组件按需导入
|
||||
export { useConversationStore as useConversationStore } from './chat/conversationStore'
|
||||
export { useMessageStore as useMessageStore } from './chat/messageStore'
|
||||
export { useStreamStore as useStreamStore } from './chat/streamStore'
|
||||
export { useArtifactStore as useArtifactStore } from './chat/artifactStore'
|
||||
|
||||
// 兼容 hook:聚合所有子 store 状态供旧组件使用
|
||||
// 使用 useSyncExternalStore 或每个子 store 的独立 hook 组合
|
||||
export function useChatStore<T>(selector: (state: ChatCompatState) => T): T {
|
||||
// 方案 A(推荐):组件直接从子 store 导入
|
||||
// 方案 B(过渡期):聚合 hook,内部使用多个 useSelector
|
||||
const conv = useConversationStore(selector)
|
||||
const msg = useMessageStore(selector)
|
||||
const stream = useStreamStore(selector)
|
||||
const art = useArtifactStore(selector)
|
||||
return selector({ ...conv, ...msg, ...stream, ...art })
|
||||
}
|
||||
```
|
||||
|
||||
> **重要**:兼容层是过渡性代码,仅保证旧组件可编译运行。新代码必须直接使用子 Store。每个 Phase 迁移一部分组件后,兼容层逐步缩小。最终删除。
|
||||
|
||||
迁移方式:每 Phase 完成后,逐步将组件的 `import { useChatStore } from './chatStore'` 改为直接从子 Store 导入。最终删除 `chatStoreCompat.ts`。
|
||||
|
||||
## 12. 不在本次范围内
|
||||
|
||||
以下项目明确排除,作为后续迭代考虑:
|
||||
|
||||
- **消息分页/懒加载**(当前所有消息全量在内存,Phase 2 考虑)
|
||||
- **文件真实上传**(当前附件是伪文本标记,需后端配合)
|
||||
- **TitleMiddleware 实现**(后端 placeholder,需 LLM driver 接入)
|
||||
- **消息导出增强**(当前仅 Markdown 导出)
|
||||
Reference in New Issue
Block a user