Towards Data Science

Tool Calling, Explained: How AI Agents Decide What to Do Next

7.1内容质量

TL;DR · AI 摘要

Tool Calling, Explained: How AI Agents Decide What to Do Next Towards Data Science Agentic AI Tool Calling, Explained: H...

核心要点

  • 主题聚焦:Tool Calling, Explained: How AI Agents Decide Wh
  • 来源:Towards Data Science,建议结合原文判断细节。
  • AI 分析暂不可用,本条为保底评分与摘要。
#AI#编程#前端#后端#安全
打开原文

工具调用解析:AI代理如何决定下一步行动 | Towards Data Science

Agentic AI

工具调用解析:AI代理如何决定下一步行动

了解大型语言模型(LLM)如何与周围世界互动,从返回数据到采取行动

Maria Mouschoutzi

2026年6月21日

12分钟阅读

分享

图片由作者使用ChatGPT Images 2.0创建

在我最近的一篇文章中,我们讨论了如何通过JSON模式、函数调用和结构化输出,从大型语言模型(LLM)中获得结构化、可由机器读取的输出。在那篇文章中,我们简要提到了函数调用的概念,将其视为一种获取结构化响应的方法。然而,函数调用的作用远不止于从模型中获取结构化数据,因为它是agentic AI工作流程的核心。因此,在今天的这篇文章中,我们将深入探讨这一主题。

在我们迄今为止所讨论的所有示例中,大型语言模型(LLM)只是作为被动的响应者使用,这意味着它接收一个问题,然后生成一个答案,仅此而已。但是,如果我们希望LLM不仅仅是回应一些内容,而是去做一些事情呢?或者更准确地说,如果我们希望根据模型的响应触发一个动作呢?这个动作可以是任何事情:查询实时数据、发送消息、查询数据库、调用外部API等等。

这正是工具调用(tool calling)所实现的功能。工具调用使LLM从一个非常聪明的文本生成器转变为一个能够真正触发动作并与其周围世界互动的实体。

那么,让我们来了解一下!

什么是工具调用?

工具调用(也称为函数调用)是一种机制,使LLM能够在生成响应的过程中请求执行外部函数或API。换句话说,模型不仅可以返回文本,还可以在用户请求的响应中执行特定的函数,并带有特定的参数。

这里需要理解的关键点是,模型本身并不执行工具。它只是决定调用哪个工具以及使用哪些参数。所选工具的实际执行发生在我们自己的代码中,其中包括对AI模型的请求。然后,我们将工具的结果反馈给AI模型,AI模型利用这些结果生成对用户的最终响应。

这就是工具调用循环,包括以下步骤:

  • 用户提交一条消息
  • AI模型将消息作为输入,并生成输出,这实际上是对使用哪个工具以及使用哪些参数的决策
  • 包含工具选择和相应参数的模型响应被传递回代码。代码在没有AI模型参与的情况下,使用所选参数执行所选工具。该执行产生某种结果(例如,计算、从API获取的信息等),然后将该结果传递回AI模型
  • AI模型将工具的结果作为输入,并基于该结果生成对用户的最终响应

再次强调,模型生成的是工具调用,而不是工具执行。这两者是截然不同的,将它们混淆是常见的误解之一。

但什么是工具调用呢?实际上,这意味着模型通过函数调用返回一个结构化、机器可读的响应,就像我们在上一篇文章中看到的那样。在该响应中,contentNone;没有自然语言的回答,只有结构化的指令,指示要调用哪个工具以及使用哪些参数。只有在我们执行了工具并把结果返回之后,模型才会为用户生成一个实际的文本响应。

让我们实际看看这个过程!

我们将从一个简单的例子开始,仅使用一个工具和一次调用,然后逐步构建到一些更有趣的场景。

1. 单个工具:天气 API

我认为,最常见的人工智能工具使用示例是天气 API(定制实时数据的基石),所以让我们想象我们正在构建一个天气助手。具体来说,我们希望创建一种机制,用户询问天气情况时,而不是让 AI 模型随意编造答案(模型会非常乐意这么做 🙃),我们希望它调用一个真实的天气函数,并从 LLM 之外的某个地方获取实际的天气数据。为了获取天气数据,我将使用 Open-Meteo,这是一个免费、开源的天气 API,且无需 API 密钥。

要使用一个工具,我们必须首先在 tools 中声明它。

python
from openai import OpenAI
import json

client = OpenAI(api_key="your_api_key")

# 第一步:定义工具
tools = [
    {
        "type": "function",
        "function": {
            "name": "get_current_weather",
            "description": "获取给定城市的当前天气",
            "parameters": {
                "type": "object",
                "properties": {
                    "city": {
                        "type": "string",
                        "description": "城市名称,例如雅典"
                    },
                    "unit": {
                        "type": "string",
                        "enum": ["celsius", "fahrenheit"],
                        "description": "使用的温度单位"
                    }
                },
                "required": ["city"]
            }
        }
    }
]

