Tool Calling:大模型如何真正连接软件系统

如果说大语言模型解决的是“理解和生成”,那么 Tool Calling 解决的,就是如何让模型真正进入软件系统。 没有 Tool Calling,大模型本质上仍然只是一个文本生成器。它可以解释天气、分析订单、讨论数据库,也可以根据已有上下文给出建议,但它无法真正查询实时天气接口、读取订单状态、修改日历,也无法调用企业内部系统完成实际操作。 Tool Calling 改变的正是这一点。它让大模型不再只负责“回答问题”,而是开始参与任务执行过程: 这也是现代 AI Agent 最基础的一层能力。 它到底是什么 Tool Calling 可以理解为:将一组程序能力暴露给大模型,让模型根据当前任务决定

如果说大语言模型解决的是“理解和生成”,那么 Tool Calling 解决的,就是如何让模型真正进入软件系统。

没有 Tool Calling,大模型本质上仍然只是一个文本生成器。它可以解释天气、分析订单、讨论数据库,也可以根据已有上下文给出建议,但它无法真正查询实时天气接口、读取订单状态、修改日历,也无法调用企业内部系统完成实际操作。

Tool Calling 改变的正是这一点。它让大模型不再只负责“回答问题”,而是开始参与任务执行过程:

理解任务 -> 判断是否需要工具 -> 选择工具 -> 构造参数 -> 执行外部能力 -> 获取结果 -> 继续决策

这也是现代 AI Agent 最基础的一层能力。

它到底是什么

Tool Calling 可以理解为:将一组程序能力暴露给大模型,让模型根据当前任务决定是否调用工具、调用哪个工具,以及传入什么参数。

假设系统中存在一个天气查询函数:

def get_weather(city: str):
    ...

为了让模型使用它,我们需要把这个函数的用途和参数结构描述给模型,例如:

{
  "name": "get_weather",
  "description": "查询指定城市的实时天气",
  "parameters": {
    "type": "object",
    "properties": {
      "city": {
        "type": "string",
        "description": "城市名称"
      }
    },
    "required": ["city"]
  }
}

当用户询问:

北京现在天气怎么样?

模型不一定直接生成一个关于天气的自然语言答案,而是可以返回一个结构化的调用请求:

{
  "name": "get_weather",
  "arguments": {
    "city": "北京"
  }
}

应用程序收到这个调用请求后,再真正执行:

result = get_weather("北京")

随后,程序把工具执行结果重新交给模型,模型根据真实返回值生成最终回答。

完整过程可以表示为:

User -> LLM -> Tool Call -> Application -> API / Database / System -> Tool Result -> LLM -> Final Response

这里有一个必须明确的概念:大模型本身并没有真的执行函数。

模型做的事情本质上依然是生成输出,只不过这个输出不再是普通自然语言,而是一个符合特定 Schema 的结构化调用请求。真正访问数据库、发送 HTTP 请求、修改文件或者调用业务接口的,始终是外部程序。

本质上是决策层和执行层的分离

Tool Calling 最核心的设计,并不是简单地“让 AI 执行代码”,而是把整个系统拆分成两个职责不同的部分。

大模型负责理解用户目标,并根据上下文判断接下来应该采取什么行动,例如是否需要调用工具、应该选择哪个工具、需要传入什么参数,以及工具返回结果之后应该继续做什么。

程序则负责真正的执行,包括访问数据库、调用第三方 API、发送邮件、修改文件、操作企业系统,以及处理权限、异常和业务规则。

可以简单理解为:

LLM = Decision Layer
Application = Execution Layer

这种分离非常重要,因为大模型的输出具有概率性。如果模型生成的任何操作都被系统无条件执行,那么模型的不确定性就会直接转化为系统风险。

因此,在真正的工程系统中,模型负责“提出一个行动”,而应用程序仍然需要负责参数校验、权限控制、超时处理、异常捕获、重试机制、审计日志以及业务规则。

Tool Calling 并没有取代传统软件工程,而是把 LLM 变成了传统软件系统中的一个新的决策组件。

Tool Calling 与结构化输出

假设模型返回一句:

我觉得应该调用天气接口查询北京天气。

人类当然可以理解这句话,但程序很难稳定地处理,因为程序还需要进一步判断模型到底想调用哪个函数、具体参数是什么、参数类型是否正确,以及是否存在缺失字段。

因此,Tool Calling 必须依赖结构化输出。

相比自然语言:

帮我调用一下天气 API,地点是北京。

程序更希望得到:

{
  "name": "get_weather",
  "arguments": {
    "city": "北京"
  }
}

这样程序才能直接读取:

name = tool_call["name"]
arguments = tool_call["arguments"]

然后完成真正的函数调用:

tools[name](**arguments)

所以从工程角度来看,Tool Calling 可以被理解为:

