引言
AI Agent的能力上限,很大程度上取决于它能否正确使用工具。在2026年,随着MCP(Model Context Protocol)协议标准化、Tool Calling API的成熟,"工具编排工程"已成为AI应用开发中最关键的技能之一。
本文从实战角度,系统讲解如何设计工具、编排工具调用链,并处理复杂的多工具协作场景。
一、工具设计原则:让AI能"理解"你的工具
1.1 工具描述的黄金法则
错误示例:
tools = [
{
"name": "query_db",
"description": "查询数据库",
"parameters": {
"type": "object",
"properties": {
"query": {"type": "string"}
}
}
}
]
正确示例:
tools = [
{
"name": "query_customer_data",
"description": """查询客户数据库,获取客户信息、订单历史和行为数据。
适用场景:
- 需要了解特定客户的基本信息(姓名、联系方式、注册时间)
- 查询某个客户的历史订单和消费记录
- 分析客户的行为模式和偏好
不适用场景:
- 不能用于修改客户数据(修改请使用 update_customer 工具)
- 不能查询系统日志(请使用 get_system_logs 工具)
注意:返回的手机号码和邮箱默认脱敏,若需完整信息需要指定 unmask=true""",
"parameters": {
"type": "object",
"properties": {
"customer_id": {
"type": "string",
"description": "客户唯一ID,格式为 'C' + 8位数字,如 'C12345678'"
},
"fields": {
"type": "array",
"items": {"type": "string"},
"description": "需要返回的字段列表,留空返回所有字段。可选值:['name', 'phone', 'email', 'orders', 'behavior']",
"default": []
},
"unmask": {
"type": "boolean",
"description": "是否返回完整的敏感信息(手机号、邮箱),默认false",
"default": False
}
},
"required": ["customer_id"]
}
}
]
关键要点:
- 描述要说明什么时候该用和什么时候不该用
- 参数说明要包含格式示例
- 标注默认值和副作用
1.2 工具粒度设计
太粗粒度(不好):
# 一个工具做太多事
def manage_order(action: str, order_id: str, **kwargs): ...
太细粒度(也不好):
# 工具太多,AI选择困难
def get_order_status(): ...
def get_order_items(): ...
def get_order_shipping(): ...
def get_order_payment(): ...
# 200个类似工具...
合适粒度(推荐):
# 按业务域组织,每个工具有明确的职责边界
def get_order_detail(order_id: str, include: list = None): ... # 聚合订单信息
def update_order_status(order_id: str, status: str, reason: str): ... # 修改状态
def cancel_order(order_id: str, refund_type: str): ... # 取消并退款
二、工具调用链设计模式
2.1 顺序调用链
适用于有明确依赖关系的任务:
from langchain.agents import AgentExecutor, create_openai_tools_agent
from langchain_core.tools import tool
@tool
def search_user(name: str) -> dict:
"""根据姓名搜索用户,返回用户ID和基本信息"""
# 实现省略
return {"user_id": "U123", "name": name, "email": "..."}
@tool
def get_user_orders(user_id: str, limit: int = 10) -> list:
"""根据用户ID获取订单列表"""
# 实现省略
return [...]
@tool
def analyze_order_trend(orders: list) -> str:
"""分析订单趋势,返回消费行为摘要"""
# 实现省略
return "该用户倾向于在月末消费,平均客单价..."
# Agent会自动编排:search_user → get_user_orders → analyze_order_trend
2.2 并行工具调用(2026新特性)
现代LLM支持同时调用多个独立工具:
import asyncio
from openai import AsyncOpenAI
client = AsyncOpenAI(api_key="sk-xxx")
# 支持并行工具调用的系统
async def parallel_tool_execution(tool_calls: list):
"""并行执行多个工具调用"""
async def execute_tool(tool_call):
tool_name = tool_call.function.name
args = json.loads(tool_call.function.arguments)
if tool_name == "get_weather":
return await get_weather_async(**args)
elif tool_name == "get_stock_price":
return await get_stock_price_async(**args)
elif tool_name == "search_news":
return await search_news_async(**args)
# 并行执行所有工具
results = await asyncio.gather(
*[execute_tool(tc) for tc in tool_calls],
return_exceptions=True
)
return results
# 使用示例
response = await client.chat.completions.create(
model="gpt-4o",
messages=[{
"role": "user",
"content": "帮我查一下今天北京天气、茅台股价、以及AI最新新闻"
}],
tools=tools,
tool_choice="required",
parallel_tool_calls=True # 启用并行工具调用
)
if response.choices[0].message.tool_calls:
results = await parallel_tool_execution(
response.choices[0].message.tool_calls
)
2.3 条件分支工具链
class ConditionalToolChain:
"""根据上下文动态决定使用哪个工具"""
def __init__(self, llm, tools_registry):
self.llm = llm
self.tools_registry = tools_registry
async def execute(self, user_query: str, context: dict) -> str:
# 根据上下文决定工具集
if context.get("user_type") == "vip":
available_tools = self.tools_registry.get_vip_tools()
elif context.get("user_type") == "guest":
available_tools = self.tools_registry.get_guest_tools()
else:
available_tools = self.tools_registry.get_standard_tools()
# 使用过滤后的工具集
response = await self.llm.chat(
messages=[{"role": "user", "content": user_query}],
tools=available_tools
)
return response
三、错误处理与容错设计
3.1 工具调用失败处理
from tenacity import retry, stop_after_attempt, wait_exponential
from typing import Optional
class RobustToolExecutor:
@retry(
stop=stop_after_attempt(3),
wait=wait_exponential(multiplier=1, min=2, max=10)
)
async def execute_with_retry(self, tool_name: str, args: dict) -> dict:
"""带重试的工具执行"""
try:
result = await self.tools[tool_name](**args)
return {"success": True, "data": result}
except TimeoutError:
return {"success": False, "error": "工具调用超时,请稍后重试"}
except PermissionError as e:
# 权限错误不重试
return {"success": False, "error": f"权限不足: {str(e)}"}
except Exception as e:
# 记录日志并向上抛出(触发重试)
logger.error(f"工具 {tool_name} 调用失败: {e}")
raise
def build_error_message(self, tool_name: str, error: str) -> str:
"""构建给LLM的错误反馈,引导AI恢复"""
return f"""工具 '{tool_name}' 调用失败。
错误信息:{error}
请根据以下策略恢复:
1. 如果是参数错误,请检查参数格式并重试
2. 如果是网络超时,可以使用备用工具 '{tool_name}_fallback'
3. 如果无法通过工具获取信息,请基于已知信息给出部分回答并说明限制"""
3.2 工具调用循环检测
class AgentLoopDetector:
"""检测Agent是否陷入无限循环"""
def __init__(self, max_same_tool_calls: int = 3):
self.call_history = []
self.max_same_tool_calls = max_same_tool_calls
def record_call(self, tool_name: str, args: dict) -> bool:
"""记录工具调用,返回是否检测到循环"""
call_signature = f"{tool_name}:{json.dumps(args, sort_keys=True)}"
self.call_history.append(call_signature)
# 检测相同工具调用是否超过阈值
same_calls = self.call_history.count(call_signature)
if same_calls >= self.max_same_tool_calls:
return True # 检测到循环
# 检测总调用次数
if len(self.call_history) > 20:
return True # 调用次数过多,可能陷入循环
return False
四、MCP协议:工具生态的未来
4.1 MCP工具服务器
from mcp import Server, Tool, CallToolResult
server = Server("my-tool-server")
@server.tool()
async def query_database(
sql: str,
database: str = "production"
) -> CallToolResult:
"""执行SQL查询,返回结果集"""
try:
result = await db_pool.execute(sql, database=database)
return CallToolResult(
content=[{"type": "text", "text": json.dumps(result, ensure_ascii=False)}]
)
except Exception as e:
return CallToolResult(
is_error=True,
content=[{"type": "text", "text": f"查询失败: {str(e)}"}]
)
# 启动MCP服务
server.run(transport="stdio")
4.2 Claude Desktop集成MCP工具
{
"mcpServers": {
"my-tools": {
"command": "python",
"args": ["-m", "my_tool_server"],
"env": {
"DB_URL": "postgresql://localhost/mydb",
"API_KEY": "xxx"
}
}
}
}
五、性能监控与可观测性
import time
from dataclasses import dataclass
from typing import Any
@dataclass
class ToolCallMetrics:
tool_name: str
duration_ms: float
success: bool
error_type: Optional[str] = None
token_cost: int = 0
class ToolCallTracker:
"""追踪工具调用的性能指标"""
def __init__(self, metrics_backend):
self.metrics = metrics_backend
async def tracked_call(self, tool_name: str, args: dict, tool_fn) -> Any:
start_time = time.monotonic()
success = True
error_type = None
try:
result = await tool_fn(**args)
return result
except Exception as e:
success = False
error_type = type(e).__name__
raise
finally:
duration_ms = (time.monotonic() - start_time) * 1000
metric = ToolCallMetrics(
tool_name=tool_name,
duration_ms=duration_ms,
success=success,
error_type=error_type
)
await self.metrics.record(metric)
# 实时告警:单次工具调用超过5秒
if duration_ms > 5000:
await self.metrics.alert(f"工具 {tool_name} 响应超时: {duration_ms:.0f}ms")
总结
高质量的工具编排工程包含四个维度:
- 工具设计:清晰的描述、合适的粒度、明确的边界
- 调用编排:顺序/并行/条件分支的选择
- 容错机制:重试、降级、循环检测
- 可观测性:性能监控、异常告警、成本追踪
工具编排的本质是在AI的理解能力和系统的可靠性之间找到平衡点。越是清晰的工具定义,AI就越不容易犯错;越是健壮的容错设计,系统在生产环境就越稳定。