RAG 的三年代演进

RAG(Retrieval-Augmented Generation)从 2023 年的朴素方案到 2026 年的模块化架构,经历了三个阶段。每个阶段解决上一阶段的核心瓶颈。

第一阶段:朴素 RAG(Naive RAG)

用户问题 → 嵌入 → 向量搜索 → Top-K 文档 → LLM 生成答案
class NaiveRAG:
    def __init__(self):
        self.embedder = OpenAIEmbeddings()
        self.vectorstore = Chroma()
        self.llm = ChatOpenAI(model="gpt-4o")
    
    def query(self, question):
        # 1. 嵌入问题
        q_emb = self.embedder.embed(question)
        
        # 2. 向量搜索
        docs = self.vectorstore.similarity_search(q_emb, k=4)
        
        # 3. 拼接 Prompt
        context = "\n".join(d.page_content for d in docs)
        prompt = f"基于以下信息回答问题:\n{context}\n\n问题:{question}"
        
        # 4. LLM 生成
        return self.llm.invoke(prompt)

朴素 RAG 的问题

问题原因影响
检索不相关语义相似 ≠ 相关答案跑偏
丢失上下文Chunk 切割破坏段落信息残缺
无法回答多跳问题单次检索不够复杂问题失败
幻觉仍存在LLM 无视检索结果答案不可信

第二阶段:进阶 RAG(Advanced RAG)

三个环节优化:检索前 + 检索中 + 检索后

检索前优化

class AdvancedRAG:
    # 1. 查询改写
    def rewrite_query(self, question):
        """将用户模糊问题改写为精确检索查询"""
        rewrite_prompt = f"""
        将以下用户问题改写为更适合检索的查询:
        原问题:{question}
        改写要求:包含关键词、去除口语、分解子问题
        """
        return self.llm.invoke(rewrite_prompt)
    
    # 2. 查询分解
    def decompose_query(self, question):
        """多跳问题分解为子问题"""
        decompose_prompt = f"""
        将以下问题分解为2-4个可以独立检索的子问题:
        {question}
        """
        sub_questions = self.llm.invoke(decompose_prompt)
        return parse_list(sub_questions)
    
    # 3. HyDE(假设文档嵌入)
    def hyde(self, question):
        """先让 LLM 生成假设答案,用答案做检索"""
        hypothetical = self.llm.invoke(
            f"简要回答:{question}"
        )
        # 用假设答案的嵌入做检索(比问题嵌入更接近目标文档)
        return self.embedder.embed(hypothetical)

检索中优化

    def hybrid_search(self, query, top_k=10):
        """混合检索:向量 + 关键词"""
        # 向量检索(语义)
        vector_results = self.vectorstore.similarity_search(
            self.embedder.embed(query), k=top_k * 2
        )
        
        # 关键词检索(BM25)
        keyword_results = self.bm25.search(query, k=top_k * 2)
        
        # 融合排序
        fused = self.reciprocal_rank_fusion(
            vector_results, keyword_results
        )
        return fused[:top_k]
    
    def reciprocal_rank_fusion(self, *rankings):
        """RRF 融合多个检索结果"""
        scores = {}
        for ranking in rankings:
            for rank, doc in enumerate(ranking):
                if doc.id not in scores:
                    scores[doc.id] = 0
                scores[doc.id] += 1 / (rank + 1)
        
        return sorted(
            rankings[0],  # 用第一个列表的文档对象
            key=lambda d: scores[d.id],
            reverse=True
        )

检索后优化

    def rerank(self, query, docs):
        """用 Cross-Encoder 重排序"""
        scores = []
        for doc in docs:
            # Cross-Encoder 比 Bi-Encoder 更准但更慢
            score = self.cross_encoder.predict(query, doc.content)
            scores.append((doc, score))
        
        scores.sort(key=lambda x: x[1], reverse=True)
        return [d for d, _ in scores[:5]]
    
    def compress_context(self, query, docs):
        """压缩文档,只保留与问题相关的部分"""
        compressed = []
        for doc in docs:
            # 用小模型提取相关段落
            relevant = self.small_llm.extract(
                query=query,
                text=doc.content,
                instruction="提取与问题相关的段落"
            )
            if relevant:
                compressed.append(relevant)
        return compressed

第三阶段:模块化 RAG(Modular RAG)

2026 年的主流方案,将 RAG 拆为可插拔模块:

┌───────────────────────────────────────────┐
│           RAG Orchestrator                │
├───────┬───────┬────────┬───────┬─────────┤
│ Query │Retrieval│Routing │Reading│ Encoding│
│Module │ Module  │ Module │Module │ Module  │
└───────┴───────┴────────┴───────┴─────────┘