让自然语言模型生成可以被程序稳定消费的结构化控制信号。

这也是为什么 JSON Schema、类型约束和 Structured Output 在 Agent 系统中如此重要。很多 Agent 系统看起来复杂,但底层真正需要解决的问题之一,就是如何让模型生成稳定、可验证、可执行的结构化结果。

Tool Schema 决定模型如何理解工具

很多 Tool Calling 的问题,并不是模型能力不够,而是 Tool Schema 设计得不够清晰。

例如,如果只提供一个工具:

search

搜索内容

模型实际上很难准确判断这个工具应该在什么场景下使用,因为它不知道搜索范围是什么,也不知道这里搜索的是互联网、数据库还是企业内部文档,更不知道 query 应该是一组关键词还是完整的自然语言问题。

如果改成:

search_internal_document

用途:
搜索公司内部技术文档和项目文档。

参数:
query: 需要查询的信息,使用简洁自然语言描述。

模型就更容易正确理解这个工具的职责边界。

因此,Tool Description 本身也是一种 Prompt Engineering。

它实际上是在告诉模型这个工具是什么、什么时候应该使用、什么时候不应该使用、输入应该是什么形式,以及这个工具能够解决什么问题。

一个成熟的 Tool 通常应该具备清晰的名称、明确的职责边界、尽可能简单的参数设计、明确的数据类型、准确的 Description,以及稳定的返回格式。

尤其应该避免设计所谓的“万能工具”,例如:

do_something(action, data)

这种接口虽然在传统程序设计中看起来很灵活,但对大模型而言反而更加模糊,因为模型需要同时理解大量隐藏语义。

相比之下:

search_document()
send_email()
create_task()
query_database()

这种职责明确的工具通常更容易被模型稳定选择。

Tool Calling Loop

复杂任务通常不可能通过一次 Tool Calling 完成,因此真正的 Agent 系统往往会形成一个循环。

一个简化后的 Tool Calling Loop 可以写成:

while True:

    response = llm(
        messages=messages,
        tools=tools
    )

    if response.has_tool_call():

        tool_call = response.tool_call

        result = execute_tool(
            tool_call.name,
            tool_call.arguments
        )

        messages.append(tool_call)
        messages.append(result)

    else:
        return response.text

这个循环实际上表达的是:

LLM -> 判断是否需要工具 -> 生成 Tool Call -> 执行工具 -> 获取 Tool Result -> 再次交给 LLM -> 继续判断

当这个循环建立起来之后,一个最基础的 Agent Runtime 实际上已经出现了,因为模型已经具备了根据环境反馈不断调整下一步行动的能力。

Tool Calling 本身并不等于完整的 Agent,但它为 Agent 提供了最重要的“行动能力”。

没有 Tool Calling,模型只能理解和生成;加入 Tool Calling 之后,模型才真正开始拥有与外部世界交互的能力。

多工具组合

单独调用一次天气 API 并不是一件复杂的事情,Tool Calling 真正有价值的地方,是模型可以根据用户的自然语言目标动态组合多个完全不同的工具。

例如用户提出:

查一下明天下午上海的天气,如果下雨,就取消我下午三点的跑步计划。

系统中可能存在三个工具:

get_weather(city, date)

get_calendar(date)

delete_calendar_event(event_id)

模型首先可以调用:

get_weather(
    city="上海",
    date="明天"
)

假设返回:

{
  "weather": "雨"
}

模型根据这个结果继续判断,然后调用:

get_calendar(
    date="明天"
)

获取日历结果:

[
  {
    "event_id": "1024",
    "title": "跑步",
    "time": "15:00"
  }
]

随后模型再调用:

delete_calendar_event(
    event_id="1024"
)

这个过程本质上是在动态生成一段工作流:

天气查询 -> 条件判断 -> 查询日历 -> 定位事件 -> 执行删除

传统软件开发中,这类流程通常需要程序员提前把所有逻辑写死,而在 Agent 系统中,大模型可以根据用户的自然语言目标,在运行时动态组合已有的软件能力。

Tool Calling 真正改变的不是某一个函数怎么调用,而是软件能力开始可以被模型按需编排。

它同样属于接口设计

Tool Calling 不只是“怎么把参数传进去”,工具返回什么同样重要。

例如一个数据库工具如果返回:

查询成功,这是查询到的一些结果……

这种自然语言对人类来说很好理解,但对于后续程序处理或者多步 Agent 调用来说并不稳定。

更合理的方式是返回结构化数据:

{
  "success": true,
  "data": [
    {
      "id": 1,
      "name": "Alice"
    }
  ]
}

如果系统更加复杂,还可以进一步统一返回格式:

{
  "status": "success",
  "data": [],
  "error": null,
  "metadata": {
    "total": 1
  }
}

