【AI应用】海兰知识库 RAG 智能问答系统 — 详细设计(二)
4. 核心模块详解
4.1 RAG 核心引擎(rag.ts)
rag.ts是系统的“大脑”,它将文档索引、语义检索和对话生成三个环节串成一条完整的流水线。
4.1.1 文档索引流程(如何让机器“读懂”文档?)
当管理员上传一份文档后,系统会异步执行以下步骤,将原始文件转化为可检索的向量:
关键设计决策:
- 异步处理:上传接口立即返回,索引过程在后台进行,避免阻塞用户操作。
- 批量 Embedding:每 10 个切片调用一次模型 API,减少请求次数。
- 容错降级:若批量调用某条失败,自动降级为逐条重试,确保其他切片不受影响。
- 清理:任意步骤失败时,删除该文档已创建的分段,并将 Document 标记为
FAILED。
如下图所示:
4.1.2 检索流程(如何从知识库中找到相关内容?)
当用户提出一个问题时,系统需要从成千上万个文本片段中快速找出最相关的几个。
计算细节:
- 余弦相似度公式:
cos(θ) = (A·B) / (||A|| * ||B||),值越接近 1 表示越相关。 - 当前为全表扫描 + 内存计算,适合万级分段规模(详见第 9 章性能设计)。
4.1.3 对话流程(如何生成有据可依的回答?)
这是用户最常使用的功能——提问并获得带引用的回答:
亮点:
- 流式输出:用户能实时看到 AI 逐字生成,体验流畅。
- 引用溯源:回答中会附带
sources事件,展示哪些文档片段被引用,增强可信度。 - 上下文窗口控制:仅加载最近 10 轮对话(20 条消息),避免 Token 溢出。
4.2 向量存储与相似度检索(vector-store.ts)
由于 SQLite 不具备原生向量索引能力,系统采用了一种轻量级混合方案:将向量序列化为 JSON 字符串存储在DocumentSegment.embedding字段,检索时在内存中实时计算相似度。
存储格式
embedding = "[0.12, -0.05, 0.98, ...]" // 维度由模型决定(如 256、768、1536)检索过滤策略(层层筛选,保证质量)
- 知识库隔离:只检索指定知识库下的分段。
- 非空过滤:跳过
embedding为空的记录。 - 启用态检查:分段和所属文档均需处于
enabled = true。 - 相似度阈值:剔除分数低于
similarityThreshold的结果。 - Top-K 截断:按相似度降序取前 K 个(默认 5)。
4.3 LLM 客户端封装(llm.ts)
llm.ts是对不同 LLM 提供商的统一抽象,屏蔽了各家 API 的差异,提供一致的聊天和 Embedding 接口。
支持的提供商矩阵
| Provider | 聊天(流式) | Embedding | 特点 |
|---|---|---|---|
| ZAI(内置) | ✅ | ✅(本地哈希) | 无需 API Key,开箱即用 |
| DeepSeek | ✅ | ✅ | 国产高性能,OpenAI 兼容 |
| OpenAI | ✅ | ✅ | 官方 API,生态完善 |
| Ollama | ✅ | ❌ | 本地私有化部署,数据不出域 |
| CUSTOM | ✅ | ✅ | 任意 OpenAI 兼容端点 |
内置 ZAI Embedding 的原理
由于z-ai-web-dev-sdk未直接提供 Embedding 接口,我们实现了一套基于词袋 + 哈希的本地向量化方法:
- 分词:CJK 字符逐字切分,英文按单词切分,并提取 bigram(双词组合)增强语义。
- 哈希映射:使用 FNV-1a 哈希将 token 映射到 256 维索引。
- TF 加权:每个维度的值为
log(1 + 词频),高频词权重更高。 - L2 归一化:确保向量模长为 1,便于余弦相似度计算。
⚠️注意:本地 Embedding 本质上是关键词重叠相似度,并非真正的语义相似度。生产环境建议配置 DeepSeek / OpenAI 等外部模型以获得更精准的检索效果。
错误处理与超时
- HTTP 错误映射:将 401、404、429 等状态码转换为友好提示。
- 超时保护:
withTimeout包装器防止请求挂死。 - 流式容错:解析到畸形 chunk 时跳过,不中断整个生成流。
4.4 文档解析(document-parser.ts)
系统支持多种格式的文档解析,统一输出为纯文本:
| 格式 | 解析库 | 额外处理 |
|---|---|---|
pdf-parse | 提取全文,记录页数 | |
| Word (.docx) | mammoth | 转换为纯文本,保留段落结构 |
| TXT / CSV | Buffer.toString | 直接读取 |
| Markdown | 正则清洗 | 去除标题标记、粗体/斜体、代码块、链接语法,保留纯文本 |
Markdown 清洗示例:
# 标题→标题**粗体**→粗体[链接](url)→链接- 连续空行合并为单个换行。
4.5 文本分块(text-splitter.ts)
将长文档切分为适合 Embedding 的短片段(每个约 500 Token),是 RAG 系统的基础环节。
Token 估算算法(中英文统一)
tokens ≈ ceil( CJK字符数 / 1.5 + 其他字符数 / 4 )- CJK(中日韩)字符信息密度高,按 1.5 字符 ≈ 1 Token。
- 英文按 4 字符 ≈ 1 Token。
分块策略(句子级 + 动态合并)
- 切分句子:按句号、问号、感叹号、换行符拆分。
- 动态合并:按顺序累积句子,直到 Token 数超过
chunkSize(500),形成一块。 - 超长句子硬切:单句若超过 1.5 倍 chunkSize,按字符强制切分。
- 尾部合并:不足
minChunkLengthToEmbed(5 Token)的尾块合并到前一块。 - 上限保护:单文档最多生成 10000 个分段,防止恶意文件导致资源耗尽。
4.6 认证与授权(auth.ts)
认证机制(有状态签名 Token)
系统采用类似 JWT 的有状态签名 Token,兼具无状态部署的便利和一定的安全性:
Token = base64url(payload) + "." + HMAC-SHA256(secret, base64url(payload)) payload = { uid: userId, exp: Date.now() + 7d }- 存储:HTTP Only Cookie(名称为
hylan_session),防止 XSS 窃取。 - 有效期:7 天,自动续期。
- 优点:无需服务端维护 Session 表,支持开发模式热重启不丢登录。
- 局限:服务端无法主动吊销(需客户端清除 Cookie 或引入黑名单)。
密码安全
当前使用SHA-256哈希存储,建议生产环境升级为bcrypt或argon2并加盐,以抵抗暴力破解。
权限控制矩阵
| 角色 | 可访问功能 |
|---|---|
| ADMIN | 用户管理、模型配置、知识库 CRUD、文档上传/删除、角色管理、API Key 管理、系统配置 |
| USER | 查看知识库列表、对话聊天、管理自己的会话历史 |
- 接口级权限通过
requireUser()/requireAdmin()中间件函数控制。 - 资源级权限:普通用户只能查询自己的会话和消息。
默认数据种子(首次启动自动创建)
| 类型 | 名称 | 凭证 | 角色 |
|---|---|---|---|
| 管理员 | admin@hylan.com | admin123 | ADMIN |
| 普通用户 | user@hylan.com | user123 | USER |
| 模型 | 名称 | Provider | 类型 |
|---|---|---|---|
| 默认对话 | GLM-4.6 (Built-in) | ZAI | CHAT |
| 默认 Embedding | Embedding-3 (Built-in Local) | ZAI | EMBEDDING |
| 角色 | 名称 | 说明 |
|---|---|---|
| 默认助手 | 默认助手 | 通用 AI 助手,未绑定知识库 |
5. API 层设计
5.1 RESTful API 概览
系统提供完整的 RESTful 接口,覆盖前端所有操作需求:
| 方法 | 端点 | 权限 | 功能 |
|---|---|---|---|
| GET | /api/auth/me | 已登录 | 获取当前用户信息 |
| POST | /api/auth/login | 公开 | 邮箱密码登录 |
| POST | /api/auth/logout | 已登录 | 登出 |
| GET | /api/knowledge | 已登录 | 知识库列表 |
| POST | /api/knowledge | ADMIN | 创建知识库 |
| GET | /api/documents | 已登录 | 文档列表(支持筛选) |
| POST | /api/documents | ADMIN | 上传文档(异步索引) |
| DELETE | /api/documents/[id] | ADMIN | 删除文档 |
| POST | /api/documents/[id]/reindex | ADMIN | 重新索引 |
| GET | /api/documents/[id]/segments | 已登录 | 查看文档分段 |
| GET | /api/models | ADMIN | 模型列表 |
| POST | /api/models | ADMIN | 添加模型 |
| GET | /api/roles | 已登录 | 角色列表 |
| POST | /api/roles | ADMIN | 创建角色 |
| GET | /api/conversations | 已登录 | 会话列表 |
| POST | /api/conversations | 已登录 | 创建会话 |
| POST | /api/chat | 已登录 | SSE 流式对话(核心接口) |
| GET | /api/recall-test | ADMIN | 检索测试(调试用) |
| GET | /api/users | ADMIN | 用户列表 |
| GET | /api/openapi-keys | ADMIN | API Key 列表 |
| POST | /api/openapi-keys | ADMIN | 生成 API Key |
5.2 OpenAI 兼容 API
为方便与 LobeChat、ChatGPT-Next-Web、Dify 等第三方工具集成,系统提供标准的 OpenAI Chat Completions 接口。
认证
Authorization: Bearer sk-hylan-xxxxxx端点
POST /api/v1/chat/completions请求体(标准字段 + 扩展)
{"model":"deepseek-chat","messages":[{"role":"user","content":"公司年假政策是什么?"}],"temperature":0.7,"max_tokens":2048,"stream":true,// ----- 海兰扩展字段 -----"knowledge_base_id":"xxx",// 指定知识库"role_id":"xxx",// 指定角色"top_k":5,"threshold":0.7}响应(流式 SSE)
data: {"id":"chatcmpl-xxx","object":"chat.completion.chunk","choices":[{"delta":{"content":"根据"}}]} data: {"id":"chatcmpl-xxx","object":"chat.completion.chunk","choices":[{"delta":{"content":"公司规定"}}]} ... data: [DONE]非流式则返回标准chat.completionJSON 对象。
6. 前端架构设计
6.1 单页应用(SPA)模式
系统虽然基于 Next.js 全栈框架,但前端采用SPA 模式运行,避免页面刷新带来的闪烁,提升交互流畅度。
- 唯一的页面入口是
src/app/page.tsx,通过view状态切换不同组件。 - 所有视图组件位于
src/components/views/,各司其职。
6.2 视图组件清单
| 视图 | 组件 | 权限 | 功能 |
|---|---|---|---|
| 仪表盘 | DashboardView | 已登录 | 统计卡片、最近会话、文档状态分布 |
| 对话 | ChatView | 已登录 | 流式对话、引用溯源、历史记录 |
| 知识库 | KnowledgeView | 已登录 | 知识库 CRUD、绑定 Embedding 模型 |
| 文档管理 | DocumentsView | 已登录 | 上传文档、查看状态、重新索引 |
| 分段查看 | SegmentsView | 已登录 | 查看分段内容和向量 |
| 召回测试 | RecallTestView | ADMIN | 输入查询测试检索效果 |
| 模型配置 | ModelsView | ADMIN | 添加/编辑/删除 LLM 模型 |
| 角色管理 | RolesView | ADMIN | 创建角色、绑定知识库 |
| API Key 管理 | OpenApiView | ADMIN | 生成/吊销 API Key |
| 会话历史 | ConversationsView | 已登录 | 全部历史会话列表 |
| 用户管理 | UsersView | ADMIN | 用户 CRUD |
| 登录 | LoginView | 公开 | 邮箱密码登录 |
6.3 状态管理策略
- 全局状态(Zustand):存储用户认证信息、当前视图、上下文 ID,并支持持久化到 localStorage(国际化设置)。
- 服务端状态(TanStack Query):缓存各视图的列表数据,支持自动刷新、乐观更新。
- 本地状态(React useState):用于表单输入、弹窗开关、加载态等短暂 UI 状态。
6.4 UI 组件体系
基于shadcn/ui构建,组件覆盖:
- 布局类:Sheet、Sidebar、Resizable Panels、Scroll Area
- 数据录入:Input、Textarea、Select、Checkbox、Slider、Switch
- 反馈类:Toast、Alert Dialog、Progress、Skeleton
- 导航类:Tabs、Breadcrumb、Command(CMDK)
- 数据展示:Table、Accordion、Chart(Recharts)
6.5 国际化(i18n)
自研轻量级 i18n 方案,通过 Zustand + persist 实现语言切换持久化:
- 语言包位于
src/messages/zh.json和src/messages/en.json - 用户可在头像下拉菜单中一键切换
- 默认语言为中文
7. 部署架构
7.1 独立部署模式(Standalone)
Next.js 的output: 'standalone'选项生成自包含的运行时,无需安装node_modules即可运行:
.next/standalone/ ├── server.js # 入口文件 ├── .next/static/ # 静态资源 └── public/ # 公共文件生产启动命令:bun .next/standalone/server.js
7.2 Caddy 反向代理
项目附带Caddyfile,提供:
- 默认反向代理到
localhost:3000 - 支持通过
XTransformPort查询参数动态转发端口(适用于多实例场景) - 自动转发真实客户端 IP(
X-Forwarded-For,X-Real-IP) - 结合 Caddy 的自动 HTTPS,一键获得生产级 TLS 加密
8. 安全设计
| 安全维度 | 措施 |
|---|---|
| 文件上传 | 限制最大 50MB,扩展名白名单(pdf/docx/txt/md/csv/tsv) |
| SQL 注入 | 全部查询通过 Prisma 参数化,杜绝字符串拼接 |
| XSS 防护 | React 默认转义;富文本通过 MDX Editor 受控渲染 |
| Cookie 安全 | 签名使用 HMAC-SHA256,防止篡改 |
| 权限最小化 | 普通用户无法访问管理接口(返回 403) |
| API Key 隔离 | 每个 Key 可独立绑定默认角色和知识库,支持过期时间 |
| 密码哈希 | SHA-256(建议生产升级为 bcrypt + salt) |
| 数据备份 | SQLite 单文件,可通过文件复制实现全量备份 |
