Mindket

RAG 教程:一步步搭出检索增强生成管线

黑色横幅,标题是“RAG 实战”,下面是“向量、分块、检索与评估”。

检索增强生成(RAG)的做法是:问题一来,先从你自己的文档里找出相关段落,再放进模型的提示词。最小的管线有六个环节:加载、分块、嵌入、存储、检索和生成。本教程用一个 Python 脚本把六个环节都搭出来,并让 Claude 标明它用了哪些段落。

本节内容

  • 用一个 Python 脚本搭出 RAG 管线的六个环节。
  • 分清索引时和查询时,知道每个设置属于哪一边。
  • 拿到带引用的回答,引用能精确到文本块,指向你自己的来源 ID。
  • 在两步式 RAG 和由智能体决定何时检索之间做出选择。
全部课程
本页目录
  1. 开始之前
  2. 搭出这条管线
  3. 索引时和查询时
  4. 一条引用里有什么
  5. 两步式 RAG,还是让智能体自己决定
  6. Alex Albert 谈带引用的搜索结果,Hamel Husain 谈长上下文
  7. 常见错误
  8. 自己试一试
  9. 下一课
  10. 自测一下
  11. 常见问题
  12. 资料来源

开始之前

你需要 Python 3、一个 Voyage AI 密钥和一个 Anthropic 密钥。Anthropic 自己没有嵌入模型,它的嵌入指南推荐的是 Voyage AI,本课也照着用。任何嵌入服务都能接进这条管线,第 2 课会比较几家的模型。

先确认你真的需要 RAG。Anthropic 的 Contextual Retrieval 文章说,小于 20 万 token(大约 500 页)的知识库可以直接放进提示词,而提示词缓存让这种做法更快、更省成本。语料装不下的时候,检索多出来的这些环节才值得。

搭出这条管线

下面的脚本就是完整的管线。切分调用来自 LangChain 的语义搜索教程,两次嵌入调用和点积来自 Voyage AI 的快速入门,search_result 块来自 Anthropic 的搜索结果指南。把它们连起来的代码、文件名和问题是我们为本课写的示例,请换成你自己的。

import numpy as np
import voyageai
import anthropic
from langchain_core.documents import Document
from langchain_text_splitters import RecursiveCharacterTextSplitter

vo = voyageai.Client()          # reads VOYAGE_API_KEY
claude = anthropic.Anthropic()  # reads ANTHROPIC_API_KEY

# 1. Load: one Document per file, with a source that never changes
docs = [
    Document(
        page_content=open("refund-policy.txt", encoding="utf-8").read(),
        metadata={"source": "kb://refund-policy", "title": "Refund policy"},
    ),
    Document(
        page_content=open("shipping.txt", encoding="utf-8").read(),
        metadata={"source": "kb://shipping", "title": "Shipping"},
    ),
]

# 2. Chunk
splitter = RecursiveCharacterTextSplitter(
    chunk_size=1000, chunk_overlap=200, add_start_index=True
)
chunks = splitter.split_documents(docs)

# 3. Embed the chunks (index time)
chunk_embeddings = vo.embed(
    texts=[c.page_content for c in chunks],
    model="voyage-4-large",
    input_type="document",
).embeddings

# 4. Store: a plain list in memory is enough for a first run

# 5. Retrieve (query time)
question = "How long do I have to return an item?"
query_embedding = vo.embed(
    texts=[question],
    model="voyage-4-large",
    input_type="query",
).embeddings[0]
similarities = np.dot(chunk_embeddings, query_embedding)
top = np.argsort(-similarities)[:4]

# 6. Generate, with citations on
results = [
    {
        "type": "search_result",
        "source": chunks[i].metadata["source"],
        "title": chunks[i].metadata["title"],
        "content": [{"type": "text", "text": chunks[i].page_content}],
        "citations": {"enabled": True},
    }
    for i in top
]
response = claude.messages.create(
    model="claude-sonnet-5-5",
    max_tokens=1024,
    messages=[
        {"role": "user", "content": [*results, {"type": "text", "text": question}]}
    ],
)

