2.5. 工具调用是从声明到执行的最后一步
工具调用是从声明到执行的最后一步
时间线锚点:2026年6月,你的团队已经用上了最新的 Claude Skills 架构。产品经理要求你在本周五之前,交付一个能“根据用户自然语言查询实时天气”的 Skill,它必须调用外部天气 API,不能硬编码数据。你已经写好了 SKILL.md,精准描述了意图,但 Skill 还只是“纸上谈兵”——那些声明在文件里的工具名称,怎么变成真实世界的网络请求?这就是本章要解决的问题:从声明到执行的最后一步,往往是最容易出故障的地方。
本章以实战为主线,你会亲手构建一个天气查询 Skill,它将通过链式调用完成“城市名→坐标→天气数据”的完整链路。你会掌握工具声明语法、参数映射、超时处理、错误传播与重试策略。读完这一章,你不但能跑通一个完整的工具调用流程,还会知道在生产环境中怎么优雅地处理意外。
你需要什么
- 操作系统:macOS / Linux / Windows(Python 3.10+ 均可)
- OpenWeatherMap 免费账号:获取 API Key(注册地址)
- Python 依赖:
requests,python-dotenv - 模型环境:Anthropic Claude API 或任何兼容 Function Calling 的模型服务(教程用 Claude 演示,但你也可以无缝迁移到 OpenAI / DeepSeek)
- 预计时间:60 分钟(包含注册和测试)
最终成果
一个完整的天气查询 Skill,接收用户输入的自然语言(如“北京现在气温多少?”),自动解析城市名,调用 Geocoding API 获取坐标,再调用天气 API 返回实时温度、湿度、风速,并在网络异常或参数错误时返回友好提示。为什么做这个? 因为它涵盖了工具调用中 90% 的典型问题:多工具串联、参数依赖、外部服务失败处理。
步骤一:准备项目骨架与环境变量
先在终端执行以下命令,创建项目目录和文件:
mkdir weather_skill && cd weather_skill
python3 -m venv venv
source venv/bin/activate # Windows 用 venv\Scripts\activate
pip install requests python-dotenv
创建 .env 文件存放 API Key:
OPENWEATHER_API_KEY=your_actual_api_key_here
在项目根目录创建 skill.py,这是工具函数的实现文件。再创建 SKILL.md,用于声明 Skill 的元信息和工具定义。
踩坑经验
不要把 API Key 写死在 SKILL.md 里,否则代码分享到 Git 仓库时会泄露。始终用环境变量或密钥管理服务注入。模型调用工具时,tools声明仅包含工具名称、描述和参数 schema,不包含密钥信息。
步骤二:在 SKILL.md 中声明工具,选择模型
打开 SKILL.md,写入以下内容(核心是 tools 字段):
# SKILL.md
name: weather-reporter
description: Get current weather for any city, using OpenWeatherMap APIs.
tools:
- name: geocode
description: Convert a city name into latitude and longitude.
parameters:
type: object
properties:
city_name:
type: string
description: The name of the city, e.g., "Beijing" or "Tokyo".
required: [city_name]
- name: get_weather
description: Retrieve current weather data for given coordinates.
parameters:
type: object
properties:
lat:
type: number
description: Latitude, e.g., 39.9042
lon:
type: number
description: Longitude, e.g., 116.4074
units:
type: string
description: Unit system (metric, imperial, standard), default metric.
default: metric
required: [lat, lon]
这里的 geocode 和 get_weather 是两份声明,它们只是描述,真正的执行逻辑在 skill.py 里。模型选择上:Claude 3.5 Sonnet 及以上版本对并行工具调用支持良好,如果使用 DeepSeek 等国产模型,需要留意思考模式下的上下文回传逻辑(本章后面会提)。
步骤三:实现工具函数 —— 把声明变成行动
在 skill.py 中编写两个函数,分别对应声明的工具。注意 参数映射:模型传递的是 JSON 对象,你需要从中提取参数,再拼成 API 要求的格式。
import os
import requests
from dotenv import load_dotenv
load_dotenv()
API_KEY = os.getenv("OPENWEATHER_API_KEY")
GEO_URL = "https://api.openweathermap.org/geo/1.0/direct"
WEATHER_URL = "https://api.openweathermap.org/data/2.5/weather"
TIMEOUT_SECONDS = 5
def geocode(city_name: str):
"""返回城市经纬度,失败时抛出具体异常"""
resp = requests.get(
GEO_URL,
params={"q": city_name, "limit": 1, "appid": API_KEY},
timeout=TIMEOUT_SECONDS
)
resp.raise_for_status() # 4xx, 5xx 自动触发 HTTPError
data = resp.json()
if not data:
raise ValueError(f"City not found: {city_name}")
return {"lat": data[0]["lat"], "lon": data[0]["lon"]}
def get_weather(lat: float, lon: float, units: str = "metric"):
"""获取指定坐标的天气"""
resp = requests.get(
WEATHER_URL,
params={
"lat": lat, "lon": lon, "appid": API_KEY, "units": units
},
timeout=TIMEOUT_SECONDS
)
resp.raise_for_status()
data = resp.json()
return {
"weather": data["weather"][0]["description"],
"temp": data["main"]["temp"],
"humidity": data["main"]["humidity"],
"wind_speed": data["wind"]["speed"]
}
注意
参数appid是 OpenWeatherMap 的要求,务必在调用时附加,否则返回 401。timeout设为 5 秒是为了防止外部服务无响应时卡死整个 Agent 流程,这一项在生产环境中必须是强制配置。
步骤四:处理工具调用的结果与重试逻辑
模型调用 geocode 后,拿到坐标再调用 get_weather。但如果城市名不存在,或者 API 密钥失效,函数会抛出异常。在 Agent 框架中,这些异常应该转换成 模型友好的错误消息,而不是让整个对话崩溃。
编写一个包装器函数 handle_error,它捕捉异常并返回一个结构化的错误信息:
def safe_geocode(city_name):
try:
return geocode(city_name)
except requests.exceptions.Timeout:
return {"error": "Geocoding API timed out, please try again."}
except requests.exceptions.HTTPError as e:
if e.response.status_code == 401:
return {"error": "API key invalid. Check your OPENWEATHER_API_KEY."}
return {"error": f"Geocoding failed: {e.response.status_code}"}
except ValueError as e:
return {"error": str(e)}
模型收到这样的错误对象后,可以根据 error 字段生成用户回复,例如:“抱歉,北京查询超时,您要再试一次吗?”或者“API 密钥配置有误,请联系管理员”。
重试策略:对于超时这类瞬时故障,可以在 Agent 框架中设置自动重试。以 Claude Skills 的常见实现为例,如果返回值中包含 error 且属于 Timeout,让模型主动重新调用 safe_geocode,最多重试 2 次。重试逻辑可以写在框架的 execute_step_with_retry 函数中(参考调研素材中的 Plan and Execute 模式)。这种策略在调研素材中也被验证有效:max_retries = 2 是一个经验值。
踩坑经验
如果模型接入的是 DeepSeek 思考模式,每次工具调用后必须在下一轮请求中携带前一轮返回的reasoning_content,否则会触发 400 错误。官方 OpenAI .NET SDK 在处理扩展字段时会丢弃这部分内容,所以如果使用该 SDK,你需要自己拼接上下文或使用 DeepSeek 官方封装的客户端。这也提醒我们:工具调用不仅是声明和执行,还涉及模型对话协议的正确延续。
步骤五:实现工具链式调用 —— 让数据流转起来
前面的 geocode 和 get_weather 是孤立的,现在要把它们串起来。在 SKILL.md 中并未声明先后顺序,但模型会通过自然语言理解来决定调用顺序。你需要提供一个编排函数,把两个工具调用封装成一个整体流程。这一步体现了“链式调用”与“数据流传递”的核心。
在 skill.py 中添加:
def run_weather_report(user_input: str):
# 简化的意图解析:提取城市名(实际应由模型完成,此处仅演示串联)
# 这里假设模型已解析出 city_name
city = user_input # 教程中我们直接用传入的 city name
geo_result = safe_geocode(city)
if "error" in geo_result:
return geo_result["error"] # 终止并返回错误
lat = geo_result["lat"]
lon = geo_result["lon"]
weather_result = get_weather(lat, lon)
return weather_result
在真实 Agent 系统中,模型读到 SKILL.md 后会自行决定:先调用 geocode,拿到坐标后自动填充到 get_weather 的参数里。这个过程就是数据流传递:前一个工具的输出 JSON 中的 lat 和 lon,被用作下一个工具的输入。你只需确保 JSON 字段命名一致,模型就能完成映射。
测试时,你可以模拟模型调用:
# test_skill.py
from skill import run_weather_report
result = run_weather_report("Beijing")
print(result)
期望输出类似:
{'weather': 'clear sky', 'temp': 26.3, 'humidity': 58, 'wind_speed': 2.5}
步骤六:处理超时与最终测试
修改 skill.py,将 get_weather 的网络请求超时设为极端值(比如 0.001 秒),观察报错是否被 safe_geocode 捕获。正常情况应该返回 {"error": "Geocoding API timed out, please try again."}。然后再恢复正常超时设置,整体流程应该顺利跑通。
现在,你可以将这个 Skill 部署到任何支持 Function Calling 的客户端。在 Claude Code 中,只需在 SKILL.md 中声明上述工具,并在启动时告知 --allowedTools,模型就能在对话中自动调用你的 Python 函数。这正印证了调研素材中反复强调的一点:工具必须在 Skill 清单中提前授权,执行只是声明的自然延伸。
回顾
| 完成的工作 | 花费时间 |
|---|---|
| 注册 OpenWeatherMap 账号并获取 API Key | 5 分钟 |
| 搭建 Python 环境,安装依赖 | 3 分钟 |
| 编写 SKILL.md 工具声明 | 8 分钟 |
| 实现 geocode 和 get_weather 函数(含参数映射) | 15 分钟 |
| 添加错误处理与重试包装器 | 10 分钟 |
| 实现链式调用流程并测试 | 10 分钟 |
| 总计 | 约 51 分钟 |
这还没有算上阅读文档和调试超时边界所花的时间。实际开发中,你会发现 60% 的时间花在处理错误和边界情况上,真正写正向逻辑的代码很少。这也是本章开头说的“从声明到执行的最后一步,是最容易出故障的地方”。
行动清单
- 创建 SKILL.md,用 YAML 声明
tools字段,每个工具写清楚名称、描述和参数 schema。 - 实现工具函数,严格对照声明中的参数名,使用
requests.get并强制设置timeout。 - 为每个工具编写异常捕获层,把技术异常转成带
error字段的友好对象,让模型能理解。 - 使用
safe_前缀包装函数,并在 Agent 框架中配置重试策略(最多 2 次)。 - 测试链式调用:先 geocode 城市名,再把坐标传给 get_weather,验证数据流是否正确。
完成了这一章,你已经学会了如何把 SKILL.md 里的工具声明转化为真实世界的网络调用,并且具备基本的容错能力。但这一切还停留在“调用单个 Skill”的层面。当你的系统同时部署多个 Skill,就需要更复杂的推理与编排策略——下一章《提示词工程与 Skills 的结合点远未被充分挖掘》 会带你探索如何将 Few-shot、Chain-of-Thought 甚至 ReAct 模式融入 Skills 架构,让 Agent 真正学会“先思考,再行动”。
agent skills 入门到精通
关于 LearnKu