好# 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 ``` > **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) 示例(本地 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 `;接口只使用 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、检索参数、风险与备选方案等)