RAG 架构:从朴素检索到智能知识系统

Embedding 选型 · Chunking 策略 · 检索增强 · Reranking · Agentic RAG · Graph RAG · 实战代码

📋 目录

01 RAG 是什么?为什么需要它?

RAG(Retrieval-Augmented Generation)是一种将外部知识检索与大语言模型生成相结合的架构模式。核心思想:与其让 LLM 死记硬背所有知识,不如在需要时动态检索相关信息,让模型基于真实数据生成回答。

┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ User Query │────→│ Retriever │────→│ Generator │ │ 用户提问 │ │ 检索相关文档 │ │ LLM 生成回答 │ └──────────────┘ └──────┬───────┘ └──────────────┘ │ ┌──────▼───────┐ │ Knowledge │ │ 知识库/文档库 │ └──────────────┘

为什么需要 RAG?

🧠 LLM 的知识局限
  • 知识截止 — 训练数据有截止日期,无法获取最新信息
  • 幻觉问题 — 模型会编造看似合理但错误的事实
  • 领域缺失 — 企业私有数据模型从未见过
  • 长尾知识 — 低频专业知识覆盖率极低
✅ RAG 的解决方式
  • 实时检索 — 每次查询获取最新文档,无截止问题
  • 事实锚定 — 基于真实文档生成,减少幻觉
  • 私有知识 — 企业文档直接作为知识源
  • 可追溯 — 回答可引用来源,支持审计

RAG vs 微调 vs 长上下文

维度RAG微调 (Fine-tuning)长上下文 (Long Context)
知识更新即时(更新文档即可)需重新训练即时(塞入新内容)
成本低(检索+推理)高(训练成本)高(Token 费用)
准确性高(事实锚定)中(可能遗忘)中(中间内容易丢失)
可解释性强(引用来源)弱(黑盒)弱(无法定位)
私有数据天然支持需安全训练环境需安全上下文传输
风格/行为调整弱(不改模型行为)强(深度定制)弱(不改模型行为)
最佳场景知识密集型问答风格/格式定制短文档一次性处理
💡 实践建议:RAG 和微调不是互斥的。最常见的生产组合是:RAG 处理事实性知识检索 + 微调处理风格和格式。例如,用 RAG 检索企业知识库,用微调让模型以品牌调性回答。

02 何时用 RAG,何时不用

✅ 适合用 RAG 的场景

❌ 不适合用 RAG 的场景

⚠️ 常见误区:不是所有 LLM 应用都需要 RAG。很多场景下,好的 Prompt Engineering + Few-shot 示例就够了。RAG 引入了检索延迟、索引维护、分块策略等复杂度——只有当模型的内置知识确实不够时才值得引入。

03 RAG Pipeline 全景

一个完整的 RAG 系统分为两大阶段:离线索引(Indexing)和在线查询(Querying)。

════════════════ 离线索引阶段 (Indexing) ════════════════ 文档集合 ──→ 文档加载 ──→ 文本分块 ──→ Embedding ──→ 向量存储 │ (PDF/HTML/DB/API) Loader Chunker Embedder VectorDB │ ┌───────▼───────┐ │ Metadata │ │ 元数据索引 │ └───────────────┘ ════════════════ 在线查询阶段 (Querying) ════════════════ 用户提问 ──→ Query 理解 ──→ 检索 ──→ Reranking ──→ 上下文组装 ──→ LLM 生成 ──→ 回答 │ │ │ │ │ Rewrite/Expand 向量+关键词 交叉编码器 去重/截断 Prompt 模板 HyDE/Multi-Q 混合检索 Cohere/BGE Source 引用 幻觉检测

朴素 RAG vs 高级 RAG

阶段朴素 RAG (Naive)高级 RAG (Advanced)
索引固定大小分块 + 单一 Embedding语义分块 + 多粒度索引 + 元数据增强
检索纯向量搜索,Top-K混合检索(向量+BM25)+ Query 改写 + HyDE
排序无(直接用检索分数)Reranking(交叉编码器/Cohere)
生成简单拼接上下文上下文压缩 + 引用标注 + 幻觉检测
评估RAGAS 框架系统性评估
典型准确率40-60%75-90%+
🎯 从朴素到高级的进化路径:先跑通朴素 RAG → 评估瓶颈 → 针对性优化。不要一开始就上全套——每个优化都有成本(延迟、复杂度、维护)。

04 Embedding 模型选型

Embedding 模型将文本转换为高维向量,是 RAG 的基石。选型直接影响检索质量和性能。

主流模型对比

