系列文章导航:AI系列文章导航目录-持续更新中

第12课:Function Calling原理与实战

📝 本文摘要:本文详解Function Calling机制——没有它Agent只能聊天,有了它Agent能调API、查数据库、发邮件。内容包括:无Function Calling时的变通方案(Prompt解析JSON/ReAct)、Function Calling的完整工作流(工具定义→模型决策→输出结构化→调用执行→结果返回)、OpenAI vs Anthropic工具调用格式对比、多函数并行调用、实战示例(天气查询+数据库+邮件发送),以及Function Calling局限性讨论。

Function Calling是Agent"动手"的基础。没有它,Agent只能聊天;有了它,Agent能调API、查数据库、发邮件——真正地"做事"。


一、Function Calling的本质

一句话理解:Function Calling让模型能够"动手做事"。没有它,模型只能聊天;有了它,模型能调API、查数据库、发邮件——真正地"做事"。

核心原理:模型不是自己执行函数,而是告诉你"我想调哪个函数、传什么参数",然后你的代码去执行。

整个流程类比:

你(开发者)  →  给模型一份"工具清单"(告诉它有哪些工具可用)
用户          →  提问题
模型          →  判断: 这个问题需要用工具吗?
                     ├─ 不需要 → 直接回答
                     └─ 需要 → 输出: "我要调get_weather,参数是{city:北京}"
你(开发者)  →  执行get_weather("北京"),把结果返回给模型
模型          →  基于工具结果生成最终回答: "北京今天晴,28°C"

1.1 没有Function Calling时怎么做

