工具调用:FunctionCall与ToolUse

工具调用:Function Call 与 Tool Use

工具调用是 Agent 的「手」,让大模型能操作外部世界。这篇讲 Function Calling 的原理、工具怎么定义、模型怎么选工具、参数怎么传、常见的工具类型,以及开发中的最佳实践。

大家好,我是黒漂技术佬。

大模型本身只能输出文字,知识有截止日期、不会算精确数学、不能查数据库、发不了邮件。接上工具之后,能力边界大大扩展。

Function Calling(函数调用,也叫 Tool Use)就是大模型调用外部工具的标准方式。现在主流模型都原生支持,是 Agent 开发的核心技术。

这篇讲工具调用的原理、工具定义、多工具选择、参数传递、常见工具类型、最佳实践。


一、什么是 Function Calling?

概念

你把工具(函数)的描述告诉大模型,模型根据用户问题判断:

  1. 需不需要调用工具
  2. 调用哪个工具
  3. 参数填什么

然后模型输出结构化的调用指令,你的程序执行这个函数,把结果返回给模型继续处理。

为什么不用普通对话?

普通对话里你也可以让模型输出「调用搜索(xxx)」,但格式不稳定、容易错。Function Calling 是模型专门训练过的,输出是标准 JSON 格式,稳定可靠。

交互流程

1. 你:用户问题 + 工具定义 → 发给模型
2. 模型:判断要调用工具 → 返回 tool_calls(JSON格式)
3. 你:执行函数,拿到结果
4. 你:把结果发给模型
5. 模型:根据结果生成最终回答

多轮的话就是 2-3-4 循环,跟 ReAct 对应。


二、工具怎么定义?

JSON Schema 格式

每个工具用 JSON Schema 描述:名字、描述、参数类型、参数说明。

{
    "type": "function",
    "function": {
        "name": "search_web",
        "description": "搜索互联网获取实时信息,回答不知道的问题",
        "parameters": {
            "type": "object",
            "properties": {
                "query": {
                    "type": "string",
                    "description": "搜索关键词"
                }
            },
            "required": ["query"]
        }
    }
}

字段说明

字段 作用
name 函数名,模型调用时返回这个名字
description 函数说明,模型靠这个判断什么时候用
parameters 参数定义,JSON Schema 格式
properties 每个参数的名字、类型、描述
required 必填参数列表

复杂参数

参数可以是嵌套对象、数组、枚举:

{
    "name": "send_email",
    "description": "发送邮件给指定收件人",
    "parameters": {
        "type": "object",
        "properties": {
            "to": {
                "type": "string",
                "description": "收件人邮箱地址"
            },
            "subject": {
                "type": "string",
                "description": "邮件主题"
            },
            "body": {
                "type": "string",
                "description": "邮件正文"
            },
            "priority": {
                "type": "string",
                "enum": ["normal", "high", "low"],
                "description": "优先级"
            }
        },
        "required": ["to", "subject", "body"]
    }
}

多个工具

一次可以传多个工具定义,模型自己选:

tools = [
    { "type": "function", "function": { "name": "search_web", ... } },
    { "type": "function", "function": { "name": "calculator", ... } },
    { "type": "function", "function": { "name": "get_weather", ... } },
]

三、调用过程详解

第一步:用户提问,带上工具定义

messages = [
    {"role": "user", "content": "北京今天天气怎么样?"}
]

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

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

第二步:模型返回工具调用

模型判断需要调用工具,返回:

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

tool_calls 就是模型要调用的工具列表。arguments 是 JSON 字符串。

第三步:执行工具函数

import json

tool_call = response.tool_calls[0]
func_name = tool_call.function.name
args = json.loads(tool_call.function.arguments)

# 执行对应的函数
if func_name == "get_weather":
    result = get_weather(**args)

第四步:把结果传回模型

messages.append(response)  # 把模型的tool_call消息加回去
messages.append({
    "role": "tool",
    "tool_call_id": "call_abc123",
    "content": result  # 比如"北京今天晴,25-32℃"
})

final_response = llm.chat(messages=messages, tools=tools)

第五步:模型生成最终回答

模型拿到工具结果,整理成自然语言回答:

北京今天天气晴朗,气温25到32摄氏度,空气质量良,适合出行。

多工具多轮

如果需要多个工具,模型可能返回多个 tool_call,或者一轮一轮调用,跟 ReAct 循环一样。


四、模型怎么选工具?

靠 description

模型主要看函数的 description 判断什么时候用。描述写得清楚,模型就选得准。

好的 description 怎么写?

坏例子:

"description": "搜索"

太简略,模型不知道什么时候用。

好例子:

"description": "当需要回答实时信息、新闻、未知事实时,调用此工具搜索互联网。
                 不要用已有知识回答时效性问题。"

说明清楚:什么时候用、什么场景用、什么情况不要用。

最佳实践

  1. 名字直观:函数名要见名知意
  2. 描述详细:说明用途、适用场景、不适用场景
  3. 参数说明清楚:每个参数是什么、格式要求、例子
  4. 工具数量适中:太多了模型容易选错,一般 5-10 个比较合适
  5. 功能不重叠:两个工具功能差不多的话模型会纠结

五、常见的工具类型

1. 搜索类

  • 网页搜索(Google、Bing、Tavily)
  • 知识库检索(企业内部 RAG)
  • 文档搜索

2. 计算类

  • 数学计算器
  • Python 代码执行(最强大,能算能画图能处理数据)
  • 单位换算