模型维度MTEB 均分中文支持推理速度许可证特点
text-embedding-3-large 3072/1024/256 ~68 ✅ 好 API 调用 商业 OpenAI 最新,支持维度裁剪
text-embedding-3-small 1536/512 ~65 ✅ 好 API 调用 商业 性价比高,适合大多数场景
bge-m3 1024 ~70 ✅ 优秀 本地 GPU MIT 多语言+多粒度+多功能,开源标杆
bge-large-zh-v1.5 1024 ~64(CMTEB) ✅ 最佳 本地 GPU MIT 中文场景首选,C-MTEB 榜首
gte-Qwen2-1.5B 1536 ~72 ✅ 优秀 本地 GPU Apache 2.0 阿里出品,MTEB 榜单前列
Cohere embed-v3 1024 ~67 ✅ 好 API 商业 支持搜索查询/文档类型标记
Voyage-3 1024 ~68 ✅ 好 API 商业 代码检索强,成本较低
Google text-005 768 ~66 ✅ 好 API 商业 Vertex AI 生态,多模态

选型决策

🔍 如何选择?

代码:OpenAI Embedding

Python — OpenAI Embedding API
from openai import OpenAI

client = OpenAI()

def embed_texts(texts: list[str], model="text-embedding-3-small") -> list[list[float]]:
    """批量生成 Embedding,支持维度裁剪"""
    response = client.embeddings.create(
        input=texts,
        model=model,
        dimensions=512  # 裁剪到 512 维,节省 2/3 存储
    )
    return [item.embedding for item in response.data]

# 单条 vs 批量
query_vec = embed_texts(["什么是 RAG?"])[0]
doc_vecs = embed_texts(["RAG 是检索增强生成", "微调是..."])

代码:本地 BGE-M3

Python — FlagEmbedding 本地推理
from FlagEmbedding import BGEM3FlagModel

model = BGEM3FlagModel('BAAI/bge-m3', use_fp16=True)

# 支持三种检索粒度:dense / sparse / colbert
embeddings = model.encode(
    ["RAG 架构详解", "向量数据库对比"],
    return_dense=True,
    return_sparse=True,
    return_colbert_vecs=True,
)

dense_vecs = embeddings['dense_vecs']       # (2, 1024) 密集向量
sparse_vecs = embeddings['lexical_weights']  # 稀疏词权重(类似 BM25)
colbert_vecs = embeddings['colbert_vecs']    # (2, seq_len, 128) token 级向量
💡 BGE-M3 的多粒度是杀手锏:一次编码同时获得 dense(语义相似)、sparse(关键词匹配)、ColBERT(细粒度交互)三种表示,可以在检索时混合使用,效果远超单一方法。详见 混合检索 章节。

05 Chunking 策略:分块的艺术

Chunking 决定了检索粒度——太粗则噪声多,太细则丢失上下文。这是 RAG 系统中最被低估但对效果影响最大的环节之一。

六大分块策略

📐 固定大小分块

最简单

按固定 token/字符数切割,带重叠窗口

✅ 简单可靠、易于实现
✅ 大小均匀,适合批量处理
❌ 可能切断语义完整性
❌ 对表格/代码不友好

📄 语义分块

推荐

基于 Embedding 相似度,在语义断点处切割

✅ 保持语义完整性
✅ 检索精度最高
❌ 需要 Embedding 计算,成本高
❌ 块大小不均匀

📋 结构化分块

推荐

按文档结构(标题/段落/章节)切割

✅ 天然语义完整
✅ 保留层级信息作为元数据
❌ 依赖文档结构质量
❌ 无结构的纯文本不适用

🔗 递归分块

LangChain 默认

按分隔符层级递归切割,优先保持段落完整

✅ 兼顾结构和大小
✅ LangChain 开箱即用
❌ 分隔符选择需要调优
❌ 对代码/表格仍不理想

🌲 父子分块

进阶

小块检索,大块提供上下文

✅ 检索精准 + 上下文丰富
✅ 最佳实践之一
❌ 索引复杂度翻倍
❌ 需要维护映射关系

🧩 多粒度分块

进阶

同时索引句子、段落、文档级,检索时合并

✅ 适应不同查询粒度
✅ 支持递归检索
❌ 存储和计算成本高
❌ 去重和合并逻辑复杂

关键参数调优

参数典型值影响调优建议
chunk_size256-1024 tokens大小直接影响检索精度和上下文量问答:256-512;摘要:512-1024
chunk_overlap10-25% of size防止边界信息丢失重叠太少→边界丢失;太多→冗余
separators文档类型相关递归分块的切分优先级Markdown: \n## → \n### → \n\n → \n

代码:LangChain 递归分块

Python — RecursiveCharacterTextSplitter
from langchain.text_splitter import RecursiveCharacterTextSplitter

splitter = RecursiveCharacterTextSplitter(
    chunk_size=512,
    chunk_overlap=64,
    separators=["\n## ", "\n### ", "\n\n", "\n", "。", "!", "?", ".", " ", ""],
    length_function=lambda x: len(x),  # 或 tiktoken 计数器
)

chunks = splitter.split_text(markdown_text)

# 每个chunk自动继承元数据
print(f"生成 {len(chunks)} 个分块")
print(f"平均长度: {sum(len(c) for c in chunks) / len(chunks):.0f} 字符")

代码:语义分块

Python — SemanticChunker (LangChain)
from langchain_experimental.text_splitter import SemanticChunker
from langchain_openai import OpenAIEmbeddings