方式1: Prompt Hack
  "请输出以下格式来调用工具:
   TOOL_CALL: {\"name\": \"get_weather\", \"args\": {\"city\": \"北京\"}}"
   
  问题:
  - 模型不一定按格式输出
  - 格式可能被模型"创造性"修改
  - 参数可能不符合预期类型
  - 每次都要写很长的Prompt

方式2: 正则匹配
  让模型输出自然语言,用正则提取意图
  "我想查北京天气" → 正则匹配 → 调get_weather("北京")
  
  问题:
  - 复杂意图很难用正则覆盖
  - 参数提取不可靠

1.2 Function Calling做了什么

核心思想: 把"工具调用"变成模型的原生能力

传统方式: 模型只输出文本 → 你从文本中提取意图 → 你调API
  问题: 提取意图不可靠,格式不稳定

FC方式:   模型输出结构化的工具调用 → 你直接调API
  优势: 100%结构化,参数类型正确,可靠性极高

本质: 模型被训练成"知道什么时候该调工具、怎么调"
     这不是Prompt技巧,而是模型能力(通过专门训练获得)
     
类比: 
  传统方式 = 你跟一个不会用电脑的人说"帮我查下天气",他口头告诉你怎么查,你自己操作
  FC方式 = 你跟一个会用电脑的人说"帮我查下天气",他直接打开天气网站查给你

1.3 Function Calling的完整流程

这是Agent最核心的循环,必须彻底理解!

┌─ 你的代码 ──────────────────────────────────────────┐
│                                                      │
│  1. 定义工具 (tools参数)                              │
│     → 告诉模型: 你有哪些工具可用,每个工具做什么    │
│  2. 发送: messages + tools → LLM API                 │
│                                                      │
├─ LLM处理 ─────────────────────────────────────────┤
│                                                      │
│  3. LLM判断: 需要调工具吗?                           │
│     ├── 不需要 → 直接生成文本回复                     │
│     └── 需要 → 生成tool_calls (结构化的工具调用)      │
│         {                                            │
│           "name": "get_weather",                     │
│           "arguments": "{\"city\": \"北京\"}"        │
│         }                                            │
│                                                      │
├─ 你的代码 ──────────────────────────────────────────┤
│                                                      │
│  4. 解析tool_calls,执行对应函数                      │
│     → 你的代码调用真实的get_weather()函数          │
│  5. 把结果加入messages,再次调用LLM                   │
│     → 告诉模型: 工具返回了什么结果                  │
│                                                      │
├─ LLM处理 ─────────────────────────────────────────┤
│                                                      │
│  6. LLM基于工具结果生成最终回复                       │
│     → "北京今天天气晴,温度28°C,湿度45%"            │
│                                                      │
└──────────────────────────────────────────────────┘

注意: 整个过程中,LLM从未直接执行任何函数!
     它只是"说"它想调什么,你的代码负责实际执行。
     这是安全性的关键——你可以在执行前做权限检查、参数验证等。

二、Function Calling实战

2.1 定义工具

from openai import OpenAI
import json

client = OpenAI()

# 工具定义(告诉模型有哪些工具可用)
tools = [
    {
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "获取指定城市的天气信息",
            "parameters": {
                "type": "object",
                "properties": {
                    "city": {
                        "type": "string",
                        "description": "城市名称,如'北京'、'上海'"
                    },
                    "unit": {
                        "type": "string",
                        "enum": ["celsius", "fahrenheit"],
                        "description": "温度单位,默认摄氏度"
                    }
                },
                "required": ["city"],
                "additionalProperties": False
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "query_order",
            "description": "查询订单信息",
            "parameters": {
                "type": "object",
                "properties": {
                    "order_id": {
                        "type": "string",
                        "description": "订单编号"
                    }
                },
                "required": ["order_id"],
                "additionalProperties": False
            }
        }
    }
]

2.2 完整的Function Calling循环

# 实际的工具实现
def get_weather(city: str, unit: str = "celsius") -> dict:
    """模拟天气API"""
    weather_data = {
        "北京": {"temp": 28, "condition": "晴", "humidity": 45},
        "上海": {"temp": 32, "condition": "多云", "humidity": 78},
        "深圳": {"temp": 35, "condition": "雷阵雨", "humidity": 85},
    }
    data = weather_data.get(city, {"temp": 25, "condition": "未知", "humidity": 50})
    if unit == "fahrenheit":
        data["temp"] = data["temp"] * 9/5 + 32
    return data

def query_order(order_id: str) -> dict:
    """模拟订单查询API"""
    orders = {
        "ORD001": {"status": "已发货", "items": ["手机壳", "充电器"], "total": 128.5},
        "ORD002": {"status": "待发货", "items": ["耳机"], "total": 299.0},
    }
    return orders.get(order_id, {"status": "未找到", "items": [], "total": 0})

# 工具映射
tool_map = {
    "get_weather": get_weather,
    "query_order": query_order,
}

# 完整的Agent循环
def agent_chat(user_message: str) -> str:
    messages = [
        {"role": "system", "content": "你是一个智能助手,可以查询天气和订单信息。"},
        {"role": "user", "content": user_message}
    ]
    
    max_rounds = 5  # 防止死循环
    
    for _ in range(max_rounds):
        response = client.chat.completions.create(
            model="gpt-4o-mini",
            messages=messages,
            tools=tools,
            tool_choice="auto"  # auto: 模型自己决定是否调工具
        )
        
        msg = response.choices[0].message
        
        # 情况1: 模型直接回复(不需要调工具)
        if msg.content and not msg.tool_calls:
            return msg.content
        
        # 情况2: 模型要调工具
        if msg.tool_calls:
            messages.append(msg)  # 把模型的tool_call加入历史
            
            for tool_call in msg.tool_calls:
                func_name = tool_call.function.name
                func_args = json.loads(tool_call.function.arguments)
                
                print(f"  → 调用工具: {func_name}({func_args})")
                
                # 执行工具
                result = tool_map[func_name](**func_args)
                
                # 把工具结果加入messages
                messages.append({
                    "role": "tool",
                    "tool_call_id": tool_call.id,
                    "content": json.dumps(result, ensure_ascii=False)
                })
    
    return "抱歉,处理过程中遇到了问题。"

# 测试
print(agent_chat("北京今天天气怎么样?"))
# → 调用工具: get_weather({'city': '北京'})
# → "北京今天天气晴,温度28°C,湿度45%。"

print(agent_chat("我的订单ORD001到哪了?"))
# → 调用工具: query_order({'order_id': 'ORD001'})
# → "您的订单ORD001已发货,包含手机壳和充电器,总金额128.5元。"

print(agent_chat("你好"))  
# → 不调工具,直接回复问候

2.3 tool_choice参数(控制模型是否/如何调用工具)

# "auto"(自动模式): 模型自己决定是否调工具(默认)
tool_choice="auto"

# "none"(禁用模式): 禁止调工具,强制纯文本回复
tool_choice="none"

# "required"(强制模式): 强制调工具,模型必须选择一个工具调用
tool_choice="required"

# 指定工具(指定模式): 强制调用某个特定工具
tool_choice={"type": "function", "function": {"name": "get_weather"}}

三、Function Calling的底层原理

3.1 模型是怎么学会调工具的

训练阶段:
1. 收集大量"用户意图→工具调用"的配对数据
2. 用SFT(Supervised Fine-Tuning,监督微调)教模型: 看到什么意图时应该输出什么工具调用
3. 用RLHF(Reinforcement Learning from Human Feedback,基于人类反馈的强化学习)优化: 工具调用正确的给奖励

推理阶段:
1. tools参数被转换为特殊Token序列,拼接到输入中
2. 模型看到这些特殊Token,"知道"有工具可用
3. 当判断需要调工具时,输出tool_calls格式的Token序列
4. 这些Token序列被API层解析为结构化的JSON

3.2 并行工具调用

# 一个回复中可以包含多个tool_calls
response = client.chat.completions.create(
    model="gpt-4o",
    messages=[{"role": "user", "content": "北京和上海今天天气怎么样?"}],
    tools=tools
)

# 模型会并行输出两个tool_calls:
# tool_calls[0]: get_weather({"city": "北京"})
# tool_calls[1]: get_weather({"city": "上海"})

# 你需要都执行,然后把两个结果都返回

四、Function Calling的工程化要点

4.1 工具描述是关键

模型选择工具完全依赖description!

❌ 差的描述:
  "获取信息"  → 模型不知道获取什么信息

✅ 好的描述:
  "查询指定城市的当前天气信息,包括温度、天气状况和湿度"
  → 模型知道什么时候该调这个工具

参数描述同样重要:
  ❌ "城市"  → 模型可能传"北京朝阳区"
  ✅ "城市名称,如北京、上海、广州" → 模型传正确的值

4.2 错误处理

def agent_chat_robust(user_message: str) -> str:
    messages = [{"role": "user", "content": user_message}]
    
    for _ in range(5):
        try:
            response = client.chat.completions.create(
                model="gpt-4o-mini",
                messages=messages,
                tools=tools
            )
        except Exception as e:
            return f"API调用失败: {e}"
        
        msg = response.choices[0].message
        
        if not msg.tool_calls:
            return msg.content or ""
        
        messages.append(msg)
        
        for tool_call in msg.tool_calls:
            try:
                func_name = tool_call.function.name
                func_args = json.loads(tool_call.function.arguments)
                
                if func_name not in tool_map:
                    result = {"error": f"未知工具: {func_name}"}
                else:
                    result = tool_map[func_name](**func_args)
                    
            except json.JSONDecodeError:
                result = {"error": "参数解析失败"}
            except TypeError as e:
                result = {"error": f"参数类型错误: {e}"}
            except Exception as e:
                result = {"error": f"执行失败: {e}"}
            
            messages.append({
                "role": "tool",
                "tool_call_id": tool_call.id,
                "content": json.dumps(result, ensure_ascii=False)
            })
    
    return "处理超时,请稍后重试。"

4.3 工具设计原则

1. 单一职责: 每个工具只做一件事
   ✅ query_order() + create_refund()
   ❌ order_operation(type="query_or_refund")

2. 清晰的输入输出: 参数类型明确,返回值结构化
3. 有边界: 工具应该有明确的成功/失败状态
4. 安全性: 危险操作需要确认(如删除、支付)
5. 幂等性: 同样的输入应该得到同样的结果

五、不同模型的Function Calling对比

模型 并行调用 强制调用 Structured Output 可靠性
GPT-4o ★★★★★
GPT-4.1 ★★★★★
Claude 3.5+ ★★★★★
DeepSeek-V3 部分 ★★★★☆
Qwen2.5 部分 部分 ★★★★☆
本地模型 有限 ★★★☆☆

📝 作业

作业1:实现一个带有3个工具的Agent

实现一个"个人助手Agent",支持以下工具:

  1. search_web(query) - 搜索网络(模拟实现)
  2. calculate(expression) - 计算数学表达式
  3. translate(text, target_lang) - 翻译文本(模拟实现)

参考答案

from openai import OpenAI
import json

client = OpenAI(base_url="http://localhost:11434/v1", api_key="ollama")

# 工具实现
def search_web(query: str) -> str:
    mock_results = {
        "Python": "Python是由Guido van Rossum创建的高级编程语言,最新版本3.12",
        "AI": "2026年AI领域最热门的方向是Agent和推理模型",
    }
    for key, val in mock_results.items():
        if key.lower() in query.lower():
            return val
    return f"搜索'{query}'的结果:未找到相关信息"

def calculate(expression: str) -> str:
    try:
        # 安全起见,只允许基本数学运算
        allowed = set("0123456789+-*/().% ")
        if all(c in allowed for c in expression):
            result = eval(expression)
            return str(result)
        return "不支持的表达式"
    except:
        return "计算错误"

def translate(text: str, target_lang: str) -> str:
    mock = {"hello": "你好", "world": "世界", "你好": "Hello", "世界": "World"}
    words = text.lower().split()
    result = " ".join(mock.get(w, w) for w in words)
    return f"[{target_lang}] {result}"

tool_map = {"search_web": search_web, "calculate": calculate, "translate": translate}

tools = [
    {
        "type": "function",
        "function": {
            "name": "search_web",
            "description": "搜索互联网获取信息",
            "parameters": {
                "type": "object",
                "properties": {"query": {"type": "string", "description": "搜索关键词"}},
                "required": ["query"],
                "additionalProperties": False
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "calculate",
            "description": "计算数学表达式,如'2+3*4'",
            "parameters": {
                "type": "object",
                "properties": {"expression": {"type": "string", "description": "数学表达式"}},
                "required": ["expression"],
                "additionalProperties": False
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "translate",
            "description": "翻译文本到目标语言",
            "parameters": {
                "type": "object",
                "properties": {
                    "text": {"type": "string", "description": "要翻译的文本"},
                    "target_lang": {"type": "string", "description": "目标语言,如'en'或'zh'"}
                },
                "required": ["text", "target_lang"],
                "additionalProperties": False
            }
        }
    }
]

def agent_chat(user_message: str) -> str:
    messages = [
        {"role": "system", "content": "你是一个个人助手,可以搜索信息、计算和翻译。"},
        {"role": "user", "content": user_message}
    ]
    
    for _ in range(5):
        response = client.chat.completions.create(
            model="qwen2.5:7b",
            messages=messages,
            tools=tools,
            tool_choice="auto"
        )
        
        msg = response.choices[0].message
        
        if msg.content and not msg.tool_calls:
            return msg.content
        
        if msg.tool_calls:
            messages.append(msg)
            for tc in msg.tool_calls:
                args = json.loads(tc.function.arguments)
                result = tool_map[tc.function.name](**args)
                print(f"  → {tc.function.name}({args}) = {result}")
                messages.append({
                    "role": "tool",
                    "tool_call_id": tc.id,
                    "content": json.dumps({"result": result}, ensure_ascii=False)
                })
    
    return "处理超时"

# 测试
print(agent_chat("帮我算一下(128+256)*3等于多少"))
print(agent_chat("Python是谁创建的?"))

下一篇文章见:AI系列文章导航目录-持续更新中

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