Multi-Agent + MCP + Hybrid Search + Full-Stack。
UniKB 是一个面向企业知识管理场景的 RAG(Retrieval-Augmented Generation)平台。它把本地文档知识通过多 Agent 协作、MCP 工具协议、混合检索与重排序,最终以流式、带检索片段的方式回答用户问题。
当前阶段说明:知识库 ACL 采用 owner-based 隔离模型(每个私有 kb 属于一个用户);多用户团队协作、角色权限、组织级知识库是 Roadmap 中的后续目标。
LLM 支持按 provider/model 按请求切换: app.agents.llm_router.get_llm(provider, model) 接受 provider + model 参数, 内部按 (provider, model) 作 cache key (lru_cache)。get_llm() 不带参数仍走环境变量里的默认组合, 向后兼容老调用点。
- 混合检索:BM25 + 向量语义 + RRF 融合
- 精排重排:Cross-Encoder(BGE-reranker)抑制幻觉, 单例缓存避免每次请求重新加载模型
- RAG 工程调优(检索质量进阶, 见 RAG 工程调优): 文档清洗(42 条正则, 占质量调优 ~60% 精力)、自适应切分(短文档整篇保留 / 长文档 5500 字符)、元数据增强(HyDE 假设性问题)、父文档召回(连坐召回)、重排候选池优化(Top-60→Top-25, 延迟 11.4s→2.3s, Recall@5 93%→98.7%)、查询改写(Query Rewriting)、离线 Recall@K 评估
- 多 Agent 协作:基于 LangGraph 的 Planner / Retriever / Coder / Reviewer 流程, Reviewer 不通过时通过
add_conditional_edges真正回环 到 Retriever 重新检索 (MAX_REVIEWER_RETRIES=2防死循环) - MCP 协议:UniKB 自身工具 (
hybrid_search/calculator/current_date) 通过 stdio / SSE 暴露成 MCP Server 给 Claude Desktop、Cursor、Trae 等客户端调用; Agent 内部暂未作为 MCP Client 去消费外部 MCP 工具 (单向)。注意:每个 MCP Server 进程启动时绑定一个 kb_id,无法在同一进程内动态切换知识库 - 文档解析:PDF、DOCX、Markdown、TXT 与图片 OCR(图片 OCR 需要本机安装 Tesseract)
- 流式问答:SSE 协议 + 多轮对话管理 + 引用溯源;Agent 模式把 graph.stream 放进 threadpool, 按节点实时推 trace 事件, 不阻塞 FastAPI 事件循环
- 工程化:JWT 鉴权(HttpOnly cookie + Bearer 双轨)+ Docker Compose 一键部署 + GitHub Actions CI + GHCR 镜像发布 CD
- 安全加固:KB ACL 防越权 (Agent 模式现在真的会按
state['kb_id']走对应私有 kb)、CORS 白名单、登录/Chat 限流、CSP/HSTS 安全响应头 - 性能优化:同步 CPU/IO _OFFLOAD 到线程池、BM25 惰性重建、HybridRetriever 进程内缓存、Reranker 单例
- 文档管理:上传大小限制、DELETE
/documents/{doc_id}删除并同步清理 vector/BM25 - 可观测性:内置 LangFuse 对接(可选,关闭时无副作用)
- 评估体系:集成 RAGAS 自动评估(4 大指标)
| 层级 | 选型 |
|---|---|
| LLM | DeepSeek / Qwen / OpenAI(按 provider + model 在请求内动态切换,缓存 key = (provider, model)) |
| Agent 框架 | LangChain + LangGraph |
| MCP 协议 | mcp 1.0+(stdio + SSE 传输;Server 启动时固定 kb_id) |
| 后端 | FastAPI + Uvicorn(异步) |
| 前端 | Next.js 14 + TypeScript(App Router) |
| 向量库 | Chroma(轻量、可零配置) |
| 检索 | BM25(rank_bm25)+ 向量 + RRF |
| 重排 | Cross-Encoder(BAAI/bge-reranker-base) |
| 数据库 | SQLite(用户/历史会话/文件元数据,SQLAlchemy 抽象,可切 PostgreSQL)+ Chroma 向量库 |
| 缓存 | Redis(Embedding / 热点问答缓存,可选;限流当前为进程内实现,多实例部署需迁移到 Redis) |
| 文件 | 本地 / MinIO 可切换 |
| 可观测 | LangFuse(可选) |
| 评估 | RAGAS |
| 工程化 | Docker Compose + GitHub Actions CI + GitHub Container Registry 发布 |
+----------------------------------------------------------------+
| Next.js 14 (Frontend) |
| Chat UI Upload Sources History (共享 AuthBar/Nav) |
+--------------------------+----------------------------------------+
| HTTPS / SSE
+--------------------------v----------------------------------------+
| FastAPI (Backend) |
| +----------+ +----------+ +-------------+ +----------+ |
| | Auth | |Documents | | Chat / SSE | | History | |
| +----------+ +----------+ +------+------+ +----------+ |
| | |
| +----------------v------------------+ |
| | LangGraph Multi-Agent | |
| | Planner -> Retriever -> Coder -> Reviewer |
| +---+----------+---------+---------+ |
| | | | |
| +------v-----+ +--v-----+ +--v-------+ |
| | RAG 链路 | |LLM 路由| |MCP 工具集| |
| | BM25+Vec+RRF| |DS/Qwen/OAI| | (可插拔)| |
| +------+-----+ +--------+ +----------+ |
| | |
| +----------+--------+----------+----------+ |
| v v v v |
| Chroma PostgreSQL Redis LangFuse (可选) |
+--------------------------------------------------------------------+
backend/tests/ 现在拆成两层:
| 类型 | 文件 | 覆盖点 | 运行条件 |
|---|---|---|---|
| 单元测试 | test_*.py (除 integration) |
auth/ACL/CORS/限流/JWT cookie/上传大小/BM25 惰性重建/retriever 缓存/reviewer JSON/文档删除 等 | 不依赖 torch / chromadb / sentence-transformers / 真实 LLM |
| 集成测试 | test_integration_retrieval.py |
HybridRetriever add/retrieve/delete + RRF + rerank + pipeline 端到端 + agent 模式 | 走 tests/_fakes.py fake 路径, 无需重包 |
CI 已拆分为两个并行 job:
backend-unit-test: 跑单元测试 + lint + 覆盖率backend-integration-test: 跑 8 个集成测试, 验证完整检索/生成链路
当前参考数据(会随测试增加变化):
- 单元测试:~172 passed (核心: chunker / reranker / agent / 路由 / chunker overlap / kb_id / LLM router)
- 集成测试:8 passed
本地跑法:
cd backend
# 单元测试
python -m pytest -v --ignore=tests/test_integration_retrieval.py
# 集成测试(fake 路径, 不需要 chromadb/torch)
UNIKB_FAKE_EMBEDDING=1 python -m pytest -v tests/test_integration_retrieval.py
# 全部
python -m pytest -vUniKB 集成了 RAGAS 做自动化评估, 覆盖 4 个核心指标:
- faithfulness: 答案是否忠实于检索上下文(抑制幻觉)
- answer_relevancy: 答案与问题的相关程度
- context_precision: 检索结果里相关 chunk 的比例
- context_recall: 回答问题所需信息被召回的比例
下面数字来自
data/eval/baseline.json, 使用 fake LLM / fake embedding / fake 检索链路跑通, 仅用于验证评估脚本和链路本身, 不代表真实模型效果。真实 baseline 需要换成real_llm模式并配置 API key。
{
"generated_at": "2026-07-23T16:24:35.529926Z",
"llm_mode": "fake_llm",
"kb_id": "default",
"mode": "rag",
"n_samples": 34,
"scores": {
"faithfulness": 1.0,
"answer_relevancy": 0.2975,
"context_precision": 1.0,
"context_recall": 1.0
},
"nan_metrics": []
}说明:
faithfulness/context_precision/context_recall都是 1.0, 因为 fake judge 总是给出肯定 verdict, 这验证了 RAGAS parser 和 evaluate() 链路能正常结束。answer_relevancy只有 ~0.30, 是因为 fake embedding 用 32 维字符 hash 向量, 语义相似度基本是随机的; 真实模型下这个数字才有参考意义。
cd backend
# fake 模式: 不需要 API key, 用于 CI / 沙箱验证
python -m tests.run_ragas_eval --kb default \
--dataset ../data/eval/qa.jsonl \
--out ../data/eval/ragas_report.json \
--baseline-out ../data/eval/baseline.json \
--llm-mode fake_llm
# 真实模型模式: 需要 .env 里配置 LLM_API_KEY
python -m tests.run_ragas_eval --kb default \
--dataset ../data/eval/qa.jsonl \
--out ../data/eval/ragas_report.json \
--baseline-out ../data/eval/baseline.json \
--llm-mode real_llm评估后会生成两个文件:
data/eval/ragas_report.json: 完整报告, 含每条样本的 question/answer/contexts/ground_truthdata/eval/baseline.json: 精简版, 只保留 scores + 元信息, 适合提交到仓库做 baseline 对比
真实 RAG 项目里, 检索质量 > 模型能力。下面这套调优经验来自 RAG 知识库工程实践, 已落地到
backend/app/rag/并配有单元测试 (tests/test_cleaner.py等)。
| 手段 | 做法 | 收益 |
|---|---|---|
文档清洗 (cleaner.py) |
42 条正则去除控制字符/零宽字符/页眉页脚/脚注/标记语言残留/LaTeX/URL 归一, 确定性、可复现、零成本 | RAG 质量调优里清洗占 ~60% 精力, 直接决定向量质量上限 |
自适应切分 (chunker.py) |
短文档(≤ adaptive_short_doc_chars, 默认 500 字符)整篇保留为 1 个 chunk;长文档改用更大的 adaptive_chunk_size(默认 5500 字符) |
短文档不再被无谓切碎;长文档减少碎片、保留更长上下文 |
元数据增强 / HyDE (metadata.py) |
入库存抽取式元数据(关键词/摘要/语言), 并用 LLM 生成 假设性问题 (HyDE) 作为检索锚点(无 LLM 时回退到首句摘要) | 问句与文档陈述句式差异大时, 语义召回更稳 |
父文档召回 / 连坐召回 (retriever.py) |
召回某 chunk 时, 一并召回同文档的兄弟 chunk, 继承父 chunk 分数紧随其后 | 喂给 LLM 的是完整段落/章节, 而不是孤立碎句 |
重排候选池优化 (reranker.py) |
先按 RRF 截断候选池到 Top-60, 再让 Cross-Encoder 精排到 Top-25 | 压住 Cross-Encoder 算力 (延迟 11.4s → 2.3s), 同时保住召回上限 (Recall@5 93% → 98.7%) |
查询改写 (query_rewrite.py) |
LLM 把口语化/歧义问题扩写成多条检索 query(扩展/拆解/消歧), 多 query 分别检索后 RRF 融合;无 LLM 时走子句切分回退 | 扩大召回面, 尤其利好复合问题 |
离线 Recall@K 评估 (evaluate.py) |
RecallEvaluator 在 (question, relevant_ids) 数据集上统计 Recall@1/3/5/10, 对比调优前后 |
调参有量化依据, 不再凭感觉 |
开关全部集中在 app/core/config.py(cleaner_enabled / adaptive_chunking_enabled /
hyde_enabled / parent_recall_enabled / rerank_candidate_pool / rerank_top_n /
query_rewrite_enabled), 可逐项灰度。
.env 中设置:
LANGFUSE_ENABLED=true
LANGFUSE_PUBLIC_KEY=pk-...
LANGFUSE_SECRET_KEY=sk-...
LANGFUSE_HOST=https://cloud.langfuse.com
RAG pipeline(retriever / reranker / llm)会自动写入 trace;不启用时无副作用。
- 多 LLM 路由(按 provider + model 按请求切换)
- BM25 + 向量混合检索 + RRF
- LangGraph 多 Agent + Reviewer 回环(add_conditional_edges)
- MCP Server 单向暴露内部工具(stdio/SSE)
- SSE 流式问答
- 多轮对话 / 历史会话 / 引用溯源
- RAGAS 自动评估脚本 + JSON 报告
- LangFuse 可观测(可选)
- Docker Compose 一键起 + healthcheck
- 知识库 ACL + 用户/知识库隔离(P0)
- 安全加固:CORS 白名单、登录/Chat 限流、JWT HttpOnly cookie、CSP/HSTS(P0/P2)
- 性能优化:线程池 offload、BM25 惰性重建、Retriever 进程内缓存、CrossEncoderReranker 单例缓存(P1)
- 用户表 SQLAlchemy 化(SQLite,可切换 PostgreSQL)(P2-12)
- 文档删除接口 + vector/BM25 同步清理(P2-13)
- Chunker 主打包路径的 overlap 真的生效(P2)
- Planner 结构化决策 + Agent 工具路由(calculator / current_date / hybrid_search)(P2)
- RAG 工程调优:文档清洗(42 条正则)、自适应切分、HyDE 元数据增强、父文档召回(连坐召回)、重排候选池优化(Top-60→Top-25)、查询改写、离线 Recall@K 评估
- 对话历史/文件元数据统一 SQLAlchemy 迁移
- MinIO 文件存储
- MCP Client(Agent 消费外部 MCP 工具,双向集成)
MIT