一. 工具的完整运行流程(Agent 调用工具)
- 用户提问
- LLM 判断:这件事我自己回答不了,需要调用哪个工具、传入什么参数
- Agent 把结构化的工具调用请求发出去
- 工具执行真实操作(联网搜索、运行代码、查数据库),拿到结果
- 把工具返回结果塞回大模型上下文
- LLM 结合工具输出整理答案返回给用户
(React框架)可以循环多轮:思考→调用工具→拿到结果→再思考→再调用工具,直到任务完成。
二.工具的实现方式
@tool 装饰器
最简单,装饰普通 Python 函数,自动提取参数 schema。
from langchain_core.tools import tool
@tool
def multiply(a: int, b: int) -> int:
"""将两个数字相乘
Args:
a: 第一个整数
b: 第二个整数
"""
return a * b
# 查看工具属性
print(multiply.name) # multiply
print(multiply.description) # 工具描述,给LLM看!非常关键
print(multiply.args) # 参数json schema,LLM要知道传什么参数
⚠️重点:
- docstring 必须写清楚用途、参数,LLM 全靠 description 判断要不要调用这个工具;描述写烂,agent 就不会用或者乱调用。
- 函数类型注解不能省,用来生成工具入参 schema。
三.把工具喂给 Agent:完整可运行示例
Function Calling Agent
现在主流是
create_openai_tools_agent,专门适配 OpenAI Function‑call(tool call)。Agent 不是直接跑 LLM,是:LLM + Prompt 模板 + 工具列表 → 生成 Agent,再用 AgentExecutor 做循环执行。
from langchain_openai import ChatOpenAI
from langchain_core.tools import tool
from langchain.agents import create_openai_tools_agent, AgentExecutor
from langchain_core.prompts import ChatPromptTemplate
# 1. 定义工具
@tool
def multiply(a: int, b: int) -> int:
"""两个整数相乘
Args:
a: 乘数1
b: 乘数2
"""
return a * b
@tool
def add(a:int, b:int) -> int:
"""两个整数相加"""
return a + b
tools = [multiply, add]
# 2. LLM,必须支持tool_call(gpt‑3.5‑turbo/gpt‑4)
llm = ChatOpenAI(model="gpt-3.5-turbo", api_key="xxx")
# 3. 构建agent使用的prompt模板
prompt = ChatPromptTemplate.from_messages([
("system", "你是一个会使用工具的助手,遇到计算就调用工具,不要自己口算。"),
("user", "{input}"),
("placeholder", "{agent_scratchpad}"), # !必须要有,存放agent中间思考、工具调用记录
])
# 4. 创建agent:核心!把llm、prompt、tools传进去
agent = create_openai_tools_agent(llm, tools, prompt)
# 5. AgentExecutor:执行器,负责循环:调用llm→解析工具→跑tool→回写结果,直到输出最终答案
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)
# 6. 调用agent
res = agent_executor.invoke({"input": "3乘以5再加10等于多少"})
print(res["output"])
注意两处传 tools:
-
create_openai_tools_agent(llm, tools, prompt):给 LLM,让它知道有哪些工具,生成 tool_call; -
AgentExecutor(agent=agent, tools=tools):执行器拿到 tool 列表,当 LLM 输出工具调用后,根据工具名字找到对应的 tool 对象,执行 _run。👉 两处都需要传入 tools,缺一不可。
🚩Agent 内部完整执行链路拆解(verbose=True 可以看到每一步)
输入问题:3乘以5再加10等于多少
Step1:组装 prompt 上下文
把:system 提示词、用户问题、agent_scratchpad(初始为空),连同工具 schema 列表一起喂给 LLM。
OpenAI 接口会把 tools 数组作为单独参数传给大模型,不是塞到文本 prompt 里面。
Step2:LLM 输出 ToolCall(工具调用)
LLM 思考:需要调用 multiply (3,5),输出结构化的 tool_call,不是自然语言。
json
{
"name":"multiply",
"args":{"a":3,"b":5}
}
Step3:Agent 解析 LLM 输出
create_openai_tools_agent 的输出解析器解析 LLM 返回,识别出要调用工具,生成AgentAction对象:
AgentAction:工具名、参数、思考日志。
Step4:AgentExecutor 执行工具
Executor 拿到 AgentAction,遍历传入的tools列表,匹配tool.name,找到对应 tool 对象,调用tool._run(**args),拿到工具返回结果。
如果工具抛异常,Executor 会捕获,把错误信息放回 scratchpad,回传给 LLM,让 LLM 重试。
Step5:把工具执行结果写回 agent_scratchpad
将「调用了什么工具、传入什么参数、返回结果是什么」作为消息追加到上下文。
Step6:循环回到 LLM
带着工具返回结果再次调用 LLM。
例子:multiply 返回 15,现在 LLM 看到结果 15,继续调用 add (15,10)。
Step7:LLM 判断不需要工具,输出最终答案
不再生成 tool_call,输出自然语言回答,循环终止,返回结果。
verbose=True打印出来日志就是这一整套循环。
ReAct Agent
create_react_agent:ReAct 范式,纯文本提示词驱动,不是 OpenAI native tool‑call。LLM 输出文本格式的思考、Action、Action Input,再由输出解析器去抠出工具调用。create_openai_tools_agent:OpenAI 原生 tool_call,结构化消息,不是 ReAct。
ReAct 的核心:Thought → Action → Action Input → Observation 循环。
要求模型输出特定格式文本,解析器去解析文本拿到工具调用,容错比原生 tool‑call 差,但是兼容所有大模型,不限于 OpenAI 。
完整可运行代码
from langchain_openai import ChatOpenAI
from langchain_core.tools import tool
from langchain.agents import create_react_agent, AgentExecutor
from langchain_core.prompts import PromptTemplate
# ---------------------- 1. 定义自定义工具 ----------------------
@tool
def calculator_add(a: int, b: int) -> int:
"""计算两个整数相加。
Args:
a: 第一个整数
b: 第二个整数
"""
return a + b
@tool
def calculator_mul(a: int, b: int) -> int:
"""计算两个整数相乘。
Args:
a: 乘数a
b: 乘数b
"""
return a * b
tools = [calculator_add, calculator_mul]
# ---------------------- 2. LLM 实例 ----------------------
llm = ChatOpenAI(
model="gpt-3.5-turbo",
api_key="sk-xxx",
temperature=0
)
# ---------------------- 3. ReAct Prompt模板(关键!) ----------------------
# ReAct 必须使用这个特定格式的prompt,告诉模型输出Thought/Action/Action Input/Observation
react_prompt = PromptTemplate.from_template("""
Answer the following question as best you can.
You have access to the following tools:
{tools}
Tool names: {tool_names}
Use the following format strictly:
Thought: you should always think about what to do
Action: the action to take, must be one of [{tool_names}]
Action Input: the input arguments for the tool, json format
Observation: the result of the tool
... (this Thought/Action/Action Input/Observation can repeat N times)
Thought: I now know the final answer
Final Answer: the final answer to the original input question
Begin!
Question: {input}
{agent_scratchpad}
""")
# ---------------------- 4. 创建 ReAct Agent & AgentExecutor ----------------------
# create_react_agent:返回原始agent(runnable),只输出AgentAction/AgentFinish
react_agent = create_react_agent(llm, tools, react_prompt)
# AgentExecutor 负责循环、解析、调用工具、维护scratchpad
agent_executor = AgentExecutor(
agent=react_agent,
tools=tools,
verbose=True, # 打印完整ReAct思考链路
handle_parsing_errors=True, # 解析失败时把错误丢回LLM重试,非常重要
max_iterations=5, # 最大循环轮次,防止死循环
)
# ---------------------- 5. 执行 ----------------------
if __name__ == "__main__":
res = agent_executor.invoke({
"input": "计算 (100 + 200) * 2 等于多少"
})
print("\n====最终输出====")
print(res["output"])
📖 verbose=True 打印出来的完整 ReAct 链路
> Entering new AgentExecutor chain...
Thought: I need to calculate (100+200)*2, first add 100 and 200.
Action: calculator_add
Action Input: {"a":100,"b":200}
Observation: 300
Thought: Now multiply 300 by 2
Action: calculator_mul
Action Input: {"a":300,"b":2}
Observation: 600
Thought: I now know the final answer
Final Answer: 600
> Finished chain.
====最终输出====
600
内部流程拆解(ReAct 完整链路)
- 把
tools、tool_names、用户问题、空的agent_scratchpad送入 prompt。 - LLM 输出文本 ,严格按照模板输出:
Thought / Action / Action Input。 ReActSingleInputOutputParser解析文本,提取工具名、参数,生成AgentAction。- AgentExecutor 执行对应 tool,拿到结果,作为
Observation。 - 将
Thought/Action/ActionInput/Observation追加到agent_scratchpad。 - 把完整 scratchpad 再次塞回 prompt,交给 LLM 继续思考。
- LLM 输出
Final Answer:→ 解析为AgentFinish,循环结束。
agent_scratchpad在 ReAct 里面就是一长段拼接的文本,不是消息数组,和 OpenAI‑tools‑agent 不一样。