Skip to content
On this page

RAG(检索增强生成)

什么是 RAG

RAG(Retrieval-Augmented Generation,检索增强生成)是一种将信息检索文本生成相结合的技术架构。它让大语言模型在生成回答之前,先从外部知识库中检索相关信息,然后基于检索到的内容进行生成,从而产出更准确、更具时效性的回答。

类比:如果 LLM 是一个「博览群书但记忆有保质期的专家」,RAG 就是给他配了一套「实时查阅资料库的能力」——回答问题前先查资料,再结合自己的知识给出回答。

为什么需要 RAG

大语言模型存在几个固有限制,RAG 正是为解决这些问题而设计的:

问题表现RAG 如何解决
知识截止模型训练数据有截止日期,无法了解最新信息检索实时更新的知识库
幻觉问题模型可能自信地编造不存在的事实用检索到的真实文档作为生成依据
领域知识缺失通用模型不了解企业内部知识接入企业私有知识库
上下文窗口限制无法一次性将所有文档塞入 prompt只检索最相关的片段
可溯源性无法知道回答依据了什么信息可以引用具体来源和文档

核心架构

RAG 的完整工作流程分为两大阶段:索引阶段(离线准备)和检索生成阶段(在线服务)。

┌──── 索引阶段(离线) ────────────────────────────────────────┐
│                                                            │
│  文档集 → [分块] → [Embedding] → [存入向量数据库]            │
│                                                            │
│  📄 PDF    ┌──────┐   ┌──────────┐   ┌──────────────────┐  │
│  📄 Markdown→ Chunks│→ │Embedding │ → │ Vector Database  │  │
│  📄 HTML   └──────┘   │  Model   │   │ (Milvus/Pinecone)│  │
│  📄 代码             └──────────┘   └──────────────────┘  │
└────────────────────────────────────────────────────────────┘

┌──── 检索生成阶段(在线) ──────────────────────────────────────┐
│                                                              │
│  用户问题 → [Query Embedding] → [相似度搜索] → [构建 Prompt] → [LLM 生成]
│                                                              │
│  "K8s Pod 一直        ┌────────┐     Top-K       ┌─────┐    │
│   CrashLoopBackOff   │向量数据库│ → 相关文档片段 → │ LLM │→ 回答
│   怎么排查?"         └────────┘                  └─────┘    │
│                                                              │
└──────────────────────────────────────────────────────────────┘

核心概念详解

1. 文档分块(Chunking)

将原始文档切分成适合检索的小片段。分块策略直接影响检索质量。

主要分块方法:

方法原理适用场景优缺点
固定大小分块按字符/token 数量均匀切分通用文本简单但可能切断语义
语义分块按段落、章节等自然边界切分结构化文档语义完整但大小不均
递归分块按层级分隔符(\n\n → \n → 句号)逐层尝试大多数场景兼顾语义和大小
滑动窗口相邻块有重叠部分防止信息丢失增加存储和计算成本

示例:递归分块

python
from langchain.text_splitter import RecursiveCharacterTextSplitter

splitter = RecursiveCharacterTextSplitter(
    chunk_size=500,          # 每块最大 500 字符
    chunk_overlap=50,        # 相邻块重叠 50 字符
    separators=["\n\n", "\n", "。", ",", " "],  # 优先在段落边界切分
)

chunks = splitter.split_text(document_content)
# 结果:["第一个语义完整的片段...", "第二个片段...", ...]

2. 嵌入(Embedding)

将文本转化为高维向量,使得语义相似的文本在向量空间中距离接近。

python
from openai import OpenAI

client = OpenAI()

# 将文本转为向量
response = client.embeddings.create(
    model="text-embedding-3-small",
    input="Kubernetes Pod CrashLoopBackOff 的常见原因和排查方法"
)

embedding = response.data[0].embedding
# 结果:[0.0234, -0.0156, 0.0891, ...] (1536 维向量)

常用 Embedding 模型:

模型提供商维度特点
text-embedding-3-smallOpenAI1536性价比高,通用性好
text-embedding-3-largeOpenAI3072精度更高
bge-large-zhBAAI1024中文优化
jina-embeddings-v3Jina AI1024多语言支持好

根据查询向量在向量数据库中找到最相似的文档片段。

python
import chromadb

# 创建向量数据库集合
client = chromadb.Client()
collection = client.create_collection("knowledge_base")

