news 2026/3/25 17:17:03

做 Agent 之前,彻底搞懂 AI 的 Function Calling / Tools

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
做 Agent 之前,彻底搞懂 AI 的 Function Calling / Tools

搞懂Agent基础系列,LLM调用链进化之路:

*1. [拒绝“裸奔”调用:LLM 接入真实世界的五次进化]

上篇(链接如上)我们解决了一个问题:让 AI 按你说的格式,老老实实返回 JSON。

现在这件事你已经会干了:

  • 用户说:「我想查上海的天气」
  • AI 乖乖返回:{ "city": "上海" }
  • 你的代码拿着这个 JSON 去调天气 API,一切顺滑

这种玩法在预先设计好的流程里特别好用:

  • 问题是固定的,比如「查天气」「查快递」「查库存」
  • 你知道自己要什么字段(城市、运单号、SKU…)
  • 也知道下一步要调哪个 API

本质上,这是在用 Structured Output 做「表单填空」:

我先设计好一张表,让 AI 帮我把表填满,然后我拿着这张表去调接口。

只要流程是「可穷举、可编排」的,这一套就非常好用。

问题来了:真正的聊天场景,根本不是这么玩的。

你把大模型接进自己应用里,用户不会配合你走流程,它会这样问:

  • 「上海天气怎么样?」—— 你心里想:应该调天气 API
  • 下一句直接换:「特斯拉股价多少?」—— 应该调股票 API
  • 再下一句:「明天有什么会议?」—— 应该查日历
  • 再下一句:「北京到上海机票多少钱?」—— 又是航班 API

每一句话都可能是一个完全不同的任务。

这时候 Structured Output 突然就不够用了:

  • 你没法提前知道用户下一句要问什么
  • 也就没法提前设计「返回什么样的 JSON」
  • 更没法写死:某个 JSON 一定对应某个 API

结果就是:第一句还能按套路提取字段,第二句开始整个链条直接断掉。

Structured Output 只解决了一个问题:「当我知道要什么字段时,AI 能不能按格式给我?」

它完全解决不了另一个更关键的问题:「当我不知道用户到底想干什么时,该调哪个 API?」

那有没有可能,让 AI 自己做这件事——用户说一句话,它自己判断:「这次该用天气 API」「这次该用股票 API」?

有的,这就是这一篇要讲的东西:

Function Calling / Tool Calling:让 AI 自己选工具,你只负责帮它把活干了。

AI 能“说”,但不能“做”

先搞清楚一个本质问题:LLM 到底能干什么?

答案很残酷:它只能生成 token(文字)

  • 它没法自己发 HTTP 请求
  • 没法自己读写数据库
  • 没法自己调用任何外部 API
  • 甚至连“真的执行一行代码”都不行

你可以把它当成一个超能版输入法 / 文案机: 你给一段话,它接着往下写。写得再聪明,本质都是在「预测下一个 token」。

但它可以干的一件事,是「看懂意图 + 拿主意」:

  • 看懂用户是在问天气,还是问股价,还是问会议
  • 决定「下一步该干什么」

所以 Tool Calling / Function Calling 的基本思路就是:

  1. 你告诉 AI:我这里有一堆工具可用(天气、股票、日历、航班……)

  2. 用户提问

  3. AI 不直接瞎编答案,而是说一句:

    「我打算调用 get_weather(city=‘上海’)」

  4. 你的程序根据这句话,去真的调用天气 API

  5. 把真实结果喂回 AI,让它用自然语言把结果解释给用户听

一句话总结:

AI 负责“决定调用谁”,程序负责“真的去干活”。

早期方案是靠 Prompt 硬凑

在 Function Calling 这种「原生能力」出现之前,大家都是硬玩 Prompt。

套路大概是这样:

System: 当用户问天气时,请输出以下格式:[FUNCTION_CALL]name: get_weatherargs: {"city": "xxx"}[/FUNCTION_CALL]User: 上海今天天气怎么样?

然后你的程序拿到这段字符串,用正则把:

[FUNCTION_CALL]...[/FUNCTION_CALL]

