跳转至

Function Calling 机制

引言

Function Calling(函数调用)是 LLM 与外部世界交互的标准化接口。各大模型提供商(OpenAI、Anthropic、Google)都提供了原生的函数调用支持,使 LLM 能够以结构化的方式描述需要调用的函数及其参数。

OpenAI Function Calling

基本流程

用户消息 → LLM 判断是否需要调用函数 → 输出函数名和参数(JSON)→ 应用执行函数 → 结果返回给 LLM → LLM 生成最终回答

工具定义

使用 JSON Schema 描述工具:

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"]
            }
        }
    }
]

完整调用示例

from openai import OpenAI
import json

client = OpenAI()

def get_weather(city, unit="celsius"):
    """实际的天气 API 调用"""
    # 模拟 API 返回
    return {"city": city, "temperature": 22, "unit": unit, "condition": "晴"}

def run_conversation(user_message):
    messages = [{"role": "user", "content": user_message}]

    # 第一轮:LLM 决定是否调用函数
    response = client.chat.completions.create(
        model="gpt-4o",
        messages=messages,
        tools=tools,
        tool_choice="auto",  # auto/none/required/specific
    )

    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)

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

            # 将结果返回给 LLM
            messages.append({
                "role": "tool",
                "tool_call_id": tool_call.id,
                "content": json.dumps(result, ensure_ascii=False)
            })

        # 第二轮:LLM 基于函数结果生成回答
        final_response = client.chat.completions.create(
            model="gpt-4o",
            messages=messages,
        )
        return final_response.choices[0].message.content

    return message.content

# 使用
print(run_conversation("北京今天天气怎么样?"))

并行函数调用(Parallel Function Calling)

GPT-4o 支持在一次回复中调用多个函数:

# LLM 可能同时返回多个 tool_calls
# 例如:"北京和上海的天气分别是什么?"
# tool_calls: [
#   {"function": {"name": "get_weather", "arguments": '{"city": "北京"}'}},
#   {"function": {"name": "get_weather", "arguments": '{"city": "上海"}'}},
# ]

Structured Outputs

确保函数参数严格遵循 JSON Schema:

tools = [
    {
        "type": "function",
        "function": {
            "name": "create_task",
            "description": "创建一个新任务",
            "parameters": {
                "type": "object",
                "properties": {
                    "title": {"type": "string"},
                    "priority": {"type": "string", "enum": ["high", "medium", "low"]},
                    "due_date": {"type": "string", "format": "date"},
                },
                "required": ["title", "priority"],
                "additionalProperties": False,  # 严格模式
            },
            "strict": True  # 启用 Structured Outputs
        }
    }
]

Anthropic Tool Use

工具定义

tools = [
    {
        "name": "get_weather",
        "description": "获取指定城市的当前天气信息",
        "input_schema": {
            "type": "object",
            "properties": {
                "city": {
                    "type": "string",
                    "description": "城市名称"
                },
                "unit": {
                    "type": "string",
                    "enum": ["celsius", "fahrenheit"],
                    "description": "温度单位"
                }
            },
            "required": ["city"]
        }
    }
]

调用示例

import anthropic

client = anthropic.Anthropic()

def run_claude_tool_use(user_message):
    messages = [{"role": "user", "content": user_message}]

    response = client.messages.create(
        model="claude-sonnet-4-20250514",
        max_tokens=1024,
        tools=tools,
        messages=messages,
    )

    # Claude 返回的内容可能包含文本和工具调用
    tool_calls = [block for block in response.content if block.type == "tool_use"]

    if tool_calls:
        # 执行工具调用
        messages.append({"role": "assistant", "content": response.content})

        tool_results = []
        for tc in tool_calls:
            result = execute_tool(tc.name, tc.input)
            tool_results.append({
                "type": "tool_result",
                "tool_use_id": tc.id,
                "content": json.dumps(result, ensure_ascii=False)
            })

        messages.append({"role": "user", "content": tool_results})

        # 获取最终回答
        final = client.messages.create(
            model="claude-sonnet-4-20250514",
            max_tokens=1024,
            tools=tools,
            messages=messages,
        )
        return final.content[0].text

    return response.content[0].text

Claude 的特殊能力

  • 思考过程:使用 extended thinking 展示工具选择的推理过程
  • 多轮工具使用:自动进行多轮工具调用直到获得足够信息
  • 工具使用与文本交织:在同一回复中混合文本和工具调用

Google Gemini Function Calling

工具定义

