LangChain Agents 实战:从工具调用到智能决策

前言

这篇是 LangChain 系列的学习笔记,这次整理的是第六章 Agents(智能体)模块。

说到 Agent,这可是整个 LangChain 框架最核心的部分了。前面我们学了 Tools(工具),知道了怎么让大模型"分析"要调用哪个工具。但你有没有发现,每次都要我们手动去检查 function_call、手动调用工具、手动把结果返回给大模型……这也太麻烦了吧?

Agent 就是来解决这个问题的。它能自动完成这整个流程:分析问题 → 选择工具 → 执行工具 → 分析结果 → 决定下一步。而且这个过程可以循环进行,Agent 会一直思考、一直调用工具,直到找到最终答案。

本篇会详细介绍 Agent 的工作原理、传统方式和新方式的使用、如何给 Agent 加上记忆,以及实战案例。看完这篇,你就能让大模型真正"动起来"了。

🏠个人主页:山沐与山


文章目录


一、Agent 是什么?

1.1 一个生动的例子

假设你问 Agent:“美团股价是多少?比 2024 年下跌了多少?”

没有 Agent 的情况(上一篇文章的做法):

  1. 你:调用大模型分析问题
  2. 大模型:返回 function_call,说要调用搜索工具
  3. 你:手动调用搜索工具查美团股价
  4. 你:把结果传回给大模型
  5. 大模型:返回 function_call,说要调用计算工具
  6. 你:手动调用计算工具
  7. 你:把结果传回给大模型
  8. 大模型:生成最终答案

累不累?每一步都要你手动操作。

有 Agent 的情况

result = agent_executor.invoke("美团股价是多少?比2024年下跌了多少?")
print(result)

一行代码,Agent 自动完成上面的所有步骤。这就是 Agent 的威力。

1.2 Agent 的核心组件

一个完整的 Agent 由三部分组成:

Agent = LLM(大脑)+ Tools(工具箱)+ Memory(记忆)
  • LLM:负责思考和决策
  • Tools:负责执行具体操作(搜索、计算、文件操作等)
  • Memory(可选):负责记住对话历史

二、传统方式:initialize_agent

2.1 最简单的例子

from langchain.agents import initialize_agent, AgentType
from langchain.tools import Tool
from langchain_community.tools.tavily_search import TavilySearchResults
from langchain_openai import ChatOpenAI
import os
import dotenv

# 加载环境变量
dotenv.load_dotenv()
os.environ["OPENAI_API_KEY"] = os.getenv("OPENAI_API_KEY1")
os.environ["TAVILY_API_KEY"] = os.getenv("TAVILY_API_KEY")

# 初始化大模型
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)

# 创建搜索工具
search = TavilySearchResults()
search_tool = Tool(
    name="Tavily搜索",
    func=search.run,
    description="用于检索互联网上的信息,特别是获取实时的新闻、天气等时效性信息。输入应该是一个明确的搜索查询。"
)

# 初始化 Agent
agent_executor = initialize_agent(
    tools=[search_tool],
    llm=llm,
    agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION,
    verbose=True  # 打印详细过程
)

# 开始提问
result = agent_executor.invoke("查询长沙天气")
print(result)

运行这段代码,你会看到类似这样的输出:

> Entering new AgentExecutor chain...
我需要查找长沙的天气信息。

Action: Tavily搜索
Action Input: 长沙天气

Observation: [根据搜索结果,长沙今天晴,温度15-25度...]

Thought: 我现在知道答案了。

Final Answer: 长沙今天天气晴朗,温度在15-25度之间...

> Finished chain.

看到没?Agent 自己完成了整个思考过程:

  1. Thought(思考):我需要查天气
  2. Action(行动):使用搜索工具
  3. Action Input(输入):长沙天气
  4. Observation(观察):得到搜索结果
  5. Final Answer(最终答案):整理成人类可读的回答