# 插入文档(自动生成 embedding)
collection.add(
    documents=[
        "Pod CrashLoopBackOff 通常是因为容器启动后立即退出。常见原因包括:1) 应用程序错误导致进程崩溃...",
        "OOMKilled 表示容器因内存不足被 Kubernetes 终止。解决方案:增加 resources.limits.memory...",
        "ImagePullBackOff 表示 Kubernetes 无法拉取容器镜像。检查:1) 镜像名称是否正确..."
    ],
    ids=["doc1", "doc2", "doc3"],
    metadatas=[
        {"source": "k8s-troubleshooting.md", "section": "pod-issues"},
        {"source": "k8s-troubleshooting.md", "section": "memory"},
        {"source": "k8s-troubleshooting.md", "section": "image"},
    ]
)

# 检索最相关的文档
results = collection.query(
    query_texts=["Pod 一直重启怎么办"],
    n_results=3
)
# 返回与查询语义最接近的文档片段

4. 上下文构建与生成

将检索到的文档片段组装成 prompt,交给 LLM 生成最终回答。

python
def generate_rag_response(question: str, retrieved_docs: list[str]) -> str:
    # 构建增强 prompt
    context = "\n\n---\n\n".join(retrieved_docs)

    prompt = f"""基于以下参考资料回答用户问题。
如果参考资料中没有相关信息,请明确说明"我没有找到相关信息",不要编造答案。
回答时请引用参考资料的来源。

## 参考资料
{context}

## 用户问题
{question}

## 回答
"""
    response = client.chat.completions.create(
        model="gpt-4o",
        messages=[{"role": "user", "content": prompt}],
        temperature=0.1  # 低温度,减少创造性发挥
    )

    return response.choices[0].message.content

完整示例:企业知识库问答系统

python
from openai import OpenAI
import chromadb

client = OpenAI()
chroma = chromadb.PersistentClient(path="./vector_db")
collection = chroma.get_or_create_collection(
    name="ops_knowledge",
    metadata={"hnsw:space": "cosine"}  # 使用余弦相似度
)

class RAGPipeline:
    """企业级 RAG 问答管道"""

    def __init__(self, collection, top_k=5, score_threshold=0.7):
        self.collection = collection
        self.top_k = top_k
        self.score_threshold = score_threshold

    def ingest(self, documents: list[dict]):
        """索引阶段:将文档导入向量数据库"""
        for doc in documents:
            # 分块
            chunks = self._chunk(doc["content"])
            for i, chunk in enumerate(chunks):
                self.collection.add(
                    documents=[chunk],
                    ids=[f"{doc['id']}_chunk_{i}"],
                    metadatas=[{
                        "source": doc["source"],
                        "title": doc["title"],
                        "chunk_index": i,
                        "updated_at": doc.get("updated_at", "")
                    }]
                )

    def query(self, question: str) -> dict:
        """检索生成阶段:回答用户问题"""
        # 1. 检索相关文档
        results = self.collection.query(
            query_texts=[question],
            n_results=self.top_k
        )

        # 2. 过滤低质量结果
        relevant_docs = []
        for doc, metadata, distance in zip(
            results["documents"][0],
            results["metadatas"][0],
            results["distances"][0]
        ):
            score = 1 - distance  # 余弦距离转相似度
            if score >= self.score_threshold:
                relevant_docs.append({
                    "content": doc,
                    "source": metadata["source"],
                    "score": score
                })

        # 3. 生成回答
        if not relevant_docs:
            return {
                "answer": "抱歉,我没有找到与您问题相关的信息。",
                "sources": [],
                "confidence": "low"
            }

        context = "\n\n".join([
            f"[来源: {d['source']}] (相关度: {d['score']:.2f})\n{d['content']}"
            for d in relevant_docs
        ])

        answer = self._generate(question, context)

        return {
            "answer": answer,
            "sources": [d["source"] for d in relevant_docs],
            "confidence": "high" if relevant_docs[0]["score"] > 0.85 else "medium"
        }

    def _chunk(self, text: str, size=500, overlap=50) -> list[str]:
        """简单的滑动窗口分块"""
        chunks = []
        start = 0
        while start < len(text):
            end = start + size
            chunks.append(text[start:end])
            start = end - overlap
        return chunks

    def _generate(self, question: str, context: str) -> str:
        """调用 LLM 生成回答"""
        response = client.chat.completions.create(
            model="gpt-4o",
            messages=[
                {"role": "system", "content": "你是一个企业知识库助手。基于提供的参考资料回答问题,引用来源。如果资料不足,明确说明。"},
                {"role": "user", "content": f"参考资料:\n{context}\n\n问题:{question}"}
            ],
            temperature=0.1
        )
        return response.choices[0].message.content

