# 基于工具调用的智能体设计与实现

# 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": "请帮我绘制一张老爷爷爬山的图片"}))
1
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这是我为您绘制的老爷爷爬山的图片:  
​  
![老爷爷爬山](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![老爷爷爬山](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)\n\n希望您喜欢这幅作品!'}
1
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秒。'}
1
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,记录工具入参、出参、耗时和异常