这就是 ReAct 模式(Reasoning + Acting)的核心思想。

2.2 AgentType 类型

initialize_agent 支持好几种 Agent 类型:

Agent 类型 特点 适用场景
ZERO_SHOT_REACT_DESCRIPTION 零样本学习,显示思考过程 单次任务,需要看清楚 Agent 的思考
OPENAI_FUNCTIONS 基于 OpenAI 的 Function Call 工具名称必须是英文
CONVERSATIONAL_REACT_DESCRIPTION 支持会话历史 多轮对话场景

最常用的是 ZERO_SHOT_REACT_DESCRIPTION,因为它的思考过程清晰,方便调试。

2.3 多工具协作

Agent 真正厉害的地方在于可以同时使用多个工具:

from langchain.tools import Tool

# 定义计算器工具
def simple_calculator(expression: str) -> float:
    """执行简单的数学计算"""
    return eval(expression)

calc_tool = Tool(
    name="Calculator",
    func=simple_calculator,
    description="用于执行数学计算。输入应该是一个数学表达式,例如 '100 * 0.8'"
)

# 创建包含多个工具的 Agent
agent_executor = initialize_agent(
    tools=[search_tool, calc_tool],  # 注意:同时传入两个工具
    llm=llm,
    agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION,
    verbose=True
)

# 提出需要多步骤的问题
result = agent_executor.invoke("美团股价是多少?比2024年下跌了多少?")

Agent 会自动规划:

  1. 先用搜索工具查询美团股价
  2. 再用搜索工具查询 2024 年的股价
  3. 最后用计算器工具算出跌幅

你只需要提问,Agent 会自己决定用哪个工具、用几次。

2.4 一个小坑:工具名称

如果你用 OPENAI_FUNCTIONS 类型,工具名称必须是英文,不能用中文!

# ❌ 错误写法
search_tool = Tool(
    name="Tavily搜索",  # 中文名称在 OPENAI_FUNCTIONS 模式下会报错
    func=search.run,
    description="..."
)

# ✅ 正确写法
search_tool = Tool(
    name="tavily_search",  # 英文名称
    func=search.run,
    description="..."
)

为什么?因为 OpenAI 的 Function Call 规范要求函数名必须匹配 ^[a-zA-Z0-9_-]+$ 这个正则表达式。


三、新方式:create_tool_calling_agent 和 create_react_agent

3.1 为什么要换新 API?

上面的 initialize_agent 已经被 LangChain 标记为"弃用"(deprecated)了。官方推荐使用新的 API,原因是:

  • 新 API 更灵活,可以自定义提示词模板
  • 更容易集成到复杂的工作流中
  • 为未来迁移到 LangGraph 做准备

所以如果你是新项目,直接用新 API 就好。

3.2 create_tool_calling_agent(Function Call 模式)

from langchain.agents import create_tool_calling_agent, AgentExecutor
from langchain_core.prompts import ChatPromptTemplate

# 定义提示词模板
prompt_template = ChatPromptTemplate.from_messages([
    ("system", "你是一个有帮助的助手,可以使用工具来回答问题。"),
    ("human", "{input}"),
    ("placeholder", "{agent_scratchpad}")  # Agent 的工作空间
])

# 创建 Agent
agent = create_tool_calling_agent(
    llm=llm,
    tools=[search_tool],
    prompt=prompt_template
)

# 创建执行器
agent_executor = AgentExecutor(
    agent=agent,
    tools=[search_tool],
    verbose=True
)

# 提问
result = agent_executor.invoke({"input": "查询2025年AI技术趋势"})
print(result)

这种方式的特点是:

  • 使用 ChatPromptTemplate(聊天式模板)
  • 必须包含 {input}{agent_scratchpad} 占位符
  • 工具名称必须是英文
  • 思考过程是隐式的(不会打印 Thought、Action 这些)

3.3 create_react_agent(ReAct 模式)

