02-Agent 智能体开发实战指南(二):工具调用系统
·
Agent 智能体开发实战指南(二):工具调用系统深度解析
系列导读:这是《Agent 智能体开发实战指南》系列的第二篇,将深入讲解 Agent 的工具调用系统,包括@tool 装饰器原理、工具设计原则、多工具协作等核心内容。
一、工具:Agent 的"手脚"
1.1 为什么需要工具?
如果把 LLM 比作"大脑",那么工具就是"手脚"——没有工具,再聪明的大脑也无法影响现实世界。
工具的本质:将外部能力封装成 LLM 可以理解和调用的接口。
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ LLM 大脑 │ ──→ │ 工具接口 │ ──→ │ 外部系统 │
│ (决策者) │ │ (翻译层) │ │ (执行者) │
└─────────────┘ └─────────────┘ └─────────────┘
1.2 工具的三大要素
| 要素 | 作用 | 示例 |
|---|---|---|
| 名称 | LLM 识别工具的标识 | get_weather |
| 描述 | LLM 理解工具用途 | “查询指定城市的天气” |
| 参数 | LLM 传递必要信息 | city: str |
二、@tool 装饰器详解
2.1 基础用法
from langchain_core.tools import tool
@tool(description="获取股价,传入股票名称,返回字符串信息")
def get_price(name: str) -> str:
return f"股票{name}的价格是 40 元"
装饰器做了什么?
- 提取函数名
get_price作为工具名 - 解析参数签名
name: str确定输入要求 - 解析返回类型
-> str确定输出格式 - 绑定
description供 LLM 理解用途 - 将普通函数转换为 LangChain 的
Tool对象
2.2 完整 Tool 对象结构
# @tool 装饰后,实际生成的对象结构:
Tool(
name="get_price",
description="获取股价,传入股票名称,返回字符串信息",
args_schema={
"name": {"type": "string", "description": "股票名称"}
},
func=<function get_price at 0x...>
)
2.3 多参数工具示例
@tool(description="查询两个城市之间的天气对比")
def compare_weather(city1: str, city2: str) -> str:
"""获取两个城市的天气并进行对比"""
# LLM 会自动传入两个参数
weather1 = get_weather(city1)
weather2 = get_weather(city2)
return f"{city1}: {weather1} | {city2}: {weather2}"
三、工具描述编写指南
3.1 描述的重要性
description 是 LLM 选择工具的唯一依据。糟糕的描述会导致:
- LLM 不知道何时调用该工具
- LLM 传入错误的参数
- LLM 完全忽略该工具
3.2 优秀描述的三要素
# 描述太简略
@tool(description="获取数据")
# 描述不够清晰
@tool(description="获取股票价格")
# 优秀的描述
@tool(description="获取股票实时价格,传入股票名称(如'华胜天成'、'贵州茅台'),返回包含股价、涨跌幅的字符串信息")
优秀描述包含:
- 功能说明:这个工具做什么
- 参数说明:需要传入什么,格式是什么
- 返回说明:会返回什么,格式是什么
- 示例值:给出典型参数示例
3.3 描述模板
@tool(description="[功能],传入 [参数名 + 格式 + 示例],返回 [返回内容 + 格式]")
实战示例:
# 天气查询
@tool(description="查询指定城市的天气情况,传入城市名称(如'深圳'、'北京'),返回包含气温、湿度、风向的详细天气信息")
# 用户信息查询
@tool(description="获取当前用户的 ID,无需参数,返回纯字符串格式的用户 ID")
# 数据检索
@tool(description="从知识库中检索相关内容,传入搜索关键词,返回最匹配的 3-5 条参考资料")
四、多工具协作场景
4.1 场景:股票查询 + 介绍
from langchain.agents import create_agent
from langchain_community.chat_models.tongyi import ChatTongyi
from langchain_core.tools import tool
@tool(description="获取股票价格,传入股票名称,返回当前股价字符串")
def get_price(name: str) -> str:
return f"股票{name}的价格是 40 元"
@tool(description="获取股票公司信息,传入股票名称,返回公司业务介绍字符串")
def get_info(name: str) -> str:
return f"股票{name},是一家 A 股上市公司,专注于计算机领域。"
agent = create_agent(
model=ChatTongyi(model="qwen3-max"),
tools=[get_price, get_info],
system_prompt="你是一个智能助手,可以回答股票相关问题,请告知我思考过程,让我知道你为什么调用某个工具"
)
for chunk in agent.stream(
{"messages": [{"role": "user", "content": "华胜天成股价多少,并介绍一下"}]},
stream_mode="values"
):
latest_message = chunk['messages'][-1]
if latest_message.content:
print(latest_message.content)
try:
if latest_message.tool_calls:
print(f"工具调用:{[tc['name'] for tc in latest_message.tool_calls]}")
except AttributeError:
pass
4.2 执行流程分析
用户:华胜天成股价多少,并介绍一下
↓
Agent 思考:用户问了两个问题——股价 + 公司介绍
↓
行动 1:调用 get_price("华胜天成")
↓
观察 1:获得股价信息
↓
行动 2:调用 get_info("华胜天成")
↓
观察 2:获得公司介绍
↓
生成答案:整合两条信息,回复用户
4.3 工具调用顺序
LLM 会自动决定工具调用顺序,通常遵循:
- 依赖关系:先获取必要数据,再进行处理
- 逻辑顺序:按人类思考的自然顺序
- 效率优先:能并行则并行(LangChain 支持并行调用)
五、工具设计最佳实践
5.1 单一职责原则
❌ 错误示例:一个工具做太多事
@tool(description="处理用户所有请求")
def handle_everything(query: str) -> str:
# 又查天气,又查股价,又写报告...
pass
✅ 正确示例:每个工具专注一件事
@tool(description="查询天气")
def get_weather(city: str) -> str:
pass
@tool(description="查询股价")
def get_price(stock: str) -> str:
pass
@tool(description="生成报告")
def generate_report(user_id: str) -> str:
pass
5.2 错误处理
工具内部应该处理异常,返回友好提示:
@tool(description="查询天气,传入城市名称")
def get_weather(city: str) -> str:
try:
# 调用天气 API
response = weather_api.get(city)
return format_weather(response)
except CityNotFoundError:
return f"未找到城市'{city}'的天气信息,请检查城市名称是否正确"
except APIError as e:
return f"天气服务暂时不可用,请稍后重试(错误码:{e.code})"
为什么?
- 避免 Agent 因工具异常而崩溃
- 让 LLM 知道发生了什么,可以调整策略
- 提供更好的用户体验
5.3 返回值格式
原则:返回 LLM 容易理解的格式
# ❌ 返回复杂对象
def get_weather(city: str) -> dict:
return {"temp": 26, "humidity": 50, "wind": "南风 1 级"}
# ✅ 返回格式化字符串
def get_weather(city: str) -> str:
return f"城市{city}天气为晴天,气温 26 摄氏度,空气湿度 50%,南风 1 级"
原因:LLM 处理自然语言最擅长,结构化数据反而需要额外解析。
5.4 参数设计
原则:参数越少越好,类型越简单越好
# 参数太多
@tool(description="查询天气")
def get_weather(city: str, date: str, unit: str, include_hourly: bool) -> str:
pass
# 简化参数
@tool(description="查询指定城市的天气")
def get_weather(city: str) -> str:
# 内部使用默认值:今天、摄氏度、不含小时预报
pass
六、实战:完整的工具集设计
6.1 智扫通客服 Agent 工具集
from langchain_core.tools import tool
from rag.rag_service import RagSummarizeService
rag = RagSummarizeService()
# 1. 知识库检索工具
@tool(description="从向量存储中检索参考资料,传入搜索关键词,返回最相关的知识内容")
def rag_summarize(query: str) -> str:
return rag.rag_summarize(query)
# 2. 天气查询工具
@tool(description="获取指定城市的天气,传入城市名称,返回包含气温、湿度、风向的字符串信息")
def get_weather(city: str) -> str:
return f"城市{city}天气为晴天,气温 26 摄氏度,空气湿度 50%,南风 1 级,AQI21"
# 3. 用户位置工具
@tool(description="获取用户所在城市的名称,无需参数,返回纯字符串格式的城市名")
def get_user_location() -> str:
return "深圳" # 实际应从用户 profile 获取
# 4. 用户 ID 工具
@tool(description="获取当前用户的 ID,无需参数,返回纯字符串格式的用户 ID")
def get_user_id() -> str:
return "1001" # 实际应从 session 获取
# 5. 时间工具
@tool(description="获取当前月份,无需参数,返回'YYYY-MM'格式的字符串")
def get_current_month() -> str:
return "2025-03"
# 6. 外部数据工具
@tool(description="从外部系统获取指定用户在指定月份的使用记录,传入 user_id 和 month,返回使用数据字符串")
def fetch_external_data(user_id: str, month: str) -> str:
# 从数据库或 API 获取
return external_data.get(user_id, {}).get(month, "")
# 7. 上下文标记工具
@tool(description="无入参,无返回值,调用后标记当前为报告生成场景,触发提示词切换")
def fill_context_for_report():
return "fill_context_for_report 已调用"
6.2 工具分类管理
agent/tools/
├── agent_tools.py # 核心业务工具
├── common_tools.py # 通用工具(时间、位置等)
└── system_tools.py # 系统工具(日志、监控等)
七、调试技巧
7.1 打印工具调用日志
for chunk in agent.stream(input_dict, stream_mode="values"):
latest_message = chunk['messages'][-1]
if latest_message.content:
print(f"🤖 Agent: {latest_message.content}")
try:
if latest_message.tool_calls:
tool_names = [tc['name'] for tc in latest_message.tool_calls]
print(f"🔧 工具调用:{tool_names}")
except AttributeError:
pass
7.2 常见问题排查
| 问题 | 可能原因 | 解决方案 |
|---|---|---|
| Agent 不调用工具 | description 不清晰 | 重写工具描述 |
| 调用错误工具 | 工具名相似 | 区分工具命名 |
| 参数传错 | 参数类型不明确 | 在 description 中说明 |
| 工具返回被忽略 | 返回格式混乱 | 统一返回格式 |
八、本章小结
核心要点
- @tool 装饰器:将 Python 函数转换为 LLM 可调用的 Tool 对象
- description 至关重要:决定 LLM 是否正确选择工具
- 单一职责:每个工具只做一件事,做好一件事
- 错误处理:工具内部处理异常,返回友好提示
- 返回值格式:优先返回自然语言字符串
下章预告
下一篇我们将深入 ReAct 框架,学习:
- ReAct 的思考 - 行动 - 观察循环
- 多步推理策略
- 如何引导 Agent 按正确顺序调用工具
- ReAct 实战案例解析
- Agent 智能体开发实战指南(一):从 LLM 到 Agent 的认知升级
- Agent 智能体开发实战指南(二):工具调用系统深度解析(本文)
- Agent 智能体开发实战指南(三):ReAct 框架深度解析
- Agent 智能体开发实战指南(四):流式输出与状态管理
- Agent 智能体开发实战指南(五):中间件系统与动态提示词
- Agent 智能体开发实战指南(六):RAG 与向量存储实战
- Agent 智能体开发实战指南(七):项目架构设计与工程化实践
- Agent 智能体开发实战指南(八):UI 集成与生产部署
本文是《Agent 智能体开发实战指南》系列的第二篇,下一篇将深入讲解 ReAct 框架。
更多推荐



所有评论(0)