# 基于语义相似度断点切割
splitter = SemanticChunker(
    OpenAIEmbeddings(),
    breakpoint_threshold_type="percentile",  # 百分位数断点
    breakpoint_threshold_amount=75,          # 前 75% 相似度内不断
)

chunks = splitter.split_text(long_document)

# 输出示例:
# Chunk 1: "RAG 是检索增强生成..." (语义连贯的第一段)
# Chunk 2: "Embedding 模型将文本..." (语义转换处断开)

代码:父子分块(Small-to-Big Retrieval)

Python — ParentDocumentRetriever
from langchain.retrievers import ParentDocumentRetriever
from langchain.storage import InMemoryStore
from langchain_chroma import Chroma
from langchain_openai import OpenAIEmbeddings
from langchain.text_splitter import RecursiveCharacterTextSplitter

# 子分块器:小而精准,用于检索
child_splitter = RecursiveCharacterTextSplitter(chunk_size=200)

# 父分块器:大而完整,用于提供上下文
parent_splitter = RecursiveCharacterTextSplitter(chunk_size=1000)

vectorstore = Chroma(embedding_function=OpenAIEmbeddings())
docstore = InMemoryStore()  # 父文档存储

retriever = ParentDocumentRetriever(
    vectorstore=vectorstore,        # 存子分块的向量
    docstore=docstore,              # 存父分块的原文
    child_splitter=child_splitter,
    parent_splitter=parent_splitter,
)

# 检索时:先搜子分块 → 返回其父分块作为上下文
results = retriever.invoke("什么是 RAG?")
# 返回的是 1000-token 的父分块,但匹配的是 200-token 的子分块
🎯 分块策略选择路径:文档有结构 → 结构化分块 → 没结构 → 递归分块 → 效果不够 → 父子分块 → 还不够 → 语义分块。大多数场景递归分块 + 合理的 chunk_size 就够了。

06 检索策略:从向量搜索到混合检索

1. 稠密检索 (Dense Retrieval)

最基础的方式:将 query 和文档都编码为向量,用余弦相似度找最近的 K 个文档。

✅ 优势
❌ 局限

2. 稀疏检索 (Sparse Retrieval / BM25)

传统关键词检索:TF-IDF 的进化版,基于词频和文档频率打分。

Python — BM25 检索(rank_bm25)
from rank_bm25 import BM25Okapi
import jieba  # 中文分词

# 中文分词
tokenized_corpus = [list(jieba.cut(doc)) for doc in documents]
bm25 = BM25Okapi(tokenized_corpus)

# 查询
query_tokens = list(jieba.cut("RAG 架构设计"))
scores = bm25.get_scores(query_tokens)
top_k_indices = scores.argsort()[-5:][::-1]

3. 混合检索 (Hybrid Retrieval) 推荐

结合稠密检索和稀疏检索,取长补短——当前业界最佳实践。

User Query ┌──────┴──────┐ │ │ Dense Sparse 向量检索 BM25 检索 (语义匹配) (关键词匹配) │ │ └──────┬──────┘ │ Score Fusion (分数融合) RRF / 加权平均 / 学习排序 │ Top-K Results
Python — 混合检索实现
import numpy as np
from rank_bm25 import BM25Okapi

def hybrid_search(
    query: str,
    query_embedding: np.ndarray,    # (dim,)
    doc_embeddings: np.ndarray,      # (n, dim)
    doc_texts: list[str],
    top_k: int = 10,
    dense_weight: float = 0.7,
    sparse_weight: float = 0.3,
) -> list[tuple[int, float]]:
    """混合检索:Dense + Sparse 分数融合"""

    # 1. Dense 检索(余弦相似度)
    dense_scores = np.dot(doc_embeddings, query_embedding) / (
        np.linalg.norm(doc_embeddings, axis=1) * np.linalg.norm(query_embedding)
    )

    # 2. Sparse 检索(BM25)
    tokenized = [doc.split() for doc in doc_texts]
    bm25 = BM25Okapi(tokenized)
    sparse_scores = np.array(bm25.get_scores(query.split()))

    # 3. Min-Max 归一化到 [0, 1]
    def normalize(scores):
        s_min, s_max = scores.min(), scores.max()
        return (scores - s_min) / (s_max - s_min + 1e-8)

    dense_norm = normalize(dense_scores)
    sparse_norm = normalize(sparse_scores)

    # 4. 加权融合
    combined = dense_weight * dense_norm + sparse_weight * sparse_norm

    # 5. 取 Top-K
    top_indices = combined.argsort()[-top_k:][::-1]
    return [(idx, combined[idx]) for idx in top_indices]

4. RRF (Reciprocal Rank Fusion)

比加权平均更稳健的融合策略,基于排名而非原始分数。