中间那一坨抠出来,当成一次函数调用描述。

问题和上一篇讲的“让 AI 输出 JSON”是一模一样的:

  • 它可能在标签外面加一堆废话
  • 标签写错、少个中括号
  • args里的 JSON 不合法
  • 参数名拼错、类型不对……

只要是「靠说服 AI 按格式输出」,就会出现同一种问题:

你把格式写得再清楚,模型也有概率「不听话」。

Function Calling:把“调函数”变成模型的母语

OpenAI 在 2023 年中正式放出 Function Calling 能力。其他厂商也陆续跟上,只是名字不同:

  • OpenAI:一开始叫 Function Calling,后来 API 里统一成tools
  • Claude:叫Tool Use
  • Gemini:文档里叫 Function Calling,但参数形式也都是 tools

不管叫什么,本质是一件事:

在模型的输出体系里,长出了一种新的“说法”: 不是对用户说话,而是对你的程序说「我要调哪个工具」。

从我们在 API 这一侧能观察到的行为,可以把 Function Calling 拆成三块:

  1. 工具定义被“塞”进了上下文
  2. 模型输出里多了一段“调用声明”
  3. API 在流里把这段“调用声明”拎出来,变成结构化字段

下面按调用链的视角拆开说。

1)告诉模型你有哪些“积木”:工具定义

你先得把手上的 API 都描述成一块块「可以调用的工具」,传给 LLM。

以 OpenAI 为例,工具定义长这样(简化版):

const tools = [{type: "function",function: { name: "get_weather", description: "获取指定城市的当前天气信息。当用户询问天气、气温时使用。", parameters: { type: "object", properties: { city: { type: "string", description: "城市名称,如上海" } }, required: ["city"] } }}];

对你来说,这就是一个 JS 对象。 对模型来说,这最终会被转换成一段更“像代码”的文本注入到上下文里,比如:

// 下面这些是你可以调用的工具:namespace functions { // 获取指定城市的当前天气信息。当用户询问天气、气温时使用。 type get_weather = (_: { // 城市名称,如上海 city: string, }) => any;}

你可以把它理解为:我们在模型脑子里声明了一堆「函数签名 + 注释」。

至于底层到底是怎么塞进去的、用的是不是 TypeScript 这套样式,不重要。重要的是:

  • 它能看见有哪些函数
  • 每个函数的名字、参数、注释都在
  • 形式接近代码,而代码是它最熟悉的语料之一

2)模型不是直接“干活”,而是输出一段“调用声明”

用户问一句:

「上海今天天气怎么样?」

和普通对话的差别在于:这次模型不会直接跟用户聊天,而是给你一段“调用计划”。

你在 API 侧拿到的大致是这种结构(不同厂商字段不一样,意思是一样的):

{ "role": "assistant", "tool_calls": [ { "id": "call_abc123", "type": "function", "function": { "name": "get_weather", "arguments": "{\"city\": \"上海\"}" } } ]}

从模型内部视角,你可以把它脑补成类似这样的输出流:

<|tool_call|>{"name": "get_weather", "arguments": {"city": "上海"}}<|end_tool_call|>

这里的<|tool_call|>/<|end_tool_call|>不是给人看的文字,而是特殊标记 token。 API 在流里一看到这些标记,就知道:

“哦,模型现在不是在跟用户说话,而是在跟我说: ——‘我要调用 get_weather,参数是 {city: “上海”}。’”

然后:

  1. 把这段 JSON 抠出来
  2. 解析成结构化数据
  3. 塞进tool_calls字段里返回给你

这一步,你可以完全当成「模型在发起一次 RPC 调用请求,只不过用的是自然语言世界里的 token」。

3)真正调用 API 的,是你这边的代码

拿到了tool_calls,接下来就是纯工程问题了:

  • 你写的get_weather函数里面发 HTTP
  • 跟真正的天气服务打交道
  • 把返回值格式化成字符串 / JSON,准备喂回模型

伪代码大概是这样:

