282 lines
18 KiB
Markdown
282 lines
18 KiB
Markdown
好# rag-local 本地知识库
|
||
|
||
纯本地的 RAG(检索增强生成)知识库系统,基于 SQLite 全栈文件型存储,无外部数据库依赖。面向 NAS / 小主机等边缘设备单机部署。
|
||
|
||
## 功能
|
||
|
||
- **文档流水线**:上传 txt / md / pdf / docx / html → 自动解析 → 四阶段分块(结构识别 → 标题感知 → 语义 → 递归兜底)→ 向量化 + 全文索引,6 态解析状态机全程可见
|
||
- **混合检索**:sqlite-vec 向量 KNN + FTS5 全文 BM25,RRF 融合排序 + LLM 二次重排,中文 gse 分词
|
||
- **RAG 问答**:SSE 流式对话,检索引用(含来源与得分)随回答展示,会话历史持久化
|
||
- **知识图谱**:解析时 LLM 抽取实体与关系,问答时实体链接 + 一跳邻居注入提示词,图谱页力导向图可视化
|
||
- **合同法律条款标注**:上传合同自动按条款切分,逐条款标注对应法条(0-10 分 + 理由),可导出标注版 HTML(打印/另存 PDF)
|
||
- **模型可配置**:对话 / 向量模型均为 OpenAI 兼容 API,可对接 Ollama、DeepSeek、硅基流动、OpenAI 等任意供应商
|
||
- **单用户门禁**:启动生成访问令牌(重启即换新),登录后 JWT 鉴权
|
||
|
||
## 技术栈
|
||
|
||
| 层 | 技术 |
|
||
|---|---|
|
||
| 后端 | Go + GoFrame v2 + Eino(LLM 编排) |
|
||
| 存储 | SQLite(modernc 纯 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: 会话/消息 │ │
|
||
│ └───────┬───────┘ └───────────────────────────┘ │
|
||
│ │ HTTP(OpenAI 兼容 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 / embedding,is_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):向量 KNN(vec0,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(HS256,24h 有效,携带令牌 SHA-256 指纹)
|
||
- 鉴权中间件比对 JWT 指纹与内存令牌指纹,不一致即 401(前端自动跳登录页)
|
||
- 公开路径白名单:`/system-config/login`、SPA 静态资源、`GET /workspace/*`(仅路径穿越防护)
|
||
|
||
## 快速开始(Docker)
|
||
|
||
```bash
|
||
docker compose up -d --build
|
||
```
|
||
|
||
- 浏览器打开 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)
|
||
|
||
示例(本地 oMLX,Apple Silicon 推荐):
|
||
|
||
```bash
|
||
# 模型文件放 models/mlx/omlx/(chat: Qwen3.5-9B-MLX-4bit;embedding: 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 流式渲染,引用折叠面板 |
|
||
| 设置页 | 模型配置 CRUD(chat/embedding 两个 tab)、连通性测试、设置默认 |
|
||
|
||
## 项目结构
|
||
|
||
```
|
||
common/ 通用层:HTTP 服务/鉴权/文件解析/中文分词/向量 JSON/协程池
|
||
kb/
|
||
consts/ 表名、状态、默认参数与协程池默认大小
|
||
model/ entity(表结构)/ dto(Req/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、检索参数、风险与备选方案等)
|