Python — RRF 融合
def reciprocal_rank_fusion(
    ranked_lists: list[list[int]],  # 多个排序列表
    k: int = 60,                    # RRF 常数,通常 60
) -> list[tuple[int, float]]:
    """RRF 融合:基于排名的融合,对分数尺度不敏感"""
    scores = {}
    for ranked_list in ranked_lists:
        for rank, doc_id in enumerate(ranked_list, start=1):
            scores[doc_id] = scores.get(doc_id, 0) + 1.0 / (k + rank)

    # 按融合分数排序
    sorted_results = sorted(scores.items(), key=lambda x: x[1], reverse=True)
    return sorted_results

# 用法:将 Dense 和 Sparse 的 Top-K 排名列表传入
dense_ranking = [3, 7, 1, 9, 5]    # Dense 检索的文档 ID 排名
sparse_ranking = [1, 5, 3, 2, 8]   # BM25 检索的文档 ID 排名
fused = reciprocal_rank_fusion([dense_ranking, sparse_ranking])

5. Query 改写与增强

🔄 Query Rewrite

用 LLM 重写 query 使其更适合检索

Prompt 示例
将以下用户问题改写为
更适合向量检索的查询:
原问题:{query}
要求:保留核心意图,去除口语化表达,
添加同义关键词

🪞 HyDE (Hypothetical Document)

让 LLM 先生成"假设性答案",用答案的 embedding 去检索

原理 假设答案在语义空间更接近真实文档
注意 LLM 生成错误时效果下降

🔀 Multi-Query

用 LLM 从多个角度重写 query,分别检索后合并

优势 覆盖多种表达方式
成本 多次检索 + Embedding

🎯 Step-back Prompting

从具体问题抽象到更宽泛的问题,检索背景知识

例:"2024 年 Q3 营收" → "公司历史营收趋势"
Python — Multi-Query Retriever
from langchain.retrievers.multi_query import MultiQueryRetriever
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
from langchain_chroma import Chroma

llm = ChatOpenAI(temperature=0)
vectorstore = Chroma(embedding_function=OpenAIEmbeddings())

# 自动生成多个查询角度
retriever = MultiQueryRetriever.from_llm(
    retriever=vectorstore.as_retriever(search_kwargs={"k": 5}),
    llm=llm,
)

# 一行代码,背后生成 3-5 个改写 query
docs = retriever.invoke("RAG 系统如何优化检索效果?")

07 Reranking:精确排序的关键

初始检索(Recall 阶段)求全不求精——宁可多返回一些相关文档。Reranking(Precision 阶段)对候选文档精确排序,大幅提升最终质量。

Initial Retrieval Reranking Final Results ───────────────── → ────────────────── → ────────────────── Top-50~100 候选 交叉编码器精排 Top-5~10 精选 (高召回,低精度) (query+doc 联合编码) (高精度,有依据) 速度: <50ms 速度: 100-500ms 最终: 用于 LLM 方法: 向量+BM25 方法: Cross-Encoder 上下文窗口有限

主流 Reranker 对比

Reranker类型中文延迟 (100 docs)成本特点
bge-reranker-v2-m3本地 Cross-Encoder✅ 优秀~200msGPU 资源开源最佳,多语言
Cohere RerankAPI✅ 好~100ms$0.002/次效果顶级,开箱即用
bce-reranker-base_v1本地 Cross-Encoder✅ 优秀~150msGPU 资源网易出品,中文场景强
ColBERTLate Interaction✅ 好~300msGPU 资源token级交互,细粒度
LLM-as-JudgeGPT-4/Claude 打分✅ 好~2-5s$0.01-0.05最智能,但太慢太贵
FlashRank轻量本地⚠️ 一般~10ms免费极速,适合实时场景
Python — BGE Reranker
from FlagEmbedding import FlagReranker

reranker = FlagReranker('BAAI/bge-reranker-v2-m3', use_fp16=True)

# 对 (query, doc) 对打分
pairs = [
    ["什么是 RAG?", "RAG 是检索增强生成,结合外部知识检索与 LLM 生成"],
    ["什么是 RAG?", "微调是通过训练数据调整模型参数的方法"],
    ["什么是 RAG?", "RAG 系统的 chunking 策略对效果影响很大"],
]

scores = reranker.compute_score(pairs)
# 输出: [0.92, 0.15, 0.78]  → 第 1 个最相关

# 实际使用:对检索结果重排
def rerank_results(query: str, docs: list[str], top_k: int = 5) -> list[int]:
    pairs = [[query, doc] for doc in docs]
    scores = reranker.compute_score(pairs)
    ranked = sorted(range(len(docs)), key=lambda i: scores[i], reverse=True)
    return ranked[:top_k]
Python — Cohere Rerank API
import cohere

co = cohere.Client("your-api-key")

results = co.rerank(
    model="rerank-v3.5",
    query="RAG 架构最佳实践",
    documents=[
        "RAG 是检索增强生成...",
        "微调需要大量标注数据...",
        "混合检索结合语义和关键词匹配...",
    ],
    top_n=3,
)

for r in results.results:
    print(f"Rank {r.index}: score={r.relevance_score:.3f}")