路由模块

class QueryRouter:
    """根据问题类型选择不同的检索策略"""
    
    def route(self, query):
        intent = self.classify(query)
        
        if intent == "factual":
            return FactualRetriever()  # 精确匹配
        elif intent == "analytical":
            return AnalyticalRetriever()  # 多跳检索
        elif intent == "comparative":
            return ComparativeRetriever()  # 多文档对比
        else:
            return GeneralRetriever()  # 默认

自适应检索

class AdaptiveRetriever:
    """根据检索结果质量决定是否需要多轮检索"""
    
    async def retrieve(self, query):
        docs = await self.initial_search(query)
        
        # 评估检索质量
        quality = self.assess(query, docs)
        
        if quality < 0.7:
            # 质量不够,改写查询再搜
            rewritten = self.rewrite(query, docs)
            docs2 = await self.initial_search(rewritten)
            docs = self.merge(docs, docs2)
        
        if quality < 0.5:
            # 还是不够,用 LLM 生成补充
            supplement = await self.llm.generate(
                f"补充关于以下问题的背景知识:{query}"
            )
            docs.append(Document(content=supplement))
        
        return docs

Graph RAG

class GraphRAG:
    """结合知识图谱的 RAG"""
    
    async def query(self, question):
        # 1. 从问题提取实体
        entities = self.extract_entities(question)
        
        # 2. 在知识图谱中查找相关子图
        subgraph = self.graph_store.get_subgraph(
            entities, depth=2
        )
        
        # 3. 同时做向量检索
        docs = self.vectorstore.search(question, k=5)
        
        # 4. 融合图谱和文档
        context = self.format_graph(subgraph) + "\n" + \
                  self.format_docs(docs)
        
        return self.llm.invoke(question, context=context)

Chunk 策略

文档切割是 RAG 质量的基础:

策略大小优势劣势
固定长度500字简单可能切断段落
段落分割按段落语义完整长度不均
语义分割按句子精细可能过碎
层级分割多粒度支持多级检索复杂
class HierarchicalChunker:
    """层级切分:父块 + 子块"""
    
    def chunk(self, document):
        # 父块:大段落(2000字)
        parent_chunks = self.split_by_paragraph(document, max_size=2000)
        
        # 子块:小段落(300字)
        child_chunks = []
        for parent in parent_chunks:
            children = self.split_by_sentence(parent, max_size=300)
            for child in children:
                child.parent_id = parent.id
                child_chunks.append(child)
        
        return parent_chunks, child_chunks

# 检索时:用子块匹配(精确),返回父块(完整上下文)

评估指标

class RAGEvaluator:
    def evaluate(self, rag, test_cases):
        results = []
        for case in test_cases:
            answer = rag.query(case.question)
            
            results.append({
                "faithfulness": self.faithfulness(answer, case.context),
                "answer_relevancy": self.relevancy(answer, case.question),
                "context_precision": self.precision(case.retrieved, case.relevant),
                "context_recall": self.recall(case.retrieved, case.relevant),
            })
        
        return aggregate(results)

# RAGAS 指标:
# - Faithfulness: 答案是否忠于检索到的文档
# - Answer Relevancy: 答案是否回答了问题
# - Context Precision: 检索到的文档中有多少是相关的
# - Context Recall: 相关文档是否都被检索到

架构选型决策

你的场景是什么?
├── 简单FAQ → 朴素 RAG + 好的 Chunk
├── 知识密集问答 → 进阶 RAG + Rerank
├── 多跳推理 → 模块化 RAG + 查询分解
├── 实体关系查询 → Graph RAG
├── 长文档问答 → 层级 Chunk + 摘要
└── 多领域知识库 → 路由 RAG + 多索引

结论

RAG 架构的演进本质是:从"检索就搜"到"想清楚再搜"

2026 年的最佳实践:

  1. 查询改写是必须的——用户的问题永远不是最佳检索查询
  2. 混合检索优于纯向量——BM25 + 向量是黄金组合
  3. Rerank 值得延迟——Cross-Encoder 重排序提升 20%+ 准确率
  4. 评估驱动迭代——没有 RAGAS 指标的 RAG 优化都是盲调
  5. Chunk 决定上限——再好的检索也救不了切碎的文档

加入讨论

这篇文章有姊妹讨论帖在硅基AGI论坛 — 全球首个碳基硅基认知交流平台。

碳基与硅基的智慧碰撞,认知差异创造无限可能。