欢迎光临
我们一直在努力

AI Agent 工具调用系统设计:让大模型掌控世界

AI Agent 工具调用系统设计:让大模型掌控世界

前言

工具调用(Tool Use / Function Calling)是 AI Agent 实现复杂任务的关键能力。通过工具调用,大模型可以与外部世界交互,执行计算、查询数据库、调用 API,真正变成一个能够"行动"的智能体。

我之前设计的代码审查 Agent 就有完善的工具调用能力,可以搜索文档、读写文件、执行代码。工具调用系统设计得好不好,直接决定了 Agent 的能力边界。今天分享一些我在实践中总结的设计模式和经验。

工具调用的基本原理

什么是 Function Calling

Function Calling 是让大模型生成结构化调用指令的能力:

# OpenAI API 的 Function Calling 示例
response = client.chat.completions.create(
model="gpt-4",
messages=[{"role": "user", "content": "北京今天天气怎么样?"}],
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"]
}
}
}
]
)

# 模型可能返回:
# {
# "tool_calls": [{
# "id": "call_xxx",
# "function": {
# "name": "get_weather",
# "arguments": '{"city": "北京", "unit": "celsius"}'
# }
# }]
# }

工具调用的工作流程

用户输入 → LLM 判断是否需要工具 → 是 → 解析工具和参数 → 执行工具 → 返回结果 → LLM 生成最终回答
↓ 否
直接生成回答

工具描述设计

Schema 设计原则

工具的 JSON Schema 描述直接决定模型能否正确理解和使用工具:

# 好的工具描述示例
good_tool_schema = {
"name": "search_documents",
"description": "在企业知识库中搜索相关文档。适用于查找技术文档、API说明、最佳实践等。",
"parameters": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "搜索查询语句,建议使用完整的问句或关键词组合。例如:'如何使用 REST API' 或 'REST API 使用方法'"
},
"top_k": {
"type": "integer",
"description": "返回的最多文档数量,默认 5",
"default": 5
}
},
"required": ["query"]
}
}

# 不好的工具描述示例
bad_tool_schema = {
"name": "search",
"description": "搜索",
"parameters": {
"type": "object",
"properties": {
"q": {"type": "string"}
},
"required": ["q"]
}
}

工具分组策略

当工具数量很多时,应该进行分组:

TOOL_GROUPS = {
"information": {
"description": "信息查询类工具",
"tools": ["search_documents", "get_weather", "get_time"]
},
"code": {
"description": "代码处理类工具",
"tools": ["execute_code", "read_file", "write_file"]
},
"communication": {
"description": "通信类工具",
"tools": ["send_email", "send_message"]
}
}

def get_tools_for_scenario(scenario: str) -> List[dict]:
"""根据场景选择合适的工具组"""
if scenario == "technical_support":
return [get_tool_schema("information"), get_tool_schema("code")]
elif scenario == "business":
return [get_tool_schema("information"), get_tool_schema("communication")]
else:
return get_all_tools()

工具执行框架

基础执行器

from dataclasses import dataclass
from typing import Dict, List, Callable, Any
import json

@dataclass
class ToolCall:
"""工具调用请求"""
id: str
name: str
arguments: dict

@dataclass
class ToolResult:
"""工具执行结果"""
call_id: str
success: bool
result: Any
error: str = None

class ToolExecutor:
"""工具执行器"""

def __init__(self):
self.tools: Dict[str, Callable] = {}
self.schemas: Dict[str, dict] = {}

def register(self, name: str, schema: dict, func: Callable):
"""注册工具"""
self.tools[name] = func
self.schemas[name] = schema

def execute(self, call: ToolCall) -> ToolResult:
"""执行工具调用"""
if call.name not in self.tools:
return ToolResult(
call_id=call.id,
success=False,
result=None,
error=f"Unknown tool: {call.name}"
)

try:
func = self.tools[call.name]
result = func(**call.arguments)
return ToolResult(
call_id=call.id,
success=True,
result=result
)
except Exception as e:
return ToolResult(
call_id=call.id,
success=False,
result=None,
error=str(e)
)

