第五章:工具层设计¶
工具(Tools)是智能体与外部世界交互的手段。每一个工具调用都是一次对外部系统的改造、查询或决策。设计良好的工具层决定了智能体系统的能力边界、安全性、可维护性和可扩展性。
如果说智能体循环是“如何思考和行动”,那么工具层就是“用什么工具去行动”。
工具层的三个核心问题
- 工具抽象:如何定义一个统一的工具接口,使得各种异构的外部能力(Bash、文件系统、网络请求、数据库、API)都能被智能体调用?
- 工具执行:如何安全地执行工具调用,处理超时、权限、错误,并向智能体反馈结果?
- 工具发现:如何让智能体知道存在哪些工具、如何使用?在静态工具列表和动态工具发现之间如何平衡?
两个参考系统的工具哲学
Claude Code 的设计:
- 内置工具与扩展工具:深度集成,每个工具为特定场景优化
- 工具类型多样:8 个执行工具、3 个网络工具、8 个 Agent 工具、5+内置专用工具
- Tool\ 泛型接口:类型安全,支持进度报告
- buildTool() 工厂函数:动态构造工具
- shouldDefer 延迟加载:条件决定是否加载工具
- FileStateCache 上下文缓存:工具执行结果的智能缓存
- 异步执行:StreamingToolExecutor 并发调用多个工具
OpenClaw 的设计:
- Tools + Skills 双层架构:
- Tools:底层能力(Bash、文件、搜索)
- Skills:Markdown 指令格式的高级操作
- 6 级技能加载优先级:
- 固定系统技能
- 会话上下文技能
- 用户自定义技能
- 领域专业技能
- 通用工具
- 动态发现技能
- 工具策略:允许/拒绝/分层权限模型
- 单线程顺序执行:每个工具一个一个执行,但可序列化结果
学习要点
通过本章的学习,你将理解:
- 如何定义工具的统一接口,支持异步、进度报告、权限检查
- 工具执行流水线的完整过程:发现 → 验证 → 执行 → 缓存
- 不同工具类型的实现特点和权衡
- 动态工具发现的必要性和实现方式
- 如何在 Python 中实现一个可扩展的工具系统
工具与技能的关系
工具和技能的关系可以用以下层级模型表示:
graph TD
A["使用者指令"] --> B["<b>Skills(高级指令)</b><br/>Markdown 格式定义<br/>.skill 文件<br/>可由智能体组合执行"]
B --> C["<b>Tools(底层能力)</b><br/>抽象接口<br/>单一职责<br/>由系统管理"]
C --> D["<b>底层系统</b><br/>bash/fs/network<br/>实际能力实现"]
style A fill:#f5e8f4,stroke:#a44a90,stroke-width:2px,color:#000000
style B fill:#e8f4f8,stroke:#4a90a4,stroke-width:2px,color:#000000
style C fill:#e8f4f8,stroke:#4a90a4,stroke-width:2px,color:#000000
style D fill:#f5e8f4,stroke:#a44a90,stroke-width:2px,color:#000000
设计决策速查
| 决策点 | Claude Code | OpenClaw | MiniHarness |
|---|---|---|---|
| 工具接口 | Tool\ | Tool Protocol | Async Protocol |
| 执行模式 | 并发 | 顺序 | 顺序(可扩展) |
| 错误处理 | 异常捕获 | 错误即观察 | 两者结合 |
| 权限管理 | check_permissions() | 工具策略 | 基础实现 |
| 缓存 | FileStateCache | 会话记忆 | 简单缓存 |
| 发现机制 | 预定义 | 6 级优先级 | 注册表 |
本章将深入这些设计细节,为你建立完整的工具系统设计能力。
1. 工具抽象接口设计¶
工具抽象接口是工具系统的核心基础,需要在通用性、类型安全、可观测性和性能间取得平衡。本节介绍 Claude Code 的泛型设计、工具实现示例、注册中心机制,以及权限管理策略。
1.1 统一工具接口的设计目标¶
工具抽象接口的设计需要平衡以下目标:
- 通用性:支持各种异构工具(文件、网络、计算、数据库等)
- 类型安全:在编译时(或运行时)检查参数和返回值类型
- 可观测性:支持进度报告、事件发射、日志记录
- 可扩展性:易于添加新工具类型
- 性能:最小化开销,支持并发执行
- 安全性:权限检查、资源限制、错误隔离
1.2 Claude Code 的 Tool\ 泛型设计¶
Claude Code 采用泛型 Tool 接口,明确定义输入、输出和进度类型:
"""
Claude Code 风格的工具接口
"""
from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import Generic, TypeVar, Optional, Dict, Any, List
import asyncio
# 类型变量
# (Claude Code 的 Tool<Input,Output,Progress> 三泛型中的 Progress,
# 本简化实现用下方具体的 ToolProgress 类承载,不再单设类型变量)
InputType = TypeVar('InputType')
OutputType = TypeVar('OutputType')
@dataclass
class ToolProgress:
"""工具执行进度"""
step: int # 当前步骤号
total_steps: int # 总步骤数
current_status: str # 当前状态描述
estimated_time_remaining: Optional[float] = None # 剩余时间(秒)
def percentage(self) -> float:
"""进度百分比"""
return (self.step / self.total_steps * 100) if self.total_steps > 0 else 0
class Tool(ABC, Generic[InputType, OutputType]):
"""通用工具抽象"""
@abstractmethod
async def call(self, input_data: InputType) -> OutputType:
"""
执行工具
Args:
input_data: 工具输入,已验证和类型检查过
Returns:
工具执行结果
Raises:
ToolExecutionError: 工具执行失败
"""
pass
@abstractmethod
def name(self) -> str:
"""工具名称"""
pass
@abstractmethod
def description(self) -> str:
"""工具描述"""
pass
@abstractmethod
def input_schema(self) -> Dict[str, Any]:
"""输入的 JSON Schema"""
pass
def check_permissions(self, context: Any) -> bool:
"""
检查当前上下文是否有权限调用此工具
Args:
context: 执行上下文(包含用户、会话信息等)
Returns:
是否有权限
"""
return True # 默认允许
async def get_progress(self) -> Optional[ToolProgress]:
"""
获取工具执行进度
Returns:
进度对象,如果工具不支持进度报告则返回 None
"""
return None
def supports_streaming(self) -> bool:
"""工具是否支持流式输出"""
return False
async def stream_output(self, input_data: InputType):
"""
流式输出(可选)
Yields:
OutputType: 逐个产生的输出
"""
if not self.supports_streaming():
output = await self.call(input_data)
yield output
⚠️ 类型系统说明:上述
Tool[InputType, OutputType]的泛型注解为设计理想。在 Python 运行时,由于动态类型特性,实际实现常使用Dict[str,Any]承载参数和返回值。这是 Python 动态类型系统与静态类型检查间的权衡——静态分析可以验证泛型约束,但运行时仍需灵活处理异构数据。关于完整的类型安全实现,请参考第 5.3 节和附录 D(MiniHarness 类型体系)。
本节展示了工具的核心设计模式与流程。为了更好地理解如何将这些模式应用于实践,下面我们通过具体的实现示例来演示工具的常见用法。
1.3 工具实现示例¶
1.3.1 简单工具:Bash 执行¶
一个简单的 Bash 命令执行工具的实现如下:
@dataclass
class BashInput:
command: str
timeout: int = 30
cwd: Optional[str] = None
@dataclass
class BashOutput:
stdout: str
stderr: str
returncode: int
execution_time: float
class BashTool(Tool[BashInput, BashOutput]):
"""Bash 命令执行工具"""
async def call(self, input_data: BashInput) -> BashOutput:
"""执行 bash 命令"""
import shlex
import subprocess
import time
start_time = time.time()
allowed_commands = {"echo", "pwd", "true", "false", "sleep"}
argv = shlex.split(input_data.command)
if not argv or argv[0] not in allowed_commands:
raise ToolExecutionError("Command is not allowed by policy")
try:
result = subprocess.run(
argv,
shell=False,
capture_output=True,
timeout=input_data.timeout,
text=True,
cwd=input_data.cwd
)
return BashOutput(
stdout=result.stdout,
stderr=result.stderr,
returncode=result.returncode,
execution_time=time.time() - start_time
)
except subprocess.TimeoutExpired:
raise ToolExecutionError(
f"Bash command timeout after {input_data.timeout}s"
)
except Exception as e:
raise ToolExecutionError(f"Bash execution failed: {str(e)}")
def name(self) -> str:
return "bash_exec"
def description(self) -> str:
return "Execute bash commands on the system"
def input_schema(self) -> Dict[str, Any]:
return {
"type": "object",
"properties": {
"command": {
"type": "string",
"description": "The bash command to execute"
},
"timeout": {
"type": "integer",
"description": "Command timeout in seconds",
"default": 30
},
"cwd": {
"type": "string",
"description": "Current working directory"
}
},
"required": ["command"]
}
def check_permissions(self, context: Any) -> bool:
"""Bash 工具需要特殊权限"""
if hasattr(context, 'is_admin'):
return context.is_admin
return False
1.3.2 支持进度报告的工具:文件复制¶
一个支持进度报告的文件复制工具,展示如何在长时间运行的操作中提供实时反馈:
@dataclass
class FileCopyInput:
source: str
destination: str
overwrite: bool = False
@dataclass
class FileCopyOutput:
success: bool
bytes_copied: int
destination: str
class FileCopyTool(Tool[FileCopyInput, FileCopyOutput]):
"""文件复制工具,支持进度报告"""
def __init__(self):
self._current_progress = None
async def call(self, input_data: FileCopyInput) -> FileCopyOutput:
"""复制文件"""
import shutil
import os
from pathlib import Path
from mini_harness.security.path_validator import PathValidator
validator = PathValidator(base_path="./workspace")
source = Path(validator.validate(input_data.source))
destination = Path(validator.validate(input_data.destination))
# 检查源文件是否存在
if not source.exists() or source.is_symlink():
raise ToolExecutionError(f"Source file not found or not allowed: {input_data.source}")
# 检查目标是否已存在
if destination.exists() and not input_data.overwrite:
raise ToolExecutionError(
f"Destination already exists: {input_data.destination}"
)
if destination.exists() and destination.is_symlink():
raise ToolExecutionError("Symlink destinations are not allowed")
# 获取文件大小用于进度报告
file_size = source.stat().st_size
chunk_size = 1024 * 1024 # 1MB chunks
bytes_copied = 0
# 自定义复制逻辑以支持进度报告
destination.parent.mkdir(parents=True, exist_ok=True)
with source.open('rb') as src, destination.open('wb') as dst:
while True:
chunk = src.read(chunk_size)
if not chunk:
break
dst.write(chunk)
bytes_copied += len(chunk)
# 更新进度
self._current_progress = ToolProgress(
step=bytes_copied,
total_steps=file_size,
current_status=f"Copying {bytes_copied}/{file_size} bytes"
)
return FileCopyOutput(
success=True,
bytes_copied=bytes_copied,
destination=input_data.destination
)
async def get_progress(self) -> Optional[ToolProgress]:
"""获取进度"""
return self._current_progress
def name(self) -> str:
return "file_copy"
def description(self) -> str:
return "Copy files with progress reporting"
def input_schema(self) -> Dict[str, Any]:
return {
"type": "object",
"properties": {
"source": {
"type": "string",
"description": "Source file path"
},
"destination": {
"type": "string",
"description": "Destination file path"
},
"overwrite": {
"type": "boolean",
"description": "Whether to overwrite existing file",
"default": False
}
},
"required": ["source", "destination"]
}
1.3.3 流式输出工具:数据库查询¶
一个支持流式输出的数据库查询工具,展示如何处理大结果集:
💡 类型参数说明:以下示例中
Tool<I,O,P>的类型参数为概念占位符。运行时实现使用Dict[str,Any]承载动态数据。如需真正的静态类型约束,可改用Pydantic BaseModel或TypedDict(见附录 D MiniHarness 实现)。
from dataclasses import dataclass
from typing import Dict, Any
import asyncio
@dataclass
class DatabaseQueryInput:
query: str
limit: int = 1000
@dataclass
class DatabaseQueryRow:
columns: Dict[str, Any]
class DatabaseQueryTool(Tool[DatabaseQueryInput, DatabaseQueryRow]):
"""数据库查询工具,支持流式输出"""
def __init__(self, connection_string: str):
self.connection_string = connection_string
async def call(self, input_data: DatabaseQueryInput) -> str:
"""执行查询并返回结果"""
# 这个实现中,我们只返回所有结果的摘要
# 实际使用应该使用流式输出
all_rows = []
async for row in self.stream_output(input_data):
all_rows.append(row)
return f"Query returned {len(all_rows)} rows"
async def stream_output(self, input_data: DatabaseQueryInput):
"""流式输出查询结果"""
# 在实际实现中,这里会连接到真实的数据库
# 示例:连接到 PostgreSQL、MySQL 等
# 模拟流式输出
for i in range(min(input_data.limit, 10)):
yield DatabaseQueryRow(
columns={
"id": i,
"name": f"Row {i}",
"value": i * 100
}
)
await asyncio.sleep(0.1) # 模拟网络延迟
def supports_streaming(self) -> bool:
return True
def name(self) -> str:
return "database_query"
def description(self) -> str:
return "Query database with streaming results"
def input_schema(self) -> Dict[str, Any]:
return {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "SQL query to execute"
},
"limit": {
"type": "integer",
"description": "Maximum number of rows to return",
"default": 1000
}
},
"required": ["query"]
}
1.4 buildTool() 工厂函数¶
Claude Code 使用工厂函数动态构造工具,支持参数化和延迟初始化:
def build_tool(tool_spec: Dict[str, Any]) -> Tool:
"""
工厂函数:根据规范构造工具
Args:
tool_spec: 工具规范
{
"type": "bash" | "file" | "network" | ...,
"name": "tool_name",
"config": {...} # 工具特定的配置
}
Returns:
构造的工具实例
"""
tool_type = tool_spec.get("type")
config = tool_spec.get("config", {})
if tool_type == "bash":
return BashTool()
elif tool_type == "file_copy":
return FileCopyTool()
elif tool_type == "database_query":
return DatabaseQueryTool(
connection_string=config.get("connection_string")
)
elif tool_type == "custom":
# 只允许从受信任配置和受控命名空间加载自定义工具
module_path = config.get("module")
class_name = config.get("class")
allowed_prefixes = ("my_harness_tools.",)
if not module_path or not module_path.startswith(allowed_prefixes):
raise ToolExecutionError("Custom tool module is not in the trusted namespace")
# 使用 importlib 动态加载
import importlib
module = importlib.import_module(module_path)
tool_class = getattr(module, class_name)
return tool_class(**config.get("args", {}))
else:
raise ValueError(f"Unknown tool type: {tool_type}")
1.5 工具注册中心¶
工具注册中心是所有工具的中枢,负责工具的注册、发现和管理。下面的流程图展示了工具的注册、发现和查询的完整流程:
graph TD
A["新工具"] -->|buildTool| B["工具实例"]
B -->|register| C["工具注册中心"]
C -->|缓存Schema| D["Schema 缓存"]
E["Agent查询"] -->|find_tool_by_name| C
C -->|查询缓存| D
D -->|返回Schema| F["工具Schema"]
C -->|返回工具| G["工具实例"]
F -->|提供给Agent| H["决策使用"]
G -->|供Agent调用| H
style A fill:#e1f5ff
style C fill:#fff3e0
style D fill:#f3e5f5
style H fill:#e8f5e9
图 5-1:工具注册与发现流程
class ToolRegistry:
"""工具注册中心,管理所有可用工具"""
def __init__(self):
self.tools: Dict[str, Tool] = {}
self.tool_cache: Dict[str, Dict[str, Any]] = {} # Schema 缓存
def register(self, tool: Tool):
"""注册工具"""
name = tool.name()
self.tools[name] = tool
# 缓存工具的 Schema
self.tool_cache[name] = {
"name": name,
"description": tool.description(),
"input_schema": tool.input_schema()
}
print(f"Tool registered: {name}")
def get(self, name: str) -> Optional[Tool]:
"""获取工具"""
return self.tools.get(name)
def list_tools(self) -> List[Dict[str, Any]]:
"""列出所有工具的 Schema"""
return list(self.tool_cache.values())
def get_tool_schema(self, name: str) -> Optional[Dict[str, Any]]:
"""获取单个工具的 Schema"""
return self.tool_cache.get(name)
def tool_exists(self, name: str) -> bool:
"""检查工具是否存在"""
return name in self.tools
def unregister(self, name: str):
"""注销工具"""
if name in self.tools:
del self.tools[name]
del self.tool_cache[name]
print(f"Tool unregistered: {name}")
# 使用示例
registry = ToolRegistry()
# 注册内置工具
registry.register(BashTool())
registry.register(FileCopyTool())
registry.register(DatabaseQueryTool("postgresql://localhost/mydb"))
# 列出所有工具
for tool_schema in registry.list_tools():
print(f"- {tool_schema['name']}: {tool_schema['description']}")
# 获取特定工具
bash_tool = registry.get("bash_exec")
if bash_tool:
print(f"Found tool: {bash_tool.name()}")
1.6 OpenClaw 的工具策略模型¶
OpenClaw 支持更细粒度的权限管理:
class ToolStrategy(Enum):
"""工具使用策略"""
ALLOW = "allow" # 允许使用
DENY = "deny" # 禁止使用
REQUIRE_APPROVAL = "require_approval" # 需要用户批准
class ToolPolicy:
"""工具策略:定义哪些智能体可以使用哪些工具"""
def __init__(self):
self.policies: Dict[str, Dict[str, ToolStrategy]] = {}
# policies[agent_id][tool_name] = strategy
def set_policy(self, agent_id: str, tool_name: str,
strategy: ToolStrategy):
"""设置工具策略"""
if agent_id not in self.policies:
self.policies[agent_id] = {}
self.policies[agent_id][tool_name] = strategy
def can_use_tool(self, agent_id: str, tool_name: str) -> bool:
"""检查智能体是否可以使用工具"""
if agent_id not in self.policies:
return True # 默认允许
strategy = self.policies[agent_id].get(
tool_name,
ToolStrategy.ALLOW
)
return strategy != ToolStrategy.DENY
def requires_approval(self, agent_id: str, tool_name: str) -> bool:
"""检查是否需要审批"""
if agent_id not in self.policies:
return False
strategy = self.policies[agent_id].get(tool_name)
return strategy == ToolStrategy.REQUIRE_APPROVAL
1.7 本节小结¶
工具抽象接口的设计决定了整个工具系统的质量和可维护性:
- Claude Code 的 Tool\ 泛型设计 提供了强类型检查,但需要 Python 的高级类型系统支持
- 工具工厂函数 支持动态工具构造和参数化配置
- 工具注册中心 管理工具的生命周期和 Schema 缓存
- 权限检查 在工具调用前进行,可以是简单的 boolean 也可以是复杂的策略模型
- OpenClaw 的工具策略模型 提供更细粒度的权限管理,支持不同智能体的不同权限
好的工具抽象应该既通用又灵活,既简单又强大。在实践中,需要根据具体场景选择合适的抽象级别。
2. 工具执行流水线¶
本节介绍工具从被调用到返回结果的完整执行过程,包括 6 个关键阶段、流水线的实现细节、进度报告机制和并发执行策略,并通过实例代码展示如何构建一个可靠的工具执行系统。
2.1 执行流水线的完整过程¶
工具从被调用到返回结果的完整流程可以分为以下 6 个阶段:
graph LR
A["工具调用请求"] -->|解析| B["1. 查找工具"]
B -->|成功| C["2. 检查权限"]
C -->|允许| D["3. 验证输入"]
D -->|有效| E["4. 执行工具"]
E -->|完成| F["5. 处理结果"]
F -->|转换| G["6. 缓存 / 记录交接"]
G -->|返回| H["工具结果"]
B -->|错误| I["错误处理器"]
C -->|拒绝| I
D -->|无效| I
E -->|超时| I
F -->|错误| I
I -->|创建错误结果| H
style B fill:#bbdefb
style C fill:#c8e6c9
style D fill:#fff9c4
style E fill:#ffccbc
style F fill:#d1c4e9
style G fill:#b2dfdb
style H fill:#f8bbd0
图 5-2:工具执行流水线的 6 个阶段
2.2 完整的执行流水线实现¶
工具执行流水线的实现可以分为四个主要部分:初始化和主流程、验证阶段、执行和结果处理、以及缓存管理。
第一部分:流水线初始化与主流程¶
首先定义执行流水线的主体和 6 个阶段的总体流程:
"""工具执行流水线"""
from dataclasses import dataclass
from typing import Optional, Dict, Any, Callable, Tuple
# 使用第4章定义的消息块类型(anthropic SDK 只提供请求侧的 ToolResultBlockParam,
# 响应侧没有 ToolResultBlock;这里沿用本书 mini_harness 的自定义类型)
from mini_harness.runtime.models import ToolUseBlock, ToolResultBlock
import asyncio
import time
import json
class ExecutionPipeline:
"""工具执行流水线"""
def __init__(self,
tool_registry: ToolRegistry,
max_result_size: int = 1024 * 1024,
default_timeout: int = 30):
self.tool_registry = tool_registry
self.max_result_size = max_result_size
self.default_timeout = default_timeout
self.execution_history = []
self.result_cache = {}
async def execute(self,
tool_use: ToolUseBlock,
context: Any,
progress_callback: Optional[Callable] = None) -> ToolResultBlock:
"""执行工具的完整流水线"""
execution_record = {
"tool_use_id": tool_use.id,
"tool_name": tool_use.name,
"start_time": time.time(),
"stages": {}
}
try:
# 阶段1:工具发现
tool, error = await self._find_tool(tool_use.name, execution_record)
if error:
return self._create_error_result(tool_use, error)
# 阶段2:权限检查
is_allowed, error = await self._check_permissions(
tool, context, execution_record
)
if not is_allowed:
return self._create_error_result(tool_use, error)
# 阶段3:输入验证
is_valid, error = await self._validate_input(
tool, tool_use.input, execution_record
)
if not is_valid:
return self._create_error_result(tool_use, error)
# 阶段4:工具执行
result, error = await self._execute_tool(
tool, tool_use.input, execution_record, progress_callback
)
if error:
return self._create_error_result(tool_use, error)
# 阶段5:结果处理
processed_result, error = await self._process_result(
result, execution_record
)
if error:
return self._create_error_result(tool_use, error)
# 阶段6:本地缓存与记录交接
await self._record_result(
tool_use.id, processed_result, execution_record
)
self.execution_history.append(execution_record)
return ToolResultBlock(
tool_use_id=tool_use.id,
content=str(processed_result),
is_error=False
)
except Exception as e:
execution_record["error"] = str(e)
self.execution_history.append(execution_record)
return self._create_error_result(tool_use, str(e))
设计说明:主流程采用“早期返回”(Early Return)模式。任何阶段失败都立即返回错误,避免继续执行。这提高了效率并简化了错误处理。这里的第 6 阶段只维护工具流水线的本地缓存和执行记录,不直接写入记忆系统;长期持久化由第 4 章运行时引擎统一决定。
第二部分:验证阶段(工具发现、权限检查、输入验证)¶
这部分处理执行前的三个验证阶段:
async def _find_tool(self, tool_name: str,
execution_record: Dict) -> Tuple[Optional[Tool], Optional[str]]:
"""阶段1:工具发现"""
start = time.time()
tool = self.tool_registry.get(tool_name)
execution_record["stages"]["find_tool"] = {
"duration": time.time() - start,
"found": tool is not None
}
if not tool:
return None, f"Tool '{tool_name}' not found"
return tool, None
async def _check_permissions(self, tool: Tool, context: Any,
execution_record: Dict) -> Tuple[bool, Optional[str]]:
"""阶段2:权限检查"""
start = time.time()
try:
has_permission = tool.check_permissions(context)
execution_record["stages"]["check_permissions"] = {
"duration": time.time() - start,
"result": has_permission
}
if not has_permission:
return False, f"Permission denied for tool '{tool.name()}'"
return True, None
except Exception as e:
return False, f"Permission check error: {str(e)}"
async def _validate_input(self, tool: Tool, input_data: Dict[str, Any],
execution_record: Dict) -> Tuple[bool, Optional[str]]:
"""阶段3:输入验证"""
start = time.time()
schema = tool.input_schema()
errors = []
required = schema.get("required", [])
for field in required:
if field not in input_data:
errors.append(f"Missing required field: {field}")
properties = schema.get("properties", {})
for field_name, field_value in input_data.items():
if field_name not in properties:
errors.append(f"Unknown field: {field_name}")
continue
field_schema = properties[field_name]
if not self._check_type(field_value, field_schema):
expected_type = field_schema.get("type", "unknown")
errors.append(
f"Field '{field_name}' validation failed: "
f"expected {expected_type}"
)
execution_record["stages"]["validate_input"] = {
"duration": time.time() - start,
"valid": len(errors) == 0,
"errors": errors
}
if errors:
return False, "; ".join(errors)
return True, None
def _check_type(self, value: Any, schema: Dict[str, Any]) -> bool:
"""基于JSON Schema的类型验证"""
import jsonschema
try:
jsonschema.validate(value, schema)
return True
except jsonschema.ValidationError:
return False
设计说明:验证阶段按顺序执行,逐步变严格。工具发现最快,然后是权限检查,最后是复杂的输入验证。这样可以快速拒绝无效请求。
第三部分:执行和结果处理(工具执行、结果处理、本地缓存)¶
这部分处理核心的执行逻辑和结果的后处理:
async def _execute_tool(self,
tool: Tool,
input_data: Dict[str, Any],
execution_record: Dict,
progress_callback: Optional[Callable] = None
) -> Tuple[Any, Optional[str]]:
"""阶段4:工具执行(带超时控制)"""
start = time.time()
try:
result = await asyncio.wait_for(
tool.call(input_data),
timeout=self.default_timeout
)
execution_record["stages"]["execute_tool"] = {
"duration": time.time() - start,
"success": True
}
return result, None
except asyncio.TimeoutError:
error = f"Tool execution timeout ({self.default_timeout}s)"
execution_record["stages"]["execute_tool"] = {
"duration": time.time() - start,
"success": False,
"error": "timeout"
}
return None, error
except Exception as e:
execution_record["stages"]["execute_tool"] = {
"duration": time.time() - start,
"success": False,
"error": type(e).__name__
}
return None, f"Tool execution error: {str(e)}"
async def _process_result(self, result: Any,
execution_record: Dict) -> Tuple[str, Optional[str]]:
"""阶段5:结果处理(序列化和截断)"""
start = time.time()
try:
if isinstance(result, str):
result_str = result
elif isinstance(result, (dict, list)):
result_str = json.dumps(result, indent=2)
else:
result_str = str(result)
if len(result_str) > self.max_result_size:
original_size = len(result_str)
result_str = result_str[:self.max_result_size]
result_str += f"\n... [Output truncated from {original_size} bytes]"
execution_record["stages"]["process_result"] = {
"duration": time.time() - start,
"truncated": True,
"original_size": original_size,
}
else:
execution_record["stages"]["process_result"] = {
"duration": time.time() - start,
"truncated": False,
"size": len(result_str)
}
return result_str, None
except Exception as e:
return None, f"Result processing error: {str(e)}"
async def _record_result(self, tool_use_id: str, result: str,
execution_record: Dict):
"""阶段6:本地结果缓存,不写入记忆系统"""
start = time.time()
self.result_cache[tool_use_id] = {
"content": result,
"timestamp": time.time()
}
execution_record["stages"]["record_result"] = {
"duration": time.time() - start,
"cached": True
}
def _create_error_result(self, tool_use: ToolUseBlock,
error: str) -> ToolResultBlock:
"""创建错误结果"""
return ToolResultBlock(
tool_use_id=tool_use.id,
content=error,
is_error=True,
error_type="ExecutionError"
)
设计说明:_execute_tool 使用 asyncio.wait_for 实现超时控制,防止卡顿。_process_result 对大结果进行截断,确保不超过最大大小限制。这两个措施都是保护系统稳定性的关键。
第四部分:执行历史和缓存管理¶
最后是查询执行历史和清理缓存的工具方法:
def get_execution_history(self, tool_use_id: Optional[str] = None):
"""获取执行历史"""
if tool_use_id:
return [r for r in self.execution_history
if r["tool_use_id"] == tool_use_id]
return self.execution_history
def clear_cache(self, max_age: int = 3600):
"""清理缓存(删除超过max_age秒的项)"""
now = time.time()
to_delete = [
key for key, value in self.result_cache.items()
if now - value["timestamp"] > max_age
]
for key in to_delete:
del self.result_cache[key]
return len(to_delete)
2.3 进度事件流¶
支持长时间运行的工具的进度报告:
from typing import Optional, Dict
import asyncio
import time
class ProgressMonitor:
"""进度监控器"""
def __init__(self, tool_use_id: str):
self.tool_use_id = tool_use_id
self.progress_events = []
def report_progress(self, step: int, total_steps: int,
status: str, estimated_time_remaining: Optional[float] = None):
"""报告进度"""
event = {
"tool_use_id": self.tool_use_id,
"timestamp": time.time(),
"step": step,
"total_steps": total_steps,
"percentage": (step / total_steps * 100) if total_steps > 0 else 0,
"status": status,
"estimated_time_remaining": estimated_time_remaining
}
self.progress_events.append(event)
return event
def get_current_progress(self) -> Optional[Dict]:
"""获取当前进度"""
return self.progress_events[-1] if self.progress_events else None
# 在工具执行中使用
async def long_running_tool_call(progress_monitor: ProgressMonitor):
"""长时间运行的工具调用示例"""
total_items = 1000
for i in range(total_items):
# 执行工作
await asyncio.sleep(0.01) # 模拟工作
# 报告进度
if i % 100 == 0:
progress_monitor.report_progress(
step=i,
total_steps=total_items,
status=f"Processing item {i}/{total_items}",
estimated_time_remaining=(total_items - i) * 0.01
)
return f"Processed {total_items} items"
2.4 并发工具执行¶
具体实现如下:
from typing import List, Dict, Any
import asyncio
class StreamingToolExecutor:
"""并发工具执行器"""
def __init__(self, pipeline: ExecutionPipeline,
max_concurrent: int = 5):
self.pipeline = pipeline
self.max_concurrent = max_concurrent
async def execute_tools(self,
tool_uses: List[ToolUseBlock],
context: Any) -> Dict[str, ToolResultBlock]:
"""
并发执行多个工具
Args:
tool_uses: 工具调用列表
context: 执行上下文
Returns:
{tool_use_id: tool_result_block}
"""
semaphore = asyncio.Semaphore(self.max_concurrent)
async def execute_with_semaphore(tool_use):
async with semaphore:
return await self.pipeline.execute(tool_use, context)
results = await asyncio.gather(
*[execute_with_semaphore(tu) for tu in tool_uses],
return_exceptions=True
)
# 组织结果:gather 的返回顺序与 tool_uses 一一对应;
# return_exceptions=True 时异常项是裸 Exception 对象,不能直接解包
result_dict = {}
for tool_use, result in zip(tool_uses, results):
if isinstance(result, Exception):
result_dict[tool_use.id] = ToolResultBlock(
tool_use_id=tool_use.id,
content=str(result),
is_error=True,
error_type=type(result).__name__
)
else:
result_dict[tool_use.id] = result
return result_dict
2.5 本节小结¶
工具执行流水线的设计决定了系统的可靠性和性能:
- 6 个执行阶段 清晰分离,每个阶段有明确的职责和错误处理
- 权限检查 在执行前进行,防止未授权的操作
- 输入验证 基于 JSON Schema,确保工具收到有效参数
- 超时控制 防止工具阻塞整个系统
- 结果处理 包括序列化、截断、缓存,处理大结果的问题
- 执行历史记录 支持审计和调试
- 并发执行 和 进度报告 支持长时间运行的工具
3. 工具类型体系¶
本节阐述工具的分类标准、能力等级划分、访问控制策略、扩展模式和能力描述,说明如何设计一个既清晰易用又灵活可扩展的工具体系。
3.1 工具分类¶
按功能和特点,Agent 系统的工具可分为以下几类:
1) 执行工具¶
在本地系统执行代码或命令的工具。
class BashTool(Tool):
"""执行 bash 命令"""
def name(self) -> str: return "bash_exec"
class PythonTool(Tool):
"""执行 Python 代码"""
def name(self) -> str: return "python_exec"
class FileReadTool(Tool):
"""读取文件内容"""
def name(self) -> str: return "file_read"
class FileWriteTool(Tool):
"""写入文件"""
def name(self) -> str: return "file_write"
class FileListTool(Tool):
"""列出目录内容"""
def name(self) -> str: return "file_list"
特点:
- 直接影响本地系统状态
- 需要严格的权限控制
- 可能长时间运行
- 需要超时保护
2) 网络工具¶
通过网络访问远程资源的工具。
class HTTPTool(Tool):
"""发送 HTTP 请求"""
def name(self) -> str: return "http_request"
class WebSearchTool(Tool):
"""网络搜索"""
def name(self) -> str: return "web_search"
class APIDelegationTool(Tool):
"""调用第三方 API"""
def name(self) -> str: return "api_call"
特点:
- 依赖网络连接
- 可能有速率限制
- 需要处理超时和重试
- 可能涉及认证令牌管理
3) 智能体工具¶
让智能体调用其他智能体的工具。
class SubAgentTool(Tool):
"""创建和委托子任务给另一个 Agent"""
def name(self) -> str: return "delegate_to_agent"
class ParallelAgentsTool(Tool):
"""并行执行多个 Agent"""
def name(self) -> str: return "parallel_agents"
特点:
- 支持分解复杂任务
- 智能体间通信和协调
- 结果聚合
- 可能导致深层递归,需要深度限制
4) 专用工具¶
针对特定领域的工具。
class DatabaseQueryTool(Tool):
"""数据库查询"""
def name(self) -> str: return "database_query"
class ImageProcessingTool(Tool):
"""图像处理"""
def name(self) -> str: return "image_process"
class CodeAnalysisTool(Tool):
"""代码分析(静态或动态)"""
def name(self) -> str: return "code_analyze"
class DataVisualizationTool(Tool):
"""数据可视化"""
def name(self) -> str: return "plot_data"
特点:
- 特定领域的深度功能
- 高度优化
- 可能有复杂的依赖
3.2 工具能力的等级¶
不依赖具体工具数量时,Claude Code 一类编码智能体的内置工具可按能力分为以下等级:
graph TD
A["工具能力等级体系"] --> L4["等级 4(专家级)"]
A --> L3["等级 3(高级)"]
A --> L2["等级 2(中级)"]
A --> L1["等级 1(基础)"]
L4 --> L4A["代码生成和优化"]
L4 --> L4B["架构分析和建议"]
L4 --> L4C["复杂的跨域任务协调"]
L3 --> L3A["深度分析工具"]
L3 --> L3B["专业工具集"]
L3 --> L3C["需要上下文的工具"]
L2 --> L2A["文件操作"]
L2 --> L2B["网络请求"]
L2 --> L2C["基本数据处理"]
L2 --> L2D["搜索功能"]
L1 --> L1A["简单的执行"]
L1 --> L1B["基本的查询"]
L1 --> L1C["初级操作"]
style L4 fill:#fff8e1,stroke:#ffb74d,stroke-width:2px,color:#000000
style L3 fill:#ffe0b2,stroke:#ffb74d,stroke-width:2px,color:#000000
style L2 fill:#c8e6c9,stroke:#2e7d32,stroke-width:2px,color:#000000
style L1 fill:#bbdefb,stroke:#1565c0,stroke-width:2px,color:#000000
3.3 OpenClaw 的工具策略模型¶
核心逻辑如下:
class ToolAccessLevel(Enum):
"""工具访问级别"""
SYSTEM = "system" # 仅系统可用
ADMIN = "admin" # 管理员可用
TRUSTED = "trusted" # 信任的用户可用
PUBLIC = "public" # 所有人可用
class ToolPolicy:
"""工具策略定义"""
def __init__(self):
self.access_levels: Dict[str, ToolAccessLevel] = {}
self.resource_limits: Dict[str, Dict[str, Any]] = {}
def set_access_level(self, tool_name: str,
level: ToolAccessLevel):
"""设置工具访问级别"""
self.access_levels[tool_name] = level
def set_resource_limit(self, tool_name: str,
max_execution_time: int = None,
max_memory: int = None,
max_disk_usage: int = None):
"""设置工具的资源限制"""
self.resource_limits[tool_name] = {
"max_execution_time": max_execution_time,
"max_memory": max_memory,
"max_disk_usage": max_disk_usage
}
def can_execute(self, tool_name: str,
user_role: str) -> bool:
"""检查用户是否可以执行工具"""
level = self.access_levels.get(
tool_name,
ToolAccessLevel.PUBLIC
)
if level == ToolAccessLevel.SYSTEM:
return user_role == "system"
elif level == ToolAccessLevel.ADMIN:
return user_role in ["system", "admin"]
elif level == ToolAccessLevel.TRUSTED:
return user_role in ["system", "admin", "trusted"]
else:
return True
为了支持不同的使用场景和性能需求,工具系统提供了多种扩展模式,使开发者能够灵活地定制工具行为。
3.4 工具的扩展模式¶
1)装饰器模式¶
该模式的实现示例:
import asyncio
def timeout_wrapper(tool: Tool, timeout: int = 30) -> Tool:
"""为工具添加超时装饰器"""
class TimeoutTool(Tool):
async def call(self, input_data):
try:
return await asyncio.wait_for(
tool.call(input_data),
timeout=timeout
)
except asyncio.TimeoutError:
raise ToolExecutionError(f"Tool timeout after {timeout}s")
def name(self) -> str:
return tool.name()
def description(self) -> str:
return tool.description()
# 其他方法委托...
return TimeoutTool()
def retry_wrapper(tool: Tool, max_retries: int = 3) -> Tool:
"""为工具添加重试装饰器"""
class RetryTool(Tool):
async def call(self, input_data):
for attempt in range(max_retries):
try:
return await tool.call(input_data)
except Exception as e:
if attempt == max_retries - 1:
raise
await asyncio.sleep(2 ** attempt) # 指数退避
# 其他方法...
return RetryTool()
2) 复合工具¶
具体代码如下:
from typing import List, Dict
class CompositeToolChain(Tool):
"""工具链:顺序执行多个工具"""
def __init__(self, tools: List[Tool],
input_mapping: Dict[str, List[str]]):
"""
input_mapping: 定义每个工具的输入如何来自前一个工具的输出
(上下文键名格式为 "<工具名>_output",与 call() 中的写入格式一致)
例如:{
"tool_2": ["tool_1_output.field_a"],
"tool_3": ["tool_2_output.field_b", "user_input.param"]
}
"""
self.tools = tools
self.input_mapping = input_mapping
async def call(self, input_data):
"""依次执行工具"""
context = {"user_input": input_data}
for tool in self.tools:
tool_name = tool.name()
# 解析这个工具的输入
tool_input = self._resolve_input(
self.input_mapping.get(tool_name, []),
context
)
# 执行工具
result = await tool.call(tool_input)
context[f"{tool_name}_output"] = result
return context[f"{self.tools[-1].name()}_output"]
def _resolve_input(self, mapping: List[str],
context: Dict) -> Dict:
"""从上下文中解析输入"""
result = {}
for ref in mapping:
parts = ref.split(".")
value = context
for part in parts:
value = value.get(part, {})
# 提取字段名
result[parts[-1]] = value
return result
3) 条件工具¶
以下是具体实现:
class ConditionalTool(Tool):
"""条件执行:根据条件选择执行哪个工具"""
def __init__(self, condition_fn, tool_if_true: Tool,
tool_if_false: Tool):
self.condition_fn = condition_fn
self.tool_if_true = tool_if_true
self.tool_if_false = tool_if_false
async def call(self, input_data):
"""根据条件选择执行"""
if self.condition_fn(input_data):
return await self.tool_if_true.call(input_data)
else:
return await self.tool_if_false.call(input_data)
def name(self) -> str:
return "conditional_tool"
3.5 工具的能力描述¶
完整的工具描述应包含:
@dataclass
class ToolCapability:
"""工具的能力描述"""
name: str
description: str
input_schema: Dict[str, Any]
output_schema: Dict[str, Any]
access_level: ToolAccessLevel
required_permissions: List[str]
estimated_execution_time: float # 秒
estimated_resource_usage: Dict[str, int] # 内存、CPU 等
examples: List[Dict[str, Any]] # 使用示例
limitations: List[str] # 工具的限制
dependencies: List[str] # 外部依赖
3.6 本节小结¶
工具类型体系和扩展模式决定了工具系统的灵活性:
- 工具分类:执行、网络、Agent、专用四大类
- 访问级别:系统、管理、信任、公开四个等级
- 扩展模式:装饰器、复合、条件等组合方式
- 能力描述:完整的元数据帮助智能体理解工具能力
好的工具类型体系应该既清晰易用,又能支持灵活的扩展和组合。
4. 动态发现与加载¶
本节介绍工具的动态发现和加载机制,包括延迟加载的决策、多级优先级加载策略、Schema 缓存、MCP 动态注册和上下文缓存等技术,阐明如何构建灵活高效的工具加载系统。
4.1 动态加载的必要性¶
静态工具列表的问题:
- 无法适应运行时变化
- 新工具需要重新部署
- 无法根据上下文选择性加载
4.2 Claude Code 的 shouldDefer 机制¶
shouldDefer 机制决定了工具是否需要延迟加载。下面的流程图展示了动态工具加载的完整流程:
graph TD
A["工具请求"] -->|检查上下文| B["shouldDefer?"]
B -->|否| C["立即加载"]
B -->|是| D["延迟加载"]
C -->|导入模块| E["创建实例"]
E -->|缓存| F["已加载工具"]
F -->|返回| G["就绪可用"]
D -->|稍后检查| H["上下文就绪?"]
H -->|是| C
H -->|否| I["返回不可用"]
style B fill:#fff3e0
style C fill:#c8e6c9
style D fill:#ffccbc
style G fill:#e8f5e9
图 5-3:shouldDefer 延迟加载机制
核心实现如下:
class ToolLoader:
"""延迟加载机制"""
def __init__(self):
self.loaded_tools = {}
self.tool_metadata = {}
def should_defer_loading(self, tool_name: str,
context: Dict) -> bool:
"""判断是否延迟加载工具"""
# 示例:某些工具只在特定上下文才加载
if tool_name == "database_query":
return context.get("user_role") != "admin"
return False
async def load_tool_on_demand(self, tool_name: str,
context: Dict) -> Optional[Tool]:
"""按需加载工具"""
if tool_name in self.loaded_tools:
return self.loaded_tools[tool_name]
if self.should_defer_loading(tool_name, context):
return None
# 动态导入和加载
try:
spec = self.tool_metadata.get(tool_name)
if not spec:
return None
module_path = spec["module"]
if not module_path.startswith("my_harness_tools."):
raise ValueError("Tool spec must use a trusted module namespace")
module = await self._async_import(module_path)
tool_class = getattr(module, spec["class"])
tool = tool_class(**spec.get("args", {}))
self.loaded_tools[tool_name] = tool
return tool
except Exception as e:
print(f"Failed to load tool '{tool_name}': {e}")
return None
async def _async_import(self, module_path: str):
"""异步导入模块"""
import importlib
return importlib.import_module(module_path)
4.3 OpenClaw 的 6 级技能加载优先级¶
实现的代码结构:
class SkillPriority(Enum):
"""技能加载优先级"""
SYSTEM = 1 # 固定系统技能
SESSION = 2 # 会话上下文技能
USER = 3 # 用户自定义
DOMAIN = 4 # 领域专业技能
BUILTIN = 5 # 通用工具
DISCOVERY = 6 # 动态发现
class SkillLoader:
"""6 级优先级技能加载器"""
def __init__(self, storage):
self.storage = storage
self.loaded_skills = {}
async def load_skills_for_session(self, session_id: str):
"""为会话加载所有技能"""
skills = []
# 1. 系统技能
skills.extend(await self._load_system_skills())
# 2. 会话上下文技能
skills.extend(await self._load_session_skills(session_id))
# 3. 用户自定义技能
skills.extend(await self._load_user_skills(session_id))
# 4. 领域技能
domain = await self._detect_domain(session_id)
skills.extend(await self._load_domain_skills(domain))
# 5. 内置工具
skills.extend(await self._load_builtin_tools())
# 6. 动态发现技能
skills.extend(await self._discover_skills_dynamically(session_id))
return skills
async def _load_system_skills(self):
"""加载系统技能(始终可用)"""
return await self.storage.get_system_skills()
async def _load_session_skills(self, session_id: str):
"""加载会话特定的技能"""
return await self.storage.get_session_skills(session_id)
async def _load_user_skills(self, session_id: str):
"""加载用户定义的技能"""
user_id = await self.storage.get_user_for_session(session_id)
return await self.storage.get_user_skills(user_id)
async def _load_domain_skills(self, domain: str):
"""加载特定领域的技能"""
if not domain:
return []
return await self.storage.get_domain_skills(domain)
async def _load_builtin_tools(self):
"""加载内置工具"""
return [
BashTool(),
FileReadTool(),
FileWriteTool(),
# ... 其他内置工具
]
async def _discover_skills_dynamically(self, session_id: str):
"""动态发现技能"""
# 扫描技能目录或 MCP
discovered = await self.storage.discover_available_skills()
return discovered
async def _detect_domain(self, session_id: str) -> str:
"""根据会话内容检测领域"""
recent_messages = await self.storage.get_recent_messages(session_id, limit=5)
# 使用 NLP 检测领域
text = " ".join(m.get_text() for m in recent_messages)
return self._classify_domain(text)
def _classify_domain(self, text: str) -> str:
"""简单的领域分类"""
keywords = {
"data": ["sql", "database", "query", "table"],
"web": ["html", "css", "javascript", "react"],
"system": ["bash", "shell", "linux", "windows"],
"code": ["python", "java", "javascript"]
}
text_lower = text.lower()
for domain, kws in keywords.items():
if any(kw in text_lower for kw in kws):
return domain
return "general"
4.4 JSON Schema 缓存¶
具体的缓存实现:
class SchemaCache:
"""工具 Schema 缓存"""
def __init__(self, max_age: int = 3600):
self.cache = {}
self.max_age = max_age
self.timestamps = {}
def get_schema(self, tool_name: str) -> Optional[Dict]:
"""获取缓存的 Schema"""
if tool_name in self.cache:
age = time.time() - self.timestamps[tool_name]
if age < self.max_age:
return self.cache[tool_name]
else:
del self.cache[tool_name]
del self.timestamps[tool_name]
return None
def set_schema(self, tool_name: str, schema: Dict):
"""缓存 Schema"""
self.cache[tool_name] = schema
self.timestamps[tool_name] = time.time()
def build_all_schemas(self, tools: List[Tool]) -> Dict[str, Dict]:
"""批量构建 Schema 缓存"""
schemas = {}
for tool in tools:
schema = {
"name": tool.name(),
"description": tool.description(),
"input_schema": tool.input_schema()
}
self.set_schema(tool.name(), schema)
schemas[tool.name()] = schema
return schemas
4.5 MCP 动态注册¶
MCP(Model Context Protocol)支持动态工具注册:
⚠️ 注意:以下代码为概念示意,非真实 MCP 调用方式。真实 MCP 客户端使用 JSON-RPC 2.0 over stdio 或 Streamable HTTP,不使用 REST 风格的
/tools/list和/tools/call端点。详见第 9.3 节与 9.4 节 MCP 协议详解。
class MCPToolRegistry:
"""MCP 协议的工具注册"""
def __init__(self):
self.mcp_servers = {}
self.available_tools = {}
async def discover_mcp_tools(self, server_url: str):
"""发现 MCP 服务器的工具"""
try:
# 连接到 MCP 服务器
async with aiohttp.ClientSession() as session:
async with session.get(f"{server_url}/tools") as resp:
tools = await resp.json()
return tools
except Exception as e:
print(f"Failed to discover MCP tools: {e}")
return []
def register_mcp_tool(self, server_url: str,
tool_name: str,
tool_spec: Dict):
"""注册来自 MCP 的工具"""
if server_url not in self.mcp_servers:
self.mcp_servers[server_url] = {}
self.mcp_servers[server_url][tool_name] = tool_spec
self.available_tools[tool_name] = {
"source": "mcp",
"server": server_url,
"spec": tool_spec
}
async def execute_mcp_tool(self, tool_name: str,
input_data: Dict) -> Any:
"""执行 MCP 工具"""
tool_info = self.available_tools.get(tool_name)
if not tool_info:
raise ValueError(f"Tool not found: {tool_name}")
server_url = tool_info["server"]
async with aiohttp.ClientSession() as session:
async with session.post(
f"{server_url}/tools/call",
json={"name": tool_name, "arguments": input_data}
) as resp:
return await resp.json()
4.6 FileStateCache 上下文缓存¶
实现如下:
class FileStateCache:
"""文件状态缓存:减少重复工具调用"""
def __init__(self, ttl: int = 60): # 缓存时间(秒)
self.cache = {}
self.timestamps = {}
self.ttl = ttl
def get(self, key: str) -> Optional[Dict]:
"""获取缓存"""
if key in self.cache:
age = time.time() - self.timestamps[key]
if age < self.ttl:
return self.cache[key]
else:
del self.cache[key]
del self.timestamps[key]
return None
def set(self, key: str, value: Dict):
"""设置缓存"""
self.cache[key] = value
self.timestamps[key] = time.time()
def invalidate(self, pattern: str):
"""失效匹配模式的缓存"""
import re
regex = re.compile(pattern)
to_delete = [k for k in self.cache.keys() if regex.match(k)]
for key in to_delete:
del self.cache[key]
del self.timestamps[key]
# 使用:缓存文件列表结果
cache = FileStateCache()
async def file_list_with_cache(directory: str) -> List[str]:
"""带缓存的文件列表"""
cache_key = f"file_list:{directory}"
# 检查缓存
cached = cache.get(cache_key)
if cached:
return cached["files"]
# 执行结构化文件列表工具,避免把目录名拼进 shell 命令
files = await file_list_tool.call({"path": directory})
# 缓存结果
cache.set(cache_key, {"files": files})
return files
4.7 本节小结¶
动态工具加载是现代智能体系统的必需能力:
- shouldDefer 延迟加载:基于上下文决定是否加载工具
- 6 级优先级加载:系统 → 会话 → 用户 → 领域 → 内置 → 发现
- Schema 缓存:减少重复构建 Schema 的开销
- MCP 动态注册:支持第三方工具的即插即用
- FileStateCache:减少重复工具调用,提高性能
这些机制共同构成了一个灵活、高效、可扩展的工具加载系统。
5. 实战:MiniHarness 工具层实现¶
本节实现一个完整的 MiniHarness 工具层,包括工具协议、注册表、执行流水线和几个内置工具。核心设计原则是“发现、权限、执行、结果”四阶段流水线。
5.1 工具抽象与基类¶
工具层的核心是统一的 Tool 基类,定义了所有工具必须实现的接口。工具的设计遵循“契约优先”原则:
from abc import ABC, abstractmethod
class Tool(ABC):
"""工具基类 - 定义工具的通用契约"""
@abstractmethod
async def call(self, params: Dict[str, Any]) -> ToolResult:
"""执行工具的核心逻辑"""
pass
@abstractmethod
def name(self) -> str:
"""工具的唯一标识符"""
pass
@abstractmethod
def description(self) -> str:
"""工具描述"""
pass
@abstractmethod
def input_schema(self) -> Dict[str, Any]:
"""JSON Schema 定义输入约束"""
pass
def check_permissions(self, context: Any) -> bool:
"""权限检查钩子 - 可被子类覆盖"""
return True
关键设计决策:
- 异步执行:所有工具使用
async/await,支持 IO 密集型操作 - Schema 驱动:通过 JSON Schema 定义输入,实现自文档化和验证
- 权限钩子:
check_permissions()允许子类实现细粒度权限控制 - 统一结果类型:
ToolResult封装成功/失败/执行时间等元数据
完整实现参见 lab/mini_harness/tools/builtin.py。
5.2 工具执行流水线¶
执行流水线是系统的核心,实现了 4 阶段的工具调用流程:
class ExecutionPipeline:
"""执行流水线 - 控制工具的调用生命周期"""
async def execute(self, tool_name: str,
input_params: Dict[str, Any]) -> ToolResultBlock:
# 阶段 1:工具发现(lookup)
tool = self.tool_registry.get(tool_name)
if not tool:
return ToolResultBlock(tool_name=tool_name, success=False,
content=f"Tool not found: {tool_name}")
# 阶段 2:权限检查(authorization)
if not tool.check_permissions({}):
return ToolResultBlock(tool_name=tool_name, success=False,
content=f"Permission denied for tool: {tool_name}")
# 阶段 3:执行工具(execution)
# 阶段 4:结果处理(result handling)
result = await tool.call(input_params)
return ToolResultBlock(
tool_name=tool_name,
success=result.success,
content=result.content,
execution_time=result.execution_time,
error_type=result.error_type,
)
四阶段设计的优点:
- 可观测性:每个阶段可独立记录日志和监控
- 可扩展性:插入速率限制、超时控制、重试逻辑等中间件
- 安全性:权限检查在执行前进行,防止越权操作
- 容错性:捕获任意异常并转换为统一的失败结果
完整实现参见 lab/mini_harness/tools/builtin.py 中的 ExecutionPipeline 类。
5.3 工具注册表¶
工具注册表充当服务发现中心和 Schema 缓存:
class ToolRegistry:
"""工具注册中心 - 管理工具的生命周期"""
def register(self, tool: Tool):
"""注册工具并缓存其 Schema"""
name = tool.name()
self.tools[name] = tool
# 缓存 Schema 避免重复调用
self.schema_cache[name] = {
"name": name,
"description": tool.description(),
"input_schema": tool.input_schema()
}
def list_tools(self) -> List[Dict[str, Any]]:
"""返回所有工具的 Schema(用于 LLM 提示词)"""
return list(self.schema_cache.values())
设计特点:
- Schema 缓存:减少重复的 Schema 构建,加速工具列表生成
- 统一发现:LLM 通过
list_tools()获取全部可用工具及其约束 - 松耦合:执行流水线仅依赖注册表接口,不需关心工具实现细节
完整实现参见 lab/mini_harness/tools/registry.py。
5.4 内置工具示例¶
三个内置工具展示了不同的设计模式:
BashTool:命令执行工具,演示了长时间运行任务的超时处理
class BashTool(Tool):
async def call(self, params: Dict[str, Any]) -> ToolResult:
import shlex
import subprocess
command = params.get("command", "")
timeout = params.get("timeout", 30)
allowed_commands = {"echo", "pwd", "true", "false", "sleep"}
argv = shlex.split(command)
if not argv or argv[0] not in allowed_commands:
return ToolResult(success=False, content="Command not in allowlist",
execution_time=0.0, error_type="PermissionError")
try:
result = subprocess.run(argv, shell=False,
timeout=timeout, text=True,
capture_output=True)
return ToolResult(
success=result.returncode == 0,
# ... 其他结果字段
)
except subprocess.TimeoutExpired:
return ToolResult(
success=False,
error_type="TimeoutError",
# ... 其他结果字段
)
FileReadTool 和 FileWriteTool:文件操作工具,演示了错误分类和限制(如 max_bytes)
这些工具的完整实现包括详细的错误处理、参数验证和边界情况处理,参见 lab/mini_harness/tools/builtin.py。
5.5 扩展路径¶
- 中间件架构:在执行流水线中插入速率限制、重试、缓存层
- 权限模型:为不同用户和工具组合配置细粒度权限
- 超时和资源限制:为每个工具实例设置 CPU/内存配额
- 并发执行:使用
asyncio.gather()实现多工具并发调用 - 工具依赖图:支持工具间的数据流依赖(DAG 执行)
- 结果缓存:基于输入哈希缓存工具结果,支持失效策略
5.6 本节小结¶
MiniHarness 工具层的核心贡献:
- 清晰的抽象边界:Tool 基类定义统一契约,工具之间互不依赖
- 四阶段执行流水线:发现→权限→执行→处理,可观测且可扩展
- Schema 驱动设计:工具通过 JSON Schema 自描述,LLM 可直接理解
- 错误优先处理:所有失败路径返回统一的
ToolResult类型
这些原则适用于任何规模的 AI Agent 系统工具层设计。
6. 本章小结¶
本章深入探讨了工具层的设计原则和实现模式,以下是主要内容的总结。
6.1 工具层设计的核心原则¶
6.1.1 统一抽象¶
无论工具类型如何(文件、网络、计算、数据库),都应该通过统一的接口暴露能力。Claude Code 的 Tool\ 泛型设计实现了强类型检查,OpenClaw 的 Tool Protocol 提供了灵活的抽象。
6.1.2 权限隔离¶
工具调用前必须进行权限检查,防止未授权的操作。不同的工具有不同的权限要求:
- 系统工具(Bash):只有管理员可用
- 数据库工具:需要认证
- 文件工具:需要文件访问权限
6.1.3 错误恢复¶
工具执行失败是常态,系统必须:
- 捕获所有异常(执行错误、超时、权限拒绝)
- 将错误转化为 ToolResultBlock 反馈给 Agent
- 支持重试策略(指数退避)
6.1.4 性能优化¶
- 并发执行:多个工具可以并行执行
- 结果缓存:避免重复调用相同工具
- Schema 缓存:减少构建工具信息的开销
6.2 工具类型体系速览¶
| 类型 | 特点 | 示例 |
|---|---|---|
| 执行工具 | 直接改变系统状态 | Bash、Python、文件操作 |
| 网络工具 | 远程调用,有延迟和限制 | HTTP、API、搜索 |
| 智能体工具 | 委托给其他智能体 | SubAgent、并行执行 |
| 专用工具 | 特定领域的深度功能 | 数据库、图像处理 |
6.3 执行流水线的 6 个阶段¶
流程如下:
flowchart LR
A["发现"] --> B["权限检查"]
B --> C["输入验证"]
C --> D["执行"]
D --> E["结果处理"]
E --> F["持久化"]
style A fill:#e8f4f8,stroke:#4a90a4,stroke-width:2px,color:#000000
style B fill:#fff4e8,stroke:#a49044,stroke-width:2px,color:#000000
style C fill:#fff4e8,stroke:#a49044,stroke-width:2px,color:#000000
style D fill:#e8f4e8,stroke:#4a9044,stroke-width:2px,color:#000000
style E fill:#fff4e8,stroke:#a49044,stroke-width:2px,color:#000000
style F fill:#c8e6c9,stroke:#2e7d32,stroke-width:2px,color:#000000
每个阶段都有明确的职责和错误处理机制,确保整个流程的可靠性。
6.4 工具扩展的三种模式¶
- 装饰器模式:为工具添加超时、重试等通用能力
- 复合工具:组合多个工具形成工具链
- 条件工具:根据条件选择执行的工具
6.5 动态加载的必要性¶
- shouldDefer 延迟加载:根据上下文决定是否加载工具,节省内存
- 6 级优先级加载:OpenClaw 的分层加载策略,支持灵活的工具组织
- MCP 动态注册:支持第三方工具的即插即用
- Schema 缓存:减少重复构建的开销
6.6 Claude Code vs OpenClaw 的工具设计对比¶
| 方面 | Claude Code | OpenClaw |
|---|---|---|
| 工具数量 | 内置工具 + 扩展工具 | 通用工具 + 技能 |
| 执行模式 | 并发 | 顺序 |
| 接口设计 | Tool\ 泛型 | Tool Protocol |
| 权限模型 | check_permissions() | 工具策略和访问级别 |
| 加载策略 | shouldDefer | 6 级优先级 |
| 缓存 | FileStateCache | 会话记忆 |
6.7 MiniHarness 的实现启示¶
通过从零实现工具层,我们学到:
- 工具抽象 需要平衡通用性和类型安全
- 执行流水线 需要完整的生命周期管理
- 工具注册表 是工具系统的中枢
- 错误处理 必须穷尽所有可能的失败模式
- 可扩展性 通过接口和工厂模式实现
6.8 与其他章节的联系¶
关系图如下:
graph TD
A["<b>运行时引擎(第4章)</b><br/>调用工具的生命周期管理"] -->|调用| B["<b>工具层(第5章)</b><br/>工具的抽象和执行<br/><br/>← 你在这里"]
B -->|ToolResult| A
A -->|重要步骤记录| C["<b>记忆系统(第6章)</b><br/>经验与摘要的持久化"]
style A fill:#e8f4f8,stroke:#4a90a4,stroke-width:2px,color:#000000
style B fill:#fff4e8,stroke:#a49044,stroke-width:2px,color:#000000
style C fill:#e8f4f8,stroke:#4a90a4,stroke-width:2px,color:#000000
运行时引擎调用工具层执行具体操作,工具层返回标准化结果;是否写入记忆系统由运行时根据重要性、审计和隐私策略决定。
6.9 设计决策速查¶
- 工具接口:选择类型安全(Tool\)还是灵活的协议(Tool Protocol)
- 执行模式:并发执行还是顺序执行,取决于工具依赖关系
- 权限模型:简单的 boolean 检查还是复杂的策略模型
- 加载策略:预加载所有工具还是按需加载
- 缓存策略:缓存结果、Schema 还是执行历史
6.10 学习建议¶
- 理解工具抽象:为什么需要统一接口,不同的设计如何影响系统
- 掌握执行流水线:6 个阶段的职责分工和错误处理
- 学习扩展模式:如何优雅地扩展工具系统,支持新的工具类型
- 实践动态加载:实现工具的延迟加载和按需发现
- 优化性能:通过缓存、并发、Schema 预加载提高性能
6.11 常见陷阱¶
- 权限检查遗漏:在执行前必须检查权限,防止安全漏洞
- 错误处理不完整:必须捕获所有可能的异常(超时、权限、格式错误等)
- 缺乏超时保护:没有超时的工具调用可能导致系统挂起
- Schema 过时:工具更新后忘记更新 Schema,导致参数错误
- 缓存不失效:缓存的数据如果不及时失效,可能导致数据不一致
6.12 进阶话题¶
- 工具的分布式执行:在多个节点执行工具
- 工具的版本管理:支持多个版本的同一工具
- 工具的性能优化:对慢工具的优化策略
- 工具的自动化测试:确保工具的正确性
- 工具的文档生成:从 Schema 自动生成文档
本章深入讲解了工具层的设计与实现,涵盖了工具抽象、执行流水线、工具类型体系、动态加载等关键主题。通过 MiniHarness 的实现,我们展示了这些概念如何落地成可运行的代码。
下一章将深入 记忆与上下文子系统,研究如何有效地存储、检索和利用过去的交互信息来提高智能体的能力。