3. 信息查询类

  • 天气查询
  • 股票行情
  • 航班/火车查询
  • 数据库查询(自然语言转 SQL)

4. 操作执行类

  • 发送邮件
  • 创建日历日程
  • 发送消息(飞书/钉钉/微信)
  • 创建工单
  • 文件读写

5. 代码类

  • 执行 Python 代码
  • 代码审查
  • 运行测试

6. 浏览器类

  • 打开网页
  • 截图
  • 点击/填写表单

六、并行工具调用

是什么

模型一次返回多个 tool_call,同时调用多个工具,不用等一个完了再调下一个。

"tool_calls": [
    {"name": "get_weather", "arguments": {"city": "北京"}},
    {"name": "get_weather", "arguments": {"city": "上海"}}
]

好处

  • 减少轮次,更快
  • 独立的查询可以并行执行,省时间

什么时候并行

  • 两个查询互相不依赖
  • 需要对比多个信息的时候

比如「对比北京和上海的天气」,就可以并行查两个城市。


七、工具调用的最佳实践

1. 工具描述要精准

description 是模型选工具的唯一依据,一定要写清楚:

  • 这个工具是干嘛的
  • 什么情况下用
  • 什么情况下不要用
  • 参数的格式和含义

2. 参数校验

模型传的参数不一定对,一定要校验:

  • 必填参数有没有
  • 类型对不对
  • 枚举值在不在范围内
  • 格式合不合法(邮箱、日期等)

不对就返回错误信息,让模型重试。

3. 错误处理要友好

工具执行失败,返回给模型的错误信息要清楚,方便它修正:

错误:城市名「火星」不支持,支持的城市列表:北京、上海、广州...

模型看到就知道换个城市名再试。

4. 结果要简洁

工具返回的结果不要太长,太长占 token 还干扰模型。做摘要再返回:

  • 搜索结果只返回前 3 条摘要
  • 数据库查询只返回关键字段
  • 长文本先总结再给模型

5. 控制工具数量

工具太多模型容易选错。按场景给工具:

  • 客服 Agent 给知识库+工单工具
  • 数据分析 Agent 给 SQL+画图工具
  • 不要把所有工具都塞进去

6. 敏感操作要确认

发邮件、删数据、下单这种有副作用的操作,执行前要人确认:

  • Agent 生成调用请求
  • 弹给用户确认
  • 用户点确认了再真正执行

7. 工具结果要标记来源

RAG 搜索的结果要标来源链接,方便用户查证,也能追责。


八、常见坑

坑 1:工具描述太模糊

"搜索工具" → 模型不知道什么时候该搜。要写成"需要实时信息、最新新闻时使用"。

坑 2:参数没校验

模型传了个不存在的参数值,程序直接崩。一定要校验参数。

坑 3:工具返回结果太长

搜索返回整篇文章,token 爆了还干扰判断。要截断、摘要。

坑 4:工具功能重叠

两个工具都能查信息,模型不知道选哪个。合并或者区分清楚场景。

坑 5:循环调用失败

工具报错了,模型用同样的参数反复调。要加错误提示和重试限制。

坑 6:安全问题

Agent 能调用删数据、发邮件这种危险操作,没有确认机制。敏感操作必须人在回路。

坑 7:arguments 是字符串

注意 tool_calls 里的 arguments 是 JSON 字符串,不是对象,要 json.loads 解析一下。


九、一个完整的多工具示例

工具定义

tools = [
    {
        "type": "function",
        "function": {
            "name": "search_company",
            "description": "查询公司的基本信息,包括成立时间、创始人、主营业务",
            "parameters": {
                "type": "object",
                "properties": {
                    "company_name": {"type": "string", "description": "公司名称"}
                },
                "required": ["company_name"]
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "get_stock_price",
            "description": "查询上市公司的当前股价和市值",
            "parameters": {
                "type": "object",
                "properties": {
                    "stock_code": {"type": "string", "description": "股票代码"}
                },
                "required": ["stock_code"]
            }
        }
    }
]

对话过程

用户: 字节跳动上市了吗?市值多少?

模型(第一轮):

先查字节跳动的基本信息,看有没有上市、股票代码是什么。

tool_call: search_company("字节跳动")

工具返回:

字节跳动成立于2012年,创始人张一鸣,旗下产品有抖音、TikTok等。
目前未上市,暂无股票代码。

模型(第二轮):

字节跳动还没上市,没有股票代码,所以查不到市值。直接回答用户。

最终回答:

字节跳动目前尚未上市,因此没有公开的市值数据。
公司成立于2012年,旗下核心产品包括抖音、TikTok等。

十、本篇小结

  • Function Calling:模型原生支持的工具调用机制,输出结构化的 JSON 调用指令
  • 工具定义:name + description + parameters(JSON Schema)
  • 调用流程:用户问题+工具定义 → 模型返回 tool_calls → 执行函数 → 结果传回 → 模型生成答案
  • 模型靠 description 判断选哪个工具,描述写得准不准很重要
  • 支持并行调用:一次返回多个 tool_call,独立任务并行执行更快
  • 常见工具类型:搜索、计算、信息查询、操作执行、代码、浏览器
  • 最佳实践:描述精准、参数校验、错误友好、结果简洁、控制数量、敏感操作要人确认
  • 常见坑:描述模糊、不校验参数、结果太长、功能重叠、安全问题

下一篇讲记忆系统:短期记忆、长期记忆、向量记忆,Agent 怎么记住用户和历史。

我是黒漂技术佬。

posted @ 2026-09-14 18:20  阿拉斯攀登  阅读(0)  评论(0)    收藏  举报