for block in response.content:
    if block.type == "text":
        print(block.text)
        for c in block.citations or []:
            print(f"  [{c.title}] {c.source}")
  1. 执行 pip install numpy voyageai anthropic langchain-core langchain-text-splitters 安装依赖,再设置 VOYAGE_API_KEY 和 ANTHROPIC_API_KEY,两个客户端都从环境变量里读取密钥。
  2. 加载:把每个文件包成 LangChain 的 Document。给它一个永远不变的 source,比如 kb://refund-policy,再加一个 title。两者都会出现在每条引用里。
  3. 分块:用 RecursiveCharacterTextSplitter,chunk_size=1000、chunk_overlap=200,这是 LangChain 教程里用的数值。add_start_index=True 会把每个块的字符偏移量保存在元数据里。
  4. 嵌入:用 input_type="document" 发送各个块的文本。模型是 voyage-4-large,也就是 Voyage 快速入门里用的那个。
  5. 存储:向量暂时放在一个 Python 列表里,脚本结束它们就没了。LangChain 提醒,生产环境要把存储持久化。
  6. 检索:用 input_type="query" 嵌入问题,和每个块的向量求点积,取得分最高的四个。Voyage 的嵌入向量已归一化为长度 1,所以点积等于余弦相似度。
  7. 生成:把这四个块作为开启了 citations 的 search_result 块传入,再在同一条用户消息里放上问题的文本块。
  8. 运行它。凡是用到某个块的文本块都带着引用,最后那个循环会把它们打印出来。

索引时和查询时

第 1 到第 4 步在文档变化时运行,第 5 步和第 6 步在每次提问时运行。这个划分决定了修改的代价:改动分块方式或嵌入模型,就得把整个语料重新嵌入一遍;改动提示词或块的数量则不用。

嵌入模型是不能悄悄更换的部分。LlamaIndex 的文档说,换了嵌入模型就必须重新建索引,而且文档和查询必须用同一个模型来嵌入。Voyage AI 写明了一个例外:它的模型页面说,4 系列生成的所有嵌入彼此兼容,第 2 课会用上这一点。

环节划分出自 LangChain 的检索概览,设置出自来源列表里的各个页面。
环节何时运行需要了解的设置在哪一课讲
加载文档变化时加载方式,以及你附上的 source 和 title本课
分块文档变化时chunk_size、chunk_overlap、切分器类型第 3 课
嵌入文档变化时model、input_type、输出维度第 2 课
存储文档变化时索引类型及其搜索设置第 2 课
检索每次提问时块的数量、混合搜索、重排序第 3、4 课
生成每次提问时提示词、引用设置本课

一条引用里有什么

如果旧教程还在发送 search-results-2025-06-09 这个测试版请求头,把它删掉就行。从 2025 年 8 月 8 日起,在 Claude API 上使用搜索结果块已经不需要测试版请求头了。

引用默认是关闭的,而且同一次请求里的所有搜索结果必须用同一个设置。开启之后不需要任何特殊的提示词。回复里凡是用到你内容的文本块,都会带一条 search_result_location 类型的引用,字段如下:

  • source 和 title:原样取自你发送的搜索结果。
  • cited_text:被引用的那个或那几个块的完整文本,不计入输出 token。
  • search_result_index:这条结果在请求里所有 search_result 块中的位置,从 0 开始。
  • start_block_index 和 end_block_index:这条结果 content 数组里的一段切片,结束位置不含在内。
