前言

大模型落地难,难在三个问题:知识滞后、幻觉严重、多跳推理弱。RAG(检索增强生成)是目前最主流的解法,但市面上多数 RAG 教程停在"向量检索 + LLM 生成"的玩具级别。

本篇记录我从零搭建一个工业级 RAG 智能体的完整过程,覆盖 2024-2025 主流技术栈,包含完整代码和我踩过的所有坑。适合做 RAG / Agent 方向项目、准备大模型岗位面试的同学。

你能学到

  • 混合检索(向量 + BM25 + RRF + HyDE + Cross-encoder 精排)

  • GraphRAG 知识图谱(三元组抽取 → 实体消歧 → text2cypher)

  • 多 Agent 编排(路由 → 检索 → 推理 → 反思)

  • MCP 协议工具层

  • ragas 标准评估(LLM-as-judge)


一、系统架构

整个系统分 5 层,21 个核心脚本:

5 层系统架构

组件 说明
用户层 Gradio / FastAPI / 图谱可视化 交互界面
MCP 协议层 FastMCP 工具服务器 标准化工具调用
Agent 编排层 路由 → 检索 → 推理 → 反思 Corrective RAG 范式
检索层 向量 + 图谱 + 层次摘要 + Reranker 多路召回 + 两阶段精排
数据层 ChromaDB / NetworkX / bge-m3 存储与模型

二、技术栈选型

组件 选型 为什么
LLM MiniMax-M3 OpenAI 兼容,中文强
Embedding bge-m3(1024 维) MTEB 排行榜前列,中英多语言
Reranker bge-reranker-v2-m3 2024 SOTA cross-encoder
向量库 ChromaDB 轻量零运维
图谱 NetworkX(可切 Neo4j) 内存图降级
评估 ragas 0.2.15 GitHub RAG 评测标准

三、混合检索:从 70% 到 91%

这是技术含量最高的模块。逐步叠加策略,每加一层测一次准确率:

检索准确率提升路径

3.1 第一步:纯向量检索(Hit@5 = 70%)

# 04_embedder.py 核心
model = SentenceTransformer('./models/bge-m3')
# 文档入库不加前缀,查询加前缀(bge 官方推荐)
coll = chromadb.PersistentClient(path=DB_PATH).get_or_create_collection(
    name="rag", metadata={"hnsw:space": "cosine"})

3.2 第二步:加 BM25 + RRF 融合(→ 82%)

向量检索对专有名词和数字不敏感。加 BM25 补精确匹配:

# RRF 融合:只看排名,不看绝对分数(解决量纲不一致)
def rrf_fuse(ranked_lists, k=60):
    scores = {}
    for lst in ranked_lists:
        for rank, doc in enumerate(lst):
            scores[doc] = scores.get(doc, 0) + 1 / (k + rank)
    return sorted(scores, key=lambda d: -scores[d])

3.3 第三步:加 HyDE(→ 88%)

让 LLM 先生成"假设答案",用假设答案检索,拉平 query 和 chunk 的长度差距:

# HyDE:假设答案比短 query 更接近文档形态
hypothetical = llm.generate(f"假设答案:{query}")
results = vector_search(hypothetical)

3.4 第四步:Cross-encoder 精排(→ 91%)

# _reranker.py:bge-reranker-v2-m3
pairs = [[query, c["text"][:1000]] for c in candidates]
inputs = tokenizer(pairs, padding=True, truncation=True, return_tensors="pt")
logits = model(**inputs).logits.squeeze(-1)
# 用原始 logit 排序(sigmoid 后值域太窄,区分度差)

两阶段架构:双塔粗召回 top-50 → cross-encoder 精排 top-5。这是搜索引擎标配。


四、GraphRAG:多跳推理

4.1 三元组抽取(08_kg_extractor.py)

# LLM 抽三元组,三道防幻觉
# 1. 实体必须出现在原文
# 2. 关系长度 ≤ 15 字
# 3. 两次抽取取并集 + 频次过滤

