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]

这里的 geocodeget_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 官方封装的客户端。这也提醒我们:工具调用不仅是声明和执行,还涉及模型对话协议的正确延续

步骤五:实现工具链式调用 —— 让数据流转起来

前面的 geocodeget_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 中的 latlon,被用作下一个工具的输入。你只需确保 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% 的时间花在处理错误和边界情况上,真正写正向逻辑的代码很少。这也是本章开头说的“从声明到执行的最后一步,是最容易出故障的地方”。


行动清单

  1. 创建 SKILL.md,用 YAML 声明 tools 字段,每个工具写清楚名称、描述和参数 schema。
  2. 实现工具函数,严格对照声明中的参数名,使用 requests.get 并强制设置 timeout
  3. 为每个工具编写异常捕获层,把技术异常转成带 error 字段的友好对象,让模型能理解。
  4. 使用 safe_ 前缀包装函数,并在 Agent 框架中配置重试策略(最多 2 次)。
  5. 测试链式调用:先 geocode 城市名,再把坐标传给 get_weather,验证数据流是否正确。

完成了这一章,你已经学会了如何把 SKILL.md 里的工具声明转化为真实世界的网络调用,并且具备基本的容错能力。但这一切还停留在“调用单个 Skill”的层面。当你的系统同时部署多个 Skill,就需要更复杂的推理与编排策略——下一章《提示词工程与 Skills 的结合点远未被充分挖掘》 会带你探索如何将 Few-shot、Chain-of-Thought 甚至 ReAct 模式融入 Skills 架构,让 Agent 真正学会“先思考,再行动”。

本文章首发在 LearnKu.com 网站上。

上一篇 下一篇
讨论数量: 0
发起讨论 查看所有版本


暂无话题~