Function Calling(拓展阅读)

2,245 字 · 预计阅读 6 分钟

模型只会读字、写字,一样东西都碰不到。可 Claude Code 能读你的文件、跑测试、看日志——中间那座桥叫 Function Calling。

这一节是拓展阅读:你不会在 Claude Code 里手写 Function Calling 的代码。但看懂它,前面几节的零碎会拼成一张图:为什么工具定义一开局就占上下文,为什么每次执行都要过权限,为什么 MCP 装多了会拖累模型。


一、模型只会说,不会做

模型是文字进、文字出。训练完就冻结,不知道你磁盘上有什么,也执行不了任何东西。

你问它「杭州今天下雨吗」,它只能根据训练数据里的常识回答,或者干脆编。想让它看今天的代码、跑一次 npm test,它做不到。

最早的绕法是让人当搬运工:让它输出一段命令,你复制到终端,跑完把输出贴回去。几次还能忍,任务一长就崩:

  • 每轮都要人盯着,半夜跑不了
  • 它输出的格式时不时变一下,多一个空行、少一个反引号,粘贴就错
  • 一次只能走一步,改五个文件要看十几次输出

问题不在模型不够聪明,在于它没有一个结构化的出口:能说清楚「我要干什么、参数是什么」,让程序去执行。

Function Calling(函数调用)就是修这个的。

你在请求里附上一份工具清单,告诉模型「这些事你能让我做」。模型想用某个工具时,不再输出一段自然语言,而是输出一条结构化的调用请求:工具名 + 参数。你的程序执行完,把结果按格式送回给模型,模型接着往下想。

一句话:模型负责提要求,执行永远在模型外面。

这一点值得较真。模型没有删你的文件,是你的程序收到它的请求后删的。分不清这条界线,后面权限怎么卡、提示注入为什么危险,都会想歪。


二、一次调用发了什么

拿一个查天气的工具举例,省略模型名和鉴权头。请求里除了消息,多了一段 tools:

json
{
  "tools": [
    {
      "name": "get_weather",
      "description": "查询指定城市的当前天气。用户问天气、气温、要不要带伞时使用。",
      "input_schema": {
        "type": "object",
        "properties": {
          "city": { "type": "string", "description": "城市名,比如 杭州" }
        },
        "required": ["city"]
      }
    }
  ],
  "messages": [{ "role": "user", "content": "杭州今天要带伞吗?" }]
}

模型看完,回你两条内容:一句人话,一个 tool_use:

json
{
  "stop_reason": "tool_use",
  "content": [
    { "type": "text", "text": "我查一下杭州的天气。" },
    {
      "type": "tool_use",
      "id": "toolu_01ABC",
      "name": "get_weather",
      "input": { "city": "杭州" }
    }
  ]
}

到这儿模型就停了,它不会自己跑函数。

你的程序拿到这条请求,做四件事:校验参数(city 在不在、是不是字符串)→ 过权限 → 真的去调天气接口 → 把结果包成 tool_result 发回去。发的时候要把模型那条回复原样放进 messages,再追加结果:

json
{
  "role": "user",
  "content": [
    {
      "type": "tool_result",
      "tool_use_id": "toolu_01ABC",
      "content": "杭州 18℃,多云,降水概率 10%"
    }
  ]
}

模型这才继续,回一句「不用带伞」。

四步走完,记住三件事:

  1. 工具清单是请求的一部分,不是模型的记忆。 每次都要重新带上;不在清单里的工具,模型不知道它存在
  2. tool_use_id 是配对用的。 一次回复里可能有好几个调用,结果回来时必须对得上号。每个 tool_use 都必须有一条 tool_result,少一条,下一次请求直接报错
  3. 模型只挑工具、填参数。 会不会执行、怎么执行、要不要问过你,全看你写的客户端

一次回复,可以同时要几个工具

模型经常一口气发好几个 tool_use,比如同时读三个文件。客户端会看情况分批:只读的并行跑,会改东西的一个一个来(Claude Code 里连续只读工具默认最多并行 10 个)。这也是为什么 Agent 有时候看起来「同时在做几件事」。

模型输出是流式的,tool_use 的 JSON 也是一段段吐出来的。客户端要等参数拼完整、校验通过,才敢执行。


三、在 Claude Code 里,一切都是 Function Calling

现在回头看 Claude Code 干的活:

你看到的动作 背后的工具
读文件 Read
改文件 Edit、Write
跑命令 Bash
搜内容 / 找文件 Grep、Glob
查网页 WebFetch、WebSearch
用 MCP 查文档 mcp__context7__query-docs 这类

对模型来说没有区别:都是清单上的一项,都有名字、描述、参数 schema。内置的和 MCP 接进来的,走的是同一个出口。

前面 Loops 那一节的循环——思考 → 调工具 → 看结果 → 再思考——第二步就是 Function Calling。循环本身不在模型里,它是客户端(Claude Code)写的一段程序。