# 使用示例
rag = RAGPipeline(collection)

# 导入文档
rag.ingest([
    {
        "id": "doc_001",
        "title": "K8s 故障排查手册",
        "source": "ops-wiki/k8s-troubleshooting.md",
        "content": "Pod CrashLoopBackOff 排查步骤:1. 使用 kubectl logs <pod> 查看容器日志...",
        "updated_at": "2026-01-15"
    }
])

# 查询
result = rag.query("Pod 一直 CrashLoopBackOff 怎么排查?")
print(result["answer"])
print(f"来源: {result['sources']}")
print(f"置信度: {result['confidence']}")

高级优化策略

1. 查询改写(Query Rewriting)

用户的原始查询往往不是最佳的检索查询。通过改写可以提升召回率。

python
def rewrite_query(original_query: str) -> list[str]:
    """将用户查询改写为多个检索友好的查询"""
    response = client.chat.completions.create(
        model="gpt-4o",
        messages=[{
            "role": "user",
            "content": f"""将以下用户问题改写为 3 个不同角度的搜索查询,以提高检索覆盖率。
只输出查询列表,每行一个。

用户问题:{original_query}"""
        }]
    )
    return response.choices[0].message.content.strip().split("\n")

# 示例
# 原始查询:"服务挂了"
# 改写后:
# - "服务不可用 故障排查 服务宕机"
# - "微服务 health check 失败 重启"
# - "生产环境 服务异常 日志报错"

2. 重排序(Re-ranking)

对初步检索结果进行二次排序,提升精确度。

python
def rerank(query: str, documents: list[str], top_k: int = 3) -> list[str]:
    """使用交叉编码器对检索结果重排序"""
    # 使用 LLM 评估每个文档与查询的相关性
    scored_docs = []
    for doc in documents:
        response = client.chat.completions.create(
            model="gpt-4o-mini",
            messages=[{
                "role": "user",
                "content": f"评估以下文档与查询的相关性,输出 0-10 的分数。\n查询:{query}\n文档:{doc}\n分数:"
            }]
        )
        score = float(response.choices[0].message.content.strip())
        scored_docs.append((doc, score))

    # 按分数排序,返回 top_k
    scored_docs.sort(key=lambda x: x[1], reverse=True)
    return [doc for doc, _ in scored_docs[:top_k]]

结合向量检索(语义匹配)和关键词检索(精确匹配)的优势。

用户查询

    ├── 向量检索(语义相似度)── 找到概念相关的文档

    ├── 关键词检索(BM25)──── 找到包含精确术语的文档

    └── 融合排序(RRF/加权)── 综合两种结果给出最终排名

RAG 评估指标

指标说明衡量方法
检索召回率正确答案是否在检索结果中标注测试集对比
检索精确率检索结果中有多少是真正相关的人工标注 + 自动化评估
回答忠实度回答是否忠于检索到的文档检查回答是否有文档外的臆断
回答相关性回答是否真正解决了用户的问题用户满意度评估
端到端准确率最终回答是否正确标注答案对比

常见问题与解决方案

问题原因解决方案
检索不到相关文档查询与文档用词差异大查询改写 + 混合检索
检索到但不相关分块粒度不当或 embedding 模型弱优化分块策略 + 换用更好的模型
回答不准确上下文太多或太少调整 top_k + 加入重排序
回答有幻觉LLM 忽略了上下文降低 temperature + 强化 prompt 约束
时效性差知识库未及时更新增量索引 + 定期刷新策略

RAG 的核心作用

  1. 知识时效性:让模型获取训练截止日期之后的最新信息,解决知识过期问题。
  2. 减少幻觉:通过真实文档作为生成依据,显著降低模型编造内容的概率。
  3. 领域适配:无需微调模型,只需接入特定领域知识库即可获得专业能力。
  4. 可解释性:每个回答都可以追溯到具体的来源文档,支持事实核查。
  5. 数据安全:企业私有数据留在自己的知识库中,不需要发送给模型供应商进行训练。

一句话总结:RAG 是让 Agent 「言之有据」的核心技术——先查再答,有据可依。