💡 Reranking 的性价比:加一个 Reranker 通常是最"便宜"的 RAG 优化——代码量少、延迟可控、效果提升显著(5-15% 的准确率提升)。如果只做一个优化,就加 Reranker。

08 生成阶段:上下文组装与幻觉控制

上下文组装策略

📦 上下文窗口有限,怎么用?

假设 LLM 上下文窗口 128K tokens,实际可用约 100K(系统提示 + 对话历史占用):

Prompt 模板设计

Python — 最佳实践 Prompt 模板
RAG_PROMPT = """你是一个知识助手。根据以下检索到的文档片段回答用户问题。

## 规则
1. 仅根据提供的文档内容回答,不要使用外部知识
2. 如果文档中没有相关信息,明确说"根据现有文档无法回答"
3. 每个论点必须引用来源,格式:[来源编号]
4. 不要编造文档中没有的信息

## 检索到的文档
{context}

## 用户问题
{query}

## 回答(引用来源,如不确定请说明)"""

def build_context(documents: list[dict]) -> str:
    """组装上下文,标注来源"""
    parts = []
    for i, doc in enumerate(documents, 1):
        source = doc.get('source', '未知来源')
        parts.append(f"[{i}] 来源: {source}\n{doc['text']}")
    return "\n\n".join(parts)

幻觉检测与控制

🔍 生成后验证
  • 引用校验 — 检查回答中的引用是否对应真实文档
  • 事实一致性 — 用 NLI 模型判断回答与文档是否矛盾
  • LLM 审查 — 用另一个 LLM 验证回答是否仅基于文档
🛡️ 生成时控制
  • 温度=0 — RAG 场景降低温度减少创造性
  • 显式约束 — Prompt 中明确禁止编造
  • 结构化输出 — 要求 JSON 格式输出含引用字段

09 进阶架构:Agentic RAG / Graph RAG / Modular RAG

当朴素 RAG 不够用时,需要更智能的架构。2024-2025 年最重要的三个进化方向:

════ Naive RAG ════ ════ Advanced RAG ════ ════ Modular RAG ════ Query → Retrieve → Generate Query → Rewrite → Hybrid 灵活组合模块: (单次,无反馈) → Rerank → Generate 路由、检索、 (优化检索,单次) 重写、生成 可独立迭代 ════ Agentic RAG ════ ════ Graph RAG ════ Agent 决策检索策略: 知识图谱 + 向量检索: - 判断是否需要检索 - 实体和关系提取 - 选择检索源 - 图遍历补充关联知识 - 多轮迭代检索 - 社区检测 + 层级摘要 - 自我评估质量 - 全局理解能力强 - 串行多工具调用

Agentic RAG:让 Agent 自主决定如何检索

核心思想:检索不再是固定 pipeline,而是 Agent 的一个工具。Agent 可以决定:

Python — Agentic RAG(LangGraph 实现)
from langgraph.prebuilt import create_react_agent
from langchain_openai import ChatOpenAI
from langchain_core.tools import tool

# 定义多种检索工具
@tool
def search_internal_docs(query: str) -> str:
    """搜索企业内部文档库"""
    # 向量检索 + BM25 混合检索
    results = hybrid_retriever.invoke(query)
    return format_results(results)

@tool
def search_web(query: str) -> str:
    """搜索互联网获取最新信息"""
    # 调用搜索 API
    return web_search_api(query)

@tool
def search_codebase(query: str) -> str:
    """搜索代码仓库"""
    # 代码专用检索
    return code_search(query)

@tool
def evaluate_sufficiency(context: str, query: str) -> str:
    """评估当前上下文是否足够回答问题"""
    prompt = f"给定上下文是否足以回答问题?回答 YES 或 NO。\n上下文: {context}\n问题: {query}"
    return ChatOpenAI(temperature=0).invoke(prompt).content

# Agent 自主决定使用哪些工具、是否需要更多检索
agent = create_react_agent(
    model=ChatOpenAI(model="gpt-4o"),
    tools=[search_internal_docs, search_web, search_codebase, evaluate_sufficiency],
)

# Agent 可能的执行路径:
# 1. 搜索内部文档 → 评估不足 → 搜索网页 → 生成回答
# 2. 搜索代码库 → 评估足够 → 直接生成
# 3. 不检索 → 直接回答(简单问题)
result = agent.invoke({"messages": [("user", "我们公司最新发布的 API 有哪些变更?")]})

Graph RAG:用知识图谱补充关联关系

向量检索的局限:只能找到语义相似的文档,无法理解实体间的关联关系。Graph RAG 通过知识图谱解决这一问题。

════════════ Graph RAG Pipeline ════════════ 文档 ──→ 实体/关系提取 ──→ 知识图谱 ──→ 社区检测 (LLM 提取) (Neo4j/NetworkX) (层级聚合) │ │ ▼ ▼ 图遍历检索 社区摘要 (关联实体发现) (全局理解) │ │ └──────┬───────────┘ ▼ 上下文组装 + LLM 生成
Python — Microsoft GraphRAG 核心流程
# Microsoft GraphRAG 流程(简化)
# pip install graphrag

