# raglocal
**Repository Path**: liuyanfu001/raglocal
## Basic Information
- **Project Name**: raglocal
- **Description**: # 本地 RAG 问答系统
轻量级本地 RAG 问答系统,专为个人和小团队设计。
本系统的核心目标是为已训练完成的大模型注入本地私有知识,扩展大模型对企业或个人内部数据的认知边界。
支持公有云LLM+本地RAG和本地LLM+RAG两种:文档全部在本地
## ✨ 核心特性
### 🔒 数据不出本机
### 🧠 Query Rewrite(查询改写)
### 📄 扫描版 PDF 自动 OCR
- **Primary Language**: Python
- **License**: MIT
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 2
- **Forks**: 0
- **Created**: 2026-09-20
- **Last Updated**: 2026-10-16
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# RAG Local Light
**RAG Local Light** 是一套**跑在个人电脑上的本地 RAG 问答系统**。它解决的问题很朴素:
> **让个人和小团队,用最低 8GB 整机(完整 RAG,含本地小模型)或 4GB 内存的低配 PC(仅向量库 DB service),完成大模型 + 本地私有知识库的部署和运行。**
不需要独立显卡,不需要 Docker,不需要服务器。两条命令启动,上传文档,提问,完事。
**最低 8GB 整机即可运行的本地 RAG 问答系统**(4GB 可跑仅向量库 DB service),专为个人和小团队设计。集成显卡即可流畅运行本地大模型,全链路数据不出本机。
核心目标:为本地私有知识库(**PDF / Word / Excel / PPT / TXT / Markdown**,扫描版 PDF 自动 OCR)注入大模型能力,让 AI 基于你的文档回答问题,而不是瞎编。
## 为什么做这个
| 场景 | 痛点 / 待解问题 | 本项目的解法 |
| -------------------------------- | --------------------------- | -------------------------- |
| 公司 / 本地沉淀了大量内部材料和智力资产(制度、手册、笔记、报告),想让 AI 帮忙处理 | 网上的大模型涉及隐私和保密,材料不能上传公有云 | 全链路数据不出本机,检索片段只进本地模型 |
| 于是转向本地部署模型,但本地模型规模较小 | 没办法把这么大量的知识一次性塞给模型——怎么让本地模型"学会"我的知识库 | 用 RAG 把知识放进向量库,模型只负责"查"和"答",不用全部装下 |
| 那就需要搭一个本地知识库 | 怎么搭:要装得下所有材料、检索性能好、框架不重、不需要太多硬件 | 4G 低配 PC 跑仅向量库、8G 整机跑完整 RAG,无 Docker / 中间件 |
| 本地显存 / 内存小,能跑多大模型? | 怎么保证回答不幻觉、能读懂我的文档、找到正确文本、召回率高不出错 | ReRank 精排 + 引文溯源 + 相关度阈值,每个回答有出处、可复核 |
> 我们想做成:**4GB 内存跑仅向量库(DB service),8GB 整机跑完整 RAG(含本地小模型),32GB 整机跑 8B 大模型;集显就能加速,全链路数据不出本机。**
> 这不是"理论上能跑",是每天都在跑——我们自己就是用户。
**核心链路**:Rewrite(问句标准化)→ RAG 检索 → ReRank 精排 → 本地大模型生成(带引文溯源),全链路数据不出本机。详见下方「重点特性」。
***
## ✨ 重点特性
### 1. 智能查询改写(Rewrite)
用户的口语化问题,先经 LLM 改写成**专业术语风格的检索问句**,再做向量检索。例:
> "血压高了平时要注意啥" → 改写为 → "高血压患者的日常饮食与生活管理建议" → 一次语义检索即命中知识库中相关健康管理条目。
**为什么不用 BM25(关键词检索)**:BM25 需把全部 25 万条片段加载进内存建倒排索引,内存开销大,且对口语化表达、近义词、术语偏差无能为力。本项目用 **rewrite 在检索前就把口语问题"翻译"成库内高频专业术语**,一次语义检索即可命中——**省内存、免倒排索引、查询更快**。这是"轻量"定位的关键设计抉择。
### 2. 中文语义检索(Embedding)
内置 **bge-small-zh-v1.5**(BAAI 智源研究院开源,中文 embedding 标杆,C-MTEB 中文评测领先),**512 维**,ONNX Runtime 版仅 ~90MB,免 torch 全家桶。模型已内置为 git submodule,`git clone --recursive` 随项目一并拉取,启动即常驻,全程本地计算、不出本机。
参考:[ModelScope 模型仓 lyf219/bge-small-zh-v1.5-onnx](https://www.modelscope.cn/models/lyf219/bge-small-zh-v1.5-onnx)
### 3. RAG 向量匹配(ChromaDB)
向量库使用 Chroma 单机版,极限规格(Chroma 官方单机基准,[Single-Node Performance](https://docs.trychroma.com/guides/performance/single-node)):**2 GB 内存 ≈ 25 万条,查询延迟均值 ~8ms**;本项目 512 维 embedding 内存占用减半。每 chunk 500 字 → **25 万条 ≈ 1.25 亿字(≈25 万页)**。详见下文"支持规格"。
### 4. 检索重排(ReRank)
内置 **bge-reranker-base**(ONNX INT8 量化版,~266MB,BAAI 开源)。向量召回 **100 条候选**(`RERANK_CANDIDATES`,默认 100)→ rerank 逐条精排 → 取 top_k 返回,**保证返回的 top_k 是"语义最相关"的片段,而非仅向量近似**。20 万 chunk 全链路实测:候选 30/50/100 的召回率几乎无差异(95.0%/95.0%/94.8%),候选数只**线性**影响 rerank 耗时(30→3.6s、50→5.9s、100→12.1s),默认取 100 以覆盖尾部/罕见片段(实测约 12s)。**注意:HTTP API 的 `use_rerank` 参数默认 false(关闭);本项目流水线(app/客户端封装)默认显式开启精排**。模型同样内置为 submodule,随项目一并拉取。
参考:[ModelScope 模型仓 lyf219/bge-reranker-base-onnx](https://www.modelscope.cn/models/lyf219/bge-reranker-base-onnx)
### 5. 引文溯源(Cite)
每条回答引用本地文档片段时,**句末标注 `[n]`**:编号 n 与下方「🔍 RAG检索详情」命中列表的编号一一对应(列表含来源《文档》+页码),点击即可反查命中片段全文与来源页;联网搜索结果则标注 `(引自:URL)`。本地小模型容易编造来源,引文标注强制其"只引用真实命中的片段,未命中不得虚构编号",让每个事实都可溯源、可审计。
参考实现:命中片段元数据自带 `source`(文件名)+ `page`(页码,Excel 显示 Sheet 名),prompt 规则要求模型逐句标注。
### 6. 多文档格式支持
支持 **PDF(文字 / 扫描 OCR)/ Word / Excel / PPT / TXT / Markdown** 六类文档入库。扫描版 PDF 自动 OCR(按页流式处理、逐页落盘、带断点续传),Excel 以 Sheet 为页码维度。入库按 chunk 500 字切分并保留来源页码,超出 500 字由系统自动切分,单条上限以接口校验为准。
### 7. 多轮上下文指代消解
多轮对话 + 上下文压缩 + 指代消解(如"它""这个方案"指代上一轮内容),历史超出本地小模型上下文窗口(4K)时自动压缩,适配小模型长对话场景。
> **第 1 轮·问**:"体检说我肝脏有点问题,该怎么办?"
> **答**:"肝脏问题通常建议清淡饮食、定期复查,避免饮酒熬夜……"
> **第 2 轮·问**:"那**它**对我的睡眠有没有影响?"
> **答**:系统自动识别这里的"它"指代上一轮的"肝脏问题",检索时消解为"肝脏问题对睡眠的影响"再作答,而不是拿"它"去查,避免理解跑偏。
### 8. 非精准片段拦截(相关度阈值)
RAG 检索返回的片段在拼入 context 前,先按相关度百分比(rel_pct)过滤:低于 `MIN_RELEVANCE_PCT`(默认 52%)的片段视为"未找到合适答案"、**不进 context**;整段命中都被过滤时,流水线走"超出知识库领域"分支(有联网结果则以联网回答为准,否则告知超领域),避免低相关内容污染回答、防止模型对无关片段编造引用。该阈值须按贵方样本校准,详见下方「部署调优(相关度阈值)」。
**完整链路**:Rewrite(问句标准化)→ RAG 检索(向量召回)→ ReRank(精排 top_k)→ 本地大模型组织输出(带引文标注)。全链路数据不出本机。
***
## 📊 基准评测(C-MTEB 官方标准)
按业界标准 **C-MTEB 检索评测**衡量本系统的召回排序能力:同一嵌入模型(bge-small-zh-v1.5),官方实测的 Retrieval 类别平均为 **61.77 分**(nDCG@10,12 个中文检索任务),我们经工程调教后,在三集官方标准集合 **DuRetrieval / CovidRetrieval / MMarcoRetrieval**(合计 30.6 万段语料、9,929 条官方 query,走 rag_service 生产链路)上实测 **70.7 分**,高出约 **8.9 分**。
完整评测报告:[C-MTEB 三集召回率评测报告](tests/test-reports/C-MTEB三集召回率评测报告_2026-10-07.md)
***
## 🏗️ 架构
```
┌──────────────────────────────────┐
│ 用户(浏览器) │
└──────────────┬───────────────────┘
│ HTTP
▼
╔══════════════════════════════════════════════════════╗
║ 本项目代码 ║
║ app_ui/(:8501,Streamlit 前端) ║
║ app.py / ui.py / client.py(RagClient HTTP 代理) ║
║ │ HTTP(含 SSE 流式 /rag/flow/query) ║
║ ▼ ║
║ rag_service/(:8100,FastAPI 唯一后端) ║
║ rag_service.py 瘦入口 · 全部端点 ║
║ flow.py RagFlowRunner:5 步流水线·SSE ║
║ search/ingest_service/store/config/embedding/ ║
║ ingest/rerank/prompts/exa ║
║ │ BGE embedding + Chroma 常驻(进程内加载一次) ║
╚═══╪═══════════╪═══════════════╪══════════════════════╝
│ │ │
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌──────────────┐
│ 本地小模型│ │ Exa │ │ chroma_db │
│(唯一LLM) │ │(联网, │ │ (向量库/持久化)│
│llama.cpp│ │ 默认关) │ │ │
│ :8888 │ │ MCP │ │ │
└────┬────┘ └────┬────┘ └──────────────┘
│ │
▼ ▼
本机进程 外部搜索服务(默认关,仅联网开时出网)
```
**项目范围**:rag_service + app_ui 是本项目代码(上图大框)。RAG flow 流水线(5 步)在后端 `flow.py` 内执行,经 SSE 接口(`/rag/flow/query`)回推,前端只做事件渲染。llama.cpp / Ollama、Exa MCP、chroma_db 是外部依赖,本项目负责编排和调用。
**数据主权**:RAG 片段、历史摘要、对话历史始终在本地;联网搜索(可选、默认关)只发送**改写后的检索问句**(含指代消解后的完整概念,本质仍是用户问题,不含任何 RAG/本地内容)。
***
## 📐 支持规格
向量库为 Chroma 单机版,HNSW 索引必须常驻内存——**库容量上限由内存决定,超过不是变慢,是换页卡死直接不可用**。
**Chroma 官方单机基准**([Single-Node Performance](https://docs.trychroma.com/guides/performance/single-node),1024 维、小文档、3 个元数据字段):
| 内存 | 库容量上限 | 查询延迟(均值) |
|---|---|---|
| 2 GB | 25 万 | 8ms |
| 4 GB | 70 万 | 5ms |
| 8 GB | 170 万 | 4ms |
| 16 GB | 360 万 | 4ms |
官方容量公式 `N = R × 0.245`(N=百万条,R=GB,1024 维);实测到 700 万条无软件层硬上限;<2GB 内存不建议部署。
**整机配置档位**(内存按整机算,RAG service 与本地大模型同机运行;为了最小化内存运行模型推理,模型均以 Q4_K_M 量化、上下文 4096、KV 缓存启动配置为 Q4_0 精简方式的配置评估):
| 档位 | 整机内存 | RAG service | 本地大模型 | 合计(RAG + 模型) |
|---|---|---|---|---|
| **仅向量库 DB service(无本地模型)** | 4 GB | 2 GB | 无 | ~2 GB |
| **RAG Lite + 本地微模型(低配)** | 8 GB | 2 GB | Qwen3-1.7B(Q4_K_M,~2 GB) | ~4 GB |
| **RAG Lite + 本地小模型(推荐)** | 16 GB | 4 GB | Qwen3-4B(Q4_K_M,实测 2.33 GB,运行 ~4 GB) | ~8 GB |
| **RAG Lite + 本地小模型(最高)** | 32 GB | 4 GB | Qwen3-8B(Q4_K_M,实测 4.68 GB,运行 ~7 GB) | ~11 GB |
| **可选(32 GB+)** | 32 GB | 4 GB | Qwen3-14B(Q4_K_M,实测 8.38 GB,运行 ~10-12 GB) | ~14-16 GB |
| **超出 32 GB(其他配置)** | >32 GB | — | — | 请改用 RAGFlow / Dify 等中型/重型 RAG 平台 |
**库容量规格**(bge-small-zh 512 维,生产链路实测推算;1 chunk = 500 字):
| RAG service 内存 | 建议库容量 | 折合文档量* | 实测依据 |
|---|---|---|---|
| 2 GB(Lite / 低配档) | ≤20 万 chunks | ~1 亿字(≈20 万页) | 20 万 chunks 全链路实测 **1.31 GB**(含双模型 + HNSW 索引 + 服务) |
| 4 GB(推荐 / 最高档) | ≤50 万 chunks | ~2.5 亿字(≈50 万页) | 20 万实测 1.31 GB → 50 万推算 ~2.1 GB,承诺值留一倍余量 |
\* 折算口径:1 chunk = 500 字,500 字/页。
**实测基准(2026-10-06,20 万 chunk 全链路压测:rag_service HTTP = BGE embedding → Chroma 20 万 HNSW → bge-reranker,CPU 推理;详见 [20 万 chunk 压测报告](tests/test-reports/20万chunk压测报告_2026-10-06.md))**:
**① 容量**(20 万库服务常驻内存,与候选数无关)
| 候选档 | 服务常驻内存 |
|---|---|
| 30 / 50 / 100 | **1.31 GB** |
**② 召回率**(不开 ReRank,向量检索本身)
| 候选档 | recall@10 | MRR@10(答案平均排位) |
|---|---|---|
| 30 | 95.0% | 第 1.1 位 |
| 50 | 95.0% | 第 1.1 位 |
| 100 | 94.8% | 第 1.15 位 |
分桶(候选 30):短段(<80 字)97.0%、中段(80-250 字)100.0%、长段(≥250 字)87.9%。
**③ ReRank**(开启,CPU 逐条打分,**不承诺延迟,按实测**)
| 候选档 | 平均延迟 |
|---|---|
| 30 | **3.6s** |
| 50 | **5.9s** |
| 100 | **12.1s** |
**④ 并发**(不开 ReRank)
| 并发数 | 候选 30 | 候选 50 | 候选 100 |
|---|---|---|---|
| 20 | ~143 QPS | ~133 QPS | ~117 QPS |
| p95@20 并发 | 176ms | 224ms | 238ms |
→ **RAG service 2 GB 档可稳定承载 20 万 chunks(全链路实测 1.31 GB)。查询延迟承诺:不开 ReRank <1 秒(实测 20-25ms);开 ReRank 不承诺延迟,按实测 3.6s 起(候选 30)**——能接受就开,不能接受就关。4 GB 档可承载 50 万+。本地大模型(Qwen3 系列 Q4_K_M)内存另算,见整机档位表。
- **默认形态**:不开 ReRank 查询 <1s;20 万库召回率 95%、答案平均排第 1.1 位
- **不支持**:>500 万 chunks、高并发多客户端读写、多租户(超出建议换 Qdrant 等生产级向量库,本项目不做)
***
## 🚀 快速开始
共 **5 步**:装软件 → 装依赖 → 装本地大模型 → 启动 → 检查配置。全程约 10-20 分钟(不含模型下载)。
### 第 1 步:克隆仓库(自动带上内置模型)
```bash
git clone --recursive https://gitee.com/liuyanfu001/raglocal.git
cd raglocal
```
`--recursive` 会一并从 ModelScope 自动拉取内置的两个模型(公开、MIT、国内快):
1. **embedding**:ONNX Runtime 版 bge-small-zh-v1.5(约 90MB,免 torch 全家桶)
2. **rerank**:bge-reranker-base ONNX INT8 量化版(约 266MB,检索重排精排)
> 若之前已用普通 `git clone` 克隆(无模型目录),补拉:`git submodule update --init`。模型加载快(秒级),启动即常驻。
### 第 2 步:装 Python 环境 + 依赖
需要 **Python 3.10+**(未装先去 安装,勾选 *Add to PATH*)。
```bash
python -m venv .venv && .venv\Scripts\activate # Windows
# python -m venv .venv && source .venv/bin/activate # macOS / Linux
pip install -r requirements.txt # 首次需几分钟(已配清华镜像)
```
> 无需 `.env` 文件——配置全部内嵌 `rag_service/config.py`,网页「模型配置」页可改(改动 POST 回后端)。
### 第 3 步:装本地大模型(LLM,二选一)
向量检索只负责"捞",最终回答靠一个本地 LLM 生成。本项目**不带 LLM**,按机器档位选一个装(低配 Qwen3-1.7B / 推荐 Qwen3-4B / 最高 Qwen3-8B)。
> llama.cpp 和 Ollama **共用同一组本地模型配置**(`config.py` 的 `OLLAMA_BASE_URL` + `OLLAMA_MODEL`,默认 `http://127.0.0.1:8888/v1` / `qwen7b`),选一个起即可,谁在 8888 上 App 就连谁。
**方式 A:Ollama(最简单,推荐新手)**
1. 下载安装 (Windows 直接装)
2. **改端口到 8888**(本项目默认连 8888,Ollama 默认 11434 会连不上):设用户环境变量 `OLLAMA_HOST=127.0.0.1:8888` 后重启 Ollama;或命令行 `set OLLAMA_HOST=127.0.0.1:8888 && ollama serve`
3. 拉模型:`ollama pull qwen7b`(对齐 config 默认 `OLLAMA_MODEL=qwen7b`;用别的模型则同步改 config/配置页)
4. 国内慢的备选:ModelScope 下 GGUF → `ollama create` 本地导入(方法同方式 B 第 2 步)
**方式 B:llama.cpp(性能 / Vulkan 加速,推荐路线)**
1. 下载预编译包:GitHub Releases ,Windows 选 `llama-*-bin-win-vulkan-x64.zip`(无独显选 `-bin-win-cpu-`;GitHub 慢用 gh-proxy 类加速镜像)
2. 下载模型(国内快,ModelScope 官方仓):,选 `Qwen3-4B-Instruct-Q4_K_M.gguf`(约 2.3GB,质量/体积平衡档)
3. 启动:`llama-server.exe -m D:\models\Qwen3-4B-Instruct-Q4_K_M.gguf --host 127.0.0.1 --port 8888`
4. HuggingFace 慢时用国内镜像
### 第 4 步:启动服务(按顺序:模型 → rag_service → app_ui)
```bash
# 窗口 1:本地模型(:8888)——第 3 步已启动(ollama serve / llama-server)则跳过
# 窗口 2:后端服务(:8100,BGE + Chroma 常驻 + RAG flow 流水线)
start_rag_service.bat
# 窗口 3:Streamlit APP(:8501,前端)
start_app_ui.bat
```
浏览器自动打开 ,上传文档 → 提问。
> 联网搜索(可选,默认关):走 Exa MCP(`https://mcp.exa.ai/mcp`,匿名免费约 1000 次/月),无需本地启动、无需配 API key;开关在问答页实时生效。
### 第 5 步:检查配置与连通
三个服务端口:**本地模型 8888 / rag_service 8100 / APP 8501**。
| 检查项 | 方法 | 期望结果 |
|---|---|---|
| 后端健康 | 浏览器开 `http://127.0.0.1:8100/rag/health` | 返回 `{"status":"ok",...}` |
| 模型连通 | 浏览器开 `http://127.0.0.1:8888/v1/models` | 返回模型列表 JSON |
| 配置核对 | 浏览器开 `http://127.0.0.1:8100/rag/config` | 返回全量配置 JSON(含本地模型地址/模型名/上传目录/台账路径) |
当前配置默认值(`rag_service/config.py`,一般用默认即可;网页「模型配置」页改动 POST 回后端):
```python
LOCAL_ENABLE = true # 本地大模型开关
OLLAMA_BASE_URL = "http://127.0.0.1:8888/v1" # 本地模型地址
OLLAMA_MODEL = "qwen7b" # 模型名(llama.cpp 填啥都行;Ollama 须匹配 `ollama list` 的 tag)
# 联网搜索(可选,默认关;问答页实时开关,不进配置页):Exa MCP
# EXA_MCP_URL = "https://mcp.exa.ai/mcp"
# EXA_API_KEY = ""(匿名额度约 1000 次/月,超限再填)
```
网页「模型配置」页默认值从 `GET /rag/config` 拉取、改动 POST 回后端,与上面一致,通常无需改。
***
## 🎯 API 调用
rag_service 启动后,任何客户端(App / 豆包 / curl)都可以直接调。**rag_service 自带 Swagger 交互式接口文档(OpenAPI)**,启动后即可在线调试所有接口,二次开发可直接参考:
| 入口 | 地址 | 用途 |
|---|---|---|
| **Swagger UI(交互式调试)** | `http://127.0.0.1:8100/docs` | 浏览器打开,每个接口可直接填参测试(GET/POST 都能点试) |
| **OpenAPI 规范(机器可读)** | `http://127.0.0.1:8100/openapi.json` | 二次开发据此生成客户端 / 对接文档 |
```powershell
# 健康检查
Invoke-RestMethod -Uri "http://127.0.0.1:8100/rag/health"
# 语义检索(use_rerank 默认 false=关闭,直接按向量相似度返回;需要精排时显式传 true,慢很多:实测候选 100 约 12s vs 不开约 20-25ms)
Invoke-RestMethod -Uri "http://127.0.0.1:8100/rag/semantic_search" -Method Post -ContentType "application/json" -Body '{"query":"高血压日常管理","top_k":3,"use_rerank":true}'
# 入库(JSON 路径方式,同机部署推荐:文件不离开磁盘、处理完不删源文件)
Invoke-RestMethod -Uri "http://127.0.0.1:8100/rag/ingest" -Method Post -ContentType "application/json" -Body '{"file_path":"D:\\docs\\入职手册.pdf","filename":"入职手册.pdf"}'
# 入库(multipart 上传方式,兼容二开:文件经 HTTP 传输,服务端落盘副本到 uploaded_files/)
Invoke-RestMethod -Uri "http://127.0.0.1:8100/rag/ingest" -Method Post -Form @{ file = Get-Item "D:\docs\入职手册.pdf" }
# 查询入库进度(带上一步返回的 task_id)
Invoke-RestMethod -Uri "http://127.0.0.1:8100/rag/ingest/status?task_id=xxxx"
```
| 端点 | 方法 | 说明 |
|---|---|---|
| `/rag/health` | GET | 存活检查 + chunks 数 |
| `/rag/semantic_search` | POST | 单条 query:向量召回候选(`RERANK_CANDIDATES`)→ 按 `use_rerank` 决定是否精排 → 返回 top_k。**`use_rerank` 默认 false(直接按向量相似度返回);true 时 rerank 精排**(命中含 `rerank_score`) |
| `/rag/flow/query` | POST | **RAG flow 端到端**(5 步流水线),SSE 流式:`task → step×N → token×N → done/error`。**5 步**:①查询语句整理(LLM 改写+指代消解)→②全网最新知识收集(Exa 联网,可关)→③本地RAG检索(向量召回+rerank)→④内外信息汇总(拼装 context)→⑤本地模型计算(流式生成+历史摘要)。请求含 `question / web_enable / local_enable / collection(默认 rag_docs) / history_summary / prev_questions / retry_step / trace`(`history_summary` 传了才喂本地模型,无独立开关) |
| `/rag/ingest` | POST | 单文件入库,**两种提交方式**:① multipart/form-data(兼容二开,文件经 HTTP 传输);② JSON `{"file_path":"...","filename":"可选","collection":"可选"}`(同机部署推荐,传服务器本地路径,文件不离开磁盘、不读进内存)。异步单飞:立即返回 `{"status":"started"\|"busy","task_id":...}`。**上传文件副本统一归档在服务端 `uploaded_files/` 目录,永久保留不自动清理** |
| `/rag/ingest/status` | GET | 入库任务进度(running/done/failed + 解析页/写库步骤 + 计数) |
| `/rag/browse` | GET | 浏览/过滤片段(offset/limit 分页) |
| `/rag/delete` | POST | 按 source / chunk_id / 清空(入库中禁删) |
| `/rag/stats` | GET | 来源文件列表 + chunks 计数 |
| `/rag/config` | GET | 全量配置(含 upload_dir / ingest_log 路径) |
| `/rag/config` | POST | 局部更新配置(None=不改),返回最新配置 |
> 详参 `docs/API接入方案.md`。
***
## ⚙️ 部署调优(相关度阈值)
**默认值**:`config.py` 中 `MIN_RELEVANCE_PCT = 52.0`(rel_pct,0~100,越大越相关;约等于余弦 cos≈0.54)。命中片段的 rel_pct 低于该值,视为"未找到合适答案"——**不进 context**,流水线走"超出知识库领域"分支(有联网返回时以联网回答为准;无联网时告知"用户提问已超出本知识库领域范围")。
**为什么默认 52%**:演示库(9386 chunks,流程测试文档 + 通用知识文档)实测——反面 query(你好/天气/手机/股票/餐厅/煮鸡蛋/量子引力等)rel_pct 全部落在 39%~51%(被过滤),而真实相关口语 query("产品立项评审要点"55.7%、"产品开发流程有哪些阶段"76.5%)全部保留。
**⚠️ 这个默认值必须按贵方样本校准,不是通用最优**:rel_pct 分布依赖 embedding 模型、领域术语、query 风格和库内容——不同库的最佳阈值差异很大。校准方法(约半小时):
1. 取 30~50 个**真实使用场景的问题**(覆盖精确术语、口语化、冷门、库里没有的);
2. 每个问题检索 top10,把"片段内容"打印出来(**不要显示分数**,避免先入为主);
3. 人工标注每段"相关 / 不相关";
4. 看分布:全部"相关"命中的最低 rel_pct 与全部"不相关"命中的最高 rel_pct——取中间值作为贵方阈值,改 `MIN_RELEVANCE_PCT` 即可。
**注意**:裸口语 query(不经 rewrite 直接调 API)分数系统性偏低,可能被阈值过滤——这是预期行为:`rewrite`(问句改写)就是先把口语转成文档术语再检索(实测把 55% 提至 74%)。生产流水线 rewrite + 阈值是配套设计。
***
## 🧪 开发者回归测试
**什么时候跑**:每次改动代码(新增特性 / 修复 / 重构)后,先跑一遍回归,再提交。项目历次压力测试中发现并修复的问题,往往会影响原有功能——这套测试就是为了防止"改了 A 不知道影响 B"。
**运行**(venv 内,全部用例读写**独立临时库**,不碰生产库 `chroma_db/`):
```bash
pip install -r dev-requirements.txt # 首次:安装 pytest(测试依赖,不污染运行 requirements)
python -m pytest # 跑全部回归
python -m pytest tests/regression/test_05_ingest_types.py -k pdf # 跑单个用例
```
**覆盖范围**(`tests/regression` 46 项 + `tests/system` 7 项,见完整矩阵 [REGRESSION_MATRIX.md](tests/regression/REGRESSION_MATRIX.md)):
| 模块 | 覆盖 |
|---|---|
| `test_01` | `/rag/health`、`/rag/stats` |
| `test_02` | `/rag/semantic_search` / `use_rerank`、边界(query 超 500→422、top_k 0/101→422) |
| `test_03` | `/rag/browse` 分页迭代 / source / keyword 过滤、边界(limit>1000、offset<0→400) |
| `test_04` | `/rag/delete` source / chunk_id / clear / 非法 mode |
| `test_05` | **6 类文档入库**(txt / PDF文字 / PDF扫描OCR / docx / xlsx / pptx)各自检索命中 + 去重 skipped |
| `test_06` | 空文档 error、去重(同内容 / 同名不同内容)、**页码溯源(跨页合并)**、**断点续传** |
| `test_07` | **引文溯源**(context 页码 + 命中列表)、rerank 不报错 |
| `test_08` | **相关度阈值**(rel_pct 口径、`_filter_relevant` 过滤、无命中走超领域分支、4 个反面 query 全被切掉) |
**约定**:新增功能时,先在矩阵文档补一行 + 在对应模块加用例(矩阵与代码同步维护);所有用例不依赖联网 / 本地大模型,检索、引文拼装直接测。
***
## 📁 项目结构
```
rag_service/ # 后端服务(FastAPI,全部功能)
├── rag_service.py # 瘦入口:全部 API 端点
├── flow.py # RagFlowRunner:5 步流水线(SSE 事件序列)
├── search.py # 检索(向量 + rerank 精排)
├── ingest_service.py # 入库:异步单飞 + task_id + status
├── store.py # Chroma store 管理
├── config.py # 全部配置(默认值内嵌 + /rag/config 接口化)
├── embedding.py / ingest.py / rerank.py / prompts.py / exa.py
└── __init__.py
app_ui/ # 前端(Streamlit)
├── app.py # 页面主入口 + RAG flow 对话编排
├── ui.py # 各 tab 渲染(配置页默认值从 /rag/config 拉取)
├── client.py # RagClient(HTTP 代理 + SSE 流式)
└── assets/styles.css
tests/
├── regression/ # 回归测试(独立临时库,不碰生产库)
├── system/ # 系统级测试(Streamlit AppTest)
├── scripts/ # 验证/探针脚本(48 个,含 e2e_rag_flow / verify_retry)
├── test-reports/ # 实测报告归档(20 万压测 / C-MTEB / 各功能验证)
└── _bench_data/ # 压测/评测数据(不上 git)
docs/ # 设计/产品/接口文档
├── 整体方案.md API接入方案.md RAG流程增强方案.md 产品介绍与特性.md
├── artifacts/ # 调研材料(入库)
└── private_docs/ # 私有材料(.gitignore,不上库)
start_rag_service.bat # 启动后端(uvicorn rag_service.rag_service:app --port 8100)
start_app_ui.bat # 启动前端(streamlit run app_ui/app.py)
models/ # 内置模型(git submodule):bge-small-zh-onnx + bge-reranker-onnx
chroma_db/ # 生产向量库(运行时生成,不入库)
uploaded_files/ # 上传文件副本 + 入库台账(运行时生成,不入库)
requirements.txt / dev-requirements.txt / pytest.ini / LICENSE
```
## 📝 License
MIT