for (const toolCall of choice.message.tool_calls ?? []) { const args = JSON.parse(toolCall.function.arguments); const result = await getWeather(args.city); // 真正的 API 调用 messages.push({ role: "tool", tool_call_id: toolCall.id, content: result });}

然后再把messages丢回 API,再来一轮模型输出。

这时模型看到的上下文里,多了一条“工具的执行结果”,大概长这样:

工具 get_weather 的返回值是:{"city": "上海", "temperature": 25, "weather": "多云"}

于是它就会继续生成一条真正写给用户的回答

「上海今天多云,气温 25 度,出门记得带件外套。」

至此,一次完整的 Tool Calling 就结束了。

本质是三层配合,但你只需要关心调用链

从调用链的视角看,Function Calling / Tools 这套东西可以浓缩成一句话:

在输出流里插入了一段“不是对用户说话,而是对你的程序说『我要调哪个工具』”的内容。

配套的三件事是:

  1. 工具定义注入上下文: 你把 API 描述成工具,LLM 看见这些“函数签名 + 注释”。
  2. 模型输出工具调用声明: 它在输出过程中,选择“用一段 JSON + 特殊标记”来表达「我要调用某个函数」。
  3. API 拦截 + 解析 + 交给你执行: API 把这段“调用声明”从输出里拎出来,变成tool_calls这样的结构化字段,你去真的调库 / 调 HTTP。

你完全不用关心「底层是怎么训练出来的」,只要记住:

  • 这不是 Prompt 小技巧,而是一套产品级别的协议
  • 模型确实“长”出了一种新的说话方式:向你的代码发起函数调用请求

为什么 description 这么关键?

既然工具定义是用“代码 + 注释”的形式塞给模型的,那很自然:

description 就是模型看到的“函数注释”,写得烂,模型就用不好。

你可以用一个简单的标准来自查:

**“如果一个只会看中文+代码的实习生,拿着这段描述,能不能大致用对这个函数?”**能,那模型大概率也能;反之亦然。

看两个极端例子。

差的写法:

description: "获取天气"

好的写法:

description: "获取指定城市的当前天气信息,包括温度和天气状况。当用户询问某地天气、气温、要不要带伞等问题时使用。"

好的 description 应该说清楚三件事:

  1. 这个函数能干什么
  2. 在什么场景下应该用它
  3. 参数怎么填

很多人觉得“工具选错了是模型不聪明”,实际上相当一部分是工具描述写得像废话,模型根本没法正确对齐。

Function Calling → Tools:从“调一个”到“调一堆”

Function Calling 解决了“能不能调函数”的问题,很快就遇到下一个:

“函数不止一个怎么办?”

典型场景:

  • 天气:get_weather
  • 股价:get_stock_price
  • 日历:list_events
  • 发邮件:send_email
  • ……

你希望的是:

  • 用户问天气 → 它挑get_weather
  • 问股价 → 挑get_stock_price
  • 问会议 → 挑list_events
  • 必要的时候还能一次挑多个

OpenAI 在 2023 年 11 月把这一层抽象成了Tools

  • 之前(2023.06):functions+function_call
  • 之后(2023.11):统一成tools+tool_choice

有两个关键变化:

  1. 语义上从“函数”升级为“工具”
  • 函数只是工具的一种形式
  • 以后还可以有别的工具类型(代码执行、文件搜索、浏览器等)
  1. 支持一次返回多个调用
  • 问「上海和北京哪个更热」时,模型可以一次吐出两条tool_call
  • 你的程序可以并行执行两个 HTTP 调用,效率直接翻倍

这个并行本质上很简单:

  • 模型负责列出「要调用哪些工具 + 参数」
  • 是否并行执行,是你程序的决策
  • LLM 不关心网络、IO,它只关心“调用计划”

一图看懂:完整的 Tools 调用循环

用过 Tools 的同学,对下面这张“循环图”应该都不陌生:

┌─────────────────────────────────────────────────────────────┐│ 你的程序 ││ ││ 1. 调用 LLM API:用户问题 + 工具定义 ││ ↓ ││ ┌─────────────────────────────────────────────────────┐ ││ │ LLM / Tools │ ││ │ 模型思考:需要调用 get_weather │ ││ │ 输出:tool_calls(函数名 + 参数) │ ││ └─────────────────────────────────────────────────────┘ ││ ↓ ││ 2. 检测到 tool_calls,执行真实函数 ││ result = get_weather("上海") // 真正发 HTTP ││ ↓ ││ 3. 把 result 作为 tool 结果喂回 LLM ││ ↓ ││ ┌─────────────────────────────────────────────────────┐ ││ │ LLM / Tools │ ││ │ 模型看到结果,生成自然语言回答 │ ││ └─────────────────────────────────────────────────────┘ ││ ↓ ││ 4. 返回最终答案给用户 ││ │└─────────────────────────────────────────────────────────────┘

关键点:至少两次 API 调用

  • 第一次:用户问题 + 工具定义 → 模型返回tool_calls
  • 第二次:tool_calls的执行结果 → 模型返回最终回答

如果任务更复杂(比如还要发邮件、写日报),这个循环可能会跑三次、四次甚至更多。这就是最原始的Agent 循环雏形

模型负责决定“下一步干什么”,程序负责真的去干,然后再把结果喂回去。

放一个完整的 TypeScript 示例

下面这个例子,你几乎可以直接 copy 改改就用。

import OpenAI from"openai";const openai = new OpenAI();// 工具定义(会被转成“代码 + 注释”的形式注入模型上下文)const tools: OpenAI.ChatCompletionTool[] = [ { type: "function", function: { name: "get_weather", description: "获取指定城市的当前天气信息。当用户询问天气、气温时使用。", parameters: { type: "object", properties: { city: { type: "string", description: "城市名称,如:上海、北京" } }, required: ["city"] } } }];// 模拟天气 API(实际项目中这里是真实的 HTTP 请求)function getWeather(city: string): string {const data: Record<string, number> = { 上海: 25, 北京: 18 };returnJSON.stringify({ city, temperature: data[city] ?? 20 });}// 核心循环asyncfunction chat(userMessage: string): Promise<string> {const messages: OpenAI.ChatCompletionMessageParam[] = [ { role: "user", content: userMessage } ];while (true) { const response = await openai.chat.completions.create({ model: "gpt-4o", messages, tools, }); const choice = response.choices[0]; // 1)模型觉得可以直接回答了 if (choice.finish_reason === "stop") { return choice.message.content || ""; } // 2)模型说:“我要调工具” if (choice.finish_reason === "tool_calls") { // 把这条“调用声明”也记进对话里 messages.push(choice.message); for (const toolCall of choice.message.tool_calls ?? []) { const args = JSON.parse(toolCall.function.arguments); const result = getWeather(args.city); // 真正执行函数 // 把工具的执行结果喂回模型 messages.push({ role: "tool", tool_call_id: toolCall.id, content: result }); } // 然后继续 while 循环,再调一次 API,让模型基于工具结果给最终答案 } }}// democonst answer = await chat("上海天气怎么样?");console.log(answer); // 例如:"上海今天气温 25 度,天气良好,适合出门。"

这个 demo 做了几件刚好对应我们上面那张图的事:

  1. 把业务 API 包装成tools 定义
  2. 让模型决定什么时候用哪个 tool
  3. 你这边真正执行getWeather
  4. 把结果再喂给模型,让它生成自然语言回答

总结一下

核心认知:

  1. **AI 不会自己调 API,它只会“写下一步 token”。**Function Calling / Tools 做的是:

    “给它一种新的说话方式,让它能对你的程序说『我要调哪个函数、带什么参数』。”

  2. 这不是高级 Prompt,而是一套标准调用链:

  • 工具定义注入上下文
  • 模型输出“调用声明”
  • API 拦截 + 解析 + 执行 + 喂回结果
  1. **description 写得好不好,直接决定工具选得准不准。**把模型当成看你代码注释的实习生来写 description,会好很多。
  2. Function Calling → Tools 是一次很自然的升级:
  • 从“只能调一个函数”到“一次能规划和触发多个工具”
  • Agent / 工作流的雏形就长在这条循环上

这条调用链目前在哪儿?

简单对一下时间线,帮你把心里那条“进化线”画完整:

时间进化解决的问题
早期Prompt 硬凑靠 AI 自觉按格式输出,极不稳定
2023.06Function Calling模型长出「函数调用」这门“母语”
2023.11Tools抽象成通用工具系统,支持多函数 / 并行调用

在这一篇为止,我们解决的是:

  • Structured Output:我知道要什么字段 → 你帮我按格式说
  • Function Calling / Tools:我不知道要调哪个 API → 你帮我决定用哪个工具

接下来还有两个更现实的问题:

  1. 想让 AI 操作 Notion / GitHub / 日历, 每个产品都要自己造一套 tools 吗?

    有没有可能:Notion 官方提供一套标准化的工具接口,所有 LLM / Agent 都能直接用?

  • A 团队写了一套「Notion Tools」
  • B 团队也要再写一套「Notion Tools」
  • 各家 AI 产品都在重复造轮子
  1. 工具越来越多,token 和上下文怎么办?

    现在只有get_weather一个工具还好说, 如果你这边有 50 个、100 个工具,全都变成“代码 + 注释”塞进上下文:

  • 上下文本身就要吃掉几十万 token
  • 模型选错工具的风险上升
  • 成本和复杂度一起炸掉

这两个问题,就是下一篇要聊的主角:

MCP:工具的“USB 接口”标准,解决跨产品复用;

Advanced Tool Use:工具太多时,怎么做搜索、裁剪和程序化调用。

​最后

我在一线科技企业深耕十二载,见证过太多因技术卡位而跃迁的案例。那些率先拥抱 AI 的同事,早已在效率与薪资上形成代际优势,我意识到有很多经验和知识值得分享给大家,也可以通过我们的能力和经验解答大家在大模型的学习中的很多困惑。

我整理出这套 AI 大模型突围资料包:

  • ✅AI大模型学习路线图
  • ✅Agent行业报告
  • ✅100集大模型视频教程
  • ✅大模型书籍PDF
  • ✅DeepSeek教程
  • ✅AI产品经理入门资料

完整的大模型学习和面试资料已经上传带到CSDN的官方了,有需要的朋友可以扫描下方二维码免费领取【保证100%免费】👇👇
​​

为什么说现在普通人就业/升职加薪的首选是AI大模型?

人工智能技术的爆发式增长,正以不可逆转之势重塑就业市场版图。从DeepSeek等国产大模型引发的科技圈热议,到全国两会关于AI产业发展的政策聚焦,再到招聘会上排起的长队,AI的热度已从技术领域渗透到就业市场的每一个角落。


智联招聘的最新数据给出了最直观的印证:2025年2月,AI领域求职人数同比增幅突破200%,远超其他行业平均水平;整个人工智能行业的求职增速达到33.4%,位居各行业榜首,其中人工智能工程师岗位的求职热度更是飙升69.6%。

AI产业的快速扩张,也让人才供需矛盾愈发突出。麦肯锡报告明确预测,到2030年中国AI专业人才需求将达600万人,人才缺口可能高达400万人,这一缺口不仅存在于核心技术领域,更蔓延至产业应用的各个环节。

​​

资料包有什么?

①从入门到精通的全套视频教程⑤⑥

包含提示词工程、RAG、Agent等技术点

② AI大模型学习路线图(还有视频解说)

全过程AI大模型学习路线

③学习电子书籍和技术文档

市面上的大模型书籍确实太多了,这些是我精选出来的

④各大厂大模型面试题目详解

⑤ 这些资料真的有用吗?

这份资料由我和鲁为民博士共同整理,鲁为民博士先后获得了北京清华大学学士和美国加州理工学院博士学位,在包括IEEE Transactions等学术期刊和诸多国际会议上发表了超过50篇学术论文、取得了多项美国和中国发明专利,同时还斩获了吴文俊人工智能科学技术奖。目前我正在和鲁博士共同进行人工智能的研究。

所有的视频教程由智泊AI老师录制,且资料与智泊AI共享,相互补充。这份学习大礼包应该算是现在最全面的大模型学习资料了。

资料内容涵盖了从入门到进阶的各类视频教程和实战项目,无论你是小白还是有些技术基础的,这份资料都绝对能帮助你提升薪资待遇,转行大模型岗位。


智泊AI始终秉持着“让每个人平等享受到优质教育资源”的育人理念‌,通过动态追踪大模型开发、数据标注伦理等前沿技术趋势‌,构建起"前沿课程+智能实训+精准就业"的高效培养体系。

课堂上不光教理论,还带着学员做了十多个真实项目。学员要亲自上手搞数据清洗、模型调优这些硬核操作,把课本知识变成真本事‌!

​​​​

如果说你是以下人群中的其中一类,都可以来智泊AI学习人工智能,找到高薪工作,一次小小的“投资”换来的是终身受益!

应届毕业生‌:无工作经验但想要系统学习AI大模型技术,期待通过实战项目掌握核心技术。

零基础转型‌:非技术背景但关注AI应用场景,计划通过低代码工具实现“AI+行业”跨界‌。

业务赋能 ‌突破瓶颈:传统开发者(Java/前端等)学习Transformer架构与LangChain框架,向AI全栈工程师转型‌。

👉获取方式:

😝有需要的小伙伴,可以保存图片到wx扫描二v码免费领取【保证100%免费】🆓**

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/3/21 13:26:36

科研跃迁新引擎:解锁书匠策AI期刊论文写作的「六维智能矩阵」

在学术研究的浩瀚星空中&#xff0c;期刊论文的撰写始终是学者们攀登高峰的必经之路。然而&#xff0c;传统写作模式正面临信息过载、逻辑碎片化、格式规范严苛等现实挑战。书匠策AI科研工具&#xff08;官网&#xff1a;www.shujiangce.com&#xff09;凭借其突破性的「六维智…

作者头像 李华
网站建设 2026/3/13 4:56:19

如何编写单元测试验证TensorFlow镜像中模型逻辑正确性

如何编写单元测试验证TensorFlow镜像中模型逻辑正确性 在现代AI系统开发中&#xff0c;一个看似微小的数值错误或接口不一致&#xff0c;可能在生产环境中引发连锁反应——分类模型突然输出全零&#xff0c;推荐系统返回空结果&#xff0c;甚至整个推理服务因NaN值崩溃。这类问…

作者头像 李华
网站建设 2026/3/21 1:20:49

模型版本控制新思路:Git LFS管理TensorFlow镜像产出物

模型版本控制新思路&#xff1a;Git LFS管理TensorFlow镜像产出物 在一家金融科技公司的AI团队中&#xff0c;数据科学家小李刚完成了一个新的反欺诈模型训练。他兴奋地准备将saved_model/目录提交到Git仓库&#xff0c;却发现一次git push花费了近40分钟&#xff0c;且本地克隆…

作者头像 李华
网站建设 2026/3/24 12:47:13

Open-AutoGLM云端实战部署全记录(阿里云环境配置大揭秘)

第一章&#xff1a;Open-AutoGLM云端部署概述Open-AutoGLM 是一款基于 AutoGLM 架构的开源大语言模型推理引擎&#xff0c;专为高效云端部署与低延迟服务响应设计。其核心优势在于支持动态批处理、多实例并行及自动缩放&#xff0c;适用于高并发场景下的自然语言理解与生成任务…

作者头像 李华
网站建设 2026/3/14 9:27:39

探索汇川 H5U PLC 程序框架:兼具性价比与实用性的自动化利器

汇川H5U PLC程序框架。 需要有威纶通/步科等触摸屏 包含34轴程序样例。 共3套&#xff0c;编程手册等 是比较完整的程序框架. PLC还是性价比挺高&#xff0c;特别是对于伺服的总线。 主打的伺服控制是ETHERCAT总线 程序写的条理分明&#xff0c;清晰易懂&#xff0c;注释清楚&…

作者头像 李华