关键词:RAG、检索增强生成、大语言模型、Embedding、向量数据库、LangChain、NLP

本文系统讲解 RAG 为什么能缓解大模型幻觉,拆解"索引—检索—生成"全链路的关键技术(分块、Embedding、重排序、Prompt 组装),并给出一段可以直接跑的代码实战。全文约 4500 字。


一、大模型为什么需要 RAG?

预训练大语言模型(LLM)有三个绕不开的痛点:

  1. 知识截止(Knowledge Cutoff):模型的知识冻结在训练数据截止的那一天,之后发生的事情它一无所知;
  2. 幻觉(Hallucination):模型本质是"下一个词预测器",对不知道的问题也会一本正经地编造答案;
  3. 私有知识缺失:公司内部文档、个人笔记从未出现在训练语料里,再微调也难以精确记住。

继续用预训练/微调往模型里"灌"知识,成本高、见效慢、还容易出现灾难性遗忘。RAG(Retrieval-Augmented Generation,检索增强生成)换了一个思路:

开卷考试代替闭卷考试。 先把相关知识从外部知识库里检索出来,塞进 Prompt,让模型"看着资料作答"。

这个由 Facebook AI Research 在 2020 年提出的范式,如今已成为大模型落地(企业知识库问答、智能客服、垂直领域助手)事实上的标准方案。

二、RAG 的核心架构

RAG 分为离线索引(Indexing)和在线查询(Query)两个阶段:

离线文档 → 分块(Chunking) → Embedding → 存入向量数据库
在线问题 → Embedding → 向量检索(Top-K) → [重排序] → 拼接Prompt → LLM生成答案

2.1 分块(Chunking)

长文档不能直接整体向量化(会稀释语义,也超上下文窗口),需要切成小块。常见策略:

  • 固定长度分块:每 512 token 切一刀,块间保留 50~100 token 重叠(overlap),避免答案被切断;
  • 语义分块:按标题、段落、句子边界切,保持语义完整;
  • 递归分块:LangChain 的 RecursiveCharacterTextSplitter,优先按 \n\n → \n → 句号的层级切分,兼顾长度与语义。

经验法则:块太大则检索噪声多,块太小则上下文不完整。一般 256~1024 token 之间调优,配合 10%~20% 的 overlap。

2.2 Embedding:把文本变成向量

Embedding 模型把每个文本块映射为一个稠密向量(如 768/1024 维),语义相近的文本在向量空间中距离也近:

{sim}(q, d) = \frac{q \cdot d}{\|q\| \|d\|} \quad   (余弦相似度)

常用选择:

模型特点
OpenAI text-embedding-3-small/large效果好、需调 API、按量计费
BGE-M3(智源)中英双语强、开源免费、支持多粒度
M3E / GTE中文场景轻量级选择

注意:索引和查询必须用同一个 Embedding 模型,换模型等于重建整个索引。

2.3 向量检索与向量数据库

查询时也把问题 Embedding 成向量,在库中找余弦相似度最高的 Top-K 个块。小规模(<10 万向量)直接用 numpy 暴力算即可;大规模则需要 ANN(近似最近邻)索引:

  • HNSW:基于图的索引,召回率和速度的性价比之王,是大多数向量库的默认选择;
  • IVF / PQ:聚类 + 量化压缩,适合千万级以上。

常用向量数据库:FAISS(本地库)、Chroma(轻量)、Milvus / Qdrant / Weaviate(生产级)、pgvector(Postgres 插件,少维护一个组件)。

2.4 重排序(Rerank)

向量检索是"粗排",Embedding 模型对细粒度相关性区分能力有限。可以再用一个交叉编码器(Cross-Encoder,如 bge-reranker)对粗排结果的 Top-20~50 做精排,把 query 和文档拼接后一起过模型算相关分,精度显著提升,代价是只能对少量候选打分。

2.5 Prompt 组装与生成

最后把检索到的上下文填进模板:

请基于以下参考资料回答问题。如果资料中没有相关信息,请明确说明"根据现有资料无法回答",不要编造。

【参考资料】
[1] {chunk_1}
[2] {chunk_2}
...

【问题】{query}

最后这条"不知道就说不知道"的指令,是工程上抑制幻觉成本最低、收益最高的一招。

三、代码实战:50 行搭一个本地文档问答系统

下面的例子用 sentence-transformers 本地 Embedding + numpy 暴力检索 + 任意 LLM API,不依赖 LangChain,方便看清每一步发生了什么:

import numpy as np
from sentence_transformers import SentenceTransformer

# ---------- 1. 离线索引 ----------
embedder = SentenceTransformer("BAAI/bge-small-zh-v1.5")   # 中文 Embedding