Claude 给你自己的内容加引用,功能是怎样一步步来的
  1. API 支持引用

    Claude 可以把它说的话对应到你发送的纯文本文档和自定义内容文档。

  2. 搜索结果块推出测试版

    你自己的工具可以返回让 Claude 引用的搜索结果,需要带 search-results-2025-06-09 请求头。

  3. 搜索结果块结束测试

    在 Claude API 和 Vertex AI 上不再需要测试版请求头,所以上面的脚本没有发送它。

  4. 工具结果里的文档也能被引用

    客户端工具可以返回让 Claude 引用的文档,智能体式 RAG 用的就是这种形态。

  5. 搜索结果块登陆 Amazon Bedrock

    在那里同样不需要测试版请求头。

来源:Claude Docs: Release notes (2026年11月5日 查阅)

文本块是 Claude 能引用的最小单位,它引用整块,不会引用块里的某个片段。想要句子级的引用,就把每个句子放进 content 里各自独立的块。如果提示词只要求输出 JSON,引用会很少,甚至没有。

两步式 RAG,还是让智能体自己决定

你搭的是两步式 RAG,这是 LangChain 对“检索总是发生在生成之前”这种做法的叫法。另一端是智能体式 RAG:把搜索做成一个工具,由模型决定要不要调用、用什么查询词。Anthropic 的搜索结果指南两种形态都讲了。结果可以像上面那样放在用户消息里,也可以从工具调用里返回,还可以用 tool_choice 强制调用,让检索每次都执行。

我们建议先从两步式开始。它的延迟更好预估,因为模型调用的次数是固定的,答错了也只需要查两个地方。当单个问题需要好几次查找时,再换成智能体。把检索函数包装成 MCP 服务器,是把它交给智能体的一种办法。

出自 LangChain 检索概览里的架构对比表。
架构谁决定检索延迟适合
两步式 RAG你的代码,每次提问都检索快,可预测常见问题、文档机器人
智能体式 RAG模型,在推理过程中决定不固定带多个工具的研究助手
混合式两者结合,并带验证步骤不固定需要质量把关的专业领域问答

Alex Albert 谈带引用的搜索结果,Hamel Husain 谈长上下文

按 Alex Albert 的 X 简介,他在 Anthropic 做研究。本课用到的 search_result 块在 2025 年 7 月上线,他当天在 X 上发布了这个消息。他在同一串帖子里用一句话讲清了用法:把来源、标题和内容交给 Claude,每条引用都会写明依据的是哪个来源、哪段文字。

放到你的脚本里,第 2 步那两个元数据字段,就成了答案和文件之间唯一的联系。所以要选人看得懂、找得到的值,比如网址,或者永远不变的页面 ID。

Hamel Husain 的 X 简介写着他在 OpenAI 做评估。有人说长上下文窗口会让 RAG 失去意义,他在 2024 年 7 月在 X 上给出了三条反驳的理由。你经常要按日期这类元数据过滤。检索这一步越来越多地在决定从哪些来源取内容。不管窗口多长,剔除明显无关的文档,结果都会更好。

按“开始之前”里 Anthropic 的大小标准,语料小可以不做检索,但过滤会把检索请回来。一旦用户只能看到自己的工单,或者只该看到今年的政策,你就需要一个带 WHERE 条件的检索步骤,第 2 课会在 pgvector 里加上它。

不管上下文窗口有多长,剔除明显无关的文档,你都会得到更好的结果。

Hamel Husain, 按他的 X 简介,在 OpenAI 做评估,引文为译文 · X (2026年11月5日 查阅)
Alex Albert(@alexalbert__)2025 年 7 月 3 日在 X 上宣布:Anthropic API 今天上线了新功能“搜索结果内容块”,让所有 RAG 内容都能带上注明出处的自然引用,把网页搜索级别的引用带到你自己的搜索工具里
Alex Albert 在 X 上,2025 年 7 月 3 日
Hamel Husain(@HamelHusain)2024 年 7 月 10 日在 X 上反驳“长上下文窗口会让 RAG 失去意义”的说法,第一条理由是:你经常要按日期、浏览量等元数据过滤,搜索和信息检索在很多实际场景里都很关键
Hamel Husain 在 X 上,2024 年 7 月 10 日
freeCodeCamp.org 的 YouTube 视频:标题说明,这是一位 LangChain 工程师用 Python 从零讲解 RAG 的课程。

