第34章:工具使用模式
概述
工具(Tool)是 Agent 与外部世界交互的桥梁。Agent 的能力上限,很大程度上取决于它能使用多少工具、如何选择工具、以及如何组合工具。本章将以设计模式(Design Pattern)的视角,系统讲解 Agent 工具使用的各种模式——从最基础的单工具调用,到复杂的工具链编排、动态选择策略、安全沙箱执行和性能优化。掌握这些模式,将帮助你构建出既强大又可靠的 Agent 工具系统。
34.1 工具抽象的本质
34.1.1 从函数到工具
在 Agent 编程中,"工具"不仅仅是函数——它是一个带有丰富语义描述的结构化抽象。工具不仅要能执行,更要能被 LLM 理解和选择。
python
from pydantic import BaseModel, Field
from typing import Any, Callable
# 普通函数——Agent无法理解
def search(query: str) -> str:
return api.search(query)
# 工具——Agent可以理解并选择
class SearchTool:
name = "web_search"
description = "在互联网上搜索最新信息,适用于需要查找实时数据、新闻、产品信息等场景"
class Args(BaseModel):
query: str = Field(..., description="搜索关键词,应简洁明确")
num_results: int = Field(default=5, description="返回结果数量,1-20")
language: str = Field(default="zh", description="搜索语言:zh/en")
async def execute(self, query: str, num_results: int = 5,
language: str = "zh") -> str:
results = await search_api(query, num_results, language)
return self._format_results(results)34.1.2 Function Calling 标准接口
python
class StandardTool:
"""标准化的工具接口"""
def __init__(self, name: str, description: str):
self.name = name
self.description = description
self._parameters: list[dict] = []
def param(self, name: str, type_: str, description: str,
required: bool = True, default: Any = None,
enum: list[str] | None = None) -> 'StandardTool':
"""链式定义参数"""
param_def = {
"type": type_,
"description": description,
}
if not required:
param_def["default"] = default
if enum:
param_def["enum"] = enum
self._parameters.append({"name": name, **param_def})
return self
def to_openai_schema(self) -> dict:
"""转换为OpenAI Function Calling格式"""
return {
"type": "function",
"function": {
"name": self.name,
"description": self.description,
"parameters": {
"type": "object",
"properties": {
p["name"]: {
"type": p["type"],
"description": p["description"],
**({"enum": p["enum"]} if p.get("enum") else {})
}
for p in self._parameters
},
"required": [
p["name"] for p in self._parameters
if "default" not in p
]
}
}
}
# 使用示例
search = (StandardTool("search", "搜索互联网获取最新信息")
.param("query", "string", "搜索关键词")
.param("num_results", "integer", "结果数量", required=False, default=5)
.param("region", "string", "搜索区域", required=False,
enum=["cn", "us", "eu", "global"]))34.2 基础工具模式
34.2.1 单一工具调用
最简单的模式:Agent 识别用户意图,选择并调用单个工具。
python
class SingleToolAgent:
"""单工具Agent"""
def __init__(self, tool, llm):
self.tool = tool
self.llm = llm
async def run(self, user_input: str) -> str:
# 1. 判断是否需要工具
decision = await self._should_use_tool(user_input)
if not decision["use_tool"]:
return await self.llm.generate(user_input)
# 2. 提取工具参数
args = await self._extract_args(user_input, self.tool)
# 3. 执行工具
result = await self.tool.execute(**args)
# 4. 格式化输出
return await self._format_response(user_input, result)34.2.2 参数提取模式
python
class ParameterExtractor:
"""工具参数提取器"""
async def extract(self, user_input: str, tool: StandardTool,
llm) -> dict:
"""从自然语言中提取工具参数"""
prompt = f"""
用户输入: {user_input}
工具: {tool.name}
描述: {tool.description}
参数: {json.dumps(tool._parameters, ensure_ascii=False)}
请从用户输入中提取工具参数,以JSON格式返回。
如果某个参数无法从输入中提取,使用默认值或null。
"""
response = await llm.generate(prompt)
try:
params = json.loads(response)
return self._validate_params(params, tool)
except json.JSONDecodeError:
return self._fallback_extraction(user_input, tool)34.3 工具链模式
34.3.1 Sequential Chain(顺序链)
python
class ToolChain:
"""工具顺序链"""
def __init__(self):
self._steps: list[tuple[StandardTool, str]] = []
def add_step(self, tool: StandardTool,
input_mapping: str = "$prev_result") -> 'ToolChain':
self._steps.append((tool, input_mapping))
return self
async def execute(self, initial_input: dict) -> str:
current_result = initial_input
for i, (tool, mapping) in enumerate(self._steps):
step_input = self._resolve_mapping(mapping, current_result)
current_result = await tool.execute(**step_input)
return current_result34.3.2 DAG执行模式
python
import networkx as nx
class DAGExecutor:
"""有向无环图工具执行器"""
def __init__(self):
self.graph = nx.DiGraph()
self._tools: dict[str, StandardTool] = {}
def add_tool(self, tool: StandardTool,
dependencies: list[str] | None = None):
self._tools[tool.name] = tool
self.graph.add_node(tool.name)
if dependencies:
for dep in dependencies:
self.graph.add_edge(dep, tool.name)
async def execute(self, initial_inputs: dict = None) -> dict:
if not nx.is_directed_acyclic_graph(self.graph):
raise ValueError("工具依赖图包含循环!")
results = initial_inputs or {}
execution_order = list(nx.topological_sort(self.graph))
for tool_name in execution_order:
tool = self._tools[tool_name]
args = {}
for pred in self.graph.predecessors(tool_name):
args[pred] = results.get(pred, "")
results[tool_name] = await tool.execute(**args)
return results34.3.3 条件分支
python
class ConditionalToolChain:
"""条件分支工具链"""
def when(self, condition: str, tools: list[StandardTool]):
"""定义条件分支"""
self._conditions.append({"condition": condition, "tools": tools})
return self
async def execute(self, input_data: dict) -> str:
for cond in self._conditions:
if self._evaluate(cond["condition"], input_data):
for tool in cond["tools"]:
result = await tool.execute(**input_data)
input_data["last_result"] = result
return result
return "没有匹配的条件分支"34.4 工具选择策略
34.4.1 基于语义的自动选择
python
class SemanticToolSelector:
"""语义工具选择器"""
def __init__(self, tools: list[StandardTool], embedder):
self.tools = tools
self.embedder = embedder
self._tool_embeddings: dict[str, list[float]] = {}
async def build_index(self):
for tool in self.tools:
index_text = f"{tool.name}: {tool.description}"
self._tool_embeddings[tool.name] = \
await self.embedder.embed(index_text)
async def select(self, query: str, top_k: int = 3) -> list[StandardTool]:
query_embedding = await self.embedder.embed(query)
scored_tools = []
for tool in self.tools:
similarity = cosine_similarity(
[query_embedding], [self._tool_embeddings[tool.name]]
)[0][0]
scored_tools.append((tool, similarity))
scored_tools.sort(key=lambda x: x[1], reverse=True)
return [t for t, s in scored_tools[:top_k]]34.4.2 ReAct式动态选择
python
class ReActToolSelector:
"""ReAct模式下的动态工具选择"""
async def think_and_act(self, task: str, tools: list[StandardTool],
llm, max_steps: int = 10) -> str:
tools_desc = "\n".join(
f"- {t.name}: {t.description}" for t in tools
)
context = f"任务: {task}\n可用工具:\n{tools_desc}"
history = []
for step in range(max_steps):
thought_prompt = f"""
{context}
历史步骤: {self._format_history(history)}
请思考下一步。格式:
思考: [推理过程]
行动: [工具名(参数)] 或 "最终答案: [回答]"
"""
response = await llm.generate(thought_prompt)
action = self._parse_action(response)
if action["type"] == "final_answer":
return action["content"]
tool = next(t for t in tools if t.name == action["tool_name"])
result = await tool.execute(**action["args"])
history.append({"action": f"{action['tool_name']}", "observation": str(result)[:500]})
context += f"\n\n观察: {str(result)[:500]}"
return "未能在限定步骤内完成任务。"34.5 工具组合与编排
34.5.1 Composite Tools(组合工具)
python
class CompositeTool(StandardTool):
"""组合工具:将多个工具封装为一个高级工具"""
def __init__(self, name: str, description: str):
super().__init__(name, description)
self._sub_tools: list[StandardTool] = []
def add_sub_tool(self, tool: StandardTool):
self._sub_tools.append(tool)
return self
async def execute(self, **kwargs) -> str:
intermediate = kwargs
for tool in self._sub_tools:
result = await tool.execute(**intermediate)
intermediate["prev_result"] = result
intermediate["input"] = str(result)
return intermediate.get("prev_result", "")
# 智能搜索 = 搜索 + 摘要
smart_search = CompositeTool("smart_search", "智能搜索:搜索并摘要")
smart_search.add_sub_tool(StandardTool("search", "搜索").param("query", "string", "查询"))
smart_search.add_sub_tool(StandardTool("summarize", "摘要").param("text", "string", "文本"))34.6 工具错误处理
34.6.1 超时与重试
python
class ResilientTool:
"""带弹性策略的工具包装器"""
def __init__(self, tool: StandardTool,
max_retries: int = 3, timeout: float = 30.0):
self.tool = tool
self.max_retries = max_retries
self.timeout = timeout
async def execute(self, **kwargs) -> dict:
try:
result = await asyncio.wait_for(
self._retry_execute(**kwargs),
timeout=self.timeout
)
return {"success": True, "data": result}
except asyncio.TimeoutError:
return {"success": False, "error": f"工具 {self.tool.name} 超时"}
except Exception as e:
return {"success": False, "error": str(e)}
async def _retry_execute(self, **kwargs):
for attempt in range(self.max_retries):
try:
return await self.tool.execute(**kwargs)
except Exception:
if attempt == self.max_retries - 1:
raise
await asyncio.sleep(2 ** attempt)34.6.2 Graceful Degradation(优雅降级)
python
class DegradableTool:
"""可降级的工具"""
def __init__(self, primary: StandardTool,
fallback: StandardTool = None,
fallback_response: str = None):
self.primary = primary
self.fallback = fallback
self.fallback_response = fallback_response
async def execute(self, **kwargs) -> Any:
try:
return await self.primary.execute(**kwargs)
except Exception as e:
if self.fallback:
return await self.fallback.execute(**kwargs)
if self.fallback_response:
return self.fallback_response
raise34.7 自定义工具开发
34.7.1 工具开发规范
工具命名:动词_名词(如 search_web、calculate_expression)
描述模板:
- 一句话功能描述
- 适用场景(2-3个)
- 不适用场景
- 输入要求和输出格式
参数设计规则:
- 必需参数不超过3个
- 使用具体类型(date 而非 string)
- 有限选项用 enum 约束
- 可选参数提供合理默认值
34.7.2 完整工具示例
python
class FileAnalysisTool(StandardTool):
"""文件分析工具"""
def __init__(self):
super().__init__("analyze_file", "分析上传的文件内容")
self.param("file_path", "string", "文件路径或URL")
self.param("analysis_type", "string", "分析类型",
enum=["summary", "statistics", "schema", "quality"])
self.param("max_rows", "integer", "最大分析行数",
required=False, default=1000)
async def execute(self, file_path: str, analysis_type: str,
max_rows: int = 1000) -> dict:
data = await self._load_file(file_path)
analyzers = {
"summary": self._summarize,
"statistics": self._statistics,
"schema": self._infer_schema,
"quality": self._check_quality,
}
return analyzers[analysis_type](data, max_rows)34.8 工具安全模式
34.8.1 输入验证与沙箱
python
class SecureToolWrapper:
"""安全工具包装器"""
def __init__(self, tool: StandardTool):
self.tool = tool
self._validators = []
def add_validator(self, validator):
self._validators.append(validator)
return self
async def execute(self, **kwargs) -> Any:
for validator in self._validators:
is_valid, error = validator(kwargs)
if not is_valid:
raise ValueError(f"输入验证失败: {error}")
return await self.tool.execute(**kwargs)
# SQL注入检测
def no_sql_injection(params: dict) -> tuple[bool, str]:
dangerous = ["DROP", "DELETE", "INSERT", "UPDATE", "UNION", "--", ";"]
for key, value in params.items():
if isinstance(value, str):
if any(kw in value.upper() for kw in dangerous):
return False, f"参数 {key} 包含可疑SQL关键字"
return True, ""34.9 工具性能模式
34.9.1 异步并发
python
class AsyncToolExecutor:
"""异步工具执行器"""
def __init__(self, max_concurrent: int = 10):
self.semaphore = asyncio.Semaphore(max_concurrent)
async def execute_many(self, calls: list[tuple[StandardTool, dict]]) -> list[dict]:
tasks = [self._execute_one(tool, args) for tool, args in calls]
return await asyncio.gather(*tasks, return_exceptions=True)
async def _execute_one(self, tool: StandardTool, args: dict) -> dict:
async with self.semaphore:
start = time.time()
try:
result = await tool.execute(**args)
return {"tool": tool.name, "success": True, "result": result,
"duration_ms": (time.time() - start) * 1000}
except Exception as e:
return {"tool": tool.name, "success": False, "error": str(e)}34.9.2 结果缓存
python
class CachedTool:
"""带缓存的工具"""
def __init__(self, tool: StandardTool, cache_ttl: int = 300):
self.tool = tool
self._cache: dict[str, tuple[float, Any]] = {}
self.ttl = cache_ttl
async def execute(self, **kwargs) -> Any:
key = hashlib.md5(json.dumps(kwargs, sort_keys=True).encode()).hexdigest()
if key in self._cache:
ts, val = self._cache[key]
if time.time() - ts < self.ttl:
return val
result = await self.tool.execute(**kwargs)
self._cache[key] = (time.time(), result)
return result最佳实践
- 工具描述即API文档:description 是 LLM 选择工具的唯一依据,必须准确详尽
- 参数约束要明确:用 enum 限制选项,用 type 指定格式
- 错误信息要友好:返回给 Agent 的错误信息应包含修复建议
- 工具粒度适中:5-15个工具为最佳范围
- 监控工具使用:记录调用频率、成功率、延迟,持续优化
常见陷阱
- 描述模糊:"处理数据" → 应该写 "查询SQL数据库并返回JSON结果"
- 参数过多:超过5个必需参数的工具调用失败率极高
- 忽略幂等性:非幂等工具重试会产生副作用
- 缺少降级:主工具失败时没有备选方案
- 无限流和缓存:高频调用导致成本爆炸
小结
工具是 Agent 能力的核心载体。本章从设计模式的视角系统讲解了工具抽象、调用、选择、编排、错误处理、安全约束和性能优化等模式。掌握这些模式,能够帮助你构建出既强大又安全的 Agent 工具系统。好的工具设计 = 清晰的接口 + 健壮的错误处理 + 合理的降级策略。
延伸阅读
- MCP协议规范: https://modelcontextprotocol.io/
- 论文: "ReAct: Synergizing Reasoning and Acting in Language Models"
- 论文: "Toolformer: Language Models Can Teach Themselves to Use Tools"
- OpenAI Function Calling: https://platform.openai.com/docs/guides/function-calling
- Anthropic Tool Use: https://docs.anthropic.com/claude/docs/tool-use