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>
681 lines
23 KiB
Markdown
681 lines
23 KiB
Markdown
# 行业知识库功能设计
|
||
|
||
> 日期: 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 模型后触发重建,验证向量正确更新
|