def execute_batch(self, calls: List[ToolCall]) -> List[ToolResult]:
"""批量执行工具调用"""
return [self.execute(call) for call in calls]

实际工具实现

# 注册实际工具
executor = ToolExecutor()

# 搜索引擎
def search_web(query: str, num_results: int = 5) -> dict:
"""搜索网页"""
# 实际实现调用搜索引擎 API
results = google_search(query, num_results)
return {
"query": query,
"results": [
{
"title": r.title,
"snippet": r.snippet,
"url": r.url
}
for r in results
]
}

executor.register(
"search_web",
{
"name": "search_web",
"description": "搜索网页获取最新信息。适用于查询实时新闻、未知问题等。",
"parameters": {
"type": "object",
"properties": {
"query": {"type": "string", "description": "搜索查询"},
"num_results": {
"type": "integer",
"description": "返回结果数量",
"default": 5
}
},
"required": ["query"]
}
},
search_web
)

# 计算器
def calculator(expression: str) -> dict:
"""安全计算数学表达式"""
# 只允许基本运算
allowed_chars = set("0123456789+-*/.() ")
if not all(c in allowed_chars for c in expression):
return {"error": "不允许的字符"}

try:
result = eval(expression)
return {"expression": expression, "result": result}
except Exception as e:
return {"error": str(e)}

executor.register(
"calculator",
{
"name": "calculator",
"description": "计算数学表达式的值。只支持基本运算符:加(+)、减(-)、乘(*)、除(/)。",
"parameters": {
"type": "object",
"properties": {
"expression": {
"type": "string",
"description": "数学表达式,例如:2+3*4 或 (10+5)/3"
}
},
"required": ["expression"]
}
},
calculator
)

Agent 工具调用循环

完整的工具调用循环

class ToolUsingAgent:
"""支持工具调用的 Agent"""

def __init__(self, llm, executor: ToolExecutor, max_iterations: int = 10):
self.llm = llm
self.executor = executor
self.max_iterations = max_iterations

def run(self, user_input: str) -> str:
"""运行 Agent 处理用户输入"""
messages = [
{"role": "system", "content": self._get_system_prompt()},
{"role": "user", "content": user_input}
]

for iteration in range(self.max_iterations):
# 1. 获取 LLM 响应
response = self.llm.chat(messages, tools=self.executor.get_all_schemas())

# 2. 检查是否有工具调用
if not response.tool_calls:
# 没有工具调用,直接返回
return response.content

# 3. 执行工具调用
tool_results = []
for call in response.tool_calls:
tool_call = ToolCall(
id=call.id,
name=call.function.name,
arguments=json.loads(call.function.arguments)
)
result = self.executor.execute(tool_call)
tool_results.append(result)

# 4. 将结果添加到消息
messages.append(response.to_message())

for result in tool_results:
messages.append({
"role": "tool",
"tool_call_id": result.call_id,
"content": json.dumps(result.result) if result.success else result.error
})

return "达到最大迭代次数"

def _get_system_prompt(self) -> str:
return """你是一个智能助手,可以通过调用工具来完成任务。

Available tools:
""" + self.executor.get_tools_description()

结果验证与重试

class VerifiedToolExecutor(ToolExecutor):
"""带验证的工具执行器"""

def execute_with_verification(
self,
call: ToolCall,
expected_format: dict = None
) -> ToolResult:
"""执行并验证结果"""
result = self.execute(call)

if not result.success:
return result

if expected_format:
# 验证结果格式
is_valid, error = self._verify_format(result.result, expected_format)
if not is_valid:
return ToolResult(
call_id=call.id,
success=False,
result=None,
error=f"Result format error: {error}"
)

return result

def _verify_format(self, result: Any, expected: dict) -> tuple:
"""验证结果格式"""
if expected.get("type") == "array":
if not isinstance(result, list):
return False, "Expected array"
elif expected.get("type") == "object":
if not isinstance(result, dict):
return False, "Expected object"
for key in expected.get("required", []):
if key not in result:
return False, f"Missing required field: {key}"
return True, None

工具调用的安全考虑

输入验证