"""
1. 索引阶段(离线):
   - 文档分块
   - LLM 提取实体和关系 → 知识图谱
   - Leiden 社区检测 → 层级社区
   - 为每个社区生成摘要

2. 查询阶段(在线):
   - Local Search: 找相关实体 → 图遍历 → 拼接邻域
   - Global Search: 找相关社区 → 汇总社区摘要
   - Drift Search: 结合 local + global
"""

# 配置文件 settings.yaml
entity_extraction:
  prompt: "提取文本中的实体和关系..."
  max_gleanings: 1  # 补充提取轮次

community_reports:
  prompt: "为以下实体群组生成社区摘要..."
  max_length: 2000
  max_levels: 3  # 社区层级深度

# 运行索引
# graphrag index --root ./workspace

# 查询
# graphrag query --root ./workspace --method local "公司的核心产品有哪些?"
# graphrag query --root ./workspace --method global "整体技术趋势是什么?"

三种 RAG 架构对比

维度朴素/高级 RAGAgentic RAGGraph RAG
架构复杂度
实现成本1-2 周2-4 周4-8 周
事实准确性中-高
全局理解能力
多跳推理
维护成本高(图谱更新)
查询延迟100-500ms1-5s2-10s
Token 成本
最佳场景文档问答、FAQ复杂决策、多源查询全局分析、趋势洞察
⚠️ 不要过度工程化:Graph RAG 很强大,但大部分场景朴素/高级 RAG 就够用。Graph RAG 真正有价值的场景是:需要跨文档推理、全局洞察(如"公司整体战略"而非"某个产品的细节")。如果只是文档问答,优先用高级 RAG + Reranker。

10 RAG 评估:如何知道你的 RAG 行不行

RAGAS 评估框架

RAGAS (Retrieval Augmented Generation Assessment) 是最广泛使用的 RAG 评估框架,提供 7+ 核心指标:

指标评估什么范围如何计算
Faithfulness回答是否忠实于检索文档0-1LLM 判断回答的每个论点是否可被文档支撑
Answer Relevancy回答是否切题0-1LLM 生成反向问题,计算与原问题的相似度
Context Precision检索文档中相关内容的排名0-1相关文档是否排在前面
Context Recall回答所需信息是否被检索到0-1参考答案中的信息在检索文档中的覆盖率
Answer Similarity回答与参考答案的语义相似度0-1Embedding 余弦相似度
Answer Correctness回答的正确性0-1加权:语义相似度 + 事实正确性
Python — RAGAS 评估
from ragas import evaluate
from ragas.metrics import (
    faithfulness,
    answer_relevancy,
    context_precision,
    context_recall,
)
from datasets import Dataset

# 准备评估数据(需要人工标注的问答对)
eval_data = {
    "question": ["什么是 RAG?", "RAG 的优势是什么?"],
    "answer": ["RAG 是检索增强生成...", "RAG 的优势包括实时性、准确性..."],
    "contexts": [
        ["RAG 是检索增强生成,结合检索和生成..."],
        ["RAG 的优势:实时知识更新、减少幻觉..."],
    ],
    "ground_truth": ["RAG 是将外部检索与LLM生成结合的架构", "实时更新、减少幻觉、可追溯来源"],
}

dataset = Dataset.from_dict(eval_data)

# 运行评估
results = evaluate(
    dataset,
    metrics=[faithfulness, answer_relevancy, context_precision, context_recall],
)

print(results)
# {'faithfulness': 0.85, 'answer_relevancy': 0.92, 
#  'context_precision': 0.78, 'context_recall': 0.88}

评估最佳实践

📊 评估流程

11 生产级 RAG:索引管理、增量更新、成本优化

索引更新策略

🔄 全量重建

定期删除旧索引,完全重建。

✅ 简单可靠,保证一致性
❌ 大型索引耗时(小时级)
❌ 重建期间服务中断

适合:< 100K 文档,日更新频率

⚡ 增量更新

只处理新增/修改/删除的文档。

✅ 更新快速(秒级)
✅ 不影响在线服务
❌ 需要变更检测机制
❌ 索引碎片化需定期整理

适合:> 100K 文档,实时更新需求

Python — 增量更新实现
import hashlib
from datetime import datetime