X 官方嵌入卡片的截图,拍摄于 2026-11-05,时间为协调世界时(UTC)。Hamel Husain 那条帖子被 X 用“Show more”截断了。点击截图可到 X 上打开原帖。

常见错误

  • 漏掉 input_type。Voyage 的指南说,做检索时不要省略它,它会在查询和文档前面加上检索专用的提示语。
  • 一次性嵌入整个语料。Voyage 的 embed 每次请求最多接收 1,000 条文本,总 token 数还按模型设有上限,voyage-4-large 是 120K。按文本嵌入页面的限制,把各个块分批发送。
  • 文档用一个模型嵌入,查询却用一个不相干的模型。只有两边来自同一个模型,或者来自厂商写明共用同一个嵌入空间的模型,向量才能互相比较。
  • 觉得多送几个块更保险。Anthropic 的搜索结果指南建议只返回最相关的结果,以免上下文溢出。Contextual Retrieval 文章提醒说,多余的信息可能分散模型的注意力,所以数量是有上限的。请在第 4 课里测一测你自己的数据。
  • 把检索到的文本当成可信内容。LangChain 警告,RAG 应用容易受到间接提示词注入,因为检索到的块和你的系统提示词共用同一个上下文窗口。没有任何提示词或分隔符方案能完全防住,所以在用户看到输出之前要先校验。
  • 丢掉来源。搜索结果的 source 可以是任何稳定的字符串,网址或者 kb://article-1234 这样的 ID 都行,但它必须存在,引用才有地方可指。

自己试一试

  • 问一个文档里没有答案的问题,看 Claude 是承认没有,还是凭记忆把空缺补上。
  • 打印 similarities[top],找出相关块和噪声块之间得分掉下来的位置。
  • 把 chunk_size 改成 500,chunk_overlap 改成 50,再把四个块增加到八个。LlamaIndex 建议块变小时调大 top-k。
  • 把一个块拆成句子大小的块,放进同一个 search_result,比较前后的 cited_text。

下一课

第 2 课展开嵌入和存储两个环节:向量到底是什么,怎样挑模型和维度,怎样把内存里的列表搬进带 pgvector 的 Postgres。四个块的上限,背后是提示词的容量,上下文窗口那一课讲的就是它,也讲了怎样查看一次请求用掉多少 token。

自测一下

  1. 为检索嵌入用户的问题时,input_type 应该设成什么?

    查看答案

    B. query Voyage 的指南说,问题用 query,要建索引的文本用 document,做检索时不要省略这个参数。

  2. 你的整个知识库是 15 万 token,Contextual Retrieval 文章建议怎么做?

    查看答案

    C. 全部放进提示词并缓存 文章说,小于 20 万 token 的知识库可以直接放进提示词,提示词缓存让这种做法更快、更省成本。

  3. 你把每个块当作搜索结果里的一个文本块传入,Claude 最细能引用到哪里?

    查看答案

    A. 整个块 文本块是最小的可引用单位,Claude 引用整块。想要更细的引用,就把一个块拆成更小的块。

  4. 你从 OpenAI 的 text-embedding-3-small 换到 voyage-4-large,已经存好的向量该怎么处理?

    查看答案

    D. 用新模型重新嵌入每一个块 LlamaIndex 说,换了嵌入模型就要重新建索引。只有厂商写明共用同一个嵌入空间的模型,比如 Voyage 4 系列,才不用这一步。

常见问题

RAG 简单说是什么?

你的代码先在自己的文档里搜索,把最匹配的内容交给模型,让它依据最新的、私有的数据回答,而不只靠记忆。LangChain 的检索概览点出它解决的两个限制:上下文有限,知识是静态的。

