AI Agent 的工具调用(Tool Calling)详解

什么是工具调用?

工具调用(Tool Calling / Function Calling)是让 LLM 能够调用外部函数的能力。它让 Agent 突破了大语言模型的知识边界,能够:

  • 获取实时信息(天气、股价、新闻)
  • 执行操作(发送邮件、创建日程、调用 API)
  • 访问外部系统(数据库、搜索引擎、代码解释器)

工具调用是 Agent 的"手",让它从"会说话"变成"会做事"。

OpenAI Function Calling 基础

import openai
import json

# 定义工具(函数)
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"]
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "send_email",
            "description": "发送邮件",
            "parameters": {
                "type": "object",
                "properties": {
                    "to": {"type": "string"},
                    "subject": {"type": "string"},
                    "body": {"type": "string"}
                },
                "required": ["to", "subject", "body"]
            }
        }
    }
]

# 调用 LLM
response = openai.chat.completions.create(
    model="gpt-4",
    messages=[
        {"role": "user", "content": "北京今天天气怎么样?"}
    ],
    tools=tools,
    tool_choice="auto"
)

# 解析工具调用
message = response.choices[0].message
if message.tool_calls:
    for tool_call in message.tool_calls:
        function_name = tool_call.function.name
        arguments = json.loads(tool_call.function.arguments)
        print(f"调用函数:{function_name},参数:{arguments}")
        # 输出:调用函数:get_weather,参数:{'city': '北京'}

完整的工具调用流程

class ToolCallingAgent:
    def __init__(self):
        self.tools = self._register_tools()
        self.tool_map = {
            "get_weather": self._get_weather,
            "send_email": self._send_email,
            "search": self._search
        }
    
    def run(self, user_input: str) -> str:
        # 第 1 步:让 LLM 决定调用什么工具
        response = self._call_llm(user_input)
        
        # 第 2 步:解析工具调用
        if response.tool_calls:
            tool_results = []
            for tool_call in response.tool_calls:
                result = self._execute_tool(tool_call)
                tool_results.append(result)
            
            # 第 3 步:将工具结果返回给 LLM
            final_response = self._call_llm_with_results(
                user_input, 
                tool_results
            )
            return final_response
        
        return response.content
    
    def _execute_tool(self, tool_call):
        """执行工具调用"""
        function_name = tool_call.function.name
        arguments = json.loads(tool_call.function.arguments)
        
        if function_name in self.tool_map:
            return self.tool_map[function_name](**arguments)
        else:
            return f"错误:未知工具 {function_name}"
    
    def _get_weather(self, city: str, unit="celsius"):
        # 模拟天气 API 调用
        return {"city": city, "temperature": 25, "weather": "晴朗"}
    
    def _send_email(self, to: str, subject: str, body: str):
        # 模拟发送邮件
        return {"status": "success", "message_id": "12345"}
    
    def _search(self, query: str):
        # 模拟搜索
        return ["结果1", "结果2", "结果3"]

工具定义的最佳实践

1. 描述要详细

# ❌ 不好的描述
{
    "name": "search",
    "description": "搜索",
    "parameters": {
        "properties": {
            "q": {"type": "string"}
        }
    }
}

# ✅ 好的描述
{
    "name": "web_search",
    "description": """
    使用搜索引擎查询网络信息。
    适用场景:
    - 需要获取实时信息(新闻、股价、天气)
    - 需要查找最新的技术文档
    - 需要了解当前事件
    不适用场景:
    - 数学计算(请使用 calculator 工具)
    - 代码执行(请使用 code_interpreter 工具)
    """,
    "parameters": {
        "properties": {
            "query": {
                "type": "string",
                "description": "搜索关键词,建议包含时间、地点等限定词"
            }
        },
        "required": ["query"]
    }
}

2. 参数设计要合理

# ❌ 过于复杂的参数
{
    "properties": {
        "config": {"type": "object"}  # LLM 很难正确填充
    }
}

# ✅ 扁平化的参数
{
    "properties": {
        "city": {"type": "string"},
        "date": {"type": "string"},
        "include_hourly": {"type": "boolean"}
    }
}

3. 错误处理要友好

def safe_tool_execution(tool_func):
    """工具调用装饰器:添加错误处理"""
    def wrapper(*args, **kwargs):
        try:
            result = tool_func(*args, **kwargs)
            return {
                "status": "success",
                "data": result
            }
        except ValueError as e:
            return {
                "status": "error",
                "error_type": "invalid_parameter",
                "message": str(e)
            }
        except Exception as e:
            return {
                "status": "error",
                "error_type": "unknown",
                "message": f"执行失败:{str(e)}"
            }
    return wrapper

@safe_tool_execution
def get_stock_price(symbol: str):
    if not symbol.isupper():
        raise ValueError("股票代码必须大写")
    # ... 获取股价

多工具调用

# LLM 可以同时调用多个工具
response = openai.chat.completions.create(
    model="gpt-4",
    messages=[{"role": "user", "content": "比较北京和上海今天的天气,发邮件告诉我"}],
    tools=tools
)

# GPT-4 可能会同时调用:
# 1. get_weather(city="北京")
# 2. get_weather(city="上海")
# 3. send_email(to="...", subject="天气对比", body="...")

总结

工具调用是 Agent 开发的核心技能:

  • 定义清晰:让 LLM 知道何时、如何调用
  • 参数简单:降低 LLM 出错的概率
  • 错误处理:优雅处理异常情况
  • 结果反馈:将工具结果返回给 LLM 继续处理

下一步:学习使用 AutoGPT 构建完全自主的 AI Agent。

更多推荐