import google.generativeai as genai

def get_weather(city: str, unit: str = "celsius") -> dict:
    """获取指定城市的天气信息。

    Args:
        city: 城市名称
        unit: 温度单位 (celsius 或 fahrenheit)

    Returns:
        天气信息字典
    """
    return {"city": city, "temperature": 22, "unit": unit}

# Gemini 可以直接从 Python 函数签名和 docstring 生成工具定义
model = genai.GenerativeModel(
    model_name="gemini-1.5-pro",
    tools=[get_weather]
)

chat = model.start_chat()
response = chat.send_message("东京的天气怎么样?")

# 自动处理函数调用
for part in response.parts:
    if part.function_call:
        result = get_weather(**part.function_call.args)
        response = chat.send_message(
            genai.protos.Content(
                parts=[genai.protos.Part(
                    function_response=genai.protos.FunctionResponse(
                        name=part.function_call.name,
                        response={"result": result}
                    )
                )]
            )
        )

平台对比

特性 OpenAI Anthropic Google Gemini
工具定义格式 JSON Schema JSON Schema Python 函数 / JSON
并行调用 支持 支持 支持
强制调用 tool_choice: required tool_choice: any tool_config
指定工具 tool_choice: {name} tool_choice: {name} allowed_function_names
结构化输出 strict: true 不支持 不支持
流式工具调用 支持 支持 支持
最大工具数 128 数百 数百
嵌套对象 支持 支持 支持

高级模式

工具选择策略

# 1. 自动选择(默认)
tool_choice = "auto"  # LLM 自行决定

# 2. 强制使用工具
tool_choice = "required"  # 必须调用至少一个工具

# 3. 指定工具
tool_choice = {"type": "function", "function": {"name": "get_weather"}}

# 4. 禁用工具
tool_choice = "none"  # 不调用任何工具

错误处理

def safe_tool_execution(tool_name, arguments, timeout=30):
    """安全的工具执行,带错误处理"""
    try:
        result = execute_with_timeout(tool_name, arguments, timeout)
        return {"status": "success", "data": result}
    except TimeoutError:
        return {"status": "error", "message": f"工具 {tool_name} 执行超时({timeout}s)"}
    except ValidationError as e:
        return {"status": "error", "message": f"参数验证失败: {e}"}
    except Exception as e:
        return {"status": "error", "message": f"执行失败: {str(e)}"}

工具结果的格式化

def format_tool_result(result, max_length=4000):
    """格式化工具结果,防止过长"""
    result_str = json.dumps(result, ensure_ascii=False, indent=2)

    if len(result_str) > max_length:
        # 截断并添加提示
        truncated = result_str[:max_length]
        return truncated + f"\n... [结果已截断,原始长度: {len(result_str)} 字符]"

    return result_str

多轮工具调用循环

def agent_tool_loop(query, tools, llm, max_iterations=10):
    """完整的多轮工具调用循环"""
    messages = [{"role": "user", "content": query}]

    for i in range(max_iterations):
        response = llm.chat(messages, tools=tools)

        if not response.tool_calls:
            return response.content  # 最终回答

        messages.append(response.message)

        for tool_call in response.tool_calls:
            result = safe_tool_execution(
                tool_call.function.name,
                json.loads(tool_call.function.arguments)
            )
            messages.append({
                "role": "tool",
                "tool_call_id": tool_call.id,
                "content": format_tool_result(result)
            })

    return "达到最大迭代次数,任务未完成。"

实践建议

工具设计清单

  • [ ] 工具名称清晰、无歧义
  • [ ] 描述详细说明何时使用、何时不使用
  • [ ] 参数有清晰的类型和描述
  • [ ] 使用 enum 限制参数范围
  • [ ] 标注 required 字段
  • [ ] 工具返回结构化结果
  • [ ] 实现超时和错误处理

常见陷阱

  1. 工具描述模糊:LLM 无法判断何时使用
  2. 参数过于复杂:嵌套过深导致参数构造错误
  3. 忽略错误处理:工具失败时 Agent 无法恢复
  4. 工具过多:工具选择困难,建议控制在 10-20 个以内
  5. 结果过长:未截断的大结果浪费上下文空间

参考文献

  • OpenAI. "Function Calling" Documentation
  • Anthropic. "Tool Use" Documentation
  • Google. "Gemini Function Calling" Documentation
  • Patil, S. G., et al. (2023). "Gorilla: Large Language Model Connected with Massive APIs"

评论 #