搭 RAG 一定要用向量数据库吗?

不一定。上面的管线把向量放在 Python 列表里,用 NumPy 的点积排序。需要持久保存向量、语料变大或者要做过滤时,数据库才派上用场。第 2 课会把向量搬进带 pgvector 的 Postgres。

RAG 比直接把文档放进提示词更好吗?

要等语料超出上下文窗口,或者用户需要按日期、权限这类条件过滤的时候才是。在那之前,把全部内容配上提示词缓存一起发送,搭起来和排查问题都更简单。

做 RAG 需要 LangChain 或 LlamaIndex 吗?

不需要。这里 LangChain 只提供切分器,其余都是普通的 API 调用。LangChain 自己的文档把 RecursiveCharacterTextSplitter 称为通用文本的推荐切分器。

应该给模型送多少个块?

先用四个,再去测量。Anthropic 在 Contextual Retrieval 文章里试了 5、10 和 20 个块,在它的数据上数量最多的那一档最有效,所以合适的数量取决于你的文档。

Claude 怎样给 RAG 的来源加引用?

通过开启了引用的 search_result 块。回复里每个被引用的文本块都带着来源、标题,以及它用到的内容块范围。

RAG 能杜绝幻觉吗?

不能。它给模型提供了依据,模型仍然可能说出依据之外的话。第 4 课用忠实度分数来衡量这个差距。

资料来源

  1. LangChain Docs: Retrieval Augmented Generation (RAG) with Deep Agents (2026年11月4日 查阅)https://docs.langchain.com/oss/python/deepagents/rag
  2. LangChain Docs: Retrieval (2026年11月4日 查阅)https://docs.langchain.com/oss/python/langchain/retrieval
  3. LangChain Docs: Build a semantic search engine (2026年11月4日 查阅)https://docs.langchain.com/oss/python/langchain/knowledge-base
  4. Claude Docs: Embeddings (2026年11月4日 查阅)https://platform.claude.com/docs/en/build-with-claude/embeddings
  5. Claude Docs: Search results (2026年11月5日 查阅)https://platform.claude.com/docs/en/build-with-claude/search-results
  6. Claude Docs: Release notes (2026年11月5日 查阅)https://platform.claude.com/docs/en/release-notes/overview
  7. Anthropic: Contextual Retrieval in AI systems (2026年11月4日 查阅)https://www.anthropic.com/research/contextual-retrieval
  8. Voyage AI Docs: Quick Start (2026年11月5日 查阅)https://www.mongodb.com/docs/voyageai/quickstart
  9. Voyage AI Docs: Text Embeddings (2026年11月5日 查阅)https://www.mongodb.com/docs/voyageai/models/text-embeddings
  10. LlamaIndex Docs: Basic strategies (2026年11月4日 查阅)https://developers.llamaindex.ai/python/framework/optimizing/basic_strategies/basic_strategies/
  11. Alex Albert on X: Search result content blocks (3 July 2025) (2026年11月5日 查阅)https://x.com/alexalbert__/status/1940823684362781039
  12. Alex Albert on X: how search result blocks work (3 July 2025) (2026年11月5日 查阅)https://x.com/alexalbert__/status/1940823696236859855
  13. Hamel Husain on X: RAG and long context windows (10 July 2024) (2026年11月5日 查阅)https://x.com/HamelHusain/status/1811050329972322708
  14. freeCodeCamp.org on YouTube: Learn RAG From Scratch (2026年11月5日 查阅)https://www.youtube.com/watch?v=sVcwVQRHIc8

分享本节课

给你的智能体一个检索工具

智能体式 RAG 把检索作为工具交给模型。Mindket 上列出了不少 MCP 服务器,可以直接连接现成的工具。

浏览 MCP 服务器

本节课的 Markdown 版本 · RAG 实战课:检索增强生成 · 全部课程