如果你喜欢看到 Agent 的思考过程,可以用 ReAct 模式:

from langchain.agents import create_react_agent
from langchain import hub

# 使用 LangChain Hub 上的官方模板
prompt_template = hub.pull("hwchase17/react")

# 创建 Agent
agent = create_react_agent(
    llm=llm,
    tools=[search_tool],
    prompt=prompt_template
)

# 创建执行器
agent_executor = AgentExecutor(
    agent=agent,
    tools=[search_tool],
    verbose=True
)

result = agent_executor.invoke({"input": "查询新零售行业发展前景"})

这种方式会显示完整的 Thought → Action → Observation 过程,跟传统方式的 ZERO_SHOT_REACT_DESCRIPTION 效果一样。

3.4 自定义 ReAct 提示词

如果你不想用官方模板,也可以自己写:

from langchain_core.prompts import PromptTemplate

template = """你是一个有帮助的助手。你可以使用以下工具:

{tools}

使用这个格式:

问题:你需要回答的输入问题
思考:你应该思考要做什么
行动:要采取的行动,应该是 [{tool_names}] 之一
行动输入:行动的输入
观察:行动的结果
... (这个 思考/行动/行动输入/观察 可以重复N次)
思考:我现在知道最终答案了
最终答案:对原始输入问题的最终答案

开始!

问题:{input}
思考:{agent_scratchpad}"""

prompt = PromptTemplate.from_template(template)

agent = create_react_agent(
    llm=llm,
    tools=[search_tool],
    prompt=prompt
)

自定义模板的好处是你可以控制 Agent 的"人格"和"行为方式"。比如你可以加上"如果不确定就说不确定"、"回答要简洁"这样的约束。

3.5 两种新方式的对比

特性 create_tool_calling_agent create_react_agent
提示词类型 ChatPromptTemplate PromptTemplate
思考过程 隐式(看不到) 显式(能看到 Thought/Action)
工具名称 必须英文 支持中文
适用场景 生产环境,不需要调试 开发调试,需要看清楚思考过程
性能 稍快(少一些输出) 稍慢(多一些输出)

我个人建议:开发的时候用 create_react_agent,方便调试;上线的时候用 create_tool_calling_agent,更简洁高效。


四、Agent 加上记忆

到目前为止,我们的 Agent 都是"金鱼记忆"——每次对话都忘记之前说过什么。这在多轮对话场景下就不够用了。

4.1 传统方式 + 记忆

from langchain.memory import ConversationBufferMemory

# 创建记忆对象
memory = ConversationBufferMemory(
    return_messages=True,  # 返回消息对象而不是字符串
    memory_key="chat_history"  # 在提示词中的变量名
)

# 初始化 Agent(注意 agent 类型变了)
agent_executor = initialize_agent(
    tools=[search_tool],
    llm=llm,
    agent=AgentType.CONVERSATIONAL_REACT_DESCRIPTION,  # 支持会话的类型
    memory=memory,
    verbose=True
)

# 第一轮对话
agent_executor.invoke({"input": "查询长沙天气"})

# 第二轮对话
agent_executor.invoke({"input": "上海呢?"})  # Agent 知道"呢"指的是查天气

看到没?第二次提问只说了"上海呢?",Agent 能理解这是延续上一个问题,要查上海的天气。

这里的关键是:

  1. 使用 ConversationBufferMemory 存储历史
  2. Agent 类型要用 CONVERSATIONAL_REACT_DESCRIPTION(带 CONVERSATIONAL 前缀)
  3. 每次调用 Agent 时,它会自动从 memory 里读取历史

4.2 新方式 + 记忆(Function Call 模式)

from langchain.memory import ConversationBufferMemory
from langchain.agents import create_tool_calling_agent, AgentExecutor
from langchain_core.prompts import ChatPromptTemplate

# 创建记忆
memory = ConversationBufferMemory(
    return_messages=True,
    memory_key="chat_history"
)

