AI实践(8)工具函数调用


Author: Once Day Date: 2026年3月2日

一位热衷于Linux学习和开发的菜鸟,试图谱写一场冒险之旅,也许终点只是一场白日梦…

漫漫长路,有人对你微笑过嘛…

全系列文章可参考专栏: AI实践成长_Once-Day的博客-CSDN博客

参考文章:


1. 概述
1.1 什么是工具函数调用(Tool Use / Function Calling)

大语言模型(LLM)本身是一个纯文本生成系统,其能力边界局限于训练数据所覆盖的知识范围。当用户提出诸如"今天北京天气如何"或"帮我查一下订单状态"这类需要实时数据的问题时,模型仅凭自身参数无法给出准确回答。工具函数调用(Tool Use / Function Calling)正是为了突破这一限制而提出的能力,它在 LLM 与外部系统之间建立了一条可靠的交互通道,使模型能够在对话过程中主动调用预先定义好的外部函数,从而获取实时信息或执行特定操作。

从技术实现上看,Function Calling 并非让模型直接执行代码,而是让模型在理解用户意图后,输出一段结构化的 JSON 数据,其中包含需要调用的函数名称及对应的参数。这一能力依赖于模型在训练阶段的专项微调——经过微调的 LLM 能够准确判断当前对话是否需要借助外部工具,并严格按照预定义的函数签名(function schema)生成符合格式要求的调用请求。整个过程中,模型扮演的是"决策者"角色,而实际的函数执行则由外部程序完成。

这种机制带来了极大的灵活性。开发者可以在单次请求中定义多个工具函数,例如同时提供天气查询、数据库检索、日程管理等不同功能。模型会根据用户的具体意图,自行选择合适的函数进行调用,甚至在复杂场景下进行多次连续调用以完成一个完整的任务链。以下是一个工具定义的基本结构示意:

{
  "name": "get_weather",
  "description": "获取指定城市的当前天气信息",
  "parameters": {
    "type": "object",
    "properties": {
      "city": {
        "type": "string",
        "description": "城市名称,例如:北京"
      }
    },
    "required": ["city"]
  }
}

上述 JSON 结构定义了一个名为 get_weather 的工具函数,包含函数名、功能描述以及参数的类型约束。模型在接收到用户关于天气的提问后,会解析意图并输出类似 {"name": "get_weather", "arguments": {"city": "北京"}} 的结构化调用指令,由客户端或服务端程序负责实际执行并将结果回传给模型,最终模型基于返回结果生成自然语言回复。

目前,主流的 LLM 服务商均已支持 Function Calling 能力,包括 OpenAIAnthropicClaude)、GoogleGemini)等。尽管各平台在 API 设计和参数格式上存在差异,但核心思路一致:通过预定义工具描述,让模型具备调用外部能力的"接口意识"。

1.2 为什么需要工具函数调用

LLM 本质上是一个基于概率的文本生成模型,其知识来源于训练语料的静态快照。这意味着模型无法感知训练截止日期之后发生的事件,也无法直接访问私有数据库、调用第三方服务或执行任何具有副作用的操作。在构建 LLM 驱动的聊天机器人或智能代理(Agent)时,这一局限尤为突出——用户的实际需求往往涉及实时数据查询、业务流程触发等模型自身无法完成的任务。函数调用机制正是为弥合这一鸿沟而设计的核心能力,它将自然语言理解与外部系统执行有机地连接在一起。

从功能定位来看,函数调用主要解决两类问题。

  • 数据获取,即获取模型自身不具备的实时信息,例如当前天气、数据库记录、知识库文档等,弥补训练数据时效性不足的缺陷。
  • 执行行动,即触发具有实际效果的外部操作,例如发送邮件、创建工单、提交表单、调用第三方 API 等,使模型从"被动回答"升级为"主动行动"。

借助这两类能力,开发者可以构建出多种形态的应用:

  • 能够调用外部工具回答问题的对话代理,例如将"伯利兹的天气如何"转换为 get_current_weather(location="Belize", unit="celsius") 的函数调用。
  • 用于从非结构化文本中提取和标记数据的 LLM 驱动方案,例如从百科文章中抽取人名、地名等实体。
  • 将自然语言转换为有效 API 调用或数据库查询语句的中间层应用。
  • 与企业知识库交互的对话式检索引擎,支持多轮问答与上下文关联。

目前主流大模型平台均已将函数调用作为标准能力提供支持,但在 API 设计、参数格式和调用模式上各有差异。下表对几个代表性平台进行了简要对比:

