Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

3.2 Function Calling 机制

🎯 本节目标:理解 Function Calling 的完整流程,能写出最小可运行的示例。


先看懂"发生了什么"

在深入代码之前,让我们用一个日常对话来模拟整个过程:

Function Calling 完整流程

注意这个循环中的角色分工

角色做什么不做什么
LLM决定是否需要工具、选择哪个工具、生成参数不实际调用任何 API
你的代码执行真正的函数/API 调用不做决策
工具定义(JSON Schema)告诉 LLM 有哪些工具可用、每个工具怎么用不包含实现逻辑

完整流程:5 步

Function Calling 流程

🎬 交互式动画:观看用户、LLM 和工具引擎之间的消息传递全过程——包含多轮工具调用的完整通信协议。

▶ 打开 Function Calling 交互动画

Function Calling 5 步流程

这个流程可能会循环多次。比如用户说"如果下雨就发邮件提醒"——先查天气,再决定是否发邮件。


机制优先:先理解,不急着写完整代码

Function Calling 的核心不是某段 Python,而是一次由模型发起、由程序执行、再把结果交还给模型的消息往返。初学者可以先把它理解为下面这个分工流程:

步骤谁负责发生了什么读者需要抓住的重点
1. 定义工具开发者告诉模型有哪些工具、每个工具需要什么参数工具定义是"说明书",不是函数实现
2. 用户提问用户提出一个可能需要外部能力的问题模型先判断是否需要工具
3. 生成调用意图LLM输出"我要调用哪个工具,以及参数是什么"模型只生成指令,不亲自执行
4. 执行工具你的程序调用真实函数、API 或数据库这里才发生真实世界的动作
5. 返回结果你的程序 → LLM把工具结果放回对话历史模型基于结果组织最终回答

以"北京和上海天气怎么样?"为例,模型不会自己查天气。它会先生成两个查询意图:一个查北京,一个查上海;程序执行查询后,把两个城市的结果交回模型;模型再用自然语言总结给用户。

三个关键对象

对象可以用一句话理解为常见内容
工具函数真正干活的"手"查天气、搜网页、读数据库、发邮件
工具 Schema写给模型看的"说明书"工具名、用途、参数、必填字段
Agent 循环让模型和工具反复协作的"调度器"判断是否继续调用工具,或是否结束回答

一次调用的消息形态

不必急着看完整 SDK 代码,先看消息在系统里的形态:

阶段消息含义示例
用户输入用户提出目标"北京和上海的天气怎么样?"
模型输出模型请求工具调用 get_weather,参数分别是 北京上海
工具结果程序返回观察结果北京晴,上海多云
最终回答模型整合结果"北京今天晴,上海多云……"

伪流程

  1. 收到用户问题。
  2. 把用户问题和工具说明一起发给模型。
  3. 如果模型选择直接回答,就结束。
  4. 如果模型请求工具调用,程序解析工具名和参数。
  5. 程序执行真实工具,并把结果写回对话历史。
  6. 再次请求模型,让它基于工具结果继续回答或继续调用工具。

💡 为什么是循环? 因为一次工具调用可能不够。"查天气然后发邮件"这样的任务就需要两轮:第一轮查天气,第二轮根据天气结果决定是否发邮件。

完整的 SDK 代码将在第 3.5 节实战中统一展开。本节先把机制讲清楚:LLM 决策,代码执行,结果再反馈给 LLM。


关键设计决策

tool_choice:控制模型的工具使用策略

tool_choice 决定模型是否可以使用工具,以及是否必须使用工具。常见策略如下:

设置含义适合场景
auto让模型自己判断是否用工具通用对话型 Agent,最常用
none禁止工具调用,只输出文本纯聊天、角色扮演、无需外部数据的任务
required强制至少调用一个工具明确需要实时数据或外部动作的任务
指定工具名强制调用某个特定工具测试工具、构建固定流程
场景推荐设置
通用对话型 Agentauto
纯聊天 / 角色扮演none
明确知道需要数据的场景required
测试某个特定工具指定工具名

strict 模式:生产环境必开

OpenAI 在 2024 年推出了 Structured Outputs 功能。可以把 strict 理解为"参数格式强校验":开启后,模型输出的参数必须符合你声明的 JSON Schema。

