文章摘要
加载中...|
此内容根据文章生成,并经过人工审核,仅用于文章内容的解释与总结 投诉

概述

Function Calling 是让 LLM 能够调用外部函数和 API 的关键技术。它将 LLM 从单纯的"对话机器人"升级为能够执行实际操作的"Agent"。本文将深入讲解 Function Calling 的原理和实践。

什么是 Function Calling

核心概念

Function Calling 允许 LLM 根据用户意图,输出结构化的函数调用参数,而不是直接返回最终答案。然后由代码执行这些函数,再将结果返回给 LLM 生成最终回复。

text
┌─────────────────────────────────────────────────────────┐
│              Function Calling 工作流程                    │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  用户: "北京现在的天气怎么样?"                             │
│     │                                                    │
│     ▼                                                    │
│  ┌─────────────────────────────────────┐               │
│  │        LLM 分析意图                  │               │
│  │   需要调用 get_weather(location)    │               │
│  └─────────────────────────────────────┘               │
│     │                                                    │
│     ▼                                                    │
│  ┌─────────────────────────────────────┐               │
│  │   返回结构化参数                      │               │
│  │   {"name": "get_weather",          │               │
│  │    "arguments": {"location": "北京"}}│               │
│  └─────────────────────────────────────┘               │
│     │                                                    │
│     ▼                                                    │
│  ┌─────────────────────────────────────┐               │
│  │       代码执行函数                    │               │
│  │   get_weather("北京") = "晴天 25°C"  │               │
│  └─────────────────────────────────────┘               │
│     │                                                    │
│     ▼                                                    │
│  ┌─────────────────────────────────────┐               │
│  │     LLM 生成最终回复                 │               │
│  │   "北京现在是晴天,温度25°C"         │               │
│  └─────────────────────────────────────┘               │
│                                                         │
└─────────────────────────────────────────────────────────┘

为什么需要 Function Calling

问题没有 Function Calling有 Function Calling
获取实时数据❌ 无法获取✅ 调用 API
执行操作❌ 只能生成代码✅ 直接调用函数
准确性❌ 可能输出错误格式✅ 结构化输出
复杂任务❌ 需要多轮对话✅ 自动规划调用

Function Calling vs 传统方式

python
# 传统方式:让 LLM 生成代码执行
user_input = "帮我计算 2 + 2"
response = llm.invoke(user_input)
# LLM 可能返回:
# "答案是 4" ✅ 正确
# "让我想想...2 加 2 等于... 5" ❌ 幻觉

# Function Calling:结构化调用
response = llm.invoke(
    user_input,
    tools=[calculate_tool]
)
# LLM 返回:
# {"name": "calculate", "arguments": {"expression": "2 + 2"}}
# 代码执行 calculate("2 + 2") = 4
# LLM 基于结果生成:"2 + 2 = 4" ✅ 准确

OpenAI Function Calling API

基本用法

python
from openai import OpenAI
import json

client = OpenAI()

# 定义工具函数
tools = [
    {
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "获取指定城市的当前天气",
            "parameters": {
                "type": "object",
                "properties": {
                    "location": {
                        "type": "string",
                        "description": "城市名称,如:北京、上海"
                    },
                    "unit": {
                        "type": "string",
                        "enum": ["celsius", "fahrenheit"],
                        "description": "温度单位"
                    }
                },
                "required": ["location"]
            }
        }
    }
]

# 发起请求
response = client.chat.completions.create(
    model="gpt-4o",
    messages=[
        {"role": "user", "content": "北京今天天气怎么样?"}
    ],
    tools=tools
)

# 检查是否需要调用函数
tool_calls = response.choices[0].message.tool_calls

if tool_calls:
    for tool_call in tool_calls:
        function_name = tool_call.function.name
        function_args = json.loads(tool_call.function.arguments)

        # 执行函数
        if function_name == "get_weather":
            result = get_weather(
                location=function_args.get("location"),
                unit=function_args.get("unit", "celsius")
            )

            # 将结果返回给 LLM
            follow_up = client.chat.completions.create(
                model="gpt-4o",
                messages=[
                    {"role": "user", "content": "北京今天天气怎么样?"},
                    response.choices[0].message,
                    {
                        "role": "tool",
                        "tool_call_id": tool_call.id,
                        "content": json.dumps(result)
                    }
                ]
            )

            print(follow_up.choices[0].message.content)

