# 基于工具调用的智能体设计与实现
# 01. 工具调用智能体
基于 ReACT 架构的智能体会将 tools(工具描述) 、 agent_scratchpad(智能体草稿) 、 工具结果 、 推理 等内容全部放到同一个 prompt 中,并通过提取 LLM的规范输出 来决定下一步的操作,这种模式会随着 LLM 输出的随机性,不同 LLM 性能的差异让程序变得异常脆弱。
而且 ReACT 架构早期设计之初是针对 LLM(文本补全模型) 进行设计的,即传入一段话,让 LLM 补全其后续的文本,随着 LLM 的发展,消息设计更友好、结构化输出更稳定的函数调用、性能更强大的 ChatModel 发布了,可以考虑将 ReACT 迁移到基于 聊天消息 + 工具调用 的架构上,思想不变,但是使用更稳定的 消息列表 + 工具调用 。
迁移流程图如下:

在上述的 工具调用智能体Prompt 中, 输出规范 会通过检测 LLM 是输出 文本内容 还是 工具调用参数 来判断下一步是什么,这样性能更加稳定,而且对于绝大部分 LLM 来说, 工具调用 支持一次性调用生成多个工具的参数,性能会更强。
在 LangChain 中,其实也为 基于工具调用的Agent 封装了一个快速创建的方法 create_tool_calling_agent() 和 预设Prompt 。
LangChain hub 工具调用 Prompt 链接: https://smith.langchain.com/hub/hwchase17/openai-tools-agent
基于工具调用的智能体文档: https://python.langchain.com/v0.1/docs/modules/agents/agent_types/tool_calling/
# 02. 实现示例
在 LangChain 中,要实现 工具调用Agent 其实也非常简单,步骤其实和 ReACT-Agent 一模一样,创建好工具列表、Prompt、LLM(支持工具调用),然后使用 create_tool_calling_agent() 创建智能体,接下来创建智能体执行者完成包装即可。
例如实现一个可以根据用户输入实现自主选择 联网搜索 + 文生图 的智能体,示例代码如下:
import dotenv
from langchain.agents import create_tool_calling_agent, AgentExecutor
from langchain_community.tools import GoogleSerperRun
from langchain_community.tools.openai_dalle_image_generation import OpenAIDALLEImageGenerationTool
from langchain_community.utilities import GoogleSerperAPIWrapper
from langchain_community.utilities.dalle_image_generator import DallEAPIWrapper
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.pydantic_v1 import BaseModel, Field
from langchain_openai import ChatOpenAI
dotenv.load_dotenv()
class GoogleSerperArgsSchema(BaseModel):
query: str = Field(description="执行谷歌搜索的查询语句")
class DallEArgsSchema(BaseModel):
query: str = Field(description="输入应该是生成图像的文本提示(prompt)")
google_serper = GoogleSerperRun(
name="google_serper",
description=(
"一个低成本的谷歌搜索API。"
"当你需要回答有关时事的问题时,可以调用该工具。"
"该工具的输入是搜索查询语句。"
),
args_schema=GoogleSerperArgsSchema,
api_wrapper=GoogleSerperAPIWrapper(),
)
dalle = OpenAIDALLEImageGenerationTool(
name="openai_dalle",
api_wrapper=DallEAPIWrapper(model="dall-e-3"),
args_schema=DallEArgsSchema,
)
tools = [google_serper, dalle]
prompt = ChatPromptTemplate.from_messages([
("system", "You are a helpful assistant"),
("placeholder", "{chat_history}"),
("human", "{input}"),
("placeholder", "{agent_scratchpad}"),
])
llm = ChatOpenAI(model="gpt-4o-mini")
agent = create_tool_calling_agent(
prompt=prompt,
llm=llm,
tools=tools,
)
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)
print(agent_executor.invoke({"input": "请帮我绘制一张老爷爷爬山的图片"}))
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
输出内容:
> Entering new AgentExecutor chain...
Invoking: `openai_dalle` with `{'query': 'An elderly man climbing a mountain, with a determined expression, wearing typical hiking gear. The background shows a scenic mountain landscape with trees and a clear sky. The scene conveys a sense of adventure and resilience.'}`
https://dalleproduse.blob.core.windows.net/private/images/6fa42821-1331-4b18-90dc-12364b6fb390/generated_00.png?se=2024-08-19T13%3A32%3A20Z&sig=vro%2B%2FrpJbsEbrMxeQnV8rbkE5GKU5tTqlCEvc908V2E%3D&ske=2024-08-23T22%3A55%3A33Z&skoid=09ba021e-c417-441c-b203-c81e5dcd7b7f&sks=b&skt=2024-08-16T22%3A55%3A33Z&sktid=33e01921-4d64-4f8c-a055-5bdaffd5e33d&skv=2020-10-02&sp=r&spr=https&sr=b&sv=2020-10-02这是我为您绘制的老爷爷爬山的图片:

希望您喜欢这幅作品!
> Finished chain.
{'input': '请帮我绘制一张老爷爷爬山的图片', 'output': '这是我为您绘制的老爷爷爬山的图片:\n\n\n\n希望您喜欢这幅作品!'}
2
3
4
5
6
7
8
9
10
11
12
13
修改成提问 马拉松的世界记录是多少? ,该智能体的回复如下:
> Entering new AgentExecutor chain...
Invoking: `google_serper` with `{'query': '马拉松世界纪录 2023'}`
2023年4月的伦敦马拉松赛,他以2小时1分25秒夺冠,这个成绩仅比当时的世界纪录慢了16秒。 2023年10月的芝加哥马拉松赛,以2小时00分35秒打破世界纪录。 仅有的三次马拉松经历,各个都是世界前六的好成绩。截至2023年10月,马拉松的世界纪录是由选手在芝加哥马拉松上创造的,时间为2小时00分35秒。
> Finished chain.
{'input': '马拉松的世界记录是多少?', 'output': '截至2023年10月,马拉松的世界纪录是由选手在芝加哥马拉松上创造的,时间为2小时00分35秒。'}
2
3
4
5
6
7
8
9
# 最新版 LangChain 用法提示
新版 LangChain 更推荐用 LCEL、
Runnable、ChatModel.bind_tools()、结构化输出和 LangGraph 来组织复杂链路;老式Chain、部分AgentExecutor写法可以读懂,但新项目应优先选择更清晰的图或 Runnable 编排。工具调用相关代码要区分两层:模型是否原生支持 tool/function calling,以及业务侧如何定义工具 schema、参数校验、错误兜底和观测日志。
如果示例中的导入路径和你当前安装版本不同,优先查当前版本包内导出位置;常见迁移方向是从
langchain拆到langchain-core、langchain-community、langchain-openai、langgraph等包。
# 拓展
工具或插件不要只看能不能调通,更要看是否可观测、可限流、可重试、可审计。联网类工具还要处理超时、空结果、搜索噪声和结果时效性。
Agent 场景里,Prompt 只是调度策略的一部分;工具描述、参数 schema、历史状态、错误反馈、停止条件和人工介入点同样会影响最终稳定性。
# 常见问题
为什么模型没有调用工具?常见原因是工具描述不清晰、参数 schema 过宽或过窄、用户问题不需要工具、模型本身不支持工具调用,或者工具绑定位置不对。
为什么工具调用后回答仍然不准?先看工具返回是否正确,再看工具结果是否被放回模型上下文,最后检查输出解析、历史消息和异常兜底是否覆盖了真实错误。
# 面试题
解释函数调用、工具调用和 Agent 的区别。
LangChain 中 tool schema 的作用是什么?为什么参数校验对生产环境很重要?
ReACT Agent 和 tool-calling Agent 的核心差异是什么?分别适合什么场景?
LangGraph 相比 LCEL 更适合解决哪些复杂编排问题?
# 生产问题排查
| 问题 | 常见原因 | 处理方式 |
|---|---|---|
| 工具没有被调用 | 工具描述弱、绑定失败、模型不支持 | 打印绑定后的模型配置,补充工具描述,换用支持工具调用的模型 |
| 参数格式错误 | schema 设计不清晰,模型生成字段不稳定 | 使用 Pydantic/JSON Schema 校验,失败后把错误反馈给模型重试 |
| 联网结果不可用 | 搜索为空、接口超时、命中低质量页面 | 增加超时、重试、结果过滤、来源白名单和降级回答 |
| Agent 循环不停止 | 缺少终止条件或工具返回被误判 | 设置最大迭代次数,记录每轮 thought/action/observation,增加停止规则 |
| 线上难以复现 | 缺少输入、工具请求和模型响应日志 | 给每次调用加 trace id,记录工具入参、出参、耗时和异常 |