工具调用机制
概述
工具调用(Tool Calling)是Agent与外部世界交互的核心机制。通过工具调用,Agent可以执行搜索、计算、API请求、文件操作等实际任务,突破了大模型只能生成文本的限制。现代大模型(如GPT-4、Claude等)都支持原生工具调用功能。
工具调用基础
什么是工具调用
工具调用是指LLM在生成回复时,能够识别出需要使用外部工具,并生成结构化的工具调用请求。应用系统执行工具后,将结果返回给LLM继续处理。
工作流程
用户输入 → LLM分析 → 决定调用工具 → 生成工具调用参数
↓
执行工具 → 获取结果 → 返回LLM → 生成最终回复核心概念
- 工具定义 - 描述工具的功能、参数和返回值
- 工具选择 - LLM根据用户需求选择合适的工具
- 参数构造 - LLM生成符合工具要求的参数
- 结果处理 - 将工具执行结果整合到回复中
工具定义规范
OpenAI工具定义格式
python
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": "search_web",
"description": "在网络上搜索信息",
"parameters": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "搜索关键词"
},
"num_results": {
"type": "integer",
"description": "返回结果数量",
"default": 5
}
},
"required": ["query"]
}
}
}
]Claude工具定义格式
python
tools = [
{
"name": "get_weather",
"description": "获取指定城市的天气信息",
"input_schema": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "城市名称"
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"]
}
},
"required": ["city"]
}
}
]工具定义最佳实践
python
class ToolDefinition:
def __init__(self, name: str, description: str, parameters: dict):
self.name = name
self.description = description
self.parameters = parameters
def to_openai_format(self) -> dict:
return {
"type": "function",
"function": {
"name": self.name,
"description": self.description,
"parameters": self.parameters
}
}
def validate(self):
assert self.name.isidentifier(), "工具名称必须是有效的标识符"
assert len(self.description) > 10, "描述应该详细说明工具功能"
assert "properties" in self.parameters, "必须定义参数属性"
class ToolRegistry:
def __init__(self):
self.tools = {}
def register(self, tool_def: ToolDefinition, handler: callable):
tool_def.validate()
self.tools[tool_def.name] = {
"definition": tool_def,
"handler": handler
}
def get_definitions(self) -> list:
return [
t["definition"].to_openai_format()
for t in self.tools.values()
]
def get_handler(self, name: str) -> callable:
return self.tools[name]["handler"]工具调用实现
OpenAI工具调用
python
from openai import OpenAI
import json
class OpenAIToolCaller:
def __init__(self, api_key: str, model: str = "gpt-4o"):
self.client = OpenAI(api_key=api_key)
self.model = model
self.registry = ToolRegistry()
def register_tool(self, name: str, description: str,
parameters: dict, handler: callable):
tool_def = ToolDefinition(name, description, parameters)
self.registry.register(tool_def, handler)
def run(self, user_message: str) -> str:
messages = [{"role": "user", "content": user_message}]
while True:
response = self.client.chat.completions.create(
model=self.model,
messages=messages,
tools=self.registry.get_definitions(),
tool_choice="auto"
)
message = response.choices[0].message
if message.content:
return message.content
if message.tool_calls:
messages.append(message)
for tool_call in message.tool_calls:
result = self.execute_tool_call(tool_call)
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": json.dumps(result, ensure_ascii=False)
})
else:
return "无法处理该请求"
def execute_tool_call(self, tool_call) -> dict:
handler = self.registry.get_handler(tool_call.function.name)
arguments = json.loads(tool_call.function.arguments)
try:
result = handler(**arguments)
return {"success": True, "result": result}
except Exception as e:
return {"success": False, "error": str(e)}
caller = OpenAIToolCaller(api_key="your-api-key")
caller.register_tool(
name="get_weather",
description="获取城市天气信息",
parameters={
"type": "object",
"properties": {
"city": {"type": "string", "description": "城市名称"}
},
"required": ["city"]
},
handler=lambda city: f"{city}今天晴天,温度25°C"
)
response = caller.run("北京今天天气怎么样?")
print(response)Claude工具调用
python
from anthropic import Anthropic
class ClaudeToolCaller:
def __init__(self, api_key: str, model: str = "claude-3-5-sonnet-20241022"):
self.client = Anthropic(api_key=api_key)
self.model = model
self.registry = ToolRegistry()
def register_tool(self, name: str, description: str,
parameters: dict, handler: callable):
tool_def = ToolDefinition(name, description, parameters)
self.registry.register(tool_def, handler)
def run(self, user_message: str) -> str:
messages = [{"role": "user", "content": user_message}]
while True:
response = self.client.messages.create(
model=self.model,
max_tokens=4096,
messages=messages,
tools=self.registry.get_definitions()
)
if response.stop_reason == "tool_use":
messages.append({
"role": "assistant",
"content": response.content
})
tool_results = []
for block in response.content:
if block.type == "tool_use":
result = self.execute_tool(block)
tool_results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": json.dumps(result, ensure_ascii=False)
})
messages.append({
"role": "user",
"content": tool_results
})
else:
for block in response.content:
if hasattr(block, "text"):
return block.text
return "无法处理该请求"
def execute_tool(self, tool_use) -> dict:
handler = self.registry.get_handler(tool_use.name)
try:
result = handler(**tool_use.input)
return {"success": True, "result": result}
except Exception as e:
return {"success": False, "error": str(e)}工具设计模式
1. 简单工具模式
适用于单一功能、参数简单的工具。
python
def calculate(expression: str) -> float:
"""
计算数学表达式
Args:
expression: 数学表达式,如 "2 + 3 * 4"
Returns:
计算结果
"""
import ast
import operator
ops = {
ast.Add: operator.add,
ast.Sub: operator.sub,
ast.Mult: operator.mul,
ast.Div: operator.truediv
}
node = ast.parse(expression, mode='eval')
def eval_node(node):
if isinstance(node, ast.Num):
return node.n
elif isinstance(node, ast.BinOp):
left = eval_node(node.left)
right = eval_node(node.right)
return ops[type(node.op)](left, right)
else:
raise ValueError("不支持的表达式")
return eval_node(node.body)2. 复合工具模式
组合多个工具完成复杂任务。
python
class CompositeTool:
def __init__(self, tools: list):
self.tools = tools
def run(self, **kwargs):
results = {}
for tool in self.tools:
if tool.can_run(kwargs):
result = tool.run(**kwargs)
results[tool.name] = result
kwargs.update(result)
return results
class ResearchTool(CompositeTool):
def __init__(self):
super().__init__([
SearchTool(),
ExtractTool(),
SummarizeTool()
])
def research(self, topic: str) -> dict:
return self.run(topic=topic)3. 条件工具模式
根据条件选择执行不同的工具。
python
class ConditionalTool:
def __init__(self):
self.conditions = []
def add_condition(self, condition: callable, tool: callable):
self.conditions.append((condition, tool))
def run(self, **kwargs):
for condition, tool in self.conditions:
if condition(kwargs):
return tool(**kwargs)
raise ValueError("没有匹配的工具")
router = ConditionalTool()
router.add_condition(
condition=lambda x: "天气" in x.get("query", ""),
tool=get_weather
)
router.add_condition(
condition=lambda x: "计算" in x.get("query", ""),
tool=calculate
)4. 链式工具模式
工具的输出作为下一个工具的输入。
python
class ToolChain:
def __init__(self, tools: list):
self.tools = tools
def run(self, initial_input: any) -> any:
result = initial_input
for tool in self.tools:
result = tool(result)
return result
pipeline = ToolChain([
lambda x: search_web(x),
lambda x: extract_key_info(x),
lambda x: summarize(x),
lambda x: format_output(x)
])
result = pipeline.run("人工智能最新进展")工具调用优化
1. 参数验证
python
from pydantic import BaseModel, ValidationError
from typing import Optional
class WeatherParams(BaseModel):
city: str
unit: Optional[str] = "celsius"
class Config:
extra = "forbid"
class ValidatedTool:
def __init__(self, param_model: type, handler: callable):
self.param_model = param_model
self.handler = handler
def run(self, **kwargs):
try:
params = self.param_model(**kwargs)
return self.handler(**params.dict())
except ValidationError as e:
return {"error": f"参数验证失败: {e}"}
weather_tool = ValidatedTool(
param_model=WeatherParams,
handler=lambda city, unit: get_weather_data(city, unit)
)2. 结果缓存
python
from functools import lru_cache
import hashlib
import json
class CachedTool:
def __init__(self, tool: callable, maxsize: int = 128):
self.tool = tool
self.cache = {}
self.maxsize = maxsize
def _hash_args(self, **kwargs) -> str:
args_str = json.dumps(kwargs, sort_keys=True)
return hashlib.md5(args_str.encode()).hexdigest()
def run(self, **kwargs):
cache_key = self._hash_args(**kwargs)
if cache_key in self.cache:
return self.cache[cache_key]
result = self.tool(**kwargs)
if len(self.cache) >= self.maxsize:
self.cache.pop(next(iter(self.cache)))
self.cache[cache_key] = result
return result3. 并行调用
python
import asyncio
from concurrent.futures import ThreadPoolExecutor
class ParallelToolExecutor:
def __init__(self, max_workers: int = 5):
self.executor = ThreadPoolExecutor(max_workers=max_workers)
async def execute_parallel(self, tool_calls: list) -> list:
loop = asyncio.get_event_loop()
tasks = [
loop.run_in_executor(
self.executor,
self.execute_single,
call
)
for call in tool_calls
]
results = await asyncio.gather(*tasks)
return results
def execute_single(self, tool_call: dict) -> dict:
handler = self.get_handler(tool_call["name"])
return handler(**tool_call["arguments"])4. 超时控制
python
import signal
from contextlib import contextmanager
class TimeoutError(Exception):
pass
@contextmanager
def timeout(seconds: int):
def timeout_handler(signum, frame):
raise TimeoutError("工具执行超时")
signal.signal(signal.SIGALRM, timeout_handler)
signal.alarm(seconds)
try:
yield
finally:
signal.alarm(0)
class TimeoutTool:
def __init__(self, tool: callable, timeout_seconds: int = 30):
self.tool = tool
self.timeout_seconds = timeout_seconds
def run(self, **kwargs):
try:
with timeout(self.timeout_seconds):
return self.tool(**kwargs)
except TimeoutError:
return {"error": "工具执行超时"}常用工具实现
搜索工具
python
import requests
from bs4 import BeautifulSoup
class SearchTool:
def __init__(self, api_key: str = None):
self.api_key = api_key
def search(self, query: str, num_results: int = 5) -> list:
if self.api_key:
return self._search_with_api(query, num_results)
else:
return self._search_with_scraping(query, num_results)
def _search_with_api(self, query: str, num_results: int) -> list:
url = "https://api.search-engine.com/search"
params = {
"q": query,
"num": num_results,
"api_key": self.api_key
}
response = requests.get(url, params=params)
data = response.json()
return [
{
"title": item["title"],
"url": item["url"],
"snippet": item["snippet"]
}
for item in data["results"]
]
def _search_with_scraping(self, query: str, num_results: int) -> list:
pass
search_tool = SearchTool()
results = search_tool.search("Python教程")文件操作工具
python
import os
from pathlib import Path
class FileTool:
def __init__(self, base_dir: str = "."):
self.base_dir = Path(base_dir)
def read(self, filepath: str) -> str:
full_path = self.base_dir / filepath
self._validate_path(full_path)
with open(full_path, 'r', encoding='utf-8') as f:
return f.read()
def write(self, filepath: str, content: str) -> bool:
full_path = self.base_dir / filepath
self._validate_path(full_path)
full_path.parent.mkdir(parents=True, exist_ok=True)
with open(full_path, 'w', encoding='utf-8') as f:
f.write(content)
return True
def list_files(self, directory: str = ".") -> list:
full_path = self.base_dir / directory
self._validate_path(full_path)
return [
{
"name": item.name,
"type": "directory" if item.is_dir() else "file",
"size": item.stat().st_size if item.is_file() else None
}
for item in full_path.iterdir()
]
def _validate_path(self, path: Path):
resolved = path.resolve()
if not str(resolved).startswith(str(self.base_dir.resolve())):
raise ValueError("路径不在允许范围内")API调用工具
python
import requests
from typing import Dict, Any
class APITool:
def __init__(self, base_url: str, headers: dict = None):
self.base_url = base_url.rstrip('/')
self.headers = headers or {}
def get(self, endpoint: str, params: dict = None) -> Dict[str, Any]:
url = f"{self.base_url}/{endpoint.lstrip('/')}"
response = requests.get(url, headers=self.headers, params=params)
return self._handle_response(response)
def post(self, endpoint: str, data: dict = None) -> Dict[str, Any]:
url = f"{self.base_url}/{endpoint.lstrip('/')}"
response = requests.post(url, headers=self.headers, json=data)
return self._handle_response(response)
def _handle_response(self, response: requests.Response) -> dict:
if response.status_code >= 400:
return {
"success": False,
"error": f"HTTP {response.status_code}: {response.text}"
}
return {
"success": True,
"data": response.json()
}工具调用调试
日志记录
python
import logging
from datetime import datetime
class ToolLogger:
def __init__(self, log_file: str = "tool_calls.log"):
self.logger = logging.getLogger("ToolLogger")
self.logger.setLevel(logging.INFO)
handler = logging.FileHandler(log_file)
formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
handler.setFormatter(formatter)
self.logger.addHandler(handler)
def log_call(self, tool_name: str, arguments: dict):
self.logger.info(f"调用工具: {tool_name}, 参数: {arguments}")
def log_result(self, tool_name: str, result: any, duration: float):
self.logger.info(
f"工具返回: {tool_name}, "
f"耗时: {duration:.2f}s, "
f"结果: {result}"
)
def log_error(self, tool_name: str, error: Exception):
self.logger.error(f"工具错误: {tool_name}, 错误: {error}")
class LoggedTool:
def __init__(self, tool: callable, logger: ToolLogger):
self.tool = tool
self.logger = logger
def run(self, **kwargs):
tool_name = self.tool.__name__
self.logger.log_call(tool_name, kwargs)
start_time = datetime.now()
try:
result = self.tool(**kwargs)
duration = (datetime.now() - start_time).total_seconds()
self.logger.log_result(tool_name, result, duration)
return result
except Exception as e:
self.logger.log_error(tool_name, e)
raise调试模式
python
class DebugToolCaller:
def __init__(self, caller):
self.caller = caller
self.debug_mode = False
self.call_history = []
def enable_debug(self):
self.debug_mode = True
def run(self, user_message: str) -> str:
if self.debug_mode:
print(f"[DEBUG] 用户输入: {user_message}")
result = self.caller.run(user_message)
if self.debug_mode:
print(f"[DEBUG] 最终回复: {result}")
return result
def get_call_history(self) -> list:
return self.call_history小结
工具调用是Agent的核心能力,通过合理设计和实现工具系统,可以让Agent具备强大的执行能力:
- 规范定义 - 使用标准的工具定义格式,提供清晰的描述
- 灵活实现 - 支持多种工具模式,适应不同场景需求
- 性能优化 - 参数验证、结果缓存、并行调用、超时控制
- 安全可靠 - 权限控制、路径验证、错误处理
- 易于调试 - 日志记录、调试模式、历史追踪
下一章我们将探讨Agent的记忆系统设计,这是实现多轮对话和知识积累的关键。