请注意,到目前为止,实际使用的工具(天气 API)并未被提及。相反,模型根据以下三方面决定调用哪个工具:函数描述(“获取给定城市的当前天气”)、参数描述(“城市名称,例如雅典”)以及强制的模式。模型仅基于这些信息判断对于给定的用户消息是否应该调用该工具,以及使用哪些参数。因此,在定义工具时,编写清晰且准确的描述对于模型成功识别并调用正确的工具至关重要。

因此,在我们定义了 tools 变量之后,就可以向 AI 模型发送请求:

python
# 第二步:将用户消息与工具定义一起发送
messages = [
    {"role": "user", "content": "雅典现在的天气如何?"}
]

response = client.chat.completions.create(
    model="gpt-4o-mini",
    tools=tools,
    messages=messages
)

print(response.choices[0].message)

以下是当我们发出这个请求时发生的情况。模型会读取用户的提问,“雅典现在的天气如何?”,并理解可用的工具 get_current_weather 可以使用实时数据来回答这个问题。因此,它不会直接生成文本回复,而是决定先调用该工具。更具体地说,此时模型的响应如下所示:

code
ChatCompletionMessage(
    content=None,
    role='assistant',
    tool_calls=[
        ChatCompletionMessageToolCall(
            id='call_abc123',
            type='function',
            function=Function(
                name='get_current_weather',
                arguments='{"city": "Athens", "unit": "celsius"}'
            )
        )
    ]
)

请注意,contentNone,因为模型没有返回文本响应,而是返回了一个工具调用。现在,我们的任务是实际执行模型选择的工具,并将结果返回给模型。在我们的情况下,这将使用 AI 模型响应中提供的参数(即城市和单位)向天气 API 发起 API 请求:

code
# 第三步:使用 Open-Meteo API 执行工具
import requests

def get_current_weather(city: str, unit: str = "celsius"):
    # 将城市名称编码为坐标
    geo = requests.get(
        "https://geocoding-api.open-meteo.com/v1/search",
        params={"name": city, "count": 1}
    ).json()
    lat = geo["results"][0]["latitude"]
    lon = geo["results"][0]["longitude"]

    # 获取当前天气
    weather = requests.get(
        "https://api.open-meteo.com/v1/forecast",
        params={
            "latitude": lat,
            "longitude": lon,
            "current": "temperature_2m,weather_code",
            "temperature_unit": unit
        }
    ).json()

    temp = weather["current"]["temperature_2m"]
    return {"city": city, "temperature": temp, "unit": unit}

# 从响应中提取工具调用
tool_call = response.choices[0].message.tool_calls[0]
arguments = json.loads(tool_call.function.arguments)

# 调用实际函数
weather_result = get_current_weather(**arguments)

然后,我们可以将工具的结果添加到消息历史中,并将所有内容发送回模型:

code
# 第四步:将助手的工具调用和工具结果添加到消息历史中
messages.append(response.choices[0].message)  # 重要:先添加工具调用
messages.append({
    "role": "tool",
    "tool_call_id": tool_call.id,  # 将结果链接到特定的工具调用
    "content": json.dumps(weather_result)
})

# 第五步:将所有内容发送回模型以获得最终响应
final_response = client.chat.completions.create(
    model="gpt-4o-mini",
    tools=tools,
    messages=messages
)

print(final_response.choices[0].message.content)

现在,我们终于得到了一个完整的文本响应:

code
雅典现在是29°C。听起来是外出的好日子!

🍨 DataCream 是一份提供人工智能、数据和科技故事和教程的电子通讯。如果您对这些主题感兴趣,请在这里订阅!

2. 让模型从多个工具中选择

现在,我们来看一个更现实的例子。在实际的代理应用中,模型通常可以访问的不是单一工具,而是多个工具,因此它需要根据用户的问题决定使用哪一个(或多个)工具。

让我们通过添加一个用于货币的额外工具来扩展最初的天气 API 示例。为此,我们将使用 Frankfurter,这是一个提供欧洲中央银行每日汇率的货币 API,同样不需要 API 密钥。因此,让我们通过添加一个用于货币转换的第二个工具来更新 tools 变量:

code
tools = [
    {
        "type": "function",
        "function": {
            "name": "get_current_weather",
            "description": "获取给定城市的当前天气",
            "parameters": {
                "type": "object",
                "properties": {
                    "city": {"type": "string", "description": "城市名称"},
                    "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]}
                },
                "required": ["city"]
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "convert_currency",
            "description": "将一种货币的金额转换为另一种货币",
            "parameters": {
                "type": "object",
                "properties": {
                    "amount": {"type": "number", "description": "要转换的金额"},
                    "from_currency": {"type": "string", "description": "源货币代码,例如 USD"},
                    "to_currency": {"type": "string", "description": "目标货币代码,例如 EUR"}
                },
                "required": ["amount", "from_currency", "to_currency"]
            }
        }
    }
]

同时,我们使用 Frankfurter API 设置实际的 convert_currency 函数:

code
def convert_currency(amount: float, from_currency: str, to_currency: str):
    response = requests.get(
        f"https://api.frankfurter.dev/v2/rate/{from_currency}/{to_currency}"
    ).json()

    rate = response["rate"]
    converted = round(amount * rate, 2)
    return {
        "amount": amount,
        "from_currency": from_currency,
        "to_currency": to_currency,
        "converted_amount": converted,
        "rate": rate
    }

通过这种方式,模型可以处理更广泛的用户请求;它现在也可以回答有关货币的问题,而不仅仅是天气 😋。现在,如果用户问“雅典的天气如何?”,模型应调用 get_current_weather。如果他们问“100 美元等于多少欧元?”,模型应调用 convert_currency。如果我们的提问与天气和货币无关,且现有的工具都无法帮助,模型将仅以文本形式回答,而不会调用任何工具。

但让我们实际看一下:

code
messages = [
    {"role": "user", "content": "200 美元等于多少欧元?"}
]

response = client.chat.completions.create(
    model="gpt-4o-mini",
    tools=tools,
    messages=messages
)

tool_call = response.choices[0].message.tool_calls[0]

让我们看一下响应:

code
print(tool_call.function.name)

从中我们得到 convert_currency。因此,模型理解了问题“200 美元等于多少欧元?”与 convert_currency 工具相关。我们再来看一下参数:

code
print(tool_call.function.arguments)

从中我们得到:

'{"amount": 200, "from_currency": "USD", "to_currency": "EUR"}'

因此,模型正确地识别出 convert_currency 是合适的工具,并填写了相应的参数,我们除了提供适当的工具描述之外,用户只需提供适当的请求信息即可。这种精确的决策机制正是工具调用成为智能代理系统基础的原因。

3. 同时调用多个工具

另一个有趣的工具调用场景是,许多模型(如 gpt-4o)可以在用户请求需要时,在单个响应中调用多个工具。这种机制被称为并行工具调用。

例如,假设用户在一个请求中询问需要同时使用 get_current_weatherconvert_currency 工具才能获得所需信息的问题:

python
messages = [
    {"role": "user", "content": "雅典的天气如何?100美元换算成欧元是多少?"}
]

response = client.chat.completions.create(
    model="gpt-4o-mini",
    tools=tools,
    messages=messages
)

for tool_call in response.choices[0].message.tool_calls:
    print(tool_call.function.name)
    print(tool_call.function.arguments)

在这种情况下,我们得到的响应如下:

code
get_current_weather
{"city": "Athens"}

convert_currency
{"amount": 100, "from_currency": "USD", "to_currency": "EUR"}

请注意,这两个工具是在一个模型响应中被调用的。然后我们可以使用提供的参数分别执行相应的工具,并将工具结果一起返回给模型。这比顺序调用要高效得多,这也是更高级的代理处理多步骤请求的方式。

我的想法:那么,是什么让这成为智能代理?

我一直对“智能代理”这个词被滥用感到困扰。如今,任何东西都可以被贴上“智能代理”的标签,比如代理、智能代理工作流,一切与“代理”相关的概念都很时髦,但正如你可能已经发现的那样,并非所有被宣传为“智能代理”的东西都是真正的智能代理。

让我们退一步思考,首先弄清楚代理到底是什么。本质上,代理是一种能够感知其环境、以某种方式处理这些信息、拥有目标,并决定采取什么行动以实现目标的实体。想想我们的工具调用机制在做什么:它感知可用的工具,决定哪一个适合处理用户的请求(如果有的话),然后将这个决定传递给其余的代码以执行。以最简单的方式来说,这就是代理性。

在现实世界的智能代理应用中,工具调用循环不会只运行一次,而是多次运行,模型会使用一次工具调用的结果来决定下一步是否以及调用哪个工具。这有时被称为 ReAct 循环(Reason + Act),正是这种机制使代理能够处理那些无法通过单次调用解决的复杂、多步骤任务。

最终,我认为工具调用最令人着迷的地方在于,它改变了大型语言模型的本质。到目前为止,语言模型基本上是一种非常复杂的输入-输出函数,它接收文本作为输入,并生成文本作为输出。但通过工具调用,我们能够访问到一个无限多的额外功能集合,并结合大型语言模型的推理能力,创造出比单独使用任何一方都更强大的系统。

✨ 感谢阅读!✨

如果你读到了这里,你可能会觉得 pialgorithms 会对你有用——这是一个我们正在构建的平台,可以帮助团队在一个安全的地方管理组织知识。

喜欢这篇帖子吗?请通过💌 Substack 和 💼 LinkedIn 关注我

所有图片均由作者创作,除非另有说明。

作者

查看 Maria Mouschoutzi 的所有文章

深入探讨

,

Llm

编程

Python

工具调用

分享这篇文章

  • 在 Facebook 上分享
  • 在 LinkedIn 上分享
  • 在 X 上分享

Towards Data Science 是一个社区出版物。提交你的见解,以触达全球读者,并通过 TDS 作者支付计划获得报酬。

更新 href 为你的实际投稿 URL

为 TDS 写作

✦ 结束 CTA ✦