0

AI Agent Function Calling工具调用实战:让大模型真正学会"动手"

2026.06.09 | youres | 22次围观

为什么你的AI只会说不会做?Function Calling的本质价值

大多数开发者第一次接触大模型API时,都会经历一个失望时刻——模型只会生成文本,不会真正执行操作。你让它查天气,它编一个;你让它查库存,它估算一个。这不是模型的问题,是你没给它"手"。

Function Calling(函数调用)就是给大模型装上这双手的机制。它让模型不再是只会嘴上说说的话痨,而是能调用真实API、查询真实数据、执行真实操作的智能体。理解这一点,是从"玩ChatGPT"到"构建AI应用"的关键跨越。

Function Calling工作原理:不是你想的那样

一个常见的误解是:Function Calling会让模型直接执行代码。实际上,它的工作流程是这样的:

  • 第一步:你告诉模型有哪些函数可用(函数描述+参数schema)
  • 第二步:模型判断是否需要调用函数,如果需要,生成函数名和参数(但不执行
  • 第三步:你的代码执行这个函数,拿到真实结果
  • 第四步:把结果喂回模型,让它生成最终回复

模型始终是"决策者"而非"执行者"。这个设计看似绕路,实则精妙——它保证了安全性,你完全控制哪些函数可以调用、参数是否合法。

实战:5分钟搭建一个能查天气+查汇率的Agent

下面用豆包大模型API演示完整流程。选择豆包是因为它的Function Calling兼容OpenAI格式,迁移成本低。

1. 定义工具函数

import json
from datetime import datetime

# 模拟天气查询(实际项目替换为真实API)
def get_weather(city: str) -> str:
    weather_db = {
        "北京": {"temp": 28, "condition": "晴", "humidity": 45},
        "上海": {"temp": 31, "condition": "多云", "humidity": 72},
        "深圳": {"temp": 33, "condition": "雷阵雨", "humidity": 85},
    }
    data = weather_db.get(city, {"temp": 25, "condition": "未知", "humidity": 50})
    return json.dumps({"city": city, **data}, ensure_ascii=False)

# 模拟汇率查询
def get_exchange_rate(from_currency: str, to_currency: str) -> str:
    rates = {"USD_CNY": 7.24, "EUR_CNY": 7.89, "JPY_CNY": 0.048}
    key = f"{from_currency}_{to_currency}"
    rate = rates.get(key, None)
    if rate is None:
        rate = 1 / rates.get(f"{to_currency}_{from_currency}", 1)
    return json.dumps({"from": from_currency, "to": to_currency, "rate": round(rate, 4)})

2. 注册工具描述

tools = [
    {
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "查询指定城市的实时天气信息,包括温度、天气状况和湿度",
            "parameters": {
                "type": "object",
                "properties": {
                    "city": {
                        "type": "string",
                        "description": "城市名称,如北京、上海、深圳"
                    }
                },
                "required": ["city"]
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "get_exchange_rate",
            "description": "查询两种货币之间的实时汇率",
            "parameters": {
                "type": "object",
                "properties": {
                    "from_currency": {"type": "string", "description": "源货币代码,如USD、CNY、EUR"},
                    "to_currency": {"type": "string", "description": "目标货币代码"}
                },
                "required": ["from_currency", "to_currency"]
            }
        }
    }
]

3. 核心调度循环

from volcengine.maas import MaasService

maas = MaasService("maas-api.ml-platform.volces.com", "cn-beijing")
maas.set_ak("你的AccessKey")
maas.set_sk("你的SecretKey")

FUNCTION_MAP = {
    "get_weather": get_weather,
    "get_exchange_rate": get_exchange_rate,
}

def run_agent(user_message: str, max_rounds=5):
    messages = [{"role": "user", "content": user_message}]
    
    for _ in range(max_rounds):
        resp = maas.chat(
            model_id="你的模型ID",
            messages=messages,
            tools=tools,
        )
        
        choice = resp.choices[0]
        
        if choice.finish_reason == "tool_calls":
            for tool_call in choice.message.tool_calls:
                fn_name = tool_call.function.name
                fn_args = json.loads(tool_call.function.arguments)
                print(f"[调用] {fn_name}({fn_args})")
                result = FUNCTION_MAP[fn_name](**fn_args)
                messages.append({
                    "role": "tool",
                    "content": result,
                    "tool_call_id": tool_call.id
                })
        else:
            print(f"[回复] {choice.message.content}")
            return choice.message.content
    return "达到最大调用轮次限制"

4. 测试运行

# 简单查询
run_agent("北京今天天气怎么样?")
# 输出: [调用] get_weather({"city": "北京"})
# 输出: [回复] 北京今天天气晴,气温28°C,湿度45%...

# 组合查询
run_agent("我明天从北京去上海出差,那边天气如何?顺便查人民币兑美元汇率")
# 模型连续调用两个函数,综合回答

3个实战踩坑与解法

坑1:参数描述模糊,模型瞎传参

这是最常见的问题。如果你把参数description写成"城市",模型可能传"北京市"、"Beijing"、"帝都",你的函数处理不了就报错。

解法:description里给枚举示例。改成"城市名称,如北京、上海、深圳",模型传参准确率从60%提升到95%以上。

坑2:多函数调用时模型"短路"

用户问"帮我查北京天气和美元汇率",有些模型只调用一个函数就返回了。

解法:在system prompt里加一句:"如果用户问题涉及多个工具,请依次调用所有相关工具后再回复。"这一句话能解决80%的短路问题。

坑3:函数执行超时拖垮整个Agent

真实场景中,外部API可能超时。同步等待一个慢API就卡住全流程。

解法:给每个函数加超时和降级逻辑:

import asyncio

async def call_with_timeout(fn, args, timeout_sec=5, fallback=None):
    try:
        return await asyncio.wait_for(fn(**args), timeout=timeout_sec)
    except asyncio.TimeoutError:
        return json.dumps({"error": f"{fn.__name__}调用超时", "fallback": fallback})

进阶:构建可扩展的工具注册体系

当你的Agent工具超过5个,手动管理FUNCTION_MAP就很痛苦了。这里分享一个我在生产环境中验证过的工具注册模式:

import inspect
from functools import wraps

tool_registry = {}

def register_tool(description: str, param_schema: dict):
    """装饰器:注册函数为可调用工具"""
    def decorator(fn):
        @wraps(fn)
        def wrapper(*args, **kwargs):
            return fn(*args, **kwargs)
        tool_registry[fn.__name__] = {
            "function": fn,
            "schema": {
                "type": "function",
                "function": {
                    "name": fn.__name__,
                    "description": description,
                    "parameters": param_schema
                }
            }
        }
        return wrapper
    return decorator

@register_tool(
    description="查询指定城市的实时天气",
    param_schema={
        "type": "object",
        "properties": {
            "city": {"type": "string", "description": "城市名,如北京、上海"}
        },
        "required": ["city"]
    }
)
def get_weather(city: str) -> str:
    pass

tools = [t["schema"] for t in tool_registry.values()]
FUNCTION_MAP = {name: t["function"] for name, t in tool_registry.items()}

这个模式的好处:新增工具只需加装饰器,不用改调度逻辑。我们项目里用这种方式管理了30+工具,零冲突。

与MCP协议的关系:Function Calling是地基

最近MCP(Model Context Protocol)很火,很多人问它和Function Calling什么关系。简单说:Function Calling是地基,MCP是标准化装修

Function Calling解决的是"模型怎么知道要调什么函数",MCP解决的是"怎么统一管理这些函数的描述和通信"。没有Function Calling的理解基础,直接上MCP容易踩坑。建议先吃透本文的Function Calling机制,再去看AI Agent MCP协议接入实战,会有更深的理解。

生产环境清单

把Function Calling从demo推到生产,你还需要:

方面demo阶段生产阶段
错误处理try-catch打印日志分级降级+自动重试+告警
参数校验模型传啥用啥JSON Schema强校验+消毒
权限控制所有函数都可调用按用户/场景动态过滤可用工具
可观测性print调试全链路trace(调用链+耗时+参数)
成本控制无限调用Token预算+调用频率限制

Function Calling不是什么高深技术,但它是AI Agent从"聊天机器人"变成"生产力工具"的分水岭。掌握它,你的Agent才真正能做事。

版权声明

本文仅代表个人观点。
本文系AI辅助作者原创,未经许可,转载请保留原文链接。

发表评论