Files
2026-09-03 15:25:28 +08:00

288 lines
18 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
好# rag-local 本地知识库
纯本地的 RAG(检索增强生成)知识库系统,基于 SQLite 全栈文件型存储,无外部数据库依赖。面向 NAS / 小主机等边缘设备单机部署。
## 功能
- **文档流水线**:上传 txt / md / pdf / docx / html → 自动解析 → 四阶段分块(结构识别 → 标题感知 → 语义 → 递归兜底)→ 向量化 + 全文索引,6 态解析状态机全程可见
- **混合检索**sqlite-vec 向量 KNN + FTS5 全文 BM25RRF 融合排序 + LLM 二次重排,中文 gse 分词
- **RAG 问答**:SSE 流式对话,检索引用(含来源与得分)随回答展示,会话历史持久化
- **知识图谱**:解析时 LLM 抽取实体与关系,问答时实体链接 + 一跳邻居注入提示词,图谱页力导向图可视化
- **合同法律条款标注**:上传合同自动按条款切分,逐条款标注对应法条(0-10 分 + 理由),可导出标注版 HTML(打印/另存 PDF)
- **模型可配置**:对话 / 向量模型均为 OpenAI 兼容 API,可对接 Ollama、DeepSeek、硅基流动、OpenAI 等任意供应商
- **单用户门禁**:启动生成访问令牌(重启即换新),登录后 JWT 鉴权
## 技术栈
| 层 | 技术 |
|---|---|
| 后端 | Go + GoFrame v2 + EinoLLM 编排) |
| 存储 | SQLitemodernc 纯 Go 驱动 + sqlite-vec 扩展) |
| 全文 | SQLite FTS5 + gse 中文分词 |
| 前端 | Vue 3 + Element Plus + Vite(前后端不分离,单端口) |
| 部署 | Docker Compose 一键启动 |
## 总体架构
```
┌─────────────────────────── 浏览器 ───────────────────────────┐
│ http://host:8080 │
└──────────────────────────────┬───────────────────────────────┘
│ 统一端口(SPA 静态资源 + REST API + SSE 流式)
┌──────────────────────────────▼───────────────────────────────┐
│ GoFrame HTTP Server (:8080) │
│ ┌───────────┐ ┌──────────────┐ ┌────────────────────────┐ │
│ │ 静态资源 │ │ API 路由 │ │ 访问令牌鉴权 / CORS / │ │
│ │ ui-src/dist│ │ /dataset │ │ panic 恢复 中间件 │ │
│ └───────────┘ │ /document ... │ └────────────────────────┘ │
│ └──────┬───────┘ │
│ ┌──────────────┼──────────────┐ │
│ ┌──────▼─────┐ ┌──────▼─────┐ ┌──────▼──────┐ │
│ │ controller │→│ service │→│ dao │ │
│ └────────────┘ └──────┬─────┘ └──────┬──────┘ │
│ │ │ │
│ ┌─────────▼──────┐ ┌────▼─────────────────────┐ │
│ │ 业务编排 │ │ SQLite × 3(文件型) │ │
│ │ RAG 问答 │ │ business.db: 业务表+向量+ │ │
│ │ 文档解析流水线 │ │ FTS(vec0 + fts5 │ │
│ │ 知识图谱抽取 │ │ system.db: 全局配置/模型 │ │
│ │ 合同条款标注 │ │ chat.db: 会话/消息 │ │
│ └───────┬───────┘ └───────────────────────────┘ │
│ │ HTTPOpenAI 兼容 API
└──────────────────────┼────────────────────────────────────────┘
┌────────▼────────┐
│ 模型供应商(可配置)│
│ Ollama/OpenAI/ │
│ DeepSeek/硅基流动 │
└─────────────────┘
```
三条数据链路(各自任务轮询驱动):
```
① 语料入库:上传文档 → 解析 → 分块 → Embedding 向量化 + 关键词索引
chunk 表 + vec0 向量 + fts5 全文同库同事务)
② 问答:问题 → 混合检索(向量 + 关键词 + RRF 融合 + LLM 重排)
(+ 知识图谱图增强)→ 流式回答 → SSE(引用 [编号] 定位到分块)
③ 合同标注:上传合同 → 按条款切分 → 逐条款多数据集召回(向量+FTS,宁滥毋缺)
→ LLM 判定(0-10 分 + 理由)→ 标注落库 → 进度递增 → 导出标注版 HTML
```
## 数据存储
三个 SQLite 文件各司其职:
| 库 | 文件 | 内容 |
|---|---|---|
| business | `data/business.db` | 数据集、文档、分块、向量(vec0)、全文索引(FTS5)、解析任务、知识图谱、合同标注 |
| system | `data/system.db` | 模型配置 |
| chat | `data/chat.db` | 会话与消息 |
表清单(12 张实体表 + 2 张虚拟表):
| 表名 | 库 | 类型 | 说明 |
|---|---|---|---|
| `model_config` | system | 实体 | 模型配置(chat / embeddingis_default 标记默认) |
| `kb_dataset` | business | 实体 | 数据集(绑定向量模型、分块参数) |
| `kb_document` | business | 实体 | 文档(6 态状态机) |
| `kb_chunk` | business | 实体 | 分块 |
| `kb_chunk_vec` | business | 虚拟表 vec0 | 分块向量(与 kb_chunk 1:1 |
| `kb_chunk_fts` | business | 虚拟表 FTS5 | 分块全文索引(存 gse 分词) |
| `kb_parse_task` | business | 实体 | 文档解析/向量化任务(parse / reembed |
| `kg_entity` | business | 实体 | 知识图谱实体(按 dataset_id+name 去重) |
| `kg_relation` | business | 实体 | 知识图谱关系(head/relation/tail 文本快照) |
| `kb_contract_task` | business | 实体 | 合同标注任务 |
| `kb_contract_clause` | business | 实体 | 合同条款(断点续跑粒度) |
| `kb_contract_mark` | business | 实体 | 标注结果(法条快照 + 理由 + 0-10 分) |
| `chat_conversation` | chat | 实体 | 问答会话 |
| `chat_message` | chat | 实体 | 问答消息(citations 存引用 JSON |
## 功能模块
### 文档解析流水线
上传后由 `StartParsePoller` 轮询驱动(5 秒间隔,单 goroutine 串行),文档状态 6 态流转:`待处理 → 解析中 → 向量生成中 → 图谱构建中 → 已完成 / 失败`
1. 按扩展名分发解析器(txt/md 直读、pdf、docx、html 均为纯 Go 解析)
2. 四阶段组合分块(SplitAuto):结构识别(条文/章节等 8 种单元模式,命中自动采用)→ 标题感知 → 语义分块 → 递归兜底
3. 批量 Embedding(每批 16 条)→ chunk + vec0 向量 + fts5 全文同事务写入
4. 知识图谱 LLM 抽取(逐 chunk 并发),**失败不阻断**:文档仍置已完成,提示「知识图谱未构建」原因
失败可重试;重跑前先清旧索引(chunk+vec+fts+kg 单事务),幂等重建。
### 混合检索与 RAG 问答
- **混合检索**HybridRetriever):向量 KNNvec0,L2 距离,预取后按数据集过滤)+ 关键词检索(FTS5 BM25,查询端 gse 分词后「引号词组 OR」连接避免空召回)→ RRF 融合(`Σ 1/(60+rank)`)→ LLM 一次调用重排(0-10 分),低于门槛 `max(最高分×50%, 6)` 剔除,取 Top 5
- **问答工作流**:检索与知识图谱图增强并行执行 → 组装引用编号 [1][2] 与提示词 → ChatModel 流式生成
- **SSE 事件顺序**`citations`(引用列表 + conversation_id)→ `delta`(增量文本)→ `done`;异常推 `error`15 秒心跳(`: ping`)保持长连接
- 引用编号与提示词中资料编号一一对应,随助手消息以 JSON 落库
### 知识图谱
- **构建**:解析流水线向量化之后,逐 chunk 调用 LLM 抽取 `{entities, relations}` JSON;实体按 `(dataset_id, name)` 去重(UNIQUE + upsert),关系带来源 chunk_id
- **图增强检索**:问题分词后与实体名匹配取 Top 3 → 一跳邻居(上限 20 条三元组)→ 以「【知识图谱】张明 -任职于-> XX科技」形式注入提示词,增强关系类问题回答
- 图谱页以 eCharts 力导向图可视化(节点大小随出入度,点击高亮一跳邻居)
### 合同法律条款标注
- 上传合同(txt/md/pdf/docx/html)时多选法律语料数据集;`StartAnnotationPoller` 轮询消费
- 条款切分:探测 `第X条` / 数字编号 / 中文数字三种行首模式,命中则按条款切分,否则整篇单条
- 逐条款(并发):每个数据集召回(向量 15 条 + FTS 15 条)→ RRF 融合截 60 候选 → LLM 一次调用判定(0-10 分 + 理由)
- **宁滥毋缺**:与问答检索相反,不做 topK 截断与门槛过滤,全部候选(含 0 分)保留——漏标比多标严重
- 断点续跑:按条款粒度续跑,重启不重复标注;单条款失败不影响任务完成
- 导出标注版 HTML(自包含,score 分级着色,可打印/另存 PDF)
### 鉴权(单用户门禁)
- **访问令牌每次启动重新生成**(32 位 hex),仅存内存、不落库,打印在启动日志;**重启即换新令牌**,旧 JWT 全部失效
- 登录:输入令牌 → 校验 → 签发 JWT(HS25624h 有效,携带令牌 SHA-256 指纹)
- 鉴权中间件比对 JWT 指纹与内存令牌指纹,不一致即 401(前端自动跳登录页)
- 公开路径白名单:`/system-config/login`、SPA 静态资源、`GET /workspace/*`(仅路径穿越防护)
## 快速开始(Docker
```bash
docker compose up -d --build
```
> **Linux 服务器首次部署前**`data/` 与 `workspace/` 由宿主机 bind mount 提供(相对 compose 文件目录,迁移时连目录内容一起拷贝)。若目录尚不存在或由 root 创建,容器内 `app` 用户(uid 1000)将无法写入 SQLite,须先执行:
> ```bash
> mkdir -p data workspace && chown -R 1000:1000 data workspace
> ```
> 迁移已有数据后还须检查 `data/system.db` 中 `model_config` 表的 `endpoint_url`:表里存的若是 `127.0.0.1`,在新机器上会指向自身,应改为实际模型服务可达地址。
- 浏览器打开 http://localhost:8080
- 启动日志中查看访问令牌(`docker compose logs rag-local` 搜索"访问令牌"
- 登录后按以下流程使用:
1. **设置** 页添加对话模型与向量模型(OpenAI 兼容接口,如 Ollama / vLLM / one-api),并点击"测试"验证连通
2. **数据集** 页新建数据集,绑定向量模型(不绑定则仅全文检索)
3. 进入数据集上传文档,等待解析完成
4. **问答** 页选择知识库开始提问,回答可展开查看引用来源
5. **知识图谱** 页查看解析时抽取的实体与关系;**合同标注** 页上传合同按条款标注法条
数据保存在 `./data``./workspace`,删除容器不丢失。
## 本地开发
```bash
# 后端(Go 1.26+
go get modernc.org/sqlite@v1.47.0 # 关键:向量扩展依赖(GoFrame 驱动链默认锁定过旧版本)
go run . # 监听 :8080,启动日志打印访问令牌
# 前端(开发热更新)
cd ui-src
npm install
npm run dev # Vite 开发服务器(5173),API 代理见 vite.config.js
```
生产构建时前端产物在 `ui-src/dist`,由 Go 直接托管。
## 模型配置
### 首次部署 / 迁移到新电脑
Python 虚拟环境(`.venv/`**不可跨机器复制**:其中 `pyvenv.cfg``bin/` 下脚本的 shebang 及 `bin/python` 符号链接都写死了本机绝对路径,复制后无法运行,这是 Python 的设计,不能靠改文件修复。复制/迁移项目目录时排除 `.venv/``.gitignore` 已排除;整体拷贝时连同复制则先删除),到新机器上重建:
```bash
./scripts/start_models.sh # 自动检测 .venv:未就绪(缺失/损坏/依赖未装全)时依 scripts/requirements.txt 重建,就绪则直接启动
```
> `.venv` 重建需按 `scripts/requirements.txt` 安装 115 个依赖(含 omlx,需可访问 GitHub),仅首次或 venv 失效时执行一次。
任意 OpenAI 兼容接口均可使用:
- **对话模型**`/v1/chat/completions`,支持 SSE 流式):用于问答与知识图谱实体抽取
- **向量模型**`/v1/embeddings`):用于分块向量化与语义检索;维度须与 `config.yml``vector.dim` 一致(默认 1024
示例(本地 oMLXApple Silicon 推荐):
```bash
# 模型文件放 models/mlx/omlx/chat: Qwen3.5-9B-MLX-4bitembedding: bge-m3),
# 依赖装在项目根 .venv/mlx + omlx + fastapi + uvicorn
./scripts/start_models.sh # 启动单实例 oMLX :18080,同时提供 chat + embedding
./scripts/stop_models.sh # 停止
# 设置页配置:
# 对话模型:endpoint http://127.0.0.1:18080/v1,模型名 Qwen3.5-9B-MLX-4bit
# 向量模型:endpoint http://127.0.0.1:18080/v1,模型名 bge-m3,维度 1024
```
> 16GB 内存机器需 `sudo sysctl iogpu.wired_limit_mb=14336` 提高 Metal 上限(oMLX 内存 guard ceiling 14GB);Qwen3.5 思考链默认开启,应用侧已在 agent / 主回答 / 重排调用中通过 `chat_template_kwargs: {"enable_thinking": false}` 关闭。
示例(Ollama):
```bash
# 拉取模型
ollama pull qwen2.5:7b
ollama pull nomic-embed-text
# 设置页配置:
# 对话模型:endpoint http://localhost:11434/v1,模型名 qwen2.5:7b
# 向量模型:endpoint http://localhost:11434/v1,模型名 nomic-embed-text,维度 768
```
> 注意:向量维度变更需清空 `data/business.db` 重建(vec0 表建表维度固定)。
## 配置说明(config.yml
| 配置 | 说明 |
|---|---|
| `server.address` | 监听地址,默认 `:8080` |
| `server.clientMaxBodySize` | 上传文件上限,默认 200MB |
| `vector.dim` | 向量维度,须与向量模型一致 |
| `database.cache.ttl` | DAO 查询缓存秒数 |
| `chat.timeout` / `chat.max_retries` | 对话模型 API 超时(秒)/ 失败重试次数 |
| `chunk.default_size` / `chunk.default_overlap` / `react.default_rounds` | 数据集分块大小/重叠/智能体轮次默认值(新建数据集传 0/-1 时使用) |
| `pool.*` | 各并行点协程池并发度(缺失或非法回退代码内默认值):`kg_extract`(4) 逐 chunk 图谱抽取、`annotation_clause`(4) 合同逐条款标注、`annotation_dataset`(8) 条款内逐数据集召回、`chat`(4) 问答检索与图增强并行、`chat_retrieve`(4) 检索向量与全文并行 |
## API 概览
所有接口除 `/system-config/login` 外均需 `Authorization: Bearer <JWT>`;接口只使用 GET / POST 两种方法。
| 分组 | 接口 |
|---|---|
| `/system-config` | `POST /login`(访问令牌登录) |
| `/model-config` | `GET /list``POST /save``POST /delete``POST /set-default``POST /test`(连通性测试) |
| `/dataset` | `GET /list``POST /save`(新建/编辑)、`POST /delete`(有文档拒绝) |
| `/document` | `POST /upload`multipart)、`GET /list``GET /detail``POST /delete``POST /reembed`(仅重算向量) |
| `/chunk` | `GET /list`(分页 + 关键词搜索)、`POST /update`(改后自动重向量化) |
| `/parse-task` | `GET /list``POST /retry`(重试失败任务) |
| `/kg-entity` `/kg-relation` | `GET /list`(按数据集过滤 + 分页) |
| `/contract` | `POST /upload`multipart + dataset_ids)、`GET /list``GET /detail``GET /summary`(风险汇总报告)、`GET /annotated`(导出标注版 HTML)、`POST /delete` |
| `/conversation` `/message` | `POST /save``GET /list``POST /delete`(级联删消息);`GET /message/list``POST /message/chat`SSE 流式问答) |
| `/workspace/*` | 源文件访问(静态服务,仅路径穿越防护) |
## 前端页面
| 页面 | 功能 |
|---|---|
| 登录页 | 输入访问令牌登录,无注册入口 |
| 数据集列表 | 新建/编辑/删除(绑定向量模型必选、分块参数传 0/-1 使用 config.yml 全局默认);变更模型/分块 → 确认弹窗提示重新处理 |
| 数据集详情 | 文档上传(拖拽)、6 态解析状态/分块数/失败重试/「图谱未构建」提示、分块预览与编辑 |
| 图谱页 | 实体/关系列表 + eCharts 力导向图 |
| 合同标注页 | 选语料数据集 → 上传合同 → 任务进度(3s 轮询)→ 条款-标注对照抽屉 → 导出标注版 HTML |
| 问答页 | 会话列表 + 知识库下拉,SSE 流式渲染,引用折叠面板 |
| 设置页 | 模型配置 CRUDchat/embedding 两个 tab)、连通性测试、设置默认 |
## 项目结构
```
common/ 通用层:HTTP 服务/鉴权/文件解析/中文分词/向量 JSON/协程池
kb/
consts/ 表名、状态、默认参数与协程池默认大小
model/ entity(表结构)/ dtoReq/Res + 路由)/ domain(领域模型)
dao/ 数据访问(每表一个文件,chunk_dao 兼管 vec0/fts5 虚拟表)
service/ 业务逻辑(每表一个文件 + chat_service 问答编排 + annotation_service 合同标注)
controller/ 接口层(每表一个文件)
ui-src/ Vue 3 前端
data/ SQLite 三个库(gitignore
workspace/ 上传的文档源文件(gitignore)
技术设计.md 实现细节/技术决策文档(版本要点、表结构约定、检索参数、风险与备选方案)
```
## 文档说明
- **README.md**:项目功能介绍(本文件)
- **CLAUDE.md**:通用开发规范(分层职责、代码模式、事务/并发/缓存约束、文档驱动开发流程)
- **技术设计.md**:实现细节与技术决策(DDL、检索参数、风险与备选方案等)