工具设计与封装
Agent 的能力边界取决于它能调用什么工具。一个设计良好的工具能让 Agent 精准完成任务,而设计不当的工具不仅帮不上忙,还可能误导模型决策、引发安全事故。本章从 API 接入规范、沙箱执行、错误处理、工具描述四个维度,讲解如何为 Agent 打造可靠、高效的工具体系。
API 接入规范
统一的工具接口定义
无论底层是 HTTP API、数据库查询还是本地脚本,Agent 看到的工具接口应统一为 JSON Schema 描述 + JSON 输入输出。这种统一性让模型不必为每种工具学习不同的调用方式。
{
"name": "query_order_status",
"description": "查询订单的当前状态,包括物流进度和预计送达时间",
"parameters": {
"type": "object",
"properties": {
"order_id": {
"type": "string",
"description": "订单编号,格式为 ORD-XXXXXX"
},
"include_logistics": {
"type": "boolean",
"description": "是否包含物流详情,默认为 true",
"default": true
}
},
"required": ["order_id"]
}
}
输入校验与类型约束
JSON Schema 不仅告诉模型参数是什么,更是 输入校验的防线。在工具执行前必须校验:
- 类型校验:
order_id是 string 而非 number,防止模型传错类型。 - 格式校验:
order_id格式为ORD-XXXXXX,可用正则约束。 - 枚举约束:
status字段只能是["active", "cancelled", "completed"],用enum限制。
模型有时会"创造"参数值(如生成一个不存在的 order_id)。校验层应在执行前拦截这类无效输入,返回明确的错误信息而非让错误传播到后端 API。
超时与重试策略
工具调用可能因网络波动、API 限流等原因失败。每个工具应配置:
- 超时时间:根据工具类型设定合理超时。数据库查询 5 秒、外部 API 15 秒、文件操作 30 秒。
- 重试策略:对可恢复错误(网络超时、429 限流)做最多 3 次重试,每次间隔递增(指数退避)。对不可恢复错误(404、参数错误)不重试,直接返回错误。
- 降级方案:关键工具应有降级路径。例如实时查询失败时,返回最近一次缓存的近似结果并标注"数据可能非最新"。
沙箱执行环境
为什么需要沙箱?
Agent 可能执行用户请求中隐含的代码(如数据分析场景下的 Python 代码),或调用涉及系统资源的工具(文件读写、网络请求)。如果直接在宿主环境执行:
- 安全风险:恶意指令可能删除文件、访问敏感数据、发起网络攻击。
- 资源风险:失控的计算任务可能耗尽 CPU/内存,影响宿主服务稳定性。
- 隔离风险:不同用户的执行环境可能互相干扰。
沙箱方案选择
| 方案 | 隔离级别 | 启动延迟 | 适用场景 |
|---|---|---|---|
| Docker 容器 | 进程+文件+网络隔离 | 500ms-2s | 通用执行、代码运行 |
| gVisor / Firecracker | 内核级隔离 | 1-3s | 高安全要求的代码执行 |
| WebAssembly(Wasm) | 模块级隔离 | <100ms | 轻量计算、函数执行 |
| 子进程 + 资源限制 | 进程级隔离 | <50ms | 简单脚本、受限操作 |
推荐组合:对代码执行类工具使用 Docker+资源限制(CPU 1核/内存 512MB/磁盘 100MB/无网络),对函数级轻量工具使用 Wasm。
资源限制配置
# Docker 沙箱配置示例
sandbox_config = {
"cpu_limit": 1, # 最多 1 个 CPU 核心
"memory_limit": "512m", # 最大内存 512MB
"timeout": 30, # 执行超时 30 秒
"network": "none", # 禁止网络访问
"disk_limit": "100m", # 最大磁盘写入 100MB
"read_only_paths": ["/data"], # 只读挂载
"writable_paths": ["/tmp/output"] # 可写挂载
}
错误处理
错误分类与分级
工具错误不是简单的"成功/失败"二分,应按严重程度分类:
- Level 0 - 可忽略:工具返回了部分数据而非完整数据(如物流信息缺一项),Agent 可以继续推理并告知用户数据不完整。
- Level 1 - 可重试:网络超时、API 限流等暂时性错误,Agent 应自动重试后继续。
- Level 2 - 需要替代:工具本身不可用,Agent 应选择替代工具或策略(如用缓存数据代替实时查询)。
- Level 3 - 需要人工介入:权限不足、数据冲突等需要用户决策的错误,Agent 应清晰说明问题并请求用户指示。
错误信息设计原则
错误信息同时服务于两个受众:LLM(需要理解错误原因以调整策略)和用户(需要知道发生了什么)。因此:
- 结构化返回:不要只返回一个字符串,返回
{success: false, error_type: "timeout", message: "...", suggestion: "..."}。 - 给模型可操作的 suggestion:如
"请尝试缩短查询范围或使用更具体的订单编号",而非"请求超时"。 - 避免暴露内部细节:对用户展示的错误信息不应包含 API 地址、数据库表名等内部信息,但对模型可以传递更多上下文以辅助推理。
工具描述最佳实践
工具的 description 是模型决定何时、如何调用工具的唯一依据。描述质量直接决定工具调用成功率。
好的描述长什么样
{
"name": "search_product_inventory",
"description": "根据产品名称或类别搜索库存信息。返回每个匹配产品的当前库存数量、仓库位置和预计补货日期。当用户询问产品是否有货、库存数量、何时补货时使用此工具。不支持模糊匹配,产品名称需精确或使用类别筛选。"
}
关键要素:
- 功能说明:工具做什么、返回什么。
- 触发条件:什么时候该用——"当用户询问..."。
- 局限说明:什么时候不该用或有什么限制——"不支持模糊匹配"。
常见问题
- 描述过于简短:
"搜索库存"——模型不知道什么时候该调用它,可能在不相关的场景误触发。 - 描述过于宽泛:
"查询所有信息"——模型可能用它替代所有其他工具,导致不该用的场景也调了。 - 缺少边界说明:没告诉模型工具的输入限制,导致模型传了格式错误的参数。
- 名称歧义:工具名
get_data太通用,模型无法区分它与其他类似工具——用search_product_inventory而非get_data。
多工具场景的描述策略
当 Agent 有 10+ 工具时,模型面临"选择哪个工具"的决策压力。此时:
- 工具名要自解释:
calculate_mortgage比calc好 10 倍。 - 描述中明确分工:
query_current_stock描述"查询实时库存",query_stock_history描述"查询历史库存变化趋势"——让模型能精确选择。 - 合并过于碎片化的工具:3 个参数几乎相同、功能只是返回字段略有差异的工具,合并为 1 个带
include_fields参数的工具更高效。
本章小结
| 设计维度 | 最佳实践 |
|---|---|
| API 标准化 | JSON Schema 定义参数,名称语义清晰,描述包含边界条件 |
| 沙箱隔离 | Docker 容器化运行,网络隔离,资源限制 |
| 错误处理 | 四层错误体系:参数错误 → 执行错误 → 超时错误 → 系统错误 |
| 安全性 | 最小权限原则,操作审计,输入校验,敏感数据脱敏 |
核心原则:工具是 Agent 的"手脚"——设计不好,Agent 再聪明也走不稳。
📖 延伸阅读
- JSON Schema —— 工具参数定义标准
- OpenAPI Specification —— REST API 设计规范
- gVisor Documentation —— 容器安全沙箱
- OWASP API Security Top 10 —— API 安全最佳实践
