Function Calling深度解析:Agent调用工具的底层原理与代码实战 🔧

导读:Function Calling是AI Agent的核心能力——没有它,Agent就只是一个"只会说不会做"的聊天机器人。今天我们从底层原理代码实战,彻底搞懂Function Calling的工作机制。学完这篇,你就能自己设计任何复杂的工具系统!💪


一、Function Calling是什么?一句话讲透 🎯

Function Calling = 让大模型能够"结构化地"调用外部函数/工具的能力。

什么意思?看这个对比:

方式 示例 问题
没有Function Calling 模型输出:“我建议你去搜索一下北京天气” 只是文字建议,不会真的去搜索
有Function Calling 模型输出:{"name": "get_weather", "args": {"city": "北京"}} 结构化指令,系统会真的调用天气API

用户提问

大模型

需要工具?

输出结构化调用请求

系统执行函数

结果返回模型

模型生成最终回答


二、Function Calling的完整工作流程 🔄

2.1 六步工作流

🔌 外部API 💻 系统 🧠 大模型 👤 用户 🔌 外部API 💻 系统 🧠 大模型 👤 用户 1. "北京今天天气怎么样?" 2. 分析:需要调用天气工具 3. 返回工具调用请求 get_weather(city="北京") 4. 调用天气API 5. 返回:晴,25°C 6. 将结果返回模型 "北京今天晴天,气温25°C ☀️"

2.2 每一步详解

步骤 执行者 动作 关键技术
1 用户 发送自然语言请求 -
2 大模型 判断是否需要工具 意图识别
3 大模型 生成结构化调用请求 JSON Schema
4 系统 解析并执行函数 函数路由
5 外部API 返回执行结果 API调用
6 大模型 基于结果生成回答 结果整合

三、工具定义的JSON Schema详解 📋

Function Calling的核心是工具定义。模型通过阅读工具定义来了解:

  • 有哪些工具可用
  • 每个工具做什么
  • 需要什么参数

3.1 一个完整的工具定义

{
    "type": "function",
    "function": {
        "name": "get_weather",
        "description": "获取指定城市的当前天气信息,包括温度、天气状况、湿度和风速。",
        "parameters": {
            "type": "object",
            "properties": {
                "city": {
                    "type": "string",
                    "description": "城市名称,如'北京'、'上海'"
                },
                "unit": {
                    "type": "string",
                    "enum": ["celsius", "fahrenheit"],
                    "description": "温度单位,默认为celsius"
                }
            },
            "required": ["city"]
        }
    }
}

3.2 Schema各字段说明

字段 作用 重要程度 说明
name 工具名称 ⭐⭐⭐⭐⭐ 模型通过名称选择工具,必须简洁明确
description 工具描述 ⭐⭐⭐⭐⭐ 模型通过描述理解工具用途,越详细越好
parameters 参数定义 ⭐⭐⭐⭐⭐ 模型根据参数定义生成正确的调用参数
required 必填参数 ⭐⭐⭐⭐ 告诉模型哪些参数是必须的
enum 参数取值范围 ⭐⭐⭐ 限制参数取值,减少错误

重点description是灵魂! 模型完全靠description来决定什么时候用这个工具。写得不好,模型就选不对。


四、代码实战:从零实现Function Calling 💻

4.1 原生OpenAI API实现

import json
from openai import OpenAI

client = OpenAI()

# Step 1: 定义工具
tools = [
    {
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "获取指定城市的天气信息",
            "parameters": {
                "type": "object",
                "properties": {
                    "city": {
                        "type": "string",
                        "description": "城市名称"
                    }
                },
                "required": ["city"]
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "calculate",
            "description": "执行数学计算",
            "parameters": {
                "type": "object",
                "properties": {
                    "expression": {
                        "type": "string",
                        "description": "数学表达式,如 '2 + 3 * 4'"
                    }
                },
                "required": ["expression"]
            }
        }
    }
]

# Step 2: 定义实际的函数实现
def get_weather(city: str) -> str:
    """实际的天气查询逻辑(这里简化为模拟)"""
    weather_data = {
        "北京": "晴天,25°C,湿度40%",
        "上海": "多云,22°C,湿度65%",
        "广州": "小雨,28°C,湿度80%",
    }
    return weather_data.get(city, f"暂无{city}的天气数据")

def calculate(expression: str) -> str:
    """实际的计算逻辑"""
    try:
        result = eval(expression, {"__builtins__": {}}, {})
        return f"{expression} = {result}"
    except Exception as e:
        return f"计算错误: {e}"

# 函数映射表
function_map = {
    "get_weather": get_weather,
    "calculate": calculate,
}

# Step 3: 发送请求
messages = [{"role": "user", "content": "北京今天天气怎么样?顺便帮我算一下25摄氏度等于多少华氏度"}]

response = client.chat.completions.create(
    model="gpt-4o",
    messages=messages,
    tools=tools,
)

# Step 4: 处理工具调用
message = response.choices[0].message

if message.tool_calls:
    # 将模型的回复加入消息历史
    messages.append(message)
    
    # 逐个执行工具调用
    for tool_call in message.tool_calls:
        func_name = tool_call.function.name
        func_args = json.loads(tool_call.function.arguments)
        
        # 调用对应的函数
        result = function_map[func_name](**func_args)
        
        # 将工具结果返回给模型
        messages.append({
            "role": "tool",
            "tool_call_id": tool_call.id,
            "content": result
        })
    
    # Step 5: 让模型基于工具结果生成最终回答
    final_response = client.chat.completions.create(
        model="gpt-4o",
        messages=messages,
        tools=tools,
    )
    
    print(final_response.choices[0].message.content)
    # 输出:北京今天是晴天,气温25°C。25°C等于77°F。

