【AI应用】海兰知识库 RAG 智能问答系统 — 详细设计(二)

📅 2026/7/13 15:40:29 👁️ 阅读次数 📝 编程学习
【AI应用】海兰知识库 RAG 智能问答系统 — 详细设计(二)

4. 核心模块详解

4.1 RAG 核心引擎(rag.ts)

rag.ts是系统的“大脑”,它将文档索引、语义检索和对话生成三个环节串成一条完整的流水线。

4.1.1 文档索引流程(如何让机器“读懂”文档?)

当管理员上传一份文档后,系统会异步执行以下步骤,将原始文件转化为可检索的向量:

PDF

DOCX

TXT/MD/CSV

用户上传文档

创建 Document 记录
status = PENDING

异步解析 parseDocument

文件类型

pdf-parse 提取文本

mammoth 提取纯文本

Buffer.toString 读取

更新 Document.content

文本分块 splitText
chunkSize=500 tokens

批量 Embedding embedBatch
BATCH_SIZE=10

存储向量到 DocumentSegment.embedding

更新 Document
status=COMPLETED, segmentCount

完成

关键设计决策

  • 异步处理:上传接口立即返回,索引过程在后台进行,避免阻塞用户操作。
  • 批量 Embedding:每 10 个切片调用一次模型 API,减少请求次数。
  • 容错降级:若批量调用某条失败,自动降级为逐条重试,确保其他切片不受影响。
  • 清理:任意步骤失败时,删除该文档已创建的分段,并将 Document 标记为FAILED

如下图所示:

4.1.2 检索流程(如何从知识库中找到相关内容?)

当用户提出一个问题时,系统需要从成千上万个文本片段中快速找出最相关的几个。

计算细节

  • 余弦相似度公式:cos(θ) = (A·B) / (||A|| * ||B||),值越接近 1 表示越相关。
  • 当前为全表扫描 + 内存计算,适合万级分段规模(详见第 9 章性能设计)。
4.1.3 对话流程(如何生成有据可依的回答?)

这是用户最常使用的功能——提问并获得带引用的回答:

数据库大模型检索模块ragChatStreamUser数据库大模型检索模块ragChatStreamUseralt[绑定了知识库][未绑定知识库]loop[流式生成]发送消息 + 会话ID加载最近20条历史消息执行 retrieve(query)返回相关片段 (sources)构建增强 System Prompt注入参考资料使用普通 System PromptchatCompletionStream(messages)逐字增量 deltaSSE 事件: dataSSE 事件: sources (引用列表)SSE 事件: done保存 Assistant 消息含 sources, latencyMs

亮点

  • 流式输出:用户能实时看到 AI 逐字生成,体验流畅。
  • 引用溯源:回答中会附带sources事件,展示哪些文档片段被引用,增强可信度。
  • 上下文窗口控制:仅加载最近 10 轮对话(20 条消息),避免 Token 溢出。

4.2 向量存储与相似度检索(vector-store.ts)

由于 SQLite 不具备原生向量索引能力,系统采用了一种轻量级混合方案:将向量序列化为 JSON 字符串存储在DocumentSegment.embedding字段,检索时在内存中实时计算相似度。

存储格式
embedding = "[0.12, -0.05, 0.98, ...]" // 维度由模型决定(如 256、768、1536)
检索过滤策略(层层筛选,保证质量)
  1. 知识库隔离:只检索指定知识库下的分段。
  2. 非空过滤:跳过embedding为空的记录。
  3. 启用态检查:分段和所属文档均需处于enabled = true
  4. 相似度阈值:剔除分数低于similarityThreshold的结果。
  5. 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)

系统支持多种格式的文档解析,统一输出为纯文本:

格式解析库额外处理
PDFpdf-parse提取全文,记录页数
Word (.docx)mammoth转换为纯文本,保留段落结构
TXT / CSVBuffer.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。
分块策略(句子级 + 动态合并)
  1. 切分句子:按句号、问号、感叹号、换行符拆分。
  2. 动态合并:按顺序累积句子,直到 Token 数超过chunkSize(500),形成一块。
  3. 超长句子硬切:单句若超过 1.5 倍 chunkSize,按字符强制切分。
  4. 尾部合并:不足minChunkLengthToEmbed(5 Token)的尾块合并到前一块。
  5. 上限保护:单文档最多生成 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哈希存储,建议生产环境升级为bcryptargon2并加盐,以抵抗暴力破解。

权限控制矩阵
角色可访问功能
ADMIN用户管理、模型配置、知识库 CRUD、文档上传/删除、角色管理、API Key 管理、系统配置
USER查看知识库列表、对话聊天、管理自己的会话历史
  • 接口级权限通过requireUser()/requireAdmin()中间件函数控制。
  • 资源级权限:普通用户只能查询自己的会话和消息。
默认数据种子(首次启动自动创建)
类型名称凭证角色
管理员admin@hylan.comadmin123ADMIN
普通用户user@hylan.comuser123USER
模型名称Provider类型
默认对话GLM-4.6 (Built-in)ZAICHAT
默认 EmbeddingEmbedding-3 (Built-in Local)ZAIEMBEDDING
角色名称说明
默认助手默认助手通用 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/knowledgeADMIN创建知识库
GET/api/documents已登录文档列表(支持筛选)
POST/api/documentsADMIN上传文档(异步索引)
DELETE/api/documents/[id]ADMIN删除文档
POST/api/documents/[id]/reindexADMIN重新索引
GET/api/documents/[id]/segments已登录查看文档分段
GET/api/modelsADMIN模型列表
POST/api/modelsADMIN添加模型
GET/api/roles已登录角色列表
POST/api/rolesADMIN创建角色
GET/api/conversations已登录会话列表
POST/api/conversations已登录创建会话
POST/api/chat已登录SSE 流式对话(核心接口)
GET/api/recall-testADMIN检索测试(调试用)
GET/api/usersADMIN用户列表
GET/api/openapi-keysADMINAPI Key 列表
POST/api/openapi-keysADMIN生成 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已登录查看分段内容和向量
召回测试RecallTestViewADMIN输入查询测试检索效果
模型配置ModelsViewADMIN添加/编辑/删除 LLM 模型
角色管理RolesViewADMIN创建角色、绑定知识库
API Key 管理OpenApiViewADMIN生成/吊销 API Key
会话历史ConversationsView已登录全部历史会话列表
用户管理UsersViewADMIN用户 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.jsonsrc/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 单文件,可通过文件复制实现全量备份