下次运行 /context,看工具定义那一栏:你看到的每一项,就是模型眼里的一个可调用工具,也是它开局就占掉一块上下文的原因。

前面几节和后面要讲的「为什么」,到这儿能串起来了:

书里讲过的 在 Function Calling 里的位置
上下文窗口里那 14.9k 大头是系统提示词和工具定义,每个工具都有一份说明书
权限模式 卡在「模型要调工具」和「工具真正执行」之间
CLAUDE.md 一段文本,每次会话自动加载,改的是模型想做什么
Skill 也是一段文本,教它怎么用已有的工具,不新增工具
MCP 往工具清单里加几项,工具变了,机制没变
工具返回的结果 和工具定义一样进上下文,下一轮跟着消息一起发回去

所以「Agent 做错事」要分两种:想错了(提示词、描述、上下文的问题),和本不该让它做(权限和工具清单的问题)。第一种改文字,第二种改闸门。


四、它不是什么

Function Calling 经常被说成「Agent 的能力」,其实它只是中间那座桥。把它和几个容易混的东西分开:

  • Function Calling ≠ Agent。 Agent = 模型 + 工具 + 循环 + 上下文管理 + 权限。Function Calling 只解决「模型怎么把要求说清楚」,循环、压缩、权限都得客户端自己写
  • Function Calling ≠ 模型自己执行。 模型只输出意图,执行在你这边。所以「AI 自己把服务器删了」这种说法,省掉了中间那个真正动手的程序
  • Function Calling ≠ 一定可靠。 它会挑错工具,也会编出不存在的工具名、把参数填错。所以要有参数校验、要有失败重试,也要能让它看到错误信息后自己改
  • 工具越多 ≠ 越好。 每一项工具都占上下文,描述重叠时模型更容易挑错。这也是后面讲 MCP 时强调「按需接」的原因之一

为什么 2023 年才普及

不是没人想过。早些年也有让模型输出固定格式、再用正则去抠的做法,问题是模型不够稳:同一个提示,今天输出 JSON,明天输出一段解释加 JSON,解析器天天修。

等模型能稳定照着 schema 输出、也能判断「这个任务该不该调工具」了,这套东西才真正能用。2023 年 6 月,OpenAI 在 API 里加了 function calling;Anthropic 后来在 Messages API 里用 tools + tool_use 实现了同一件事;2024 年底 MCP 把这套思路往「工具怎么被发现和共享」推了一步。

叫法也乱:Function Calling、Tool Calling、Tool Use,说的是同一件事。早期强调「调用函数」,现在更常说「调用工具」,因为工具不一定是个函数,也可能是一次查询、一次浏览器操作。

你不用写,但你在影响它

在 Claude Code 里,你确实不用碰 tools 参数和 tool_result。但你每天都在间接改它:

  • 写进 CLAUDE.md 的话、Skill 的 description,会影响它选哪个工具、什么时候选
  • 权限规则决定哪些 tool_use 能真的执行
  • 接哪些 MCP,决定清单有多长、模型要在一堆相似工具里挑

懂这层机制,排查问题就有方向:它的工具选错了,先看描述和工具数量,别急着换模型;它做了不该做的事,先看权限,别只在 CLAUDE.md 里写「不要」。


小结

  1. 模型只会输出文字。Function Calling 给它一个结构化出口:说清要调哪个工具、参数是什么;执行永远在模型外面
  2. 一次调用要发两次请求:第一次带上工具清单、拿回 tool_use;第二次带上 tool_result、拿回最终回答。工具清单每轮都要带,tool_use_id 必须配对
  3. 它不是 Agent,只是一座桥。Claude Code 的每个动作都走这座桥,所以上下文、权限、MCP、Skill 的很多现象,都能在这里找到解释

这一节写得怎么样?
满分 100

作者会看每一条打分和留言。真实感受就好,低分也欢迎,有了反馈会帮助我们更好的优化课程。

常见问题

这一节的常见问题还在整理。读到没看懂的地方,可以先在下面「本节答疑」里问作者。

本节答疑

拓展阅读

这一节的拓展阅读还在整理。

随堂测验·5 道题·约 3 分钟
检验一下这一节的关键概念
单选和多选共 5 题,答对 4 题通过。交卷后逐题看解析,可以重做。
尚未测验
—
待测验NOT YET
待测验5 道题等你来答

免费章节到这里就读完了

下一节《安装 Claude Code》起是会员内容,未开通只能试读前 30%。开通专业版可阅读全文,并解锁全部官方课程与后续更新。

开通专业版

支持 7 天无理由退款 · 早鸟价 99 元/年(售完恢复365/年)

课程中涉及的第三方项目,其著作权与商标归各自权利人所有;引用处的署名与许可见《开源与来源说明》。