# 定义提示词(注意增加了 chat_history)
prompt_template = ChatPromptTemplate.from_messages([
    ("system", "你是一个有用的助手,可以使用工具回答问题。"),
    ("placeholder", "{chat_history}"),  # 对话历史
    ("human", "{input}"),
    ("placeholder", "{agent_scratchpad}")
])

# 创建 Agent
agent = create_tool_calling_agent(
    llm=llm,
    tools=[search_tool],
    prompt=prompt_template
)

# 创建执行器(传入 memory)
agent_executor = AgentExecutor(
    agent=agent,
    tools=[search_tool],
    memory=memory,
    verbose=True
)

# 多轮对话
agent_executor.invoke({"input": "查询自动化行业发展前景"})
agent_executor.invoke({"input": "新零售呢?"})

这里的关键是在 ChatPromptTemplate 里加了一个 ("placeholder", "{chat_history}"),用来插入对话历史。

4.3 新方式 + 记忆(ReAct 模式)

ReAct 模式也支持记忆,可以用官方模板:

from langchain import hub

# 使用支持会话历史的 ReAct 模板
prompt_template = hub.pull("hwchase17/react-chat")

agent = create_react_agent(
    llm=llm,
    tools=[search_tool],
    prompt=prompt_template
)

agent_executor = AgentExecutor(
    agent=agent,
    tools=[search_tool],
    memory=memory,
    verbose=True,
    handle_parsing_errors=True  # 处理解析错误
)

agent_executor.invoke({"input": "查询AI行业趋势"})
agent_executor.invoke({"input": "那机器学习呢?"})

注意这里加了 handle_parsing_errors=True,因为 ReAct 模式需要解析 Thought/Action 这些结构化输出,有时候大模型会输出格式不对,加上这个参数可以优雅地处理错误。

4.4 记忆的选择

LangChain 提供了多种记忆类型:

记忆类型 特点 适用场景
ConversationBufferMemory 存储完整对话历史 短对话
ConversationBufferWindowMemory 只保留最近 K 轮 长对话,控制成本
ConversationSummaryMemory 用 LLM 总结历史 长对话,需要完整上下文

在 Agent 场景下,我一般用 ConversationBufferMemory 或 ConversationBufferWindowMemory。


五、实战案例

来几个真实的例子感受一下 Agent 的威力。

5.1 股票分析助手

# 准备两个工具:搜索 + 计算
search_tool = Tool(
    name="search",
    func=search.run,
    description="搜索互联网信息,特别是股票价格、新闻等"
)

calc_tool = Tool(
    name="calculator",
    func=simple_calculator,
    description="执行数学计算,输入应该是数学表达式"
)

# 创建 Agent
agent_executor = initialize_agent(
    tools=[search_tool, calc_tool],
    llm=llm,
    agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION,
    verbose=True
)

# 复杂的分析任务
result = agent_executor.invoke(
    "比亚迪和美团的股价各是多少?哪个比2024年涨幅更大?"
)

Agent 会自动:

  1. 搜索比亚迪股价(当前和 2024 年)
  2. 搜索美团股价(当前和 2024 年)
  3. 计算比亚迪涨幅
  4. 计算美团涨幅
  5. 比较两者,给出答案

你不需要告诉它怎么做,它自己会规划。

5.2 多轮咨询机器人

memory = ConversationBufferMemory(
    return_messages=True,
    memory_key="chat_history"
)

agent_executor = initialize_agent(
    tools=[search_tool],
    llm=llm,
    agent=AgentType.CONVERSATIONAL_REACT_DESCRIPTION,
    memory=memory,
    verbose=True
)

# 第一轮:查询天气
agent_executor.invoke({"input": "长沙天气怎么样?"})

# 第二轮:追问细节
agent_executor.invoke({"input": "那适合出门吗?"})

# 第三轮:换个城市
agent_executor.invoke({"input": "上海呢?"})