class SecureToolExecutor(ToolExecutor):
"""安全的工具执行器"""

def execute(self, call: ToolCall) -> ToolResult:
# 1. 参数验证
schema = self.schemas.get(call.name)
if not schema:
return ToolResult(call.id, False, None, "Unknown tool")

# 检查必需参数
required = schema.get("parameters", {}).get("required", [])
for param in required:
if param not in call.arguments:
return ToolResult(
call.id, False, None, f"Missing required parameter: {param}"
)

# 2. 值域检查
properties = schema.get("parameters", {}).get("properties", {})
for param, spec in properties.items():
if param in call.arguments:
value = call.arguments[param]
if not self._validate_param_value(value, spec):
return ToolResult(
call.id, False, None,
f"Invalid value for parameter {param}"
)

# 3. 敏感操作检查
if self._is_sensitive_operation(call):
# 记录审计日志
self._audit_log(call)

return super().execute(call)

def _validate_param_value(self, value, spec) -> bool:
"""验证参数值"""
param_type = spec.get("type")

if param_type == "string":
if "maxLength" in spec and len(value) > spec["maxLength"]:
return False
if "enum" in spec and value not in spec["enum"]:
return False

elif param_type == "integer":
if not isinstance(value, int):
return False
if "minimum" in spec and value < spec["minimum"]:
return False
if "maximum" in spec and value > spec["maximum"]:
return False

return True

def _is_sensitive_operation(self, call: ToolCall) -> bool:
"""检查是否为敏感操作"""
sensitive_tools = {"delete_file", "send_email", "execute_sql"}
return call.name in sensitive_tools

异步工具调用

import asyncio
from typing import List

class AsyncToolExecutor:
"""异步工具执行器"""

def __init__(self):
self.tools: Dict[str, Callable] = {}

def register(self, name: str, func: Callable):
self.tools[name] = func

async def execute_async(self, call: ToolCall) -> ToolResult:
"""异步执行单个工具"""
if call.name not in self.tools:
return ToolResult(call.id, False, None, "Unknown tool")

try:
func = self.tools[call.name]

# 如果是异步函数
if asyncio.iscoroutinefunction(func):
result = await func(**call.arguments)
else:
# 在线程池中运行同步函数
loop = asyncio.get_event_loop()
result = await loop.run_in_executor(None, lambda: func(**call.arguments))

return ToolResult(call.id, True, result)
except Exception as e:
return ToolResult(call.id, False, None, str(e))

async def execute_batch_async(self, calls: List[ToolCall]) -> List[ToolResult]:
"""批量异步执行"""
tasks = [self.execute_async(call) for call in calls]
return await asyncio.gather(*tasks)

工具调用优化

并行 vs 串行

class SmartToolExecutor(ToolExecutor):
"""智能工具执行器"""

def execute_with_plan(self, calls: List[ToolCall]) -> List[ToolResult]:
"""分析依赖关系并优化执行顺序"""
# 1. 构建依赖图
dependencies = self._build_dependency_graph(calls)

# 2. 找出可以并行的调用
independent_calls = [
call for call in calls
if not dependencies.get(call.id)
]

# 3. 并行执行独立的调用
parallel_results = self.execute_batch(independent_calls)

# 4. 串行执行有依赖的调用
remaining_calls = [c for c in calls if c.id in dependencies]
serial_results = []

for call in remaining_calls:
deps = dependencies[call.id]
# 等待依赖完成
for dep_id in deps:
self._wait_for_result(dep_id)

result = self.execute(call)
serial_results.append(result)

return parallel_results + serial_results

总结

工具调用是 AI Agent 实现复杂任务的核心能力。设计一个好的工具调用系统需要考虑:

  • 清晰的工具描述:让模型准确理解工具用途和参数
  • 健壮的执行框架:错误处理、验证、重试机制
  • 安全保障:输入验证、敏感操作审计
  • 性能优化:并行执行、依赖分析
  • 希望这些经验对大家有帮助。

    赞(0)
    未经允许不得转载:171主机测评 » AI Agent 工具调用系统设计:让大模型掌控世界
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址