02LangChain 检索器与 Agent 知识点详解
LangChain 检索器与 Agent 知识点详解
本文档整理自 LangChain 中文网两篇教程:
本文只保留知识点,不展开环境安装、API Key、Notebook 等准备内容。适合在已经理解 ChatModel、PromptTemplate、LCEL、Runnable 的基础上继续学习。
一、整体关系
检索器和 Agent 分别解决两类问题:
检索器 Retriever
用于从外部知识中找相关资料
常见于 RAG
Agent
用于让模型自己决定下一步行动
常见于搜索、工具调用、执行任务、多步推理
它们经常会组合在一起:
用户问题
|
v
Agent 判断是否需要查资料
|
v
调用检索器或搜索工具
|
v
拿到外部信息
|
v
模型生成最终回答
二、Document 文档对象
1. 是什么
Document 是 LangChain 表示一段文本和相关元数据的标准对象。它主要有两个字段:
Document(
page_content="文本内容",
metadata={"source": "来源信息"}
)
page_content:真正要被模型或检索器使用的文本。metadata:描述这段文本的附加信息,例如来源、页码、标题、位置、时间等。
2. 解决什么问题
外部知识通常不是孤立字符串,而是带来源和结构的内容。比如:
- 一篇网页中的某一段。
- 一个 PDF 的第 5 页。
- 一份文档的某个章节。
- 数据库里某条记录的说明文本。
如果只保存文本,后面很难知道答案来自哪里。Document 把内容和来源绑定在一起。
3. 为什么重要
RAG 和检索应用中,模型的回答质量高度依赖检索到的上下文。Document 是“可被检索的知识单元”。
你可以把它理解为:
Document = 一小段可搜索文本 + 这段文本的身份证
4. 示例
from langchain_core.documents import Document
documents = [
Document(
page_content="Dogs are great companions, known for their loyalty and friendliness.",
metadata={"source": "mammal-pets-doc"},
),
Document(
page_content="Cats are independent pets that often enjoy their own space.",
metadata={"source": "mammal-pets-doc"},
),
]
三、向量与 Embedding
1. 是什么
Embedding 是把文本转换成数值向量的过程。向量是一组数字,用来表示文本语义。
例如:
"cat" -> [0.12, -0.31, 0.88, ...]
"kitten" -> [0.10, -0.29, 0.84, ...]
"airplane" -> [-0.77, 0.44, 0.03, ...]
语义越相近的文本,向量在空间中的距离通常越近。
2. 解决什么问题
普通关键词搜索依赖字面匹配,而用户问题和资料原文可能表达不同。
例如用户问:
Which animal is loyal?
资料里写的是:
Dogs are great companions, known for their loyalty.
关键词不完全一样,但语义相关。Embedding 可以支持这种语义搜索。
3. 为什么重要
大语言模型应用经常需要“按意思找资料”,而不是“按关键词找资料”。Embedding 是向量搜索、RAG、语义检索的基础。
4. 初学者理解
Embedding 把文字变成坐标
向量搜索就是找坐标附近的内容
四、VectorStore 向量存储
1. 是什么
VectorStore 是用于存储和搜索向量的组件。它通常保存:
- 文档原文。
- 文档元数据。
- 文档对应的 embedding 向量。
教程中使用的是 Chroma:
from langchain_chroma import Chroma
from langchain_openai import OpenAIEmbeddings
vectorstore = Chroma.from_documents(
documents,
embedding=OpenAIEmbeddings(),
)
2. 解决什么问题
如果你有几千、几万甚至更多文档,不可能每次都把所有内容给模型。向量存储可以先帮你找到最相关的几段。
3. 为什么需要向量存储
RAG 的关键不是“把所有资料都塞给模型”,而是:
先搜索最相关的片段,再把片段放进提示词
向量存储就是完成“搜索最相关片段”的基础设施。
4. 常见能力
向量存储通常支持:
- 根据字符串做相似度搜索。
- 根据向量做相似度搜索。
- 返回相似度分数。
- 异步搜索。
- MMR 搜索,兼顾相关性和多样性。
五、similarity_search 相似度搜索
1. 是什么
similarity_search 根据查询文本找到语义最相近的文档。
docs = vectorstore.similarity_search("cat")
2. 解决什么问题
当用户提出问题时,我们需要从知识库中找出可能有用的内容。相似度搜索就是最基础的检索方式。
3. 为什么返回的是 Document
因为后续不仅要用文本回答,还可能需要来源信息。
例如:
for doc in docs:
print(doc.page_content)
print(doc.metadata)
4. 初学者易错点
相似度搜索不等于答案生成。它只负责“找资料”,不负责“组织答案”。
检索器:找到相关文档
LLM:根据相关文档生成答案
六、similarity_search_with_score
1. 是什么
带分数的相似度搜索会返回文档和分数。
results = vectorstore.similarity_search_with_score("cat")
返回结构类似:
[
(Document(...), 0.37),
(Document(...), 0.48),
]
2. 解决什么问题
有时你需要判断检索结果是否足够相关。分数可以作为过滤、排序、调试的参考。
3. 为什么要小心解释分数
不同向量库的分数含义可能不同。有的分数越高越相似,有的是距离,越低越相似。教程中提到 Chroma 返回的是距离度量,因此与相似度大致相反。
4. 实用建议
不要盲目写死一个阈值。先观察你所用向量库的分数分布,再决定过滤规则。
七、similarity_search_by_vector
1. 是什么
先手动把查询转成 embedding,再用向量进行搜索。
embedding = OpenAIEmbeddings().embed_query("cat")
docs = vectorstore.similarity_search_by_vector(embedding)
2. 解决什么问题
有时查询向量不是来自用户文本,而是你自己计算或处理过的向量。这个方法允许直接按向量搜索。
3. 为什么初学阶段较少用
大多数场景直接用字符串查询就够了:
vectorstore.similarity_search("cat")
直接按向量搜索更适合高级检索、缓存 embedding、混合检索等场景。
八、Retriever 检索器
1. 是什么
Retriever 是 LangChain 中的检索抽象。它接收查询,返回相关 Document 列表。
query -> Retriever -> List[Document]
2. 解决什么问题
VectorStore 本身不是标准 Runnable,不方便直接接入 LCEL 链。Retriever 是 Runnable,可以使用:
.invoke().batch().stream(),视具体实现而定- 异步调用
这让它可以自然接进 LangChain 链。
3. 为什么要从 VectorStore 转成 Retriever
因为 RAG 链需要把检索步骤作为链的一环:
用户问题 -> 检索器 -> 文档 -> 提示词 -> 模型 -> 答案
如果检索器是 Runnable,就可以和 LCEL 组合。
4. as_retriever
最常见写法:
retriever = vectorstore.as_retriever(
search_type="similarity",
search_kwargs={"k": 1},
)
含义:
search_type="similarity":使用相似度搜索。search_kwargs={"k": 1}:只取最相关的 1 条。
5. batch 批量检索
retriever.batch(["cat", "shark"])
适合一次处理多个查询。
九、RunnableLambda 包装检索逻辑
1. 是什么
RunnableLambda 可以把普通 Python 函数包装成 LangChain Runnable。
教程中用它把 vectorstore.similarity_search 包装成一个简单检索器:
from langchain_core.runnables import RunnableLambda
retriever = RunnableLambda(vectorstore.similarity_search).bind(k=1)
2. 解决什么问题
当某个函数本身不是 Runnable,但你想把它放进 LCEL 链里时,可以用 RunnableLambda。
3. 为什么有用
LangChain 很多组合能力都围绕 Runnable 展开。把普通函数转换成 Runnable 后,就能参与:
|.invoke().batch().stream()
4. 初学者理解
RunnableLambda = 把普通函数改造成 LangChain 链里的一个积木
十、Agent 是什么
1. 是什么
Agent 是使用大语言模型作为推理引擎的系统。它不只是回答文本,而是可以决定:
- 是否要调用工具。
- 调用哪个工具。
- 给工具传什么参数。
- 是否需要多次调用工具。
- 什么时候结束并返回最终答案。
2. 解决什么问题
普通 LLM 只能基于输入生成文本,不能主动查天气、搜索网页、查数据库、运行函数。Agent 让模型能“采取行动”。
3. 为什么需要 Agent
有些任务不是一次模型调用就能完成:
用户:我所在城市今天的天气怎么样?
模型需要:
- 理解用户要查天气。
- 知道要调用搜索或天气工具。
- 构造查询参数。
- 读取工具结果。
- 总结成人类可读回答。
这就是 Agent 的价值。
十一、Tool 工具
1. 是什么
工具是 Agent 可以调用的外部能力。教程中使用 Tavily 搜索工具:
from langchain_community.tools.tavily_search import TavilySearchResults
search = TavilySearchResults(max_results=2)
tools = [search]
2. 解决什么问题
工具让模型突破“只能生成文本”的限制,可以访问实时信息或执行动作。
常见工具包括:
- 搜索引擎。
- 数据库查询。
- 计算器。
- API 调用。
- 文件读写。
- 检索器。
- 自定义业务函数。
3. 为什么工具要有清晰描述
模型需要根据工具名称、参数和描述判断是否调用它。如果工具描述模糊,模型容易乱用或不用。
4. 工具本身也可以 invoke
工具可以单独调用:
search_results = search.invoke("what is the weather in SF")
这有助于先确认工具本身能正常工作,再交给 Agent。
十二、bind_tools
1. 是什么
.bind_tools() 把工具列表绑定到聊天模型,让模型知道有哪些工具可以调用。
model_with_tools = model.bind_tools(tools)
2. 解决什么问题
默认模型不知道你的程序里有哪些工具。绑定后,模型可以在回复中生成工具调用请求。
3. 为什么绑定工具还不等于执行工具
这是初学者最容易混淆的地方。绑定工具后,模型可能返回:
response.tool_calls
但这只是“模型想调用工具”的声明,不代表工具已经被执行。
bind_tools 后的模型:我建议调用 search 工具
Agent:真的去调用 search 工具,并把结果交回模型
十三、tool_calls
1. 是什么
tool_calls 是模型输出中的工具调用请求,包含工具名和参数。
示意:
[
{
"name": "tavily_search_results_json",
"args": {"query": "weather san francisco"},
"id": "..."
}
]
2. 解决什么问题
它把模型的自然语言判断变成结构化动作请求,程序可以据此调用对应工具。
3. 为什么有时 content 为空
当模型决定调用工具时,它可能暂时不输出自然语言答案,而是输出 tool_calls。因为它还需要等待工具结果,才能生成最终回答。
4. 初学者理解
tool_calls 不是最终答案
tool_calls 是模型发出的行动计划
十四、create_react_agent
1. 是什么
create_react_agent 是 LangGraph 提供的高级构造器,用来创建 ReAct 风格 Agent。
from langgraph.prebuilt import create_react_agent
agent_executor = create_react_agent(model, tools)
2. 解决什么问题
它帮你组织 Agent 的循环:
接收用户消息
|
v
模型判断是否调用工具
|
v
如果要调用工具,执行工具
|
v
把工具结果交回模型
|
v
模型继续判断或生成最终答案
3. 为什么传入原始 model 而不是 model_with_tools
教程中特别指出,create_react_agent(model, tools) 会在内部处理工具绑定。因此通常传原始模型和工具列表即可。
4. Agent 返回什么
Agent 返回的是状态,里面通常包含整个消息列表:
response = agent_executor.invoke({
"messages": [HumanMessage(content="hi!")]
})
response["messages"]
它不只是返回最后一句话,因为中间可能包含:
- 用户消息。
- AI 工具调用消息。
- ToolMessage 工具结果。
- AI 最终回答。
十五、无状态 Agent
1. 是什么
默认 Agent 不会记住前一轮对话。每次调用只知道当前传入的消息。
2. 解决什么问题
无状态适合独立任务,比如单次搜索、单次问答。
3. 为什么聊天场景不够用
如果用户先说:
Hi, I'm Bob.
下一轮问:
What's my name?
无状态 Agent 不会知道 Bob,除非你手动传入历史或添加内存。
十六、流式消息 stream
1. 是什么
Agent 可以用 .stream() 流式返回中间步骤。
for chunk in agent_executor.stream(
{"messages": [HumanMessage(content="whats the weather in sf?")]}
):
print(chunk)
2. 解决什么问题
Agent 可能要经历多个步骤,等待完整结果会比较慢。流式消息可以让你看到:
- 模型准备调用工具。
- 工具执行结果。
- 模型最终回答。
3. 为什么重要
对 Agent 来说,中间过程往往比最终答案更重要。流式输出可以帮助调试,也能改善用户体验。
十七、流式事件 astream_events
1. 是什么
.astream_events() 可以更细粒度地监听 Agent 运行事件,比如:
- 链开始。
- 链结束。
- 模型流式 token。
- 工具开始。
- 工具结束。
2. 解决什么问题
.stream() 更像看大步骤,.astream_events() 更像看详细日志。
3. 为什么有用
你可以在前端展示:
正在搜索...
搜索完成
正在生成答案...
也可以把每个 token 实时展示出来。
4. 适用场景
适合需要精细 UI、调试工具调用过程、记录运行日志的 Agent 应用。
十八、MemorySaver 与 Agent 记忆
1. 是什么
MemorySaver 是 LangGraph 的内存检查点工具,用来让 Agent 记住对话状态。
from langgraph.checkpoint.memory import MemorySaver
memory = MemorySaver()
agent_executor = create_react_agent(model, tools, checkpointer=memory)
2. 解决什么问题
它让 Agent 可以跨多次调用保存历史状态。
3. 为什么需要 thread_id
添加 memory 后,调用时要传入 thread_id:
config = {"configurable": {"thread_id": "abc123"}}
thread_id 用来区分不同对话线程。
thread_id = 这个 Agent 对话的会话编号
4. 示例流程
for chunk in agent_executor.stream(
{"messages": [HumanMessage(content="hi im bob!")]},
config,
):
print(chunk)
for chunk in agent_executor.stream(
{"messages": [HumanMessage(content="whats my name?")]},
config,
):
print(chunk)
如果两次使用相同 thread_id,Agent 可以利用历史知道用户叫 Bob。
十九、Agent 与普通链的区别
| 对比项 | 普通链 | Agent |
|---|---|---|
| 执行路径 | 固定 | 动态 |
| 是否调用工具 | 开发者提前写死 | 模型自己决定 |
| 适合任务 | 稳定流程 | 需要判断和行动的任务 |
| 可预测性 | 更强 | 更弱 |
| 调试难度 | 较低 | 较高 |
| 典型场景 | 翻译、摘要、RAG 问答 | 搜索、工具调用、多步任务 |
二十、Agent 什么时候适合用
适合:
- 用户问题可能需要实时搜索。
- 模型需要在多个工具中选择。
- 任务步骤不固定。
- 可能需要多次工具调用。
- 用户希望自然语言驱动操作。
不适合:
- 流程完全固定。
- 必须强可控、强可预测。
- 工具调用成本很高且不能出错。
- 简单一次问答就能完成。
二十一、检索器与 Agent 如何结合
检索器也可以变成工具,交给 Agent 使用。这样 Agent 可以自行决定是否检索。
典型流程:
用户输入
|
v
Agent 判断问题是否需要知识库
|
v
需要时调用检索工具
|
v
读取检索结果
|
v
生成答案
与固定 RAG 链相比:
固定 RAG:每次都检索
Agentic RAG:模型判断是否检索、检索几次、用什么查询检索
二十二、核心心智模型
1. Document 是知识单元
外部知识先变成一个个 Document,每个 Document 都有内容和来源。
2. VectorStore 是语义索引
它把 Document 转成向量并存起来,支持按语义找相关内容。
3. Retriever 是可组合的检索接口
它把“找资料”封装成 Runnable,可以接进 LCEL 和 RAG。
4. Tool 是模型能调用的能力
工具让模型从“只会说”变成“能请求外部动作”。
5. Agent 是动态决策循环
Agent 的核心不是某个工具,而是“模型决定下一步做什么”的循环。
二十三、学习顺序建议
- 先理解
Document。 - 再理解 embedding 如何表示语义。
- 再用
VectorStore做相似度搜索。 - 再把
VectorStore转成Retriever。 - 再把
Retriever接入 RAG。 - 再理解工具
Tool。 - 再学习
bind_tools和tool_calls。 - 再用
create_react_agent创建 Agent。 - 再学习 Agent 的流式输出。
- 最后给 Agent 加
MemorySaver。
二十四、一句话总结
检索器解决的是:
如何从外部知识中找到相关内容
Agent 解决的是:
如何让模型自己决定是否调用工具、调用什么工具、如何继续下一步
一个偏“找资料”,一个偏“做决策”。它们组合起来,就是很多高级 LLM 应用的基础。
更多推荐




所有评论(0)