Agent 能理解:

  • “那适合出门吗?” → 基于长沙天气判断
  • “上海呢?” → 查询上海天气

这就是有记忆的威力。

5.3 新闻搜索助手

result = agent_executor.invoke({
    "input": "查询长沙和芷江最近有什么新闻"
})

Agent 会:

  1. 先搜索长沙的新闻
  2. 再搜索芷江的新闻
  3. 把两个结果整合成一段回答

它知道要分两次搜索,而不是一次搜索"长沙和芷江"。


六、Agent 的工作原理

让我用一个完整的执行过程来说明 Agent 是怎么运行的。

6.1 完整的执行流程

假设用户问:“美团股价比去年跌了多少?”

[1] 用户输入
    ↓
[2] Agent 接收任务
    ↓
[3] LLM 思考:
    "我需要知道美团现在的股价和去年的股价,然后计算差值"
    ↓
[4] LLM 决策:
    "首先要搜索美团现在的股价"
    ↓
[5] 选择工具:search_tool
    ↓
[6] 执行工具:search.run("美团股价")
    ↓
[7] 观察结果:
    "美团股价为 180 元"
    ↓
[8] LLM 再次思考:
    "现在知道了当前股价,还需要去年的股价"
    ↓
[9] 选择工具:search_tool
    ↓
[10] 执行工具:search.run("美团2024年股价")
    ↓
[11] 观察结果:
    "2024年美团股价为 200 元"
    ↓
[12] LLM 再次思考:
    "现在两个数据都有了,需要计算差值"
    ↓
[13] 选择工具:calculator_tool
    ↓
[14] 执行工具:calculator.run("180 - 200")
    ↓
[15] 观察结果:
    "-20"
    ↓
[16] LLM 最终思考:
    "我现在有足够信息生成答案了"
    ↓
[17] 生成最终答案:
    "美团股价比去年下跌了20元,跌幅为10%"
    ↓
[18] 返回给用户

整个过程完全自动,Agent 会不断循环"思考 → 行动 → 观察",直到找到答案。

6.2 关键参数

verbose=True:打印详细过程,方便调试

agent_executor = AgentExecutor(
    agent=agent,
    tools=tools,
    verbose=True  # 开启详细日志
)

handle_parsing_errors=True:处理解析错误

agent_executor = AgentExecutor(
    agent=agent,
    tools=tools,
    handle_parsing_errors=True  # 优雅处理错误
)

有时候 LLM 输出的格式不规范,这个参数可以让 Agent 重试而不是直接报错。

max_iterations:限制最大循环次数

agent_executor = AgentExecutor(
    agent=agent,
    tools=tools,
    max_iterations=10  # 最多循环10次
)

防止 Agent 陷入死循环,消耗太多 Token。

early_stopping_method:提前停止策略

agent_executor = AgentExecutor(
    agent=agent,
    tools=tools,
    early_stopping_method="generate"  # 达到 max_iterations 时生成答案
)

到达最大循环次数时,让 Agent 生成一个"目前能给出的答案",而不是报错。


七、常见问题

学 Agent 过程中,这几个问题经常被问到:

7.1 Agent 一直调用同一个工具,陷入循环怎么办?

问题原因:工具的 description 写得不清楚,LLM 不知道工具是干什么的。

解决方案:写清楚工具的用途、输入格式、输出格式。

# ❌ 不好的写法
Tool(
    name="search",
    func=search.run,
    description="搜索"  # 太简单了
)

# ✅ 好的写法
Tool(
    name="search",
    func=search.run,
    description="用于在互联网上搜索信息。输入应该是一个明确的搜索查询字符串,例如'北京天气'、'比亚迪股价'。返回相关的搜索结果摘要。"
)

7.2 两个工具名字一样,Agent 该调用哪个?

问题原因:工具名称冲突,Agent 无法区分。

解决方案:确保每个工具的 name 是唯一的。

