LangChain 检索器与 Agent 知识点详解

本文档整理自 LangChain 中文网两篇教程:

本文只保留知识点,不展开环境安装、API Key、Notebook 等准备内容。适合在已经理解 ChatModelPromptTemplateLCELRunnable 的基础上继续学习。

一、整体关系

检索器和 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 链。RetrieverRunnable,可以使用:

  • .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

有些任务不是一次模型调用就能完成:

用户:我所在城市今天的天气怎么样?

模型需要:

  1. 理解用户要查天气。
  2. 知道要调用搜索或天气工具。
  3. 构造查询参数。
  4. 读取工具结果。
  5. 总结成人类可读回答。

这就是 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 的核心不是某个工具,而是“模型决定下一步做什么”的循环。

二十三、学习顺序建议

  1. 先理解 Document
  2. 再理解 embedding 如何表示语义。
  3. 再用 VectorStore 做相似度搜索。
  4. 再把 VectorStore 转成 Retriever
  5. 再把 Retriever 接入 RAG。
  6. 再理解工具 Tool
  7. 再学习 bind_toolstool_calls
  8. 再用 create_react_agent 创建 Agent。
  9. 再学习 Agent 的流式输出。
  10. 最后给 Agent 加 MemorySaver

二十四、一句话总结

检索器解决的是:

如何从外部知识中找到相关内容

Agent 解决的是:

如何让模型自己决定是否调用工具、调用什么工具、如何继续下一步

一个偏“找资料”,一个偏“做决策”。它们组合起来,就是很多高级 LLM 应用的基础。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