4.2 LangChain方式实现(更简洁)

from langchain.tools import tool
from langchain_openai import ChatOpenAI
from langgraph.prebuilt import create_react_agent

# 用@tool装饰器定义工具(更Pythonic)
@tool
def get_weather(city: str) -> str:
    """获取指定城市的天气信息。当用户询问天气时使用。
    
    Args:
        city: 城市名称,如'北京'、'上海'
    """
    weather_data = {
        "北京": "晴天,25°C,湿度40%",
        "上海": "多云,22°C,湿度65%",
    }
    return weather_data.get(city, f"暂无{city}的天气数据")

@tool
def calculate(expression: str) -> str:
    """执行数学计算。当需要精确计算时使用。
    
    Args:
        expression: 数学表达式
    """
    return f"{expression} = {eval(expression, {'__builtins__': {}}, {})}"

# 创建Agent(自动处理Function Calling的整个流程)
agent = create_react_agent(
    model=ChatOpenAI(model="gpt-4o"),
    tools=[get_weather, calculate],
)

# 直接调用,框架自动处理工具调用循环
result = agent.invoke({
    "messages": [("user", "北京天气怎么样?算一下25°C等于多少°F")]
})

💡 对比:LangChain帮你封装了所有繁琐的循环逻辑,你只需要定义工具和Agent,剩下的框架自动搞定。


五、并行工具调用 ⚡

当用户的一个请求需要调用多个工具时,模型可以一次性返回多个工具调用请求:

5.1 并行调用流程

用户: 北京和上海天气怎么样?

模型分析

返回2个工具调用

get_weather(city='北京')

get_weather(city='上海')

并行执行

并行执行

汇总结果

模型生成最终回答

5.2 并行调用代码

# 模型可能一次返回多个tool_calls
# message.tool_calls = [
#     ToolCall(name="get_weather", args={"city": "北京"}, id="call_1"),
#     ToolCall(name="get_weather", args={"city": "上海"}, id="call_2"),
# ]

# 并行执行所有工具调用
import concurrent.futures

def execute_tool_calls(tool_calls):
    """并行执行多个工具调用"""
    with concurrent.futures.ThreadPoolExecutor() as executor:
        futures = {}
        for tc in tool_calls:
            func = function_map[tc.function.name]
            args = json.loads(tc.function.arguments)
            futures[tc.id] = executor.submit(func, **args)
        
        results = {}
        for tc_id, future in futures.items():
            results[tc_id] = future.result()
    
    return results

六、高级技巧:动态工具注册 🔄

6.1 根据上下文动态选择工具

def get_tools_for_context(user_role):
    """根据用户角色动态选择可用工具"""
    base_tools = [search, calculate]  # 所有人都有
    
    role_tools = {
        "admin": [delete_data, manage_users],     # 管理员额外工具
        "analyst": [query_db, export_report],     # 分析师额外工具
        "viewer": [],                              # 访客无额外工具
    }
    
    return base_tools + role_tools.get(user_role, [])

# 使用
admin_agent = create_react_agent(
    model=llm,
    tools=get_tools_for_context("admin"),
)

6.2 工具注册策略对比

策略 适用场景 优点 缺点
静态注册 工具数量少(<10) 简单直接 工具多了模型选不准
动态注册 工具数量多(>10) 按需加载,准确率高 需要额外的路由逻辑
分层注册 复杂系统 结构清晰 实现复杂

七、常见问题与解决方案 🐛

问题 原因 解决方案
🔴 模型不调用工具 工具描述不够明确 在description中加"当…时使用"
🔴 模型选错工具 工具描述太相似 让每个工具的描述更独特
🔴 参数格式错误 参数描述不清晰 在description中给出示例
🔴 无限循环调用 缺少终止条件 设置max_iterations
🔴 工具返回太长 没有限制输出 在工具中截断输出

八、本期小结 📝

知识点 核心内容
什么是Function Calling 让模型结构化地调用外部工具
工作流程 用户提问→模型判断→生成调用→执行→返回结果→生成回答
工具定义 name + description + parameters(JSON Schema)
关键要点 description是灵魂,required要标对,enum限制取值
框架选择 原生API灵活但繁琐,LangChain简洁且高效

🔥 Function Calling是Agent从"能说"到"能做"的关键桥梁。掌握它,你就掌握了Agent开发的核心技能!


📢 下期预告:《多模态Agent来了!让你的智能体同时"看"图、"听"音、"读"视频》—— Agent不只是处理文字,还能看图片、听语音!下期解锁多模态Agent!🖼️🎵


📌 三连走起!Function Calling,Agent的灵魂技能! 💪

📚 专栏第7/24期,大模型基座篇接近尾声!

作者:高炉炼铁智能化技术研究者,专注钢铁冶金与人工智能 交叉领域。

👍 如果觉得有帮助,请点赞、收藏、转发!
版权归作者所有,未经许可请勿抄袭,套用,商用(或其它具有利益性行为)
🔔 关注专栏,不错过后续精彩内容

Logo

这里是“一人公司”的成长家园。我们提供从产品曝光、技术变现到法律财税的全栈内容,并连接云服务、办公空间等稀缺资源,助你专注创造,无忧运营。

更多推荐