chore: import zh skill tool-design

This commit is contained in:
wehub-skill-sync
2026-07-13 21:36:21 +08:00
commit 53b53c11f5
5 changed files with 1213 additions and 0 deletions
+9
View File
@@ -0,0 +1,9 @@
# WeHub 来源说明
- Skill 名称:`tool-design`
- 中文类目:agent tool 接口设计
- 上游仓库:`muratcankoylan__Agent-Skills-for-Context-Engineering`
- 上游路径:`skills/tool-design/SKILL.md`
- 上游链接:https://github.com/muratcankoylan/Agent-Skills-for-Context-Engineering/blob/HEAD/skills/tool-design/SKILL.md
- 本仓库为 WeHub 中文 Skill 汉化包,基于 skill 市场筛选 Top200 清单整理
- 原作者、版权和许可证信息以上游仓库为准
+296
View File
@@ -0,0 +1,296 @@
---
name: tool-design
description: 本技能适用于代理系统中工具接口层的设计,具体包括:编写代理能够据此进行路由的工具描述、设计工具 schema 与响应格式、命名规范、可操作的错误恢复信息、MCP 服务器设计、工具集整合,以及决定何时增删单个工具。当工作单元为单个工具或一组工具时使用。关于项目形态、流水线架构以及任务-模型匹配度的决策,请转至 project-development;关于是否引入子代理的决策,请转至 multi-agent-patterns。
---
# 面向代理的工具设计
将每个工具设计为确定性系统与非确定性代理之间的一份契约。与人面向的 API 不同,面向代理的工具必须仅通过描述就让契约毫无歧义:代理从描述中推断意图,并生成必须符合预期格式的调用。每一个歧义都成为一个潜在的故障模式,这是任何提示工程都无法修复的。
本技能的工作单元是单个工具或一个工具目录。项目形态、流水线架构、任务-模型匹配度以及项目级别的成本决策,归属 `project-development`。是否引入子代理的决策,归属 `multi-agent-patterns`。本技能负责的是将确定性代码与代理连接起来的接口层。
## 何时激活
当工作单元是一个工具时,激活本技能:
- 编写新的工具描述、schema 或响应格式。
- 调试代理选错工具或生成了格式不正确的调用的情况。
- 整合功能重叠的工具目录(经典的"我们有 17 个工具,代理一半时间选错"的情况)。
- 设计可操作的错误消息,以便代理能够自我纠正。
- 在整个目录中一致地命名工具和参数(MCP 命名空间、动词-名词命名)。
- 在添加第三方工具之前,根据整合原则对其进行评估。
对于归属于其他技能的相邻工作,请勿激活本技能:
- 决定项目是否应该使用 LLM,或流水线应包含哪些阶段:`project-development`
- 决定是将工作分配给多个子代理,还是使用带更多工具的单个代理:`multi-agent-patterns`
- 在轨迹层面降低工具输出的 Token 权重(观察掩码、大规模选择格式选项):`context-optimization`
## 核心概念
围绕整合原则设计工具:如果人类工程师无法明确判断在某种情况下应该使用哪个工具,那么代理也不可能做得更好。精简工具集,直到每个工具都有一个明确无歧义的用途,因为代理通过比较描述来选择工具,任何重叠都会引入选择错误。
将每个工具描述视为塑造代理行为的提示工程。描述不是给人类看的文档——它被注入到代理的上下文中,直接引导推理过程。撰写的描述应回答工具的作用、何时使用以及返回什么,因为这三个问题正是代理在工具选择过程中评估的内容。
## 详细主题
### 工具-代理接口
**工具作为契约**
将每个工具设计为一份自包含的契约。当人类调用 API 时,他们会阅读文档、理解约定并发出适当的请求。代理必须从一个描述块中推断出整份契约。通过包含格式示例、预期模式和显式约束,使契约毫无歧义。不要遗漏调用者需要知道的任何信息,因为代理在发出调用之前无法提出澄清性问题。
**工具描述即提示**
撰写工具描述时要清楚,它们会直接加载到代理上下文中,并共同引导代理行为。一个模糊的描述(比如"搜索数据库")配上晦涩的参数名,会迫使代理去猜测——而猜测会导致错误的调用。相反,应包含使用场景、参数格式示例和合理的默认值。描述中的每一个字要么有助于提高工具选择准确率,要么有害。
**命名空间与组织**
随着工具集合的壮大,使用公共前缀对工具进行命名空间分组,因为代理会受益于层级分组。当代理需要数据库操作时,它会路由到 `db_*` 命名空间;当需要网络交互时,它会路由到 `web_*` 命名空间。没有命名空间,代理必须在一个扁平列表中评估每个工具,这会导致选择准确率随工具数量增加而下降。
### 整合原则
**单个综合工具**
构建单个综合工具,而不是构建多个功能重叠的窄工具。与其分别实现 `list_users``list_events``create_event`,不如实现一个 `schedule_event`,它在一次调用中查找可用性并完成排程。该综合工具在内部处理完整的工作流,免除了代理按正确顺序链式调用的负担。
**为什么整合有效**
实施整合,因为代理的上下文和注意力是有限的。集合中的每个工具在工具选择时都会争夺注意力,每个描述都会消耗上下文预算的 Token,而重叠的功能会制造歧义。整合消除了冗余描述,消除了选择歧义,并缩小了有效工具集。Vercel 的 d0 案例研究是一个具体例子,展示了如何将专用工具缩减为更小的原始工具集,并取得了更好的可测量结果(claim-tool-design-vercel-d0-reduction)。
**何时不进行整合**
当工具具有根本不同的行为、服务于不同的上下文或必须能够独立调用时,保持工具分离。过度整合会产生另一个问题:一个带有过多参数和模式的工具会让代理难以正确参数化。
### 架构精简
将整合原则推向逻辑极致:移除大多数专用工具,转而使用原始的、通用的能力。生产环境中的证据表明,这种方法可以超越复杂的多工具架构。
**文件系统代理模式**
通过提供一个单一的命令执行工具来提供直接的文件系统访问,而不是为数据探索、schema 查找和查询验证构建定制工具。代理使用标准的 Unix 工具(grep、cat、find、ls)来探索和操作系统。这种方式行之有效,因为文件系统是一种经过验证的抽象,模型对其有深刻理解;标准工具的行为是可预测的;代理可以灵活地组合原始操作,而不是被限制在预定义的工作流中;并且文件中良好的文档取代了摘要工具。
**何时精简优于复杂**
在以下情况下选择精简:数据层文档完善且结构一致;模型具有足够的推理能力;专用工具反而约束了模型而非赋能模型;维护脚手架的时间多于改进结果的时间。在以下情况下避免精简:底层数据混乱或文档不佳;领域需要模型缺乏的专业知识;安全约束必须限制代理的操作;操作确实受益于结构化工作流。
**为未来模型而构建**
设计能够从模型改进中受益的最小化架构,而不是锁定当前局限性的复杂架构。问问自己,每个工具是开启了新能力,还是约束了模型本可自行处理的推理——作为"护栏"构建的工具,随着模型的改进,往往会成为负担。
有关生产环境的证据,请参见[架构精简案例研究](./references/architectural_reduction.md)。
### 工具描述工程
**描述结构**
将每个工具描述组织为回答四个问题:
1. 该工具做什么?准确说明工具完成的任务——避免使用"有助于"或"可用于"这类模糊语言。
2. 何时应使用?指明直接触发条件("用户询问价格")和间接信号("需要当前市场汇率")。
3. 接受哪些输入?描述每个参数,包括类型、约束、默认值和格式示例。
4. 返回什么?记录输出格式、结构、成功响应示例和错误条件。
**默认参数选择**
设置默认值以反映常见用例。默认值通过消除不必要的参数指定来减少代理负担,并防止因遗漏参数而导致的错误。选择能在不要求代理理解每个选项的情况下产生有用结果的默认值。
### 响应格式优化
提供响应格式选项(简洁 vs. 详细),因为工具响应的大小会显著影响上下文使用。简洁格式仅返回关键字段,适用于确认场景。详细格式返回完整对象,适用于完整上下文驱动决策的场景。在工具描述中记录何时使用每种格式,以便代理学会适当选择。
### 错误消息设计
为两类受众设计错误消息:调试问题的开发者和从故障中恢复的代理。对于代理而言,每条错误消息都必须是可操作的——它必须说明哪里出了问题以及如何纠正。对于可重试的错误,包含重试指导;对于输入错误,包含修正后的格式示例;对于不完整的请求,包含具体缺失的字段。一条只说了"失败"的错误消息不提供任何恢复信号。
### 工具定义 Schema
在所有工具之间建立一致的 schema。使用动词-名词模式作为工具名(`get_customer``create_order`),跨工具使用一致的参数名(始终使用 `customer_id`,而不是有时用 `id`、有时用 `identifier`),并使用一致的返回字段名。一致性降低了代理的认知负担,并改善了跨工具泛化能力。
### 工具集合设计
将工具集合限制为具有非重叠用途的最小集合,因为描述重叠会导致模型混淆,而更多的工具并不总能带来更好的结果。当确实需要更多工具时,使用命名空间创建逻辑分组。实现选择机制:按领域对工具进行分组、基于示例的选择提示,以及路由到专用子工具的伞状工具。
### MCP 工具命名要求
始终使用 MCP(模型上下文协议)的全限定工具名,以避免出现"找不到工具"的错误。
格式:`ServerName:tool_name`
```python
# 正确:全限定名
"Use the BigQuery:bigquery_schema tool to retrieve table schemas."
"Use the GitHub:create_issue tool to create issues."
# 错误:非限定名
"Use the bigquery_schema tool..." # 在多个服务器时可能失败
```
如果没有服务器前缀,当有多个 MCP 服务器可用时,代理可能无法定位工具。建立包含服务器上下文的命名规范,用于所有工具引用。
### 使用代理优化工具
将观察到的工具故障反馈给代理,以诊断问题并改进描述。在将报告的效率提升推广到目标工具目录之前,将其视为工作负载特例。
**工具测试代理模式**
```python
def optimize_tool_description(tool_spec, failure_examples):
"""
Use an agent to analyze tool failures and improve descriptions.
Process:
1. Agent attempts to use tool across diverse tasks
2. Collect failure modes and friction points
3. Agent analyzes failures and proposes improvements
4. Test improved descriptions against same tasks
"""
prompt = f"""
Analyze this tool specification and the observed failures.
Tool: {tool_spec}
Failures observed:
{failure_examples}
Identify:
1. Why agents are failing with this tool
2. What information is missing from the description
3. What ambiguities cause incorrect usage
Propose an improved tool description that addresses these issues.
"""
return get_agent_response(prompt)
```
这创建了一个反馈循环:使用工具的代理生成故障数据,然后代理利用这些数据改进工具描述,进而减少未来的故障。
### 测试工具设计
根据五个标准评估工具设计:无歧义性、完整性、可恢复性、效率和一致性。通过呈现代表性的代理请求并评估生成的工具调用是否符合预期行为来进行测试。
## 实用指南
### 工具选择框架
在设计工具集合时:
1. 确定代理必须完成的独立工作流
2. 将相关操作分组为综合性工具
3. 确保每个工具都有清晰、无歧义的用途
4. 记录错误情况和恢复路径
5. 使用实际代理交互进行测试
### 工具审核清单
在将每个工具添加到代理之前,使用此清单进行审核:
1. **名称**:动词-名词模式,如果目录涉及多个领域则使用命名空间。
2. **描述**:说明工具的作用、何时使用以及返回什么。
3. **Schema**:每个参数都有类型、约束、默认值和示例值。
4. **返回形状**:成功和错误的负载已文档化且机器可读。
5. **恢复**:每个错误都告诉代理在重试前需要更改什么。
6. **重叠**:没有其他工具具有相同的触发场景。
7. **整合决策**:除非需要独立调用,否则合并相邻的窄工具。
8. **Token 影响**:大型响应支持简洁模式或文件引用模式。
## 示例
**示例 1:设计良好的工具**
```python
def get_customer(customer_id: str, format: str = "concise"):
"""
Retrieve customer information by ID.
Use when:
- User asks about specific customer details
- Need customer context for decision-making
- Verifying customer identity
Args:
customer_id: Format "CUST-######" (e.g., "CUST-000001")
format: "concise" for key fields, "detailed" for complete record
Returns:
Customer object with requested fields
Errors:
NOT_FOUND: Customer ID not found
INVALID_FORMAT: ID must match CUST-###### pattern
"""
```
**示例 2:糟糕的工具设计**
此示例展示了几个工具设计的反模式:
```python
def search(query):
"""Search the database."""
pass
```
**该设计的问题:**
1. **名称模糊**"search" 含义不明确——搜索什么?目的为何?
2. **缺少参数**:什么数据库?查询应采用什么格式?
3. **无返回描述**:此函数返回什么?列表?字符串?错误处理?
4. **无使用场景**:代理何时应使用此工具而非其他工具?
5. **无错误处理**:如果数据库不可用会发生什么?
**故障模式:**
- 代理可能在本应使用更具体工具时调用此工具
- 代理无法确定正确的查询格式
- 代理无法解释结果
- 代理无法从故障中恢复
## 指导原则
1. 撰写的描述应回答作用、何时使用以及返回什么
2. 使用整合来减少歧义
3. 实现响应格式选项以提高 Token 效率
4. 为代理恢复而设计错误消息
5. 建立并遵循一致的命名规范
6. 限制工具数量并使用命名空间进行组织
7. 使用实际代理交互测试工具设计
8. 根据观察到的故障模式进行迭代
9. 反思每个工具是赋能还是约束了模型
10. 优先选择原始的通用工具而非专用封装
11. 在文档质量上的投入胜过工具复杂度的堆砌
12. 构建能够从模型改进中受益的最小化架构
## 注意事项
1. **描述模糊**:像"在数据库中搜索客户信息"这样的描述留下了太多未回答的问题。应准确说明数据库、查询格式和返回形状。
2. **晦涩的参数名**:名为 `x``val``param1` 的参数迫使代理猜测含义。使用无需进一步阅读文档就能传达目的的描述性名称。
3. **缺少错误恢复指导**:以通用消息(如"发生错误")失败的工具不提供恢复信号。每个错误响应都必须告诉代理哪里出了问题以及下一步该尝试什么。
4. **跨工具命名不一致**:在一个工具中使用 `id`,在另一个工具中使用 `identifier`,在第三个工具中使用 `customer_id`,会造成混淆。在整个工具集合中标准化参数名称。
5. **MCP 命名空间冲突**:当多个 MCP 工具提供者注册了相似名称的工具(例如,两个服务器都暴露了 `search`)时,代理无法区分。始终使用全限定格式 `ServerName:tool_name`,并在添加新提供者时审计是否存在冲突。
6. **工具描述腐化**:随着底层 API 的演变——参数被添加、返回格式变化、错误码迁移——描述会变得不准确。将描述视为代码:对其进行版本管理,在 API 变更时进行审查,并对照当前行为进行测试。
7. **过度整合**:让单个工具处理过多工作流会产生庞大的参数列表,使代理难以选择正确的组合。如果某个工具需要超过 8-10 个参数或服务于根本不同的用例,则应拆分。
8. **参数爆炸**:过多的可选参数会压垮代理的决策能力。代理必须评估的每个参数都会增加认知负担。提供合理的默认值,将相关选项分组为格式预设,并将不常用的参数移入 `options` 对象。
9. **缺少错误上下文**:只说"失败"或"无效输入"而不指明哪个输入、为什么失败或有效输入是什么样的错误消息,会让代理无法自我纠正。在每条错误响应中包含无效值、预期格式以及一个具体示例。
## 集成
本技能负责工具接口层。相邻决策由其他技能负责:
- `project-development`:项目形态、流水线阶段的选择、任务-模型匹配度、项目级别的成本估算。如果问题是"什么样的流水线架构是正确的"而不是"什么样的工具 API 是正确的",请转至该技能。
- `multi-agent-patterns`:决定一个带更多工具的代理是否优于两个带较小工具目录的代理。如果问题是"是否应拆分为子代理",请转至该技能。
- `context-optimization`:轨迹级别的 Token 效率、观察掩码、在大量工具调用中选择响应格式选项。如果问题是"如何降低累积工具输出的 Token 权重",请转至该技能。
- `context-fundamentals`:关于工具定义如何消耗注意力预算的概念性问题。如果问题是"为什么添加工具会降低路由准确率",请从该技能开始。
- `evaluation`:判断工具集是否在整体上改善了代理的结果。
## 参考文献
内部参考:
- [最佳实践参考](./references/best_practices.md) - 阅读时机:从头设计新工具或审计现有工具集合的质量差距时
- [架构精简案例研究](./references/architectural_reduction.md) - 阅读时机:考虑移除专用工具转而使用原始工具,或评估复杂工具架构是否合理时
本集合中的相关技能:
- context-fundamentals - 工具上下文交互
- evaluation - 工具测试模式
外部资源:
- MCP(模型上下文协议)文档 - 阅读时机:为多服务器代理环境实现工具或调试工具路由故障时
- 框架工具约定 - 阅读时机:采用新代理框架,需要将工具设计原则映射到框架特定 API 时
- 面向代理的 API 设计最佳实践 - 阅读时机:将现有的人面向 API 转换为面向代理的工具接口时
- Vercel d0 代理架构案例研究 - 阅读时机:评估是否要整合工具,或寻找架构精简的生产环境证据时
---
## 技能元数据
**创建时间**2025-12-20
**最后更新**2026-05-15
**作者**:面向上下文工程的代理技能贡献者
**版本**2.2.0
+205
View File
@@ -0,0 +1,205 @@
# 架构精简:生产环境实证
本文档为智能体工具设计中的架构精简方法提供了详细的实证依据和实现模式。
## 案例研究:Text-to-SQL 智能体
一个生产环境的 Text-to-SQL 智能体按照架构精简原则进行了重构。原始架构使用专门的工具,配合大量提示工程和精细的上下文管理。精简后的架构仅使用一个 bash 命令执行工具。
### 原始架构(大量专门工具)
原始系统包含:
- GetEntityJoins:查找实体之间的关系
- LoadCatalog:加载数据目录信息
- RecallContext:检索之前的上下文
- LoadEntityDetails:获取实体规格
- SearchCatalog:搜索数据目录
- ClarifyIntent:澄清用户意图
- SearchSchema:搜索数据库模式
- GenerateAnalysisPlan:创建查询计划
- FinalizeQueryPlan:完成查询计划
- FinalizeNoData:处理无数据的情况
- JoinPathFinder:查找连接路径
- SyntaxValidator:验证 SQL 语法
- FinalizeBuild:完成查询构建
- ExecuteSQL:运行 SQL 查询
- FormatResults:格式化查询结果
- VisualizeData:创建可视化图表
- ExplainResults:解释查询结果
每个工具都解决了一个团队预期模型会遇到的特定问题。其假设是模型会在复杂模式中迷失方向、做出糟糕的连接操作,或者幻觉出错误的表名。
### 精简架构(两个原始工具)
精简后的系统包含:
- ExecuteCommand:在沙箱中运行任意 bash 命令
- ExecuteSQL:针对数据库运行 SQL 查询
智能体使用标准的 Unix 工具来探索语义层:
```python
from vercel_sandbox import Sandbox
sandbox = Sandbox.create()
await sandbox.write_files(semantic_layer_files)
def execute_command(command: str):
"""Execute arbitrary bash command in sandbox."""
result = sandbox.exec(command)
return {
"stdout": result.stdout,
"stderr": result.stderr,
"exit_code": result.exit_code
}
```
智能体现在使用 `grep``cat``find``ls` 来浏览包含维度定义、度量计算和连接关系的 YAML、Markdown 和 JSON 文件。
### 对比结果
| 指标 | 原始架构(17 个工具) | 精简架构(2 个工具) | 变化 |
|--------|---------------------|-------------------|--------|
| 平均执行时间 | 274.8 秒 | 77.4 秒 | 快 3.5 倍 |
| 成功率 | 80%4/5 | 100%5/5 | +20% |
| 平均 Token 用量 | ~102k tokens | ~61k tokens | 减少 37% |
| 平均步骤数 | ~12 步 | ~7 步 | 减少 42% |
原始架构中最差的情况:724 秒、100 步、145,463 个 token,并且执行失败。精简架构成功完成了相同的查询,仅用时 141 秒、19 步和 67,483 个 token。
## 为什么精简有效
### 文件系统是强大的抽象
文件系统经过了 50 多年的持续改进。像 `grep` 这样的标准 Unix 工具文档齐全、行为可预测,且模型能够理解。为 Unix 已经解决的问题构建自定义工具,只会增加复杂性而没有价值。
### 工具在限制推理能力
专门的工具正在解决模型本可以自行处理的问题:
- 预过滤模型本可以自行浏览的上下文
- 约束模型本可以自行评估的选项
- 用模型不需要的验证逻辑包装交互
每一道护栏都成了维护负担。每次模型更新都需要重新校准约束条件。团队花在维护脚手架上的时间比改进智能体还要多。
### 良好的文档取代工具的复杂性
语义层本身已有完善的文档:
- 结构化 YAML 中的维度定义
- 命名清晰的度量计算
- 可浏览文件中的连接关系
自定义工具只是对原本就清晰可读的内容进行了摘要。模型需要的是直接读取文档的权限,而不是构建在文档之上的抽象层。
## 实现模式
### 文件系统智能体
```python
from ai import ToolLoopAgent, tool
from sandbox import Sandbox
# Create sandboxed environment with your data layer
sandbox = Sandbox.create()
await sandbox.write_files(data_layer_files)
# Single primitive tool
def create_execute_tool(sandbox):
return tool(
name="execute_command",
description="""
Execute a bash command in the sandbox environment.
Use standard Unix tools to explore and understand the data layer:
- ls: List directory contents
- cat: Read file contents
- grep: Search for patterns
- find: Locate files
The sandbox contains the semantic layer documentation:
- /data/entities/*.yaml: Entity definitions
- /data/measures/*.yaml: Measure calculations
- /data/joins/*.yaml: Join relationships
- /docs/*.md: Additional documentation
""",
execute=lambda command: sandbox.exec(command)
)
# Minimal agent
agent = ToolLoopAgent(
model="claude-opus-4.5",
tools={
"execute_command": create_execute_tool(sandbox),
"execute_sql": sql_tool,
}
)
```
### 成功的前提条件
这种模式在以下条件满足时有效:
1. **文档质量高**:文件结构良好、命名一致,且包含清晰的定义。
2. **模型能力足够**:模型能够独立推理复杂问题,无需手把手指导。
3. **安全约束允许**:沙箱限制了智能体可以访问和修改的内容。
4. **领域可浏览**:问题空间可以通过文件检查来探索。
### 何时不宜使用
在以下情况中,精简会失败:
1. **数据层混乱**:遗留的命名惯例、未记录的连接、不一致的结构。模型会更快地生成糟糕的查询。
2. **需要专业知识**:无法在文件中记录的领域专业知识。
3. **安全需要限制**:出于安全或合规原因必须加以约束的操作。
4. **工作流确实复杂**:受益于结构化编排的多步骤流程。
## 设计原则
### 减法即加法
最好的智能体可能正是工具最少的那些。每个工具都是为模型做出的一个选择。有时,模型在面对原始能力而不是受限工作流时,能做出更好的选择。
### 信任模型的推理能力
现代模型能够处理复杂性。因为不信任模型的推理能力而限制其推理,往往适得其反。在构建护栏之前,先测试模型实际能做到什么。
### 投资上下文,而非工具
基础比精巧的工具更重要:
- 清晰的文件命名规范
- 结构良好的文档
- 一致的数据组织方式
- 可读的关系定义
### 为未来的模型而构建
模型改进的速度比工具更新更快。针对今天模型局限性优化的架构,对于明天模型的能力来说可能过度受限。构建那些能从模型进步中受益的最小化架构。
## 评估框架
在考虑架构精简时,请评估:
1. **维护开销**:维护工具与改进结果相比,哪个占用了更多时间?
2. **失败分析**:失败是由模型局限引起的,还是由工具约束引起的?
3. **文档质量**:如果授予直接访问权限,模型能否直接浏览你的数据层?
4. **约束必要性**:护栏是在防范真实风险,还是出于假设性担忧?
5. **模型能力**:自从工具设计以来,模型是否已经有所改进?
## 结论
架构精简并非普遍适用,但这一原则挑战了一个常见的假设:即更复杂的工具会带来更好的结果。有时恰恰相反。从最简单的架构开始,只在确有必要时才增加复杂性,并持续追问:工具是在赋能模型,还是在限制模型的能力。
## 参考来源
- Vercel Engineering"We removed 80% of our agent's tools"2025 年 12 月)
- AI SDK ToolLoopAgent 文档
- Vercel Sandbox 文档
+175
View File
@@ -0,0 +1,175 @@
# Tool Design Best Practices(工具设计最佳实践)
本文档为设计面向智能体系统的工具提供了额外的实践与指导准则。
## 工具哲学
工具是智能体与世界之间的主要接口。与传统 API 面向理解底层系统的开发者不同,工具必须面向语言模型进行设计——语言模型通过描述推断意图,并根据自然语言请求生成调用。这一根本差异要求我们重新思考如何设计和记录工具接口。
目标是创建能够让智能体无需大量试错即可发现、理解并正确使用的工具。工具定义中的每个歧义都会成为一个潜在的故障模式。每个不明确的参数名都会迫使智能体去猜测。每个缺失的示例都会让智能体在面对边界情况时失去指引。
## 描述工程原则
### 原则一:回答基本问题
每个工具描述都应清晰回答四个问题。工具做什么?以具体术语准确说明工具完成什么功能,避免使用「有助于」或「可用于」等模糊用语。何时使用?提供具体的触发场景和上下文,包括直接触发条件和指示工具适用性的间接信号。接受哪些输入?用类型、约束和默认值来记录参数,说明每个参数控制什么。返回什么?描述输出格式和结构,包括成功响应和错误条件的示例。
### 原则二:使用一致的结构
在代码库中所有工具描述之间保持结构一致。当智能体遇到一个新工具时,它应能根据从其他工具学到的模式,预测在哪里找到特定信息。这可以减少认知开销,并防止因格式不一致导致的错误。
推荐的结构包括:首句中包含简要描述、带有使用上下文的详细说明、带有清晰类型信息的参数节、描述输出结构的返回值节,以及列出可能故障模式及恢复指导的错误节。
### 原则三:包含具体示例
示例弥合了抽象描述与实际使用之间的差距。包含展示常见参数组合的典型调用示例、边界情况及其处理方法的示例,以及错误响应及相应恢复操作的示例。
好的示例具体而非泛化。不要使用「使用类似 '123' 的 ID」,而应使用「格式:'CUST-######'(例如:'CUST-000001')」。不要使用「提供一个日期」,而应使用「格式:'YYYY-MM-DD'(例如:'2024-01-15')」。
## 命名约定
### 参数命名
参数名称应能自说明。使用能够清晰指示用途而无需额外解释的名称。除非是「id」或「url」等广泛理解的缩写,否则优先使用完整单词而非缩写。在不同工具之间对相似概念使用一致的命名。
好的参数名包括:customer_id、search_query、output_format、max_results、include_details。差的参数名包括:x、val、param1、info。
### 枚举值
当参数接受枚举值时,在所有工具之间使用一致的命名。对于布尔型选项,使用前缀模式,如肯定选项使用「include_」(include_history、include_metadata),否定选项使用「exclude_」(exclude_archived、exclude_inactive)。对于类别值,使用一致的术语,如「format」: "concise" | "detailed",而非在某些工具中混用「format」: "short" | "long",在另一些工具中使用「format」: "brief" | "complete"。
## 错误消息设计
### 双重受众
错误消息面向具有不同需求的两类受众。调试问题的开发者需要详细的技术信息,包括堆栈跟踪和内部状态。从故障中恢复的智能体需要可操作的指导,告知其出了什么问题以及如何纠正。
以智能体恢复为首要考虑来设计错误消息。用清晰的语言说明具体出了什么问题。提供描述智能体下一步应做什么的解决指导。对输入错误包含纠正后的格式。添加有效输入的示例。
### 错误消息结构
```json
{
"error": {
"code": "INVALID_CUSTOMER_ID",
"category": "validation",
"message": "客户 ID 'CUST-123' 不符合要求的格式",
"expected_format": {
"description": "客户 ID 必须为 9 个字符",
"pattern": "CUST-######",
"example": "CUST-000001"
},
"resolution": "提供符合模式 CUST-###### 的客户 ID",
"retryable": true
}
}
```
### 常见错误模式
验证错误应说明收到了什么、期望什么格式、以及如何纠正。速率限制错误应说明等待时间和重试指导。未找到错误应建议替代方案或验证步骤。系统错误应说明重试是否合适并建议替代方案。
## 响应格式优化
### 令牌与准确性的权衡
详细响应提供全面的信息但消耗大量上下文令牌。简洁响应最大限度地减少令牌使用但可能缺乏必要的细节。最佳方式是提供格式选项,让智能体能够根据需要请求合适的详细程度。
### 格式选项模式
```python
def get_customer_response(format: str = "concise"):
"""
检索客户信息。
Args:
format: 响应格式 - 'concise' 仅返回关键字段,
'detailed' 返回完整客户记录
"""
if format == "concise":
return {
"id": customer.id,
"name": customer.name,
"status": customer.status
}
else: # detailed
return {
"id": customer.id,
"name": customer.name,
"email": customer.email,
"phone": customer.phone,
"address": customer.address,
"status": customer.status,
"created_at": customer.created_at,
"history": customer.history,
"preferences": customer.preferences
}
```
### 何时使用每种格式
在快速验证或简单查询、仅需确认、或在初始检索后的后续工具调用中使用简洁格式。在基于客户数据做决策时、当输出成为其他处理的输入时、以及当完整上下文对正确性至关重要时使用详细格式。
## 工具集设计
### 管理工具膨胀
随着智能体系统的发展,工具集往往会不断膨胀。更多工具可以支持更多功能,但也带来了选择上的挑战。研究表明,工具描述的重叠会导致模型混淆。关键在于,如果人类工程师都无法明确判断在特定情况下应该使用哪个工具,那么智能体也不可能做得更好。
### 整合指南
将属于同一工作流中连续步骤的工具整合为一个处理整个工作流的工具。例如,不要使用 list_users、list_events 和 create_event,而是实现一个 schedule_event,在一次调用中查找可用性并完成调度。
将行为根本不同的工具保持分离,即使它们共享某些功能。用于不同上下文的工具应保持分离以防止混淆。
即使在相似领域操作的工具之间也要保持清晰的边界。通过精心设计将功能重叠降至最低。
### 工具选择指导
在设计工具集时,考虑智能体需要哪些信息来做出正确选择。如果多个工具可能适用于同一场景,在描述中说明区别。使用命名空间来创建逻辑分组,帮助智能体在工具空间中导航。
## 测试工具设计
### 评估标准
根据清晰度、完整性、可恢复性、效率和一致性标准来评估工具设计。清晰度衡量智能体是否能确定何时使用该工具。完整性衡量描述是否包含所有必要信息。可恢复性衡量智能体是否能从错误中恢复。效率衡量工具是否支持适当的响应格式。一致性衡量工具是否遵循命名和模式约定。
### 智能体测试模式
通过呈现有代表性的智能体请求并评估生成的工具调用来测试工具:
1. 准备包含多种智能体请求的测试用例
2. 让智能体为每个请求制定工具调用
3. 根据预期模式评估调用正确性
4. 识别常见故障模式
5. 根据发现结果完善工具定义
## 应避免的反模式
### 模糊描述
不好的:「搜索数据库以获取客户信息。」这留下了太多未解答的问题。什么数据库?哪些信息可用?查询应采用什么格式?
好的:「通过 ID 或邮箱检索客户信息。在用户询问特定客户详情、历史或状态时使用。返回包含 id、name、email、account_status 及可选订单历史的客户对象。」
### 隐晦的参数名
不好的:名为 x、val 或 param1 的参数迫使智能体猜测含义。
好的:名为 customer_id、max_results 或 include_history 的参数能自说明。
### 缺少错误处理
不好的:工具在失败时只返回通用错误或没有任何错误处理。
好的:工具提供具体的错误类型、消息和解决指导。
### 不一致的命名
不好的:某些工具中使用 id,另一些使用 identifier,某些工具中使用 customer_id,另一些使用 user_id,指代相似概念。
好的:在所有工具中对相似概念保持一致的命名模式。
## 工具设计清单
在部署新工具之前,验证描述是否清晰说明了工具的功能及使用时机。验证所有参数是否具有描述性名称和清晰的类型信息。验证返回值是否附带结构和示例文档。验证错误情况是否包含可操作的消息。验证工具是否遵循其他位置使用的命名约定。验证示例是否展示了常见使用模式。验证当响应大小变化显著时是否提供了格式选项。
+528
View File
@@ -0,0 +1,528 @@
"""
Tool Description Engineering -- Generation and Evaluation Utilities.
Use when: building, auditing, or iterating on tool descriptions for agent
systems. Provides templates for structured descriptions, a scoring evaluator
that flags vague or incomplete descriptions, error-message generators that
produce agent-recoverable responses, and a builder that assembles complete
tool schemas.
Typical workflow:
1. Define a tool spec with ``ToolSchemaBuilder``.
2. Generate a rendered description with ``generate_tool_description``.
3. Score the description with ``ToolDescriptionEvaluator.evaluate``.
4. Generate error templates with ``ErrorMessageGenerator.generate``.
Example::
builder = ToolSchemaBuilder("get_customer")
builder.set_description("Retrieve customer record", "Full details...")
builder.add_parameter("customer_id", "string", "CUST-######", required=True)
schema = builder.build()
desc = generate_tool_description(schema)
scores = ToolDescriptionEvaluator().evaluate(desc, schema)
"""
from __future__ import annotations
from dataclasses import dataclass, field
from typing import Any, Dict, List, Optional, Protocol, Sequence
import json
import re
__all__ = [
"generate_tool_description",
"generate_usage_context",
"ToolDescriptionEvaluator",
"ErrorMessageGenerator",
"ToolSchemaBuilder",
]
# ---------------------------------------------------------------------------
# Protocols -- lightweight structural typing for tool specs
# ---------------------------------------------------------------------------
class ToolSpec(Protocol):
"""Structural interface expected by generation helpers.
Use when: passing tool metadata objects that were not built with
``ToolSchemaBuilder`` (e.g., third-party specs).
"""
name: str
description: str
triggers: Sequence[str]
examples: Sequence[Any]
parameters: Sequence[Dict[str, Any]]
returns: Dict[str, Any]
errors: Sequence[Dict[str, Any]]
@dataclass
class _BuiltToolSpec:
"""Concrete implementation of ToolSpec returned by ToolSchemaBuilder.build()."""
name: str
description: str
triggers: List[str]
examples: List[Dict[str, str]]
parameters: List[Dict[str, Any]]
returns: Dict[str, Any]
errors: List[Dict[str, Any]]
# ---------------------------------------------------------------------------
# Description Templates
# ---------------------------------------------------------------------------
TOOL_DESCRIPTION_TEMPLATE: str = """
## {tool_name}
{detailed_description}
### When to Use
{usage_context}
### Parameters
{parameters_description}
### Returns
{returns_description}
### Errors
{errors_description}
"""
PARAM_TEMPLATE: str = """
- **{param_name}** ({param_type}{required_label})
{param_description}
{default_label}
"""
# ---------------------------------------------------------------------------
# Generation helpers
# ---------------------------------------------------------------------------
def generate_tool_description(tool_spec: ToolSpec) -> str:
"""Render a complete markdown tool description from *tool_spec*.
Use when: producing human-readable or agent-injectable documentation
from a structured spec object.
"""
description: str = TOOL_DESCRIPTION_TEMPLATE.format(
tool_name=tool_spec.name,
detailed_description=tool_spec.description,
usage_context=generate_usage_context(tool_spec),
parameters_description=_generate_parameters(tool_spec.parameters),
returns_description=_generate_returns(tool_spec.returns),
errors_description=_generate_errors(tool_spec.errors),
)
return description
def generate_usage_context(tool_spec: ToolSpec) -> str:
"""Build the 'When to Use' section from triggers and examples.
Use when: the caller needs only the usage-context fragment rather
than the full rendered description.
"""
contexts: list[str] = []
for trigger in tool_spec.triggers:
contexts.append(f"- When {trigger}")
if tool_spec.examples:
contexts.append("\n**Examples**:\n")
for example in tool_spec.examples:
if isinstance(example, dict):
contexts.append(f"- Input: {example.get('input', '')}")
contexts.append(f" Output: {example.get('tool_call', '')}")
else:
contexts.append(f"- {example}")
return "\n".join(contexts)
def _generate_parameters(parameters: Sequence[Dict[str, Any]]) -> str:
"""Render parameter list to markdown."""
parts: list[str] = []
for p in parameters:
required_label = " | required" if p.get("required") else " | optional"
default = p.get("default")
default_label = f"Default: {default}" if default is not None else ""
parts.append(
f"- **{p['name']}** ({p['type']}{required_label})\n"
f" {p['description']}\n"
f" {default_label}".rstrip()
)
return "\n".join(parts)
def _generate_returns(returns: Optional[Dict[str, Any]]) -> str:
"""Render the returns section to markdown."""
if not returns:
return "No return value documented."
desc = returns.get("description", "")
rtype = returns.get("type", "object")
return f"{rtype} -- {desc}"
def _generate_errors(errors: Sequence[Dict[str, Any]]) -> str:
"""Render error definitions to markdown."""
if not errors:
return "No error conditions documented."
parts: list[str] = []
for err in errors:
parts.append(f"- **{err['code']}**: {err['description']} -- {err.get('resolution', '')}")
return "\n".join(parts)
# ---------------------------------------------------------------------------
# Evaluator
# ---------------------------------------------------------------------------
class ToolDescriptionEvaluator:
"""Score a rendered description against quality criteria.
Use when: auditing existing tool descriptions for clarity,
completeness, accuracy, actionability, and consistency.
"""
CRITERIA: List[str] = [
"clarity",
"completeness",
"accuracy",
"actionability",
"consistency",
]
def evaluate(self, description: str, tool_spec: ToolSpec) -> Dict[str, float]:
"""Return per-criterion scores (0.0 -- 1.0) for *description*.
Use when: running automated quality checks on tool descriptions
before deploying them into an agent system.
"""
results: Dict[str, float] = {
"clarity": self._check_clarity(description),
"completeness": self._check_completeness(description, tool_spec),
"accuracy": self._check_accuracy(description, tool_spec),
"actionability": self._check_actionability(description),
"consistency": self._check_consistency(description, tool_spec),
}
return results
# -- private scoring helpers ------------------------------------------
def _check_clarity(self, description: str) -> float:
"""Score description clarity (0-1).
Use when: detecting vague or ambiguous language that would
confuse an agent during tool selection.
"""
vague_terms: list[str] = ["help", "assist", "thing", "stuff", "handle"]
vague_count: int = sum(1 for term in vague_terms if term in description.lower())
ambiguous: list[str] = ["it", "this", "that"]
ambiguous_count: int = sum(1 for term in ambiguous if f" {term} " in description)
clarity: float = 1.0 - (vague_count * 0.1) - (ambiguous_count * 0.05)
return max(0.0, clarity)
def _check_completeness(self, description: str, tool_spec: ToolSpec) -> float:
"""Score presence of required sections (0-1).
Use when: verifying a description has all mandatory sections
before publishing.
"""
required_patterns: list[tuple[str, str]] = [
("description", r"## " + re.escape(str(getattr(tool_spec, "name", "")))),
("parameters", r"### Parameters"),
("returns", r"### Returns"),
("errors", r"### Errors"),
]
present: int = sum(
1 for _, pattern in required_patterns if re.search(pattern, description)
)
return present / len(required_patterns)
def _check_accuracy(self, description: str, tool_spec: ToolSpec) -> float:
"""Score alignment between description text and spec metadata.
Use when: detecting description rot where the text no longer
matches the current tool spec.
"""
score = 1.0
# Check that tool name appears in description
if hasattr(tool_spec, "name") and tool_spec.name not in description:
score -= 0.3
# Check parameter names appear
if hasattr(tool_spec, "parameters"):
for param in tool_spec.parameters:
pname = param.get("name", "") if isinstance(param, dict) else ""
if pname and pname not in description:
score -= 0.15
return max(0.0, score)
def _check_actionability(self, description: str) -> float:
"""Score whether the description contains actionable cues.
Use when: confirming agents can determine correct usage from
the description alone.
"""
signals: list[str] = ["Use when", "Returns", "Errors", "Args", "Parameters"]
found: int = sum(1 for s in signals if s in description)
return min(1.0, found / max(1, len(signals)))
def _check_consistency(self, description: str, tool_spec: ToolSpec) -> float:
"""Score naming and formatting consistency.
Use when: checking that parameter and section naming follows
conventions across the tool collection.
"""
# Penalise mixed naming styles (camelCase vs snake_case)
camel = len(re.findall(r"[a-z][A-Z]", description))
snake = len(re.findall(r"[a-z]_[a-z]", description))
if camel > 0 and snake > 0:
return 0.5
return 1.0
# ---------------------------------------------------------------------------
# Error Message Generator
# ---------------------------------------------------------------------------
class ErrorMessageGenerator:
"""Produce structured, agent-recoverable error messages.
Use when: building error responses that tell agents what went wrong,
why, and how to correct the call.
"""
TEMPLATES: Dict[str, str] = {
"NOT_FOUND": json.dumps({
"error": "{error_code}",
"message": "{specific_message}",
"resolution": "{how_to_resolve}",
"example": "{correct_format}",
}, indent=2),
"INVALID_INPUT": json.dumps({
"error": "{error_code}",
"message": "Invalid {field}: {received_value}",
"expected_format": "{expected_format}",
"resolution": "Provide value matching {expected_format}",
}, indent=2),
"RATE_LIMITED": json.dumps({
"error": "{error_code}",
"message": "Rate limit exceeded",
"retry_after": "{seconds}",
"resolution": "Wait {seconds} seconds before retrying",
}, indent=2),
}
def generate(self, error_type: str, context: Dict[str, str]) -> str:
"""Render an error message for *error_type* using *context* values.
Use when: a tool needs to return a structured error that an agent
can parse and act on.
"""
template: str = self.TEMPLATES.get(error_type, self.TEMPLATES["INVALID_INPUT"])
return template.format(**context)
# ---------------------------------------------------------------------------
# Schema Builder
# ---------------------------------------------------------------------------
class ToolSchemaBuilder:
"""Fluent builder for complete tool schemas.
Use when: defining a new tool's schema programmatically and want
compile-time structure rather than hand-written dictionaries.
"""
def __init__(self, name: str) -> None:
self.name: str = name
self.description: str = ""
self.detailed_description: str = ""
self.parameters: List[Dict[str, Any]] = []
self.returns: Optional[Dict[str, Any]] = None
self.errors: List[Dict[str, str]] = []
self._triggers: List[str] = []
self._examples: List[Dict[str, str]] = []
def set_description(self, short: str, detailed: str) -> "ToolSchemaBuilder":
"""Set short and detailed description sections.
Use when: providing both a one-line summary and a full
multi-paragraph description for the tool.
"""
self.description = short
self.detailed_description = detailed
return self
def add_parameter(
self,
name: str,
param_type: str,
description: str,
required: bool = False,
default: Optional[Any] = None,
enum: Optional[List[str]] = None,
) -> "ToolSchemaBuilder":
"""Append a parameter definition.
Use when: declaring each accepted input for the tool.
"""
self.parameters.append({
"name": name,
"type": param_type,
"description": description,
"required": required,
"default": default,
"enum": enum,
})
return self
def set_returns(
self,
return_type: str,
description: str,
properties: Dict[str, Any],
) -> "ToolSchemaBuilder":
"""Define the return value schema.
Use when: documenting what the tool sends back on success.
"""
self.returns = {
"type": return_type,
"description": description,
"properties": properties,
}
return self
def add_error(
self,
code: str,
description: str,
resolution: str,
) -> "ToolSchemaBuilder":
"""Register an error condition with recovery guidance.
Use when: enumerating known failure modes so agents can
handle them gracefully.
"""
self.errors.append({
"code": code,
"description": description,
"resolution": resolution,
})
return self
def build(self) -> "_BuiltToolSpec":
"""Assemble and return the complete tool spec.
Use when: the builder is fully configured and the schema is
ready for registration, serialization, or passing to
``generate_tool_description``.
Returns a ``_BuiltToolSpec`` object that satisfies the ``ToolSpec``
protocol, so it can be used directly with ``generate_tool_description``
and ``ToolDescriptionEvaluator``.
"""
return _BuiltToolSpec(
name=self.name,
description=self.detailed_description or self.description,
triggers=self._triggers,
examples=self._examples,
parameters=list(self.parameters),
returns=self.returns or {},
errors=list(self.errors),
)
def add_trigger(self, trigger: str) -> "ToolSchemaBuilder":
"""Add an activation trigger for the tool.
Use when: documenting when agents should select this tool.
"""
self._triggers.append(trigger)
return self
def add_example(
self, input_text: str, tool_call: str
) -> "ToolSchemaBuilder":
"""Add a usage example.
Use when: providing concrete input/output pairs that help agents
understand expected usage.
"""
self._examples.append({"input": input_text, "tool_call": tool_call})
return self
# ---------------------------------------------------------------------------
# CLI entry point
# ---------------------------------------------------------------------------
if __name__ == "__main__":
# Quick demo: build a schema, render it, and evaluate it.
builder = ToolSchemaBuilder("get_customer")
builder.set_description(
"Retrieve customer record by ID",
"Fetches a customer object from the primary datastore. "
"Supports concise and detailed response formats.",
)
builder.add_parameter(
"customer_id", "string",
'Customer identifier in CUST-###### format (e.g., "CUST-000001")',
required=True,
)
builder.add_parameter(
"format", "string",
'"concise" for key fields, "detailed" for complete record',
required=False,
default="concise",
enum=["concise", "detailed"],
)
builder.set_returns(
"object",
"Customer object with requested fields",
{"id": {"type": "string"}, "name": {"type": "string"}},
)
builder.add_error("NOT_FOUND", "Customer ID not in datastore", "Verify ID format and retry")
builder.add_error("INVALID_FORMAT", "ID does not match CUST-######", "Use CUST-###### pattern")
spec = builder.build()
print("=== Built Spec ===")
print(f"Name: {spec.name}")
print(f"Parameters: {[p['name'] for p in spec.parameters]}")
print(f"Errors: {[e['code'] for e in spec.errors]}")
# Generate and evaluate description
description = generate_tool_description(spec)
print("\n=== Generated Description ===")
print(description)
evaluator = ToolDescriptionEvaluator()
scores = evaluator.evaluate(description, spec)
print("\n=== Evaluation Scores ===")
for criterion, score in scores.items():
print(f" {criterion}: {score:.2f}")
# Generate an error message example
err_gen = ErrorMessageGenerator()
err_msg = err_gen.generate("NOT_FOUND", {
"error_code": "NOT_FOUND",
"specific_message": "No customer with ID CUST-999999",
"how_to_resolve": "Check ID and retry",
"correct_format": "CUST-######",
})
print("\n=== Sample Error Message ===")
print(err_msg)