平台 能力名称 工具定义格式 并行调用 强制调用
OpenAIGPT-4o Function Calling JSON Schema 支持 支持(tool_choice
AnthropicClaude Tool Use JSON Schema 支持 支持(tool_choice
GoogleGemini Function Calling OpenAPI Schema 支持 支持(function_calling_config

各平台的核心工作流程基本一致:开发者通过 JSON Schema 定义工具函数的名称、描述和参数约束,模型根据用户输入和系统提示自主决定是否调用以及调用哪些函数,最终在获取函数执行结果后整合生成自然语言响应。

在具体实现层面,工具通常被划分为客户端工具和服务端工具两类:

  • 客户端工具的执行发生在调用方(即开发者的应用程序)一侧,由应用程序接收模型输出的调用指令后自行完成函数执行并回传结果;

  • 服务端工具则由模型服务商在其基础设施上直接执行,开发者无需处理中间的调用与回传过程。两种模式适用于不同的场景,后续章节将以 Claude 为例分别进行详细实践。

2. 函数实例(获取天气)

以一个典型的天气查询场景为例,展示函数调用的完整实现过程。下面的代码基于 OpenAIPython SDK,定义了一个名为 get_weather 的工具函数,用于获取指定地点的当前温度,位置参数需要以"城市+国家"的格式传入。开发者通过 tools 参数将函数的 schema 描述提交给模型,模型在理解用户意图后自主决定是否触发调用:

from openai import OpenAI

# 配置 API 中转站端点
client = OpenAI(
    base_url="https://api.aaaaapi.com/v1",
    api_key="your-api-key"  # 替换为你的 API 密钥
)

# 定义工具(函数)
tools = [{
    "type": "function",
    "name": "get_weather",
    "description": "获取指定地点的当前温度。",
    "parameters": {
        "type": "object",
        "properties": {
            "location": {
                "type": "string",
                "description": "城市和国家,例如:波哥大,哥伦比亚"
            }
        },
        "required": ["location"],
        "additionalProperties": False
    }
}]

# 调用模型,触发函数调用
response = client.responses.create(
    model="gpt-4.1",
    input=[{"role": "user", "content": "今天巴黎的天气怎么样?"}],
    tools=tools
)

print(response.output)

在上述代码中,tools 列表内的每个元素对应一个工具定义,其中 type 固定为 "function"namedescription 分别描述函数名称与用途,parameters 则使用标准的 JSON Schema 格式约束参数类型和必填项。当用户输入"今天巴黎的天气怎么样"时,模型会解析出意图与天气查询相关,并从对话内容中提取出地点信息,随后生成一条结构化的函数调用指令,而非直接返回文本回复。

模型返回的 response.output 中会包含一个 function_call 类型的输出项,其内容类似于 {"name": "get_weather", "arguments": "{\"location\": \"巴黎, 法国\"}"}。可以看到,模型不仅识别了用户提到的"巴黎",还自动补全了国家信息以符合参数描述中的格式要求。这一结果本身并不包含天气数据,而是一条等待执行的调用指令。

函数调用的完整交互流程可以概括为五个步骤:

在这里插入图片描述

首先,开发者通过 tools 参数传入函数 schema,告知模型可用工具的用途和参数格式。其次,模型根据用户输入判断是否需要调用工具,若需要则生成包含函数名和参数的调用指令。随后,应用程序解析模型输出,调用对应的自定义代码(如请求天气 API 获取实时数据)。接着,将函数执行的返回值以 tool result 的形式回传给模型。最后,模型基于原始问题和函数返回结果,生成一段完整的自然语言回答呈现给用户,例如"巴黎今天的气温为 18°C,多云"。

需要注意的是,整个流程中模型本身并不执行任何函数代码,它的职责仅限于意图识别、参数提取和结果整合。实际的 API 调用、数据库查询等操作均由开发者的应用程序负责完成。

3. 函数调用工作流

函数调用的起点是向模型提供准确且完整的函数 schema 定义。这份 schema 是模型理解工具能力边界的唯一依据——模型并不知道函数的实际实现逻辑,它完全依赖 schema 中的描述信息来判断何时调用、如何构造参数。因此,schema 的质量直接决定了函数调用的准确率。一个标准的函数 schema 需要包含以下核心字段:

字段 说明
type 固定为 "function",标识工具类型
name 函数名称,如 get_weather,应简洁且语义明确
description 函数用途和适用场景的自然语言描述,需详细清晰
parameters 函数参数的 JSON Schema 定义,包含类型、描述、约束等
strict 是否启用严格模式,推荐设为 true 以确保输出严格符合 schema

其中 description 字段的撰写尤为关键。模糊的描述会导致模型在不恰当的时机触发调用,或者在应该调用时选择了错误的函数。例如,将天气查询函数的描述写为"获取天气"远不如"获取指定城市当前的实时气温和天气状况,适用于用户询问某地天气的场景"来得准确。当系统中同时存在多个功能相近的工具时,清晰的 description 能够帮助模型做出正确的路由决策。

在参数定义层面,遵循一些最佳实践能够显著提升调用的可靠性。首先,应为每个参数添加详细的 description 说明,明确格式要求和示例值,例如 location 参数注明"城市和国家,例如:巴黎,法国"。其次,必须通过 type 字段指定参数的数据类型(stringnumberobject 等),避免类型歧义。对于有限取值的参数,应使用 enum 进行约束,如温度单位限定为 ["celsius", "fahrenheit"]。最后,通过 required 数组显式声明必填参数,确保模型不会遗漏关键信息。以下是一个遵循最佳实践的参数定义示例:

"parameters": {
    "type": "object",
    "properties": {
        "location": {
            "type": "string",
            "description": "城市和国家,例如:巴黎,法国"
        },
        "unit": {
            "type": "string",
            "enum": ["celsius", "fahrenheit"],
            "description": "温度单位,默认为 celsius"
        }
    },
    "required": ["location"],
    "additionalProperties": false
}

当模型决定调用函数后,应用程序需要按照规范的流程处理调用指令并完成结果回传。首先从 response.output 中解析出函数调用信息,提取 name(函数名)、arguments(参数 JSON 字符串)和 call_id(调用标识符)三个关键字段。然后根据函数名路由到对应的实现代码,将 arguments 反序列化为实际参数并执行。函数执行完成后,将返回值格式化为字符串(支持纯文本或 JSON 格式),通过 function_call_output 类型的消息回传给模型,其中必须携带对应的 call_id 以便模型正确关联请求与结果。

在这里插入图片描述

在实际工程中,还需要对异常情况进行处理。函数执行可能因网络超时、参数非法、服务不可用等原因失败,此时应将错误信息以结构化的方式回传给模型,而非直接抛出异常。模型在接收到错误描述后,通常能够生成合理的兜底回复,例如告知用户"当前天气服务暂时不可用,请稍后再试",从而保证对话体验的连贯性。

4. 客户端工具实践

客户端工具(Client Tools)是 Claude 工具调用体系中最常用的模式。其核心特征在于:工具的实际执行发生在开发者的应用程序一侧,Claude 仅负责理解用户意图、决定是否调用工具并生成结构化的调用指令,而具体的函数逻辑、API 请求、数据处理等工作均由客户端代码完成。这种设计赋予开发者对工具执行过程的完全控制权,适用于需要访问本地资源、私有数据库或自定义业务逻辑的场景。

客户端工具的完整交互流程分为四个阶段。首先,开发者在 API 请求中通过 tools 参数定义可用工具的名称、描述和输入参数的 JSON Schema,同时传入用户的自然语言消息。随后,Claude 评估当前对话是否需要借助工具来完成回答,若判断需要,则构造一条格式规范的工具调用请求,此时 API 响应中的 stop_reason 字段值为 tool_use,明确表示模型希望执行工具调用而非直接生成文本回复。

在这里插入图片描述

以下是一个基于 Claude API 的客户端工具调用完整示例,实现了天气查询功能:

import os
import anthropic
import json

API_KEY = os.getenv("CLAUDE_API_KEY")
API_BASE = os.getenv("CLAUDE_API_BASE")

client = anthropic.Anthropic(
    api_key=API_KEY,
    base_url=API_BASE,
)

TOOLS = [
    {
        "name": "get_weather",
        "description": "获取指定城市的当前天气信息,当用户询问某地天气时使用。",
        "input_schema": {
            "type": "object",
            "properties": {
                "location": {
                    "type": "string",
                    "description": "城市名称,例如:San Francisco, CA",
                }
            },
            "required": ["location"],
        },
    }
]

# 第一步:定义工具并发送用户消息
response = client.messages.create(
    model="claude-sonnet-4.6",
    max_tokens=10000,
    tools=TOOLS,
    messages=[{"role": "user", "content": "What's the weather in San Francisco?"}],
)

# 第二步:检查模型是否请求工具调用
if response.stop_reason == "tool_use":
    # 提取工具调用信息
    tool_block = next(b for b in response.content if b.type == "tool_use")
    tool_name = tool_block.name
    tool_input = tool_block.input
    tool_use_id = tool_block.id

    # 第三步:执行本地工具函数
    def get_weather(location: str) -> dict:
        # 实际场景中此处调用天气 API
        return {"temperature": "15°C", "condition": "partly cloudy"}

    result = get_weather(tool_input["location"])

    # 第四步:将结果回传给 Claude
    final_response = client.messages.create(
        model="claude-sonnet-4.6",
        max_tokens=10000,
        tools=TOOLS,
        messages=[
            {"role": "user", "content": "What's the weather in San Francisco?"},
            {"role": "assistant", "content": response.content},
            {
                "role": "user",
                "content": [
                    {
                        "type": "tool_result",
                        "tool_use_id": tool_use_id,
                        "content": json.dumps(result),
                    }
                ],
            },
        ],
    )
    print(final_response.content[0].text)

上述代码中有几个关键细节值得注意。Claude 的工具定义使用 input_schema 字段(而非 OpenAIparameters),这是两个平台在 API 设计上的主要差异之一。当 stop_reasontool_use 时,response.content 中会包含一个 typetool_use 的内容块,其中的 id 字段是调用标识符,在回传结果时必须通过 tool_use_id 进行关联,否则 Claude 无法正确匹配调用请求与返回结果。

在结果回传阶段,需要构造完整的对话历史传入第二次 API 调用,包括原始用户消息、Claude 的工具调用响应(作为 assistant 消息)以及工具执行结果(作为 user 消息中的 tool_result 内容块)。Claude 在接收到工具返回的数据后,会将其与原始问题结合分析,生成一段完整的自然语言回答,例如"旧金山目前的天气为 15°C,局部多云"。整个过程中,模型始终不直接执行任何代码,所有工具逻辑的安全性和正确性由开发者自行保障。

5. 服务端工具实践

与客户端工具不同,服务端工具(Server Tools)的执行完全发生在 Anthropic 的基础设施上,开发者无需编写任何工具实现代码,也无需处理调用指令的解析与结果回传。Claude 在判断需要使用服务端工具后,会在服务端自动完成工具执行,并将结果直接整合到最终响应中。这种模式极大地简化了开发流程,尤其适用于 Anthropic 已经内置支持的通用能力场景。

目前 Claude 提供的服务端工具主要包括 web_search(网页搜索)和 web_fetch(网页内容获取)两种。前者允许模型在对话过程中主动检索互联网上的实时信息,后者则可以抓取指定 URL 的页面内容进行分析。开发者只需在 tools 参数中声明这些工具,无需定义 input_schema,因为服务端工具的参数结构由 Anthropic 预先规定。

import os
import anthropic

API_KEY = os.getenv("CLAUDE_API_KEY")
API_BASE = os.getenv("CLAUDE_API_BASE")

client = anthropic.Anthropic(api_key=API_KEY, base_url=API_BASE)

# 使用服务端工具 - web_search
response = client.messages.create(
    model="claude-sonnet-4.6",
    max_tokens=10240,
    tools=[
        {
            "type": "web_search_20260209",
            "name": "web_search",
            "max_uses": 5,  # 限制单次对话中的最大搜索次数
        }
    ],
    messages=[{"role": "user", "content": "搜索一下 2026年3月13日 GitHub 热门项目, 简要介绍它们。"}],
)

print(response.content)

上述代码的结构相比客户端工具明显更为简洁。服务端工具通过特定的 type 标识(如 web_search_20260209)进行声明,而非使用通用的 "function" 类型。max_uses 参数用于控制工具在单次对话中的调用上限,防止模型过度依赖搜索而消耗过多资源。整个过程中,开发者只需发起一次 API 调用即可获得最终结果,不存在客户端工具中"接收调用指令—执行函数—回传结果"的多轮交互。

在这里插入图片描述

服务端工具的一个重要特性是支持内部采样循环(sampling loop)。当一个问题需要多步检索才能获得完整答案时,Claude 可以在服务端连续执行多次工具调用——例如先搜索关键词获取相关链接,再抓取特定页面提取详细内容——整个过程对开发者完全透明,最终返回的响应已经是经过多轮工具调用后综合整理的结果。

下表对客户端工具与服务端工具的核心差异进行了对比:

特性 客户端工具 服务端工具
执行位置 开发者应用程序 Anthropic 服务端
需要回传结果 是,需构造 tool_result 否,自动整合
自定义逻辑 完全自定义 仅限预置工具能力
多轮调用处理 开发者手动编排 服务端自动循环
典型场景 数据库查询、业务 API、本地计算 网页搜索、URL 内容分析

在实际项目中,客户端工具与服务端工具通常会混合使用。例如一个智能客服系统可以同时配置 web_search 用于检索公开信息,以及自定义的 query_order 客户端工具用于查询内部订单数据库。Claude 会根据用户的具体问题自主选择合适的工具组合,开发者只需确保客户端工具的调用链路正确处理即可。







Alt

Once Day

也信美人终作土,不堪幽梦太匆匆......

如果这篇文章为您带来了帮助或启发,不妨点个赞👍和关注!

(。◕‿◕。)感谢您的阅读与支持~~~

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