一个可运行的 Skill-first + FAISS + BM25 + LangGraph RAG 系统,支持多模型厂商、分层记忆、Web 聊天界面,以及本地 FAISS 稠密召回 + BM25 稀疏召回 混合检索。

本项目是一个本地知识库 RAG 系统,核心思路是:
- 先用
Skill做目录和文件级路由 - 再执行 skill 检索
- 若证据不足,再通过
FAISS + BM25做本地混合补召回 - 通过
LangGraph编排全过程 - 结合分层记忆实现跨轮连续对话
它不是单纯的“文档切块 + 向量库问答”演示,而是把以下约束真正放进了执行链路:
Skill-firstPDF/Excel references强制读取无证据拒答引用校验多轮会话记忆
Skill-first检索主路径FAISS + BM25本地混合补召回LangGraph编排问答流程- 自动注册并调用
.agent/skills/*/SKILL.md PDF/Excel强制先读 skill referencesExcel结构化分析(LLM 规划 + pandas 执行)JSON FAQ记录级索引与字段级加权检索- 多模型厂商支持(chat / embedding / rerank)
- 分层记忆(SlidingWindow + SummaryBlocks + LongTermMemory)
- Web 聊天页 + REST API
- 电商客服问答闭环:未命中问题自动入待处理池,人工补答后回写 FAQ 并重建索引
后端与编排:
Python 3.11+FastAPILangGraphPydantic / pydantic-settings
检索与数据处理:
FAISSBM25pandasopenpyxlpypdfnumpy
模型调用:
openaiSDK(兼容多厂商 OpenAI-compatible API)requests
当前默认模型组合:
chat:zhipu / glm-5embedding:bailian / text-embedding-v4rerank:bailian / qwen3-rerank
主流程(简化):
/query
-> load_memory_context
-> analyze_query
-> route_by_skill_index
-> refine_query_plan
-> run_skill_retrieval
-> assess_evidence
-> run_vector_retrieval (FAISS + BM25 if needed)
-> fuse
-> rerank
-> generate
-> verify_citations
-> persist_memory
详细工作流图:
flowchart TD
A["用户 / 浏览器"] --> B["POST /query"]
B --> C["RAGService.query"]
C --> D["LangGraph 工作流"]
D --> E["load_memory_context<br/>读取 SlidingWindow / SummaryBlocks / LongTermMemory<br/>按需生成 effective_query"]
E --> F["analyze_query<br/>抽取 hard_terms / soft_terms / intent / answer_shape"]
F --> G["route_by_skill_index<br/>基于 knowledge/*/data_structure.md 做目录和文件路由"]
G --> H["refine_query_plan<br/>结合 candidate_files 做 file-aware soft_terms"]
H --> I["run_skill_retrieval<br/>先走 skill-first 检索"]
I --> J["assess_evidence<br/>判断 skill 证据是否足够"]
J -->|mode=skill| M["fuse_evidence"]
J -->|mode=vector| K["run_vector_retrieval"]
J -->|mode=hybrid 且 skill 不足| K
J -->|mode=hybrid 且 skill 足够| M
K["run_vector_retrieval<br/>FAISS dense + BM25 sparse<br/>默认受 candidate_files 限制"] --> M["fuse_evidence<br/>融合 skill 与 vector 证据"]
M --> N["rerank_evidence<br/>百炼 rerank / LLM fallback / builtin lexical"]
N --> O["generate_answer<br/>先做明确证据支撑校验<br/>无明确证据直接拒答"]
O --> P["verify_citations<br/>引用只能来自当前 evidence pool"]
P --> Q["persist_memory<br/>写入当前轮,按阈值生成摘要块"]
Q --> R["query 后处理<br/>返回 answer_support / evidence_preview<br/>必要时写入待人工补答池"]
R --> S["返回 answer / citations / confidence / evidence_preview"]
flowchart TD
A["run_skill_retrieval"] --> B["SkillManager"]
B --> C["按 selected_skills 逐个执行"]
C --> D["rag-skill"]
C --> E["其他 skills"]
D --> F["SkillRouter<br/>先路由到 candidate_dirs / candidate_files"]
F --> G["SkillRetriever<br/>在候选文件内做 chunk 检索"]
D --> H["ExcelStructuredAnalyzer<br/>命中 xlsx 时优先走结构化分析"]
G --> I["skill_evidence"]
H --> I
I --> J["若 mode=hybrid/vector 且需补召回"]
J --> K["VectorStore.search<br/>FAISS dense 检索 + BM25 sparse 检索<br/>weighted / rrf 融合"]
K --> L["fuse -> rerank"]
L --> M["明确证据支撑校验"]
M -->|通过| N["生成答案"]
M -->|不通过| O["拒答 + 进入待人工补答池"]
flowchart TD
A["POST /ingest"] --> B["扫描 knowledge/"]
B --> C["按 manifest 比较文件哈希"]
C --> D["仅重解析变更文件"]
D --> E["MD / TXT<br/>chunk_text 切块并保留行号"]
D --> F["PDF<br/>优先同名 txt sidecar<br/>否则按页切块"]
D --> G["Excel<br/>按 sheet / row 生成结构化行块"]
D --> H["JSON / FAQ<br/>按记录入块并保留 question / answer / label metadata"]
E --> I["写入 storage/parsed/chunks.jsonl"]
F --> I
G --> I
H --> I
I --> J["重载 ChunkRepository / SkillRouter"]
J --> K["EmbeddingGateway 生成向量"]
K --> L["构建本地 FAISS + BM25 混合索引"]
L --> M["写入 storage/vector/faiss_hybrid_index.pkl"]
N["检索未命中"] --> O["storage/feedback/customer_service_gaps.json"]
O --> P["人工审核 / 补答"]
P --> Q["写回 knowledge/E-commerce Data/faq.json"]
Q --> A
关键原则:
Skill先行,向量层只做补召回- 混合检索默认受
candidate_files限制 - 无证据时拒答,不做无依据生成
- 引用必须来自本轮 evidence pool
- 追问改写由模型判断是否需要结合上下文
当前默认向量层不是单纯 embedding 矩阵点积,而是 FAISS dense retrieval + local BM25 sparse retrieval。
设计如下:
- 稠密召回
- 由 embedding provider 生成向量
- 当前默认使用百炼
text-embedding-v4 - 使用
FAISS IndexFlatIP做稠密检索
- 稀疏召回
- 使用本地 BM25 统计索引
- 基于 chunk 文本和文件名联合建模
- 查询方式
mode=vector直接走混合检索mode=hybrid在 skill 证据不足时触发混合检索
- 混合排序
- 默认
weighted - 支持
rrf
- 默认
- 检索后处理
- 结果仍会进入
fuse -> rerank -> generate流程
- 结果仍会进入
当前相关实现见:
src/rag_graph/vector_store/index.pysrc/rag_graph/service.py
rag-skill-main/
├─ .agent/
│ └─ skills/
│ ├─ rag-skill/
│ │ ├─ SKILL.md
│ │ └─ references/
│ │ ├─ pdf_reading.md
│ │ ├─ excel_reading.md
│ │ └─ excel_analysis.md
│ └─ skill-creator/
├─ knowledge/
├─ src/
│ └─ rag_graph/
│ ├─ api/
│ ├─ feedback/
│ ├─ graph/
│ ├─ memory/
│ ├─ models/
│ ├─ parser_cache/
│ ├─ query_runtime/
│ ├─ skill_runtime/
│ └─ vector_store/
├─ storage/
├─ .env.example
├─ requirements.txt
├─ pyproject.toml
└─ README.md
Python >= 3.11- 安装
faiss-cpu - Windows / Linux / macOS(示例命令使用 PowerShell)
- 注册百炼,智谱api并配置到系统变量(这两家都会赠送新人token)
项目使用 RAG_ 前缀环境变量,并兼容部分厂商默认变量。
常用变量如下:
| 变量 | 说明 | 默认值 |
|---|---|---|
RAG_CHAT_PROVIDER |
chat 提供方 | zhipu |
RAG_CHAT_MODEL |
chat 模型 | glm-5 |
RAG_EMBED_PROVIDER |
embedding 提供方 | bailian |
RAG_EMBED_MODEL |
embedding 模型 | text-embedding-v4 |
RAG_RERANK_PROVIDER |
rerank 提供方 | bailian |
RAG_RERANK_MODEL |
rerank 模型 | qwen3-rerank |
RAG_VECTOR_BACKEND |
向量后端 | faiss |
RAG_FAISS_HYBRID_RANKER |
混合排序器 | weighted |
RAG_FAISS_DENSE_WEIGHT |
dense 权重 | 0.65 |
RAG_FAISS_SPARSE_WEIGHT |
BM25 权重 | 0.35 |
RAG_BM25_K1 |
BM25 k1 参数 | 1.5 |
RAG_BM25_B |
BM25 b 参数 | 0.75 |
ZHIPU_API_KEY |
智谱 API Key | 无 |
DASHSCOPE_API_KEY |
百炼 API Key | 无 |
当前仅支持 faiss,即使环境里残留旧值,也应改为 faiss。示例配置见 .env.example。
- 安装依赖
python -m pip install --user -r requirements.txt- 配置模型与后端
$env:RAG_CHAT_PROVIDER = "zhipu"
$env:RAG_CHAT_MODEL = "glm-5"
$env:RAG_EMBED_PROVIDER = "bailian"
$env:RAG_EMBED_MODEL = "text-embedding-v4"
$env:RAG_RERANK_PROVIDER = "bailian"
$env:RAG_RERANK_MODEL = "qwen3-rerank"
$env:RAG_VECTOR_BACKEND = "faiss"
$env:ZHIPU_API_KEY = "your_zhipu_key"
$env:DASHSCOPE_API_KEY = "your_dashscope_key"- 启动服务
$env:PYTHONPATH = "src"
python -m uvicorn rag_graph.api.main:app --host 0.0.0.0 --port 8000- 首次构建索引
Invoke-RestMethod -Method Post `
-Uri "http://127.0.0.1:8000/ingest" `
-ContentType "application/json" `
-Body '{"force": false}'这一步会:
-
解析
knowledge/中的文件 -
写入
storage/parsed/chunks.jsonl -
构建
FAISS + BM25本地混合索引 -
同步写入
storage/vector/faiss_hybrid_index.pkl -
打开页面
-
聊天页:
http://127.0.0.1:8000/ -
OpenAPI:
http://127.0.0.1:8000/docs -
发起查询(API 示例)
$body = @{
query = "三一重工的前三大股东是谁?"
mode = "hybrid" # skill | hybrid | vector
top_k = 8
session_id = "demo-session-1"
actor_id = "demo-user-1"
} | ConvertTo-Json
Invoke-RestMethod -Method Post `
-Uri "http://127.0.0.1:8000/query" `
-ContentType "application/json" `
-Body $body- 健康检查
Invoke-RestMethod "http://127.0.0.1:8000/health"你会在返回结果中看到:
vector_backendvector_index_readyvector_index_metadatavector_dimensioncustomer_service_feedbackanswer_supportevidence_preview
当同时满足下面两个条件时,系统会自动把问题记入待处理池:
- 当前问题已经完成一次完整检索
- 本轮最终没有检索到任何明确可用的知识证据
默认存储位置:
- 待处理池:
storage/feedback/customer_service_gaps.json - FAQ 问答库:
knowledge/E-commerce Data/faq.json
闭环流程:
- 用户提出电商客服问题
- 系统完成检索,但没有拿到任何明确可回答证据
/query返回customer_service_feedback.captured=true- 运营或人工客服查看待处理问题
- 人工填写标准答案
- 系统把新问答对写回
faq.json - 自动触发一次
/ingest - 后续同类问题可直接命中 FAQ
另外,/query 还会返回:
answer_support- 标识当前答案是否有明确证据支撑
- 包含
max_support_score和门槛值
evidence_preview- 返回本轮实际命中的原始证据预览
- 包含来源文件、位置、检索来源、分数和片段摘要
查看待处理问题:
Invoke-RestMethod `
"http://127.0.0.1:8000/customer-service/gaps?status=open&limit=50"人工补答并回写 FAQ:
$body = @{
answer = "进入订单详情页后提交退款申请即可。"
reviewer = "ops"
label = "refund"
auto_ingest = $true
} | ConvertTo-Json
Invoke-RestMethod -Method Post `
-Uri "http://127.0.0.1:8000/customer-service/gaps/<gap_id>/resolve" `
-ContentType "application/json; charset=utf-8" `
-Body $bodyGET /GET /chatGET /healthPOST /ingestPOST /queryPOST /evaluateGET /skillsPOST /skills/executeGET /customer-service/gapsPOST /customer-service/gaps/{gap_id}/resolve
项目中 skill 设计与思路参考了 ConardLi/rag-skill。
本项目用于学习与工程实践演示。示例数据请按各自来源和使用条款处理。