本文译者为 360 奇舞团前端开发工程师
原文标题:理解 LangChain 智能体:
create_react_agent与create_tool_calling_agent原文作者:Anil Goyal 原文地址:medium.com/@anil.goyal...
当我们使用 LangChain 构建 AI 智能体时,首先要做的是选择正确的智能体架构。 目前常用的2种架构是create_react_agent和create_tool_calling_agent。两者都可以让AI使用外部工具,但由于它们的运作方式不同,因此适合的场景也有区别。
本文将探讨create_react_agent和create_tool_calling_agent有哪些区别,如何向大语言模型发送提示词,期望从模型获得哪种响应格式。它们的底层工作原理是什么,分别适合在哪种场景下使用。
什么是Agent?
在深入细节之前,让我们先了解一下什么是 Agent。Agent系统的工作流程如下:
- 推理---对问题和任务进行思考与分析;
- 行动---调用工具或外部服务处理问题;
- 观察---查看获取的结果;
- 迭代---不断重复上述过程,直到达到预期的目标。
不同类型智能体之间的关键区别在于:它们与 大语言模型 的交互方式不同,也就是它们在执行推理 与工具选择过程时,与模型之间的通信方式存在差异。
核心区别:提示词
无论是 create_react_agent 还是 create_tool_calling_agent,它们都会让模型自主决定使用哪些工具**、**何时使用。而两者之间最关键的区别在于:它们如何向 LLM 发送提示词,以及期望 LLM 返回的响应格式。
可以理解为它们是在用两种不同的"语言"与模型沟通:
- ReAct Agent: "请一步一步地思考,并用文字告诉我你的推理过程。"
- Tool Calling Agent: "这是函数定义,请直接用结构化数据调用它们。"
于是,LLM 会根据所接受的提示方式来回应,从而形成完全不同的交互模式。
一、create_react_agent:基于文本推理
什么是 ReAct?
ReAct 是 "Reasoning + Acting"(推理 + 行动)的缩写。它是一种让智能体通过"文本化思考循环"来展示自身思维过程的范式。这种循环的结构通常包括以下几个步骤:
- Thought(思考): 智能体思考下一步该做什么,分析问题或任务。
- Action(行动): 智能体决定使用哪个工具、以及如何使用它。
- Observation(观察): 智能体查看执行后的结果,并理解输出内容。
- Repeat(重复): 这一循环不断重复,直到任务完成为止。
以下是 ReAct agent实际上会发送给模型的内容:
csharp
#(1)ReAct agent向大语言模型发送如下内容:
"你可以使用这些工具:[add, multiply]
问题:先计算 5+3,然后将结果乘以 2
思考:"
#(2)模型回复:
"我需要先把 5 和 3 相加,然后将结果乘以 2
行动:add
行动输入(Action Input):{"a": 5, "b": 3}"
#(3)智能体调用 add(5, 3),得到结果8。把这个结果加入提示词中,再将更新后的提示词发送给 模型:
"你可以使用这些工具:[add, multiply]
问题:先计算 5+3,然后将结果乘以 2
思考:我需要先把 5 和 3 相加,然后将结果乘以 2
行动:add
行动输入:{"a": 5, "b": 3}。
观察:8
思考:"
#(4)模型回复:"现在我得到了 8,我需要将它乘以 2。
"行动:multiply
行动输入:{"a": 8, "b": 2}"
# (5)智能体执行 multiply(8, 2),得到结果 16。然后再次将该结果加入提示词,形成最终的提示词:
"你可以使用这些工具:[add, multiply]。
问题:先计算 5+3,然后将结果乘以 2
思考:我需要先把 5 和 3 相加,然后将结果乘以 2
行动:add。行动输入:{"a": 5, "b": 3}
观察:8
思考:现在我得到了 8,我需要将它乘以 2
行动:multiply
行动输入:{"a": 8, "b": 2}
观察:16
思考:"
#(6)LLM 回复:"完美!我现在得到了最终答案。最终答案:(5+3) × 2 的结果是 16"
可以看到,ReAct agent 不是通过一轮对话就回答了问题,也就是说模型并不会一次性计划好所有步骤。相反,agent与模型之间会进行多轮对话,每轮对话结束后都会调用不同的工具。
正是这种迭代式的特性, 使得 ReAct agent非常透明且易于调试 ---你可以清楚地看到推理过程是如何逐步展开的!
create_react_agent如何工作
ini
# 导入 LangChain 中的智能体创建方法
from langchain.agents import AgentExecutor, create_react_agent
# 导入用于创建自定义提示模板的类
from langchain_core.prompts import PromptTemplate
# 导入 MultiServer MCP 客户端,用于连接和调用外部工具服务
from langchain_mcp_adapters.client import MultiServerMCPClient
# 导入 Ollama LLM 语言模型
from langchain_ollama import OllamaLLM
from langchain import hub
import asyncio
import os
os.environ["USER_AGENT"] = "my-langchain-agent/1.0"
async def main():
# 创建 MultiServerMCPClient 客户端,用于管理外部工具(这里是 math server)
client = MultiServerMCPClient({
"math": {
"command": "python",
"args": ["mathserver.py"],
"transport": "stdio",
}
})
# 获取外部工具列表
tools = await client.get_tools()
# 创建 Ollama LLM 实例,选择使用 "mistral" 模型
llm = OllamaLLM(model="mistral")
# 定义自定义 ReAct 提示模板
template = """尽可能回答以下问题。你可以访问这些工具:
{tools}
使用如下格式:
Question: 你必须回答的问题
Thought: 考虑该怎么做
Action: 采取行动, 应该是 [{tool_names}] 中之一
Action Input: 输入必须是有效的JSON格式, 例如 {{"a": 5, "b": 3}}
Observation: 观察结果
... (这一 Thought/Action/Action Input/Observation 可以重复N次)
Thought: 现在我知道了最终答案
Final Answer: 获得最终答案
IMPORTANT: 输入始终要采用JSON格式。针对数学运算,使用 {{"a": number1, "b": number2}}.
开始!
Question: {input}
Thought:{agent_scratchpad}"""
prompt = PromptTemplate.from_template(template)
agent = create_react_agent(llm=llm, tools=tools, prompt=prompt)
agent_executor = AgentExecutor(
agent=agent,
tools=tools,
verbose=True,
handle_parsing_errors=True
)
result = await agent_executor.ainvoke({"input": "What's 5 + 3, then multiply by 2"})
print("\nResult:")
print(result["output"])
if __name__ == "__main__":
asyncio.run(main())
当运行代码时,控制台会打印类似如下输出:
vbnet
Entering new AgentExecutor chain...
我需要先把两个数字相加,然后再将结果乘以 2。
Action: ["add", {"a": 5, "b": 3}]
Observation: {"result": 8}
Thought: 既然我已经得到了和,我将把它乘以 2。
Action: ["multiply", {"a": 8, "b": 2}]
Observation: {"result": 16}
Thought: 现在我知道最终答案了。
Final Answer: 结果是 16
ReAct Agents的关键特征
- 基于文本的通信:使用自然语言进行推理和交流。
- 透明的推理:可以清楚地看到智能体的思考过程。
- 模型无关:可以与任何文本生成模型配合使用。
- 易于调试: 决策过程容易追踪,便于查找问题。
- 详细输出:显示所有推理步骤,从思考到行动再到观察。
- 字符串参数:工具参数通常以字符串形式传递。
二、create_tool_calling_agent:基于函数调用
工具调用(也称为函数调用)利用了现代大型语言模型的原生能力,可以直接调用函数。与基于文本的推理不同,模型知道如何使用结构化数据调用工具。
工具调用提示策略(Tool Calling Prompting Strategy)
工具调用agent的效率更高,因为它可以在一次与模型的交互中进行多次工具调用,相比于 ReAct agent,以更少的交互完成整个流程。
以下是一个工具调用agent向模型发送的内容示例:
makefile
# 工具调用消息
messages = [
{
"role": "system",
"content": "你可以调用数学工具。"
},
{
"role": "user",
"content": "什么是 5 + 3?"
}
]
# 提供给模型的Tool格式
tools = [
{
"type": "function",
"function": {
"name": "add",
"description": "把两个数字相加",
"parameters": {
"type": "object",
"properties": {
"a": {"type": "integer"},
"b": {"type": "integer"}
},
"required": ["a", "b"]
}
}
}
]
# 模型用结构化的FUNCTION CALL响应:
{
"tool_calls": [
{
"id": "call_123",
"type": "function",
"function": {
"name": "add",
"arguments": '{"a": 5, "b": 3}'
}
}
]
}
create_tool_calling_agent如何工作
ini
from langchain.agents import AgentExecutor, create_tool_calling_agent
from langchain_groq import ChatGroq
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_mcp_adapters.client import MultiServerMCPClient
import asyncio
import os
os.environ["USER_AGENT"] = "my-langchain-agent/1.0"
os.environ["GROQ_API_KEY"] = "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"
async def main():
client = MultiServerMCPClient({
"math": {
"command": "python",
"args": ["mathserver.py"],
"transport": "stdio",
}
})
# 获取tools
tools = await client.get_tools()
llm = ChatGroq(
model="llama3-8b-8192",
temperature=0
)
# 为tool calling agent创建正确的提示词模板
prompt = ChatPromptTemplate.from_messages([
("system", "你是一个有用的助手,可以访问搜索tools。对于有关 DASA 的问题,请使用 DASA_search 工具;对于其他查询,请使用通用的 search 工具。"),
("human", "{input}"),
MessagesPlaceholder("agent_scratchpad"),
])
agent = create_tool_calling_agent(llm=llm, tools=tools, prompt=prompt)
agent_executor = AgentExecutor(
agent=agent,
tools=tools,
verbose=True,
handle_parsing_errors=True
)
# 现在我们可以使用agent回答问题了。
result = await agent_executor.ainvoke({"input": "What's 5 + 3, then multiply by 2"})
print("\nResult:")
print(result["output"])
if __name__ == "__main__":
asyncio.run(main())
当运行代码时,控制台会打印类似如下输出:
css
> Entering new AgentExecutor chain...
Invoking: 对 `{'a': 5, 'b': 3}` 执行相加操作
Invoking: 对 `{'a': 8, 'b': 2}` 执行相乘操作
(5 + 3) × 2 的结果是 16.
> Finished chain.
Tool Calling Agents的关键特征
-
**基于函数的通信:**使用结构化的函数调用方式进行通信。
-
**隐式推理:**决策过程在模型内部完成,不需要显式地展示推理步骤。
-
**依赖模型:**需要模型本身支持原生的工具调用功能。
-
**输出简洁:**输出中不会显示模型的推理过程,只返回最终结果或结构化调用。
-
**高效执行:**执行速度更快,使用的 token 更少。
-
结构化参数: 始终以正确的 JSON 或字典格式传递参数。
上述例子中使用的MCP server如下:
python
"""
使用 FastMCP 的Math Server
这是一个简单的 MCP 服务器,用于提供基本的数学运算功能。
"""
from fastmcp import FastMCP
import math
# 创建 MCP server 实例
mcp = FastMCP("Math Server")
@mcp.tool()
def add(a: float, b: float) -> float:
"""将两个数字相加"""
return a + b
@mcp.tool()
def subtract(a: float, b: float) -> float:
"""计算两个数字的差值."""
return a - b
@mcp.tool()
def multiply(a: float, b: float) -> float:
"""将两个数字相乘"""
return a * b
@mcp.tool()
def divide(a: float, b: float) -> float:
"""用第二个数字除以第一个数字"""
if b == 0:
raise ValueError("不能用0做除数")
return a / b
if __name__ == "__main__":
# 运行server
mcp.run()
三、注意事项:
如果你查看上面的 Python 代码,会发现我在 ReAct agent 中使用了 Mistral 模型 ,而在 Tool Calling Agent 中使用了 ChatGroq 模型。
原因如下:
- Mistral 擅长自然语言推理和基于文本的思维模式,因此非常适合用于 ReAct agent。
- 相反,ChatGroq 具备原生的函数调用(function calling)支持,并且能够理解 JSON schema ,因此非常适合于 Tool Calling Agent。