开始之前
你需要 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}")- 执行
pip install numpy voyageai anthropic langchain-core langchain-text-splitters安装依赖,再设置VOYAGE_API_KEY和ANTHROPIC_API_KEY,两个客户端都从环境变量里读取密钥。 - 加载:把每个文件包成 LangChain 的
Document。给它一个永远不变的source,比如kb://refund-policy,再加一个title。两者都会出现在每条引用里。 - 分块:用
RecursiveCharacterTextSplitter,chunk_size=1000、chunk_overlap=200,这是 LangChain 教程里用的数值。add_start_index=True会把每个块的字符偏移量保存在元数据里。 - 嵌入:用
input_type="document"发送各个块的文本。模型是voyage-4-large,也就是 Voyage 快速入门里用的那个。 - 存储:向量暂时放在一个 Python 列表里,脚本结束它们就没了。LangChain 提醒,生产环境要把存储持久化。
- 检索:用
input_type="query"嵌入问题,和每个块的向量求点积,取得分最高的四个。Voyage 的嵌入向量已归一化为长度 1,所以点积等于余弦相似度。 - 生成:把这四个块作为开启了
citations的search_result块传入,再在同一条用户消息里放上问题的文本块。 - 运行它。凡是用到某个块的文本块都带着引用,最后那个循环会把它们打印出来。
索引时和查询时
第 1 到第 4 步在文档变化时运行,第 5 步和第 6 步在每次提问时运行。这个划分决定了修改的代价:改动分块方式或嵌入模型,就得把整个语料重新嵌入一遍;改动提示词或块的数量则不用。
嵌入模型是不能悄悄更换的部分。LlamaIndex 的文档说,换了嵌入模型就必须重新建索引,而且文档和查询必须用同一个模型来嵌入。Voyage AI 写明了一个例外:它的模型页面说,4 系列生成的所有嵌入彼此兼容,第 2 课会用上这一点。
| 环节 | 何时运行 | 需要了解的设置 | 在哪一课讲 |
|---|---|---|---|
| 加载 | 文档变化时 | 加载方式,以及你附上的 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数组里的一段切片,结束位置不含在内。
API 支持引用
Claude 可以把它说的话对应到你发送的纯文本文档和自定义内容文档。
搜索结果块推出测试版
你自己的工具可以返回让 Claude 引用的搜索结果,需要带 search-results-2025-06-09 请求头。
搜索结果块结束测试
在 Claude API 和 Vertex AI 上不再需要测试版请求头,所以上面的脚本没有发送它。
工具结果里的文档也能被引用
客户端工具可以返回让 Claude 引用的文档,智能体式 RAG 用的就是这种形态。
搜索结果块登陆 Amazon Bedrock
在那里同样不需要测试版请求头。
来源:Claude Docs: Release notes (2026年11月5日 查阅)
文本块是 Claude 能引用的最小单位,它引用整块,不会引用块里的某个片段。想要句子级的引用,就把每个句子放进 content 里各自独立的块。如果提示词只要求输出 JSON,引用会很少,甚至没有。
两步式 RAG,还是让智能体自己决定
你搭的是两步式 RAG,这是 LangChain 对“检索总是发生在生成之前”这种做法的叫法。另一端是智能体式 RAG:把搜索做成一个工具,由模型决定要不要调用、用什么查询词。Anthropic 的搜索结果指南两种形态都讲了。结果可以像上面那样放在用户消息里,也可以从工具调用里返回,还可以用 tool_choice 强制调用,让检索每次都执行。
我们建议先从两步式开始。它的延迟更好预估,因为模型调用的次数是固定的,答错了也只需要查两个地方。当单个问题需要好几次查找时,再换成智能体。把检索函数包装成 MCP 服务器,是把它交给智能体的一种办法。
| 架构 | 谁决定检索 | 延迟 | 适合 |
|---|---|---|---|
| 两步式 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 里加上它。
不管上下文窗口有多长,剔除明显无关的文档,你都会得到更好的结果。

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。
自测一下
为检索嵌入用户的问题时,input_type 应该设成什么?
查看答案
B. query Voyage 的指南说,问题用 query,要建索引的文本用 document,做检索时不要省略这个参数。
你的整个知识库是 15 万 token,Contextual Retrieval 文章建议怎么做?
查看答案
C. 全部放进提示词并缓存 文章说,小于 20 万 token 的知识库可以直接放进提示词,提示词缓存让这种做法更快、更省成本。
你把每个块当作搜索结果里的一个文本块传入,Claude 最细能引用到哪里?
查看答案
A. 整个块 文本块是最小的可引用单位,Claude 引用整块。想要更细的引用,就把一个块拆成更小的块。
你从 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 课用忠实度分数来衡量这个差距。
资料来源
- LangChain Docs: Retrieval Augmented Generation (RAG) with Deep Agents https://docs.langchain.com/oss/python/deepagents/rag
- LangChain Docs: Retrieval https://docs.langchain.com/oss/python/langchain/retrieval
- LangChain Docs: Build a semantic search engine https://docs.langchain.com/oss/python/langchain/knowledge-base
- Claude Docs: Embeddings https://platform.claude.com/docs/en/build-with-claude/embeddings
- Claude Docs: Search results https://platform.claude.com/docs/en/build-with-claude/search-results
- Claude Docs: Release notes https://platform.claude.com/docs/en/release-notes/overview
- Anthropic: Contextual Retrieval in AI systems https://www.anthropic.com/research/contextual-retrieval
- Voyage AI Docs: Quick Start https://www.mongodb.com/docs/voyageai/quickstart
- Voyage AI Docs: Text Embeddings https://www.mongodb.com/docs/voyageai/models/text-embeddings
- LlamaIndex Docs: Basic strategies https://developers.llamaindex.ai/python/framework/optimizing/basic_strategies/basic_strategies/
- Alex Albert on X: Search result content blocks (3 July 2025) https://x.com/alexalbert__/status/1940823684362781039
- Alex Albert on X: how search result blocks work (3 July 2025) https://x.com/alexalbert__/status/1940823696236859855
- Hamel Husain on X: RAG and long context windows (10 July 2024) https://x.com/HamelHusain/status/1811050329972322708
- freeCodeCamp.org on YouTube: Learn RAG From Scratch https://www.youtube.com/watch?v=sVcwVQRHIc8