稳定的 Tool Result Schema 可以显著降低模型在多步任务中的不确定性,也能让后续代码更容易处理工具结果。

因此,一个完整的 Tool Calling 系统实际上同时需要关注两套 Schema:

Tool Input Schema
Tool Output Schema

前者决定模型如何调用工具,后者决定模型和程序如何理解工具执行结果。

最大的问题是可靠调用

实现一个 Tool Calling Demo 并不困难,真正进入生产环境之后,问题才会大量出现。

模型可能选择错误的工具,例如本来应该使用内部文档搜索,却调用了互联网搜索;也可能生成错误参数,例如接口需要数值类型的 user_id,模型却传入用户姓名;还可能因为上下文判断失误不断重复调用同一个工具,甚至执行不应该自动执行的高风险操作。

因此,生产级 Tool Calling 必须建立额外的安全和可靠性约束。

最基础的一层是 Schema Validation。模型生成的参数不能直接执行,而应该先经过字段完整性、类型合法性和值域检查。

例如:

validate(arguments)

只有参数验证通过之后,系统才应该继续调用真正的业务函数。

第二层是 权限控制。模型拥有某个 Tool,并不意味着它应该获得这个系统的全部权限。

例如数据库工具最好拆分为:

query_database
execute_database_write

而不是直接提供一个可以执行任意 SQL 的万能接口。

第三层是 高风险操作确认。删除文件、转账、发送邮件、取消订单、修改数据库这类具有明显副作用或者不可逆性的操作,不应该只因为一次模型判断就直接执行。

更合理的设计通常是:

LLM 提出操作 -> 系统识别高风险行为 -> 请求用户确认 -> 真正执行

第四层是 运行限制。Agent 必须设置最大执行步数、最大 Tool Call 次数、超时时间和 Token 上限,否则一次错误的规划就可能形成无限调用循环。

因此,真正的 Tool Calling 工程远不只是让模型“会调函数”,而是要控制模型在什么条件下可以调用、调用什么、调用多少次,以及调用失败之后如何恢复。

Tool Calling 与 Workflow

一个常见误区是,既然模型已经可以自己判断应该调用哪个工具,那么是不是就不再需要 Workflow。

实际上,在生产环境中通常恰恰相反。

完全自由的 Agent 可以让模型自行选择任何工具并决定下一步动作,这种方式灵活性很高,但可控性较差;完全固定的 Workflow 则能够保证流程稳定,但缺乏根据复杂自然语言动态调整的能力。

真正实用的 Agent 系统往往位于两者之间:

确定性 Workflow + 局部 LLM 决策 + Tool Calling

例如一个订单处理系统可以设计为:

订单查询 -> LLM 判断用户诉求 -> 业务规则校验 -> LLM 选择处理方式 -> Tool Calling -> 结果确认

在这种结构中,大模型负责它最擅长的理解、判断和规划,而传统代码负责约束、验证、执行和一致性。

不是让 LLM 替代 Workflow,而是让 LLM 成为 Workflow 中可以进行动态判断的节点。

Tool Calling、MCP 和 Agent 的关系

Tool Calling、MCP 和 Agent 经常一起出现,但它们并不是同一个概念。

Tool Calling 是能力。

它描述的是模型能够根据任务决定什么时候调用工具,以及如何生成工具参数。

MCP 是协议。

它解决的是如何以更加标准化的方式向模型或者 Agent 暴露工具、资源和外部能力。

Agent 是完整系统。

一个实际的 Agent 往往包含 LLM、Tool Calling、State、Memory、Workflow 和 Runtime 等多个组成部分。

可以简单理解为:

Agent
├── LLM
├── Tool Calling
├── State
├── Memory
├── Workflow
└── Runtime
    └── MCP / API / Function
        └── External Systems

因此,Tool Calling 并不等于 Agent,但绝大多数真正能够执行任务的 Agent,都离不开 Tool Calling。

它真正改变了什么?

过去的软件系统通常是:

用户 -> UI -> 预先写好的业务逻辑 -> API

程序员需要提前设计用户能够做什么、应该点击哪个按钮,以及每一种操作最终对应哪个函数。

加入 LLM 和 Tool Calling 之后,系统开始变成:

用户自然语言目标 -> LLM 理解 -> 动态选择工具 -> 组合已有能力 -> 完成任务

过去的软件交互逻辑更接近:

用户点击某个按钮 -> 调用某个函数

而 Agent 系统逐渐开始变成:

用户告诉系统自己想要什么 -> 模型决定应该调用哪些函数

这并不是简单地给原来的软件增加一个聊天框,而是在改变软件能力的组织方式。

Tool Calling 的本质,是把自然语言和结构化程序能力连接起来,让 LLM 从“生成内容的模型”变成“能够参与软件系统决策的组件”。

而现代 AI Agent 的很多能力,正是从这里开始的。