class IncrementalIndexer:
    """增量索引器:只处理变更的文档"""
    
    def __init__(self, vector_store, doc_registry):
        self.vector_store = vector_store
        self.registry = doc_registry  # 文档元数据注册表
    
    def compute_hash(self, content: str) -> str:
        return hashlib.sha256(content.encode()).hexdigest()
    
    async def sync_documents(self, documents: list[dict]):
        """同步文档:新增、更新、删除"""
        current_hashes = {doc['id']: self.compute_hash(doc['content']) for doc in documents}
        stored_hashes = self.registry.get_all_hashes()
        
        to_add = []      # 新增
        to_update = []   # 内容变更
        to_delete = []   # 已删除
        
        for doc_id, content_hash in current_hashes.items():
            if doc_id not in stored_hashes:
                to_add.append(doc_id)
            elif stored_hashes[doc_id] != content_hash:
                to_update.append(doc_id)
        
        for doc_id in stored_hashes:
            if doc_id not in current_hashes:
                to_delete.append(doc_id)
        
        # 执行变更
        for doc_id in to_add:
            doc = next(d for d in documents if d['id'] == doc_id)
            await self._index_document(doc)
        
        for doc_id in to_update:
            await self._remove_from_index(doc_id)
            doc = next(d for d in documents if d['id'] == doc_id)
            await self._index_document(doc)
        
        for doc_id in to_delete:
            await self._remove_from_index(doc_id)
        
        # 更新注册表
        self.registry.update_hashes(current_hashes)
        
        return {"added": len(to_add), "updated": len(to_update), "deleted": len(to_delete)}

成本优化

优化方向策略节省
Embedding 成本批量编码 + 缓存 + 增量更新60-80%
存储成本维度裁剪(3072→512)+ 量化(FP16/INT8)50-75%
检索延迟HNSW 索引 + 预过滤 + 缓存热门 query50-90%
LLM 成本上下文压缩 + 小模型 Rerank + 分级模型30-60%
Token 用量Context Compression + 引导式检索40-70%
Python — LLM 缓存热门 Query
import hashlib
import json
from datetime import timedelta

class QueryCache:
    """缓存热门 Query 的检索结果,避免重复检索"""
    
    def __init__(self, redis_client, ttl=timedelta(hours=24)):
        self.redis = redis_client
        self.ttl = ttl
    
    def _cache_key(self, query: str, top_k: int) -> str:
        # 对 query 做语义归一化后哈希
        normalized = query.strip().lower()
        content_hash = hashlib.md5(normalized.encode()).hexdigest()
        return f"rag:cache:{content_hash}:{top_k}"
    
    async def get(self, query: str, top_k: int) -> list[dict] | None:
        key = self._cache_key(query, top_k)
        cached = await self.redis.get(key)
        if cached:
            return json.loads(cached)
        return None
    
    async def set(self, query: str, top_k: int, results: list[dict]):
        key = self._cache_key(query, top_k)
        await self.redis.set(key, json.dumps(results), ex=self.ttl)

12 方案对比与选型决策树

向量数据库对比

数据库类型开源向量搜索全文搜索混合检索元数据过滤适合场景
ChromaDB嵌入式⚠️ 基础⚠️原型/小规模/本地开发
LanceDB嵌入式本地/边缘/单节点生产
Qdrant独立服务中等规模/需要过滤
Weaviate独立服务多模态/需要模块化
Pinecone全托管⚠️⚠️免运维/快速上线
Milvus分布式⚠️⚠️大规模(亿级)/企业
pgvectorPG 扩展已有 PG/混合查询
💡 选型建议:已在用 PostgreSQL → pgvector(零额外运维);本地开发 → LanceDB(轻量、高性能);需要生产级独立服务 → Qdrant(开源+易用);不想管服务 → Pinecone(全托管)。

RAG 框架对比

框架语言特点适合谁
LangChainPython/JS生态最大、组件最全、抽象较重快速原型、全功能 RAG
LlamaIndexPython/TS专注数据索引、查询引擎设计精良数据密集型 RAG
HaystackPythonPipeline 架构清晰、生产友好生产级搜索/QA
RAGFlowPython深度文档解析、模板化分块复杂文档(表格/图片)
DifyPython可视化编排、内置 RAG 管道低代码/快速搭建
自建任意完全控制、无依赖、成本高对架构有极致要求

选型决策树

你的需求是什么? │ ┌─────────────┼─────────────┐ │ │ │ 文档问答 复杂检索 全局洞察 │ │ │ ┌───────┴───────┐ 多源、多跳 知识图谱 │ │ 需要决策 关联分析 简单 FAQ 专业问答 │ │ │ │ │ │ 高级 RAG 高级 RAG Agentic Graph + Reranker + 父子分块 RAG RAG + 混合检索 + HyDE │ │ │ │ │ │ 框架选择: 框架选择: 框架选择: 框架选择: Dify/LC LlamaIndex LangGraph GraphRAG (快速) (数据友好) (Agent) (图谱)

13 完整代码示例

生产级 RAG 系统(FastAPI + LangChain + Qdrant)

Python — 完整 RAG 服务
"""
生产级 RAG 服务:FastAPI + LangChain + Qdrant + BGE-M3 + Reranker
功能:文档上传、索引管理、混合检索、Reranking、流式生成
"""

from fastapi import FastAPI, UploadFile, Query
from fastapi.responses import StreamingResponse
from pydantic import BaseModel
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
from langchain_qdrant import Qdrant
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.retrievers import EnsembleRetriever
from langchain_community.retrievers import BM25Retriever
from FlagEmbedding import FlagReranker
from qdrant_client import QdrantClient
import asyncio

app = FastAPI(title="RAG Service")