docs = [
    "RAG(检索增强生成)由 Facebook AI Research 于 2020 年提出,核心思想是先检索后生成。",
    "向量数据库通过 ANN 索引(如 HNSW)加速高维向量的相似度搜索。",
    "大模型的幻觉指模型编造看似合理但与事实不符的内容,RAG 可以显著缓解该问题。",
    "Transformer 架构由 Vaswani 等人在 2017 年的论文《Attention Is All You Need》中提出。",
    "重排序(Rerank)使用交叉编码器对粗排结果精排,能显著提升检索精度。",
]

# BGE 系列建议给查询加指令前缀,文档侧不用
doc_vecs = embedder.encode(docs, normalize_embeddings=True)  # (5, 512)

# ---------- 2. 在线检索 ----------
def retrieve(query: str, top_k: int = 2):
    q = embedder.encode("为这个句子生成表示以用于检索相关文章:" + query,
                        normalize_embeddings=True)
    scores = doc_vecs @ q                       # 归一化后点积 = 余弦相似度
    top_idx = np.argsort(scores)[::-1][:top_k]
    return [(docs[i], float(scores[i])) for i in top_idx]

# ---------- 3. 组装 Prompt 并生成 ----------
def build_prompt(query: str, contexts: list) -> str:
    ctx = "\n".join(f"[{i+1}] {c}" for i, (c, _) in enumerate(contexts))
    return f"""请基于以下参考资料回答问题。若资料中没有相关信息,请回答"根据现有资料无法回答"。

【参考资料】
{ctx}

【问题】{query}"""

query = "大模型幻觉是什么意思?怎么解决?"
contexts = retrieve(query, top_k=2)
prompt = build_prompt(query, contexts)
print(prompt)
# 之后把 prompt 发给任意 LLM(OpenAI / DeepSeek / 本地 Qwen)即可:
# answer = llm.chat(prompt)

运行后可以看到,retrieve 准确地捞出了"幻觉"和"RAG"两条相关文档。真实项目中把 numpy 部分换成 FAISS/Chroma,把 llm.chat 接到你的模型 API,就是一个可上线的最小 RAG。

生产级简化版(LangChain + Chroma)

from langchain_community.document_loaders import DirectoryLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_community.embeddings import HuggingFaceBgeEmbeddings
from langchain_community.vectorstores import Chroma

docs = DirectoryLoader("./my_docs", glob="**/*.md").load()
chunks = RecursiveCharacterTextSplitter(chunk_size=512, chunk_overlap=64).split_documents(docs)

emb = HuggingFaceBgeEmbeddings(model_name="BAAI/bge-small-zh-v1.5")
vectordb = Chroma.from_documents(chunks, emb, persist_directory="./chroma_db")
retriever = vectordb.as_retriever(search_kwargs={"k": 4})

relevant = retriever.invoke("大模型幻觉怎么解决?")

四、RAG 常见翻车点与优化清单

症状可能原因优化手段
检索不到相关内容分块太碎/太大;Embedding 不适合领域调整 chunk 策略;换领域 Embedding 或微调
检索到了但答错Prompt 没约束;上下文太多稀释了重点加"只依据资料回答"指令;控制 Top-K
专有名词检索差向量检索对精确匹配弱混合检索:BM25(关键词)+ 向量,RRF 融合
问题依赖上下文指代多轮对话中"它""这个"检索不到先用 LLM 改写问题(query rewriting)再检索
需要跨块推理答案分散在多个块中增大 Top-K;Parent-Child 索引;或用 GraphRAG
需要评估效果没有指标体系用 RAGAS 评 faithfulness / answer relevancy

进阶方向还包括:HyDE(先让 LLM 生成假想答案再检索)、Self-RAG(模型自主判断何时检索、自我反思)、Agentic RAG(把检索作为 Agent 的一个工具,多步规划调用)。

五、小结

  • RAG = 检索(外脑)+ 生成(表达),用"开卷考试"思路缓解幻觉与知识过时;
  • 核心链路:分块 → Embedding → 向量检索 → 重排序 → Prompt → 生成,每一环都有调优空间;
  • 原型一天能搭出来,但做好需要持续打磨分块策略、混合检索、Rerank 与评估体系;
  • RAG 不会消失:上下文窗口再大,"把最相关的资料给模型"依然是成本与效果的最优平衡点。

参考资料

  1. Lewis et al. Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks. NeurIPS 2020.
  2. Karpukhin et al. Dense Passage Retrieval for Open-Domain Question Answering. EMNLP 2020.
  3. Chen et al. BGE M3-Embedding: Multi-Lingual, Multi-Functionality, Multi-Granularity Text Embeddings. 2024.
  4. Gao et al. Retrieval-Augmented Generation for Large Language Models: A Survey. 2023.
  5. Asai et al. Self-RAG: Learning to Retrieve, Generate, and Critique through Self-Reflection. ICLR 2024.

更多推荐