开启严格模式时要注意三点:

  • 声明 strict: true:告诉模型必须严格遵守 Schema。
  • 禁止额外字段:通常需要设置 additionalProperties: false,避免模型凭空加参数。
  • 完整声明参数类型:每个字段都要有明确的类型、含义和必填规则。

生产环境强烈建议开启,它可以显著减少因参数格式错误导致的运行时崩溃。

并行工具调用:独立任务同时做

当多个工具之间没有依赖关系时,可以让模型一次返回多个调用指令:

用户:"同时查一下北京、上海、广州的天气" → 模型一次性返回 3 个 tool_calls → 你的代码并行执行 3 个查询(用 ThreadPoolExecutor 或 asyncio)→ 总等待时间 ≈ 最慢的那一个(而不是三个相加)

适用条件:工具之间互相独立。如果有依赖(先查天气→再决定是否发邮件),应设 parallel_tool_calls=False


错误处理:让 LLM 自己解决问题

这是初学者最容易忽略的设计要点:工具不要让异常直接炸掉整个 Agent,而要把可理解的错误返回给模型。

错误做法更好的做法
网络超时后直接抛异常,整个 Agent 中断返回"搜索超时,请换更短的关键词或稍后重试"
参数非法时只说 Invalid input返回哪个字段错了、期望格式是什么、收到的值是什么
权限不足时隐藏细节告诉模型需要用户授权或更换工具

为什么这样设计? 因为错误信息会出现在 LLM 的上下文中。一个聪明的 LLM 看到"搜索超时"后,可能会尝试缩短关键词重试;看到"API Key 无效"后会告知用户检查配置。这比直接抛异常优雅得多。


Function Calling 与 MCP 的关系

学完基础机制后,你会听到 MCP(Model Context Protocol) 这个概念——Anthropic 于 2024 年底推出的工具调用标准协议。

对比项Function CallingMCP
本质单平台的 API 能力跨平台的标准协议
工具绑定写死在代码里以独立服务运行,任何客户端可用
适合场景快速原型、单模型项目多模型共享、团队协作、生态复用

可以把它们理解为:Function Calling 是"自己做饭",MCP 是"点外卖"。前者灵活自由,后者标准化可复用。掌握了 Function Calling 后,理解 MCP 会非常自然——本质上就是把工具的定义和执行从代码中抽离出来变成独立服务。

详细内容见 第16章 Agent 通信协议


📝 动手练习

在继续阅读之前,试着回答以下问题(不要急着写代码,先用自然语言思考):

练习 1:如果要给 Agent 添加一个"当前时间"工具,它的 Schema 应该包含哪些信息?description 中应该说明什么?

参考答案

一个好的"当前时间"工具定义需要包含:

  • 工具名:例如 get_current_time,让模型一眼知道它是获取当前时间。
  • 使用时机:当用户问"现在几点""今天几号""当前日期"等问题时调用。
  • 参数设计:如果只返回本地当前时间,可以无参数;如果支持时区,则需要 timezone 参数。
  • 返回格式:例如返回日期、时间、时区和星期,方便模型组织回答。

关键点:即使工具没有参数,也要明确告诉模型"这个工具不需要用户提供额外信息"。

练习 2:下面的工具描述有什么问题?LLM 可能犯什么错?

{"description": "处理邮件相关的事情"}
参考答案

问题太多了:

  • 太模糊 —— "处理邮件"可能指发送、读取、删除、归档……LLM 不知道具体做什么
  • 没有说明何时使用 vs 何时不使用
  • 没有参数描述

更好的版本可以这样写:向指定邮箱发送邮件;仅在用户明确要求发送时调用;不适用于查询邮件或管理邮箱。


小结

要点一句话记住
核心思想LLM 当大脑做决策,代码当手脚去执行
5 步流程定义 → 发送 → 决策 → 执行 → 回答
最重要的事工具的 description 写得好不好,决定了 Agent 的智商
安全兜底错误信息返回给 LLM 而非抛异常
生产必开strict: true + additionalProperties: false
进阶方向MCP 协议实现跨平台工具复用

下一节:3.3 自定义工具的设计与实现