# ===== 初始化 =====
COLLECTION = "knowledge_base"

qdrant_client = QdrantClient(host="localhost", port=6333)
embeddings = OpenAIEmbeddings(model="text-embedding-3-small", dimensions=512)
reranker = FlagReranker("BAAI/bge-reranker-v2-m3", use_fp16=True)
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0, streaming=True)

vector_store = Qdrant(
    client=qdrant_client,
    collection_name=COLLECTION,
    embeddings=embeddings,
)

splitter = RecursiveCharacterTextSplitter(
    chunk_size=512, chunk_overlap=64,
    separators=["\n## ", "\n### ", "\n\n", "\n", "。", ".", " "],
)

# ===== 文档上传与索引 =====
class IndexRequest(BaseModel):
    content: str
    source: str
    metadata: dict = {}

@app.post("/index")
async def index_document(req: IndexRequest):
    """索引单个文档"""
    chunks = splitter.split_text(req.content)
    
    metadatas = [
        {"source": req.source, "chunk_index": i, **req.metadata}
        for i in range(len(chunks))
    ]
    
    await asyncio.to_thread(
        vector_store.add_texts,
        texts=chunks,
        metadatas=metadatas,
    )
    
    return {"status": "indexed", "chunks": len(chunks)}

# ===== 混合检索 + Reranking =====
async def hybrid_retrieve(query: str, top_k: int = 20) -> list[dict]:
    """混合检索:向量 + BM25 + Reranking"""
    
    # 1. 向量检索
    vector_retriever = vector_store.as_retriever(
        search_kwargs={"k": top_k}
    )
    
    # 2. BM25 检索
    all_docs = vector_store.similarity_search("", k=10000)  # 获取全部文档
    bm25_retriever = BM25Retriever.from_documents(all_docs)
    bm25_retriever.k = top_k
    
    # 3. 混合检索
    ensemble_retriever = EnsembleRetriever(
        retrievers=[vector_retriever, bm25_retriever],
        weights=[0.7, 0.3],
    )
    candidates = ensemble_retriever.invoke(query)
    
    # 4. Reranking
    pairs = [[query, doc.page_content] for doc in candidates]
    scores = await asyncio.to_thread(reranker.compute_score, pairs)
    
    # 5. 按重排分数排序
    ranked = sorted(
        zip(candidates, scores),
        key=lambda x: x[1],
        reverse=True,
    )
    
    return [
        {
            "content": doc.page_content,
            "score": score,
            "metadata": doc.metadata,
        }
        for doc, score in ranked[:5]  # 最终取 Top-5
    ]

# ===== 生成回答 =====
RAG_PROMPT = """基于以下检索到的文档回答问题。

文档:
{context}

问题:{query}

要求:
1. 仅基于文档内容回答
2. 引用来源 [编号]
3. 不确定时说明"""

@app.get("/query")
async def query_rag(q: str = Query(...)):
    """RAG 问答"""
    # 检索
    results = await hybrid_retrieve(q)
    
    # 组装上下文
    context_parts = [
        f"[{i+1}] 来源: {r['metadata'].get('source', '未知')}\n{r['content']}"
        for i, r in enumerate(results)
    ]
    context = "\n\n".join(context_parts)
    
    # 流式生成
    prompt = RAG_PROMPT.format(context=context, query=q)
    
    async def stream():
        async for chunk in llm.astream(prompt):
            yield chunk.content
    
    return StreamingResponse(stream(), media_type="text/plain")

@app.get("/health")
async def health():
    return {"status": "ok"}

14 替代方案

当 RAG 不是最佳选择时

替代方案何时用vs RAG
Long Context 文档 < 200K tokens,无需持久索引 更简单,但成本高、中间内容易丢失
Fine-tuning 需要调整风格/格式/行为 不提供新知识,但改变模型行为
Prompt Caching 相同前缀频繁复用 降低成本,不解决知识问题(参考 Agent 提示词工程 中 OpenClaw 的 Cache Boundary)
知识注入 (Context Stuffing) 小量固定知识(如公司简介) 最简单——直接塞 System Prompt
SQL/结构化查询 结构化数据、精确查询 Text-to-SQL 比 RAG 更精确
混合方案 大部分生产场景 RAG + 微调 + Long Context 按需组合
🎯 实际案例:一个客服系统可能同时用——System Prompt 注入品牌指南(固定知识)+ RAG 检索产品文档(动态知识)+ 微调调整回答风格(行为定制)。三者互补而非互斥。

RAG 是 Agent 记忆系统的核心组件。以下是与 Agent 研究的关联:

🔗 相关 Agent 模块
🧠 Agent 研究中的 RAG 相关发现
📐 从 Agent 到 SaaS 的映射
Agent 概念SaaS RAG 对应
Agent 长期记忆向量数据库 + 文档索引
Agent 工具调用检索 API / Search API
Agent 上下文窗口管理上下文压缩 / 父子分块
Agent 自评估循环Reranking + 质量评估
Multi-Agent 协作多源 RAG / Agentic RAG