定义工具函数的最佳实践

python
# ✅ 好的工具定义
good_tool = {
    "name": "search_products",
    "description": "搜索产品目录,根据关键词查找匹配的产品",
    "parameters": {
        "type": "object",
        "properties": {
            "query": {
                "type": "string",
                "description": "搜索关键词,如:蓝牙耳机、运动鞋"
            },
            "category": {
                "type": "string",
                "description": "产品类别,如:电子产品、服装、食品"
            },
            "min_price": {
                "type": "number",
                "description": "最低价格"
            },
            "max_price": {
                "type": "number",
                "description": "最高价格"
            }
        },
        "required": ["query"]
    }
}

# ❌ 差的工具定义
bad_tool = {
    "name": "search",
    "description": "搜索",  # 太简单
    "parameters": {
        "type": "object",
        "properties": {
            "args": {  # 不明确
                "type": "string"
            }
        }
    }
}

工具定义要点

要点说明示例
清晰的名称动词开头,明确功能get_weather ✅ / weather
详细描述说明功能和用途"获取指定城市的当前天气和未来预报"
参数说明每个参数都要有描述"城市名称,如:北京、上海"
必填标记明确哪些参数必填"required": ["location"]
枚举值限制可选值范围"enum": ["celsius", "fahrenheit"]

多工具调用

并行调用多个工具