# ❌ 错误
tools = [
    Tool(name="search", func=search1.run, description="..."),
    Tool(name="search", func=search2.run, description="...")  # 名字重复了!
]

# ✅ 正确
tools = [
    Tool(name="web_search", func=search1.run, description="..."),
    Tool(name="image_search", func=search2.run, description="...")
]

7.3 Agent 执行一次任务消耗太多 Token 怎么办?

问题原因

  • 使用了 ConversationBufferMemory,历史对话越来越长
  • Agent 循环次数太多

解决方案

  1. 换记忆类型:改用 ConversationBufferWindowMemory,只保留最近几轮对话
  2. 限制循环次数:设置 max_iterations 参数限制最大循环次数
  3. 换更便宜的模型:比如用 gpt-4o-mini 替代 gpt-4

7.4 看到 API 弃用警告怎么办?

警告内容

DeprecationWarning: initialize_agent is deprecated.
Use create_tool_calling_agent or create_react_agent instead.

不用慌:代码还能正常运行,只是官方推荐使用新的 API。

建议

  • 如果是老项目,可以继续使用,暂不影响
  • 如果是新项目,直接用 create_tool_calling_agent 或 create_react_agent
  • 如果要迁移,按照第三章的新方式重写即可

八、传统 vs 新方式对比

最后总结一下传统方式和新方式的区别:

特性 传统方式 新方式
初始化函数 initialize_agent() create_tool_calling_agent() / create_react_agent()
Agent 类型 AgentType.XXX 枚举 通过不同的 create 函数区分
提示词 内置,无法自定义 完全自定义,使用 PromptTemplate
状态 已弃用 官方推荐
灵活性
学习曲线 简单 稍复杂
适用场景 快速原型、学习 生产环境、复杂需求

我的建议:

  • 如果你是刚接触 LangChain,先用传统方式理解概念
  • 如果你要做正式项目,直接用新方式
  • 如果你要迁移老代码,可以慢慢从传统方式改成新方式

九、总结

这篇文章介绍了 LangChain 的 Agents 模块,核心内容包括:

  1. Agent 的本质:Agent = LLM(思考)+ Tools(执行)+ Memory(记忆),本质上就是一个自动循环:分析问题 → 选择工具 → 执行工具 → 分析结果 → 决定下一步

  2. 两套 API:传统方式(initialize_agent)已弃用但简单易学;新方式(create_tool_calling_agent / create_react_agent)是官方推荐

  3. 两种模式:Function Call 模式(隐式思考,适合生产环境)和 ReAct 模式(显式思考过程,适合开发调试)

  4. 记忆管理:给 Agent 加上 Memory,就能进行多轮对话

  5. 实战技巧:写清楚工具描述、合理设置 max_iterations、打开 verbose 调试、处理解析错误

关键要点总结表

概念 说明 适用场景 注意事项
initialize_agent 传统方式,简单直接 快速原型、学习 已弃用,但仍可用
create_tool_calling_agent 新方式 Function Call 模式 生产环境 工具名必须英文
create_react_agent 新方式 ReAct 模式 开发调试 可以看到思考过程
ConversationBufferMemory 存储完整对话历史 短对话 会消耗较多 Token
verbose=True 打印详细过程 开发调试 生产环境建议关闭

下一步:到这里,LangChain 的核心模块我们就都学完了:Model I/O、Chains、Memory、Tools、Agents。接下来的内容会更高级,比如 LangGraph(Agent 的升级版)、向量数据库、RAG 应用等等。


热门专栏推荐

等等等还有许多优秀的合集在主页等着大家的光顾,感谢大家的支持

文章到这里就结束了,如果有什么疑问的地方请指出,诸佬们一起来评论区一起讨论😊
希望能和诸佬们一起努力,今后我们一起观看感谢您的阅读🙏
如果帮助到您不妨3连支持一下,创造不易您们的支持是我的动力🌟

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