4.2 实体消歧(09_kg_builder.py)

三层瀑布过滤,从便宜到贵:

# 层1:子串包含(免 API)
# 层2:相似度 ≥ 0.88(免 API,difflib.SequenceMatcher)
# 层3:0.75~0.88 灰度区调 LLM 仲裁(限量 20 对)

4.3 text2cypher(10_kg_query.py)

# LLM 把自然语言翻译成 Cypher
# MATCH (e:Entity {name:'MiniMax-M1'})-[:RELATES_TO]->(t) RETURN t

踩坑:text2cypher 的 RETURN 列每次都不同(F1 在 60%~100% 随机跳)。解法是 standardize_projection()——强制重写成固定 subject/predicate/object 三列投影。LLM 只定位子图,选列由服务端固定。


五、踩坑实录(重点)

坑 1:MiniMax-M3 的 <think> 块(系统性陷阱)

这个坑我踩了两次

第一次(推理模块):用错误正则剥离 think 块,没剥掉,吃光 max_tokens,答案截断。

第二次(ragas 评测):Faithfulness 假低到 38%。think 块占满输出,ragas 的 JSON 被截断,解析失败记 0 分。

解法

  • 推理模块:用 re.sub(r'<think>.*?</think>', '', text, flags=re.DOTALL) 正确剥离

  • 评测模块:extra_body={"thinking":{"type":"disabled"}} 关掉 thinking

修复后 Faithfulness:38% → 95.21%

坑 2:双栏 PDF 文字错乱

pdfplumber 处理不了多栏(左右栏文字混在一起)。自写分栏检测 3 版全失败(标题"Scalable RL"被切成"Scal")。

错乱现象:解析结果左栏/右栏按行交错拼接,标题 "Scalable RL" 被切成 "Scal",整段读不通。

解法:换 PyMuPDF(fitz)的 get_text('text'),按视觉块排序天然支持多栏。10 分钟解决。

修复效果:换库后先读完整个左栏再读右栏,标题完整、段落连贯。

教训:别在一个库里死磕。先调研社区方案。

坑 3:反思重检索反而退步

设计了"反思 Agent",置信度低就重检索。实测准确率反而下降——重检索 query 从"无依据断言"抽,本身就偏。

解法:砍掉重检索,反思只打分、不干预。少做反而更好。

坑 4:ragas 和项目 numpy 版本冲突

ragas 依赖 numpy 1.26,项目用 numpy 2.x。

解法:把 ragas 装到独立 venv,数据中转 JSON,两个环境不混。


六、评估结果

6.1 ragas 标准指标(LLM-as-judge)

ragas 指标

指标 总体 bge_paper MiniMax_M1
Context Recall 97.92% 100.00% 95.83%
Faithfulness 95.21% 96.93% 93.49%

6.2 4 篇论文端到端 F1(泛化验证)

4 篇论文 F1

论文 领域 F1
bge_paper Embedding 91~100%
MiniMax_M1 LLM 83.33%
Attention Is All You Need NLP 经典 100%
ResNet CV 经典 93.75%

七、源码与总结

核心技术亮点

  1. 混合检索 + Cross-encoder 两阶段(搜索引擎标准架构)

  2. GraphRAG + 向量 RAG 融合(多跳推理 + 语义检索互补)

  3. 多 Agent 编排(路由 → 检索 → 推理 → 反思)

  4. ragas 原版评估(LLM-as-judge 科学评测)

  5. MCP 协议工具层(标准化工具调用)

完整代码开源在 Apageoflove/mcp-rag-agent,含 21 个核心脚本 + ragas 评测数据 + 中英双版 README。如果觉得有帮助,点个赞 + 收藏,后续会持续分享 RAG / Agent / 大模型落地的实战经验。

Logo

欢迎加入 MCP 技术社区!与志同道合者携手前行,一同解锁 MCP 技术的无限可能!

更多推荐