python
tools = [
    {
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "获取天气信息",
            "parameters": {
                "type": "object",
                "properties": {
                    "location": {"type": "string"}
                },
                "required": ["location"]
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "get_time",
            "description": "获取指定时区的当前时间",
            "parameters": {
                "type": "object",
                "properties": {
                    "timezone": {
                        "type": "string",
                        "description": "时区,如:Asia/Shanghai"
                    }
                },
                "required": ["timezone"]
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "convert_currency",
            "description": "货币汇率转换",
            "parameters": {
                "type": "object",
                "properties": {
                    "amount": {"type": "number"},
                    "from": {"type": "string"},
                    "to": {"type": "string"}
                },
                "required": ["amount", "from", "to"]
            }
        }
    }
]

# LLM 可能并行调用多个工具
response = client.chat.completions.create(
    model="gpt-4o",
    messages=[
        {"role": "user", "content": "帮我查一下北京天气、纽约时间,还有100美元换人民币是多少?"}
    ],
    tools=tools
)

# 处理所有工具调用
tool_calls = response.choices[0].message.tool_calls

tool_results = []
for tool_call in tool_calls:
    function_name = tool_call.function.name
    function_args = json.loads(tool_call.function.arguments)

    # 根据名称调用不同函数
    if function_name == "get_weather":
        result = get_weather(**function_args)
    elif function_name == "get_time":
        result = get_time(**function_args)
    elif function_name == "convert_currency":
        result = convert_currency(**function_args)

    tool_results.append({
        "tool_call_id": tool_call.id,
        "role": "tool",
        "content": json.dumps(result)
    })

# 将所有结果返回给 LLM
final_response = client.chat.completions.create(
    model="gpt-4o",
    messages=[
        {"role": "user", "content": "帮我查一下北京天气、纽约时间,还有100美元换人民币是多少?"},
        response.choices[0].message,
        *tool_results
    ]
)

使用 LangChain 简化

python
from langchain_openai import ChatOpenAI
from langchain.tools import tool
from langchain_core.utils.function_calling import convert_to_openai_function

# 定义工具
@tool
def get_weather(location: str, unit: str = "celsius") -> str:
    """获取指定城市的当前天气"""
    # 实际调用天气 API
    return f"{location}: 晴天,25°{unit[0].upper()}"

@tool
def get_time(timezone: str) -> str:
    """获取指定时区的当前时间"""
    # 实际获取时间
    return f"{timezone}: 14:30"

@tool
def convert_currency(amount: float, from_: str, to: str) -> str:
    """货币汇率转换"""
    # 实际调用汇率 API
    rate = 7.2 if to == "CNY" else 1
    return f"{amount} {from_} = {amount * rate:.2f} {to}"

# 绑定工具到 LLM
llm = ChatOpenAI(model="gpt-4o")
tools = [get_weather, get_time, convert_currency]
llm_with_tools = llm.bind_functions(tools)

# 调用
response = llm_with_tools.invoke(
    "帮我查一下北京天气、纽约时间,还有100美元换人民币是多少?"
)

# 处理工具调用
from langchain.tools.render import render_text_description

if response.tool_calls:
    # 执行工具调用
    for tool_call in response.tool_calls:
        tool_name = tool_call["name"]
        tool_args = tool_call["args"]

        for tool in tools:
            if tool.name == tool_name:
                result = tool.func(**tool_args)
                print(f"{tool_name}: {result}")

链式调用

工具结果再调用

python
class AgentWithChaining:
    def __init__(self, llm, tools):
        self.llm = llm
        self.tools = {tool.name: tool for tool in tools}

    def run(self, query: str, max_iterations: int = 5):
        messages = [{"role": "user", "content": query}]

        for iteration in range(max_iterations):
            # 调用 LLM
            response = self.llm.invoke(
                messages,
                tools=[tool["function"] for tool in self.tools.values()]
            )

            messages.append(response.message)

            # 检查是否需要调用工具
            if not response.tool_calls:
                break

            # 执行工具调用
            for tool_call in response.tool_calls:
                tool_name = tool_call.function.name
                tool_args = json.loads(tool_call.function.arguments)

                if tool_name in self.tools:
                    result = self.tools[tool_name](**tool_args)

                    messages.append({
                        "role": "tool",
                        "tool_call_id": tool_call.id,
                        "content": str(result)
                    })

        return messages[-1]["content"]

# 使用
agent = AgentWithChaining(llm, tools)
result = agent.run("帮我查北京天气,如果温度超过30度,告诉我需要注意什么")

工具间依赖

python
@tool
def find_restaurants(location: str, cuisine: str = None) -> str:
    """查找餐厅"""
    # 返回餐厅列表
    return "['海底捞(王府井店)', '全聚德(前门店)', '鼎泰丰(国贸店)']"

@tool
def get_restaurant_details(restaurant_name: str) -> str:
    """获取餐厅详细信息"""
    # 返回餐厅详情
    return f"{restaurant_name}: 地址xxx, 电话xxx, 人均150元"

@tool
def make_reservation(restaurant_name: str, time: str, people: int) -> str:
    """预订餐厅"""
    # 调用预订 API
    return f"已预订 {restaurant_name} {time} {people}人位"

# LLM 会自动处理工具依赖
agent = AgentWithChaining(llm, [
    find_restaurants,
    get_restaurant_details,
    make_reservation
])

result = agent.run("帮我在王府井附近找个川菜餐厅,今晚7点2个人")

错误处理与重试

函数执行错误处理

python
from functools import wraps
import json

def handle_tool_errors(func):
    """工具错误处理装饰器"""
    @wraps(func)
    def wrapper(**kwargs):
        try:
            return func(**kwargs)
        except ValueError as e:
            return {
                "error": "invalid_arguments",
                "message": str(e)
            }
        except Exception as e:
            return {
                "error": "execution_failed",
                "message": f"工具执行失败: {str(e)}"
            }
    return wrapper

@tool
@handle_tool_errors
def get_weather(location: str) -> str:
    """获取天气信息"""
    if not location:
        raise ValueError("location 参数不能为空")

    # 实际 API 调用
    try:
        response = requests.get(f"https://api.weather.com/{location}")
        return response.json()
    except requests.RequestException as e:
        raise Exception(f"天气 API 调用失败: {e}")

参数验证

python
from pydantic import BaseModel, Field, validator

class WeatherParams(BaseModel):
    """天气参数验证"""
    location: str = Field(..., min_length=2, description="城市名称")
    unit: str = Field("celsius", regex="^(celsius|fahrenheit)$")

    @validator('location')
    def location_must_not_be_empty(cls, v):
        if not v or v.strip() == "":
            raise ValueError('location 不能为空')
        return v.strip()

@tool
def get_weather_validated(location: str, unit: str = "celsius") -> str:
    """获取天气信息(带参数验证)"""
    # 验证参数
    params = WeatherParams(location=location, unit=unit)

    # 实际调用
    return f"{params.location}: 晴天,25°{params.unit[0].upper()}"

自动重试

python
@tool
def retry_on_failure(func, max_retries=3):
    """工具重试装饰器"""
    @wraps(func)
    def wrapper(**kwargs):
        last_error = None
        for attempt in range(max_retries):
            try:
                return func(**kwargs)
            except Exception as e:
                last_error = e
                if attempt < max_retries - 1:
                    continue
                raise Exception(f"重试 {max_retries} 次后仍失败: {e}")
    return wrapper

@tool
@retry_on_failure
def get_weather_with_retry(location: str) -> str:
    """获取天气信息(带重试)"""
    # 可能失败的操作
    response = requests.get(f"https://api.weather.com/{location}")
    response.raise_for_status()
    return response.json()

实战:构建工具增强的 Agent

完整示例:智能客服 Agent

python
from openai import OpenAI
import json
from typing import List, Dict, Any

class CustomerServiceAgent:
    def __init__(self, api_key: str):
        self.client = OpenAI(api_key=api_key)
        self.tools = self._init_tools()
        self.conversation_history = []

    def _init_tools(self) -> List[Dict]:
        """初始化工具"""
        return [
            {
                "type": "function",
                "function": {
                    "name": "search_faq",
                    "description": "搜索常见问题知识库",
                    "parameters": {
                        "type": "object",
                        "properties": {
                            "query": {
                                "type": "string",
                                "description": "搜索关键词"
                            }
                        },
                        "required": ["query"]
                    }
                }
            },
            {
                "type": "function",
                "function": {
                    "name": "get_order_status",
                    "description": "查询订单状态",
                    "parameters": {
                        "type": "object",
                        "properties": {
                            "order_id": {
                                "type": "string",
                                "description": "订单编号"
                            }
                        },
                        "required": ["order_id"]
                    }
                }
            },
            {
                "type": "function",
                "function": {
                    "name": "cancel_order",
                    "description": "取消订单",
                    "parameters": {
                        "type": "object",
                        "properties": {
                            "order_id": {
                                "type": "string",
                                "description": "订单编号"
                            },
                            "reason": {
                                "type": "string",
                                "description": "取消原因"
                            }
                        },
                        "required": ["order_id"]
                    }
                }
            },
            {
                "type": "function",
                "function": {
                    "name": "transfer_to_human",
                    "description": "转接人工客服",
                    "parameters": {
                        "type": "object",
                        "properties": {
                            "reason": {
                                "type": "string",
                                "description": "转接原因"
                            },
                            "priority": {
                                "type": "string",
                                "enum": ["low", "medium", "high"],
                                "description": "优先级"
                            }
                        },
                        "required": ["reason"]
                    }
                }
            }
        ]

    def _execute_tool(self, tool_name: str, tool_args: Dict) -> Any:
        """执行工具函数"""
        # 这里实现实际的工具逻辑
        if tool_name == "search_faq":
            return self._search_faq(tool_args["query"])
        elif tool_name == "get_order_status":
            return self._get_order_status(tool_args["order_id"])
        elif tool_name == "cancel_order":
            return self._cancel_order(tool_args["order_id"], tool_args.get("reason"))
        elif tool_name == "transfer_to_human":
            return self._transfer_to_human(tool_args["reason"], tool_args.get("priority", "medium"))

    def _search_faq(self, query: str) -> str:
        """搜索 FAQ"""
        # 实际实现会连接知识库
        return f"找到与'{query}'相关的常见问题..."

    def _get_order_status(self, order_id: str) -> str:
        """查询订单状态"""
        # 实际实现会连接订单系统
        return f"订单 {order_id} 状态:已发货"

    def _cancel_order(self, order_id: str, reason: str = None) -> str:
        """取消订单"""
        # 实际实现会调用订单 API
        return f"订单 {order_id} 已取消"

    def _transfer_to_human(self, reason: str, priority: str) -> str:
        """转接人工"""
        # 实际实现会创建工单
        return f"已转接人工客服,原因:{reason},优先级:{priority}"

    def chat(self, user_message: str) -> str:
        """处理用户消息"""
        # 添加用户消息到历史
        self.conversation_history.append({
            "role": "user",
            "content": user_message
        })

        # 调用 LLM
        response = self.client.chat.completions.create(
            model="gpt-4o",
            messages=self.conversation_history,
            tools=self.tools
        )

        assistant_message = response.choices[0].message
        self.conversation_history.append(assistant_message)

        # 处理工具调用
        max_iterations = 5
        for _ in range(max_iterations):
            if not assistant_message.tool_calls:
                break

            # 执行所有工具调用
            tool_messages = []
            for tool_call in assistant_message.tool_calls:
                tool_name = tool_call.function.name
                tool_args = json.loads(tool_call.function.arguments)

                result = self._execute_tool(tool_name, tool_args)

                tool_messages.append({
                    "role": "tool",
                    "tool_call_id": tool_call.id,
                    "content": json.dumps(result)
                })

            # 添加工具结果到历史
            self.conversation_history.extend(tool_messages)

            # 继续对话
            response = self.client.chat.completions.create(
                model="gpt-4o",
                messages=self.conversation_history,
                tools=self.tools
            )

            assistant_message = response.choices[0].message
            self.conversation_history.append(assistant_message)

        return assistant_message.content

# 使用
agent = CustomerServiceAgent(api_key="your-api-key")

# 示例对话
print(agent.chat("我的订单12345什么时候到?"))
print(agent.chat("我想取消这个订单"))
print(agent.chat("你们支持七天无理由退货吗?"))

工具设计模式

1. 原子化工具

python
# ✅ 好:每个工具做一件事
@tool
def get_user_name(user_id: str) -> str:
    """获取用户姓名"""
    pass

@tool
def get_user_email(user_id: str) -> str:
    """获取用户邮箱"""
    pass

# ❌ 差:一个工具做多件事
@tool
def get_user_info(user_id: str, field: str) -> str:
    """获取用户信息"""
    pass

2. 工具组合

python
@tool
def calculate_discount(price: float, discount_percent: float) -> float:
    """计算折扣后价格"""
    return price * (1 - discount_percent / 100)

@tool
def calculate_tax(price: float, tax_rate: float) -> float:
    """计算税费"""
    return price * tax_rate

@tool
def calculate_final_price(base_price: float, discount: float, tax_rate: float) -> float:
    """计算最终价格(组合其他工具)"""
    discounted = calculate_discount(base_price, discount)
    with_tax = calculate_tax(discounted, tax_rate)
    return discounted + with_tax

3. 幂等工具

python
@tool
def create_document(document_id: str, content: str) -> str:
    """创建文档(幂等)"""
    if document_exists(document_id):
        return update_document(document_id, content)
    else:
        return insert_document(document_id, content)

小结

Function Calling 是连接 LLM 与外部世界的桥梁:

核心要点

  1. 工作原理

    • LLM 输出结构化的函数调用参数
    • 代码执行函数并返回结果
    • LLM 基于结果生成最终回复
  2. 工具设计

    • 清晰的命名和描述
    • 详细的参数说明
    • 合理的必填标记
    • 原子化和幂等性
  3. 高级用法

    • 多工具并行调用
    • 链式调用处理复杂任务
    • 错误处理和重试机制
  4. 最佳实践

    • 做好参数验证
    • 优雅的错误处理
    • 合理的重试策略
    • 详细的工具描述

下一篇文章将介绍 Agent 记忆与上下文管理,让 Agent 能够记住历史对话。

赞赏博主
评论 隐私政策