【学习笔记】工具设计,Agent 的手比大脑更容易出问题-8/16
上一篇我们讲了 Harness Engineering。模型之外的一切,决定 Agent 能不能从“会回答”变成“能行动”。这一篇,我们拆 Harness 里最具体的一层:
Tools工具看起来很简单:给模型几个函数,让它能查数据库、读文件、跑命令、发请求。
然后模型自己决定怎么用。
很多团队一开始都是这么做的。结果很快会遇到这些问题:
Agent 总是选错工具。 工具参数填得乱七八糟。 同一个动作绕了三步。 工具返回一大坨 JSON,模型看不懂重点。 错误信息只写 failed,Agent 不知道怎么恢复。 工具权限太大,一次调用就能产生危险副作用。这时候继续换模型,可能会有一点改善。
但更大的问题在工具本身,工具不是 API wrapper,工具是 Agent 的行动接口。
一、工具决定 Agent 能做什么
模型再聪明,也不能直接操作世界。它要通过工具行动。
对代码 Agent 来说,工具可能是:
read_file write_file apply_patch run_tests search_code git_diff browser_open database_query deploy_preview对业务 Agent 来说,工具可能是:
search_orders refund_payment create_ticket send_email query_user_profile update_subscription工具一旦接入,Agent 的能力边界就变了。它不再只是生成文本。它开始产生副作用。
所以工具设计不是小细节。
它决定了:
Agent 能不能选对动作。 能不能填对参数。 能不能理解结果。 能不能从错误中恢复。 能不能在权限边界内完成任务。二、好工具的五个标准
我建议先用五个标准评估工具。
名称清晰 描述准确 参数少而强 输出可读可验证 错误信息可恢复2.1 名称清晰
工具名不是给人看的,主要是给模型看的。如果工具名含糊,模型会猜。
比如:
handle_user process_data do_action run这些名字对模型不友好。它们没有告诉模型工具到底做什么。
更好的名字应该接近动作和对象:
search_customer_orders create_refund_request read_repository_file run_unit_tests get_pull_request_diff不要怕名字长,工具名不是 shell 命令,它是行动语义。
2.2 描述准确
很多工具描述写得像 API 文档。
比如:
Call order service.这对 Agent 没什么帮助。
它需要知道:
什么时候用? 什么时候不要用? 输入代表什么? 输出里哪些字段重要? 失败时怎么办? 有没有副作用?一个更好的工具描述应该像这样:
Search customer orders by user_id or email. Use this before answering questions about order status or refund eligibility. This tool is read-only. If no orders are found, ask the user to confirm the account identifier.注意这里有几个关键信息:只读、适用场景、失败后的下一步。
这比参数列表更有用。
2.3 参数少而强
参数越多,模型越容易填错。尤其是有多个可选参数、布尔开关、嵌套对象的时候。
不要把内部 API 原样暴露给 Agent。内部 API 可能是给程序员调用的。Agent 工具应该是给模型决策用的。
比如内部接口需要:
{ "userId": "...", "includeArchived": false, "includeLineItems": true, "sortBy": "created_at", "sortOrder": "desc", "limit": 20, "offset": 0 }但 Agent 工具可以简化成:
{ "account_identifier": "...", "lookback_days": 90 }复杂性应该留在工具实现里,不要把复杂性丢给模型。
2.4 输出可读可验证
很多工具最大的问题,不是调用失败。而是调用成功以后,输出太难用。
比如返回一整段原始 JSON,字段很多,嵌套很深,状态码含义不清,时间格式混乱。
模型看到了,但不一定能可靠理解。
更好的输出应该包含:
摘要 关键字段 来源 置信度或状态 下一步建议 原始数据引用比如订单查询工具,不一定要把所有字段塞给模型。
可以返回:
{ "summary": "Found 2 orders in the last 90 days.", "orders": [ { "order_id": "A123", "status": "delivered", "refund_eligible": false, "reason": "Delivered more than 30 days ago" } ], "source": "orders_service", "next_step": "If user asks for refund, explain ineligibility policy." }这不是为了让输出变漂亮,而是为了让模型少误读。
2.5 错误信息可恢复
最差的错误信息是:
failed或者:
500 internal error这对 Agent 没用,它不知道是参数错了、权限不够、服务超时、还是资源不存在。
可恢复错误应该告诉模型:
错误类型 是否可重试 用户是否需要补信息 是否需要换工具 是否需要升级人工例如:
{ "error_type": "missing_identifier", "message": "No user_id or email was provided.", "recoverable": true, "suggested_next_step": "Ask the user for an email or account ID." }或者:
{ "error_type": "permission_denied", "recoverable": false, "suggested_next_step": "Escalate to a human operator." }这类输出会显著提高 Agent 的恢复能力。
三、原子工具 vs 通用工具
工具粒度是最难判断的设计问题之一。太原子,会让 Agent 做很多低价值编排;太通用,又会让工具语义变模糊。
比如你给 Agent 一个通用工具:
call_api(endpoint, method, payload)这看起来很强,但风险也很高。模型要自己决定 endpoint。自己构造 payload。自己理解错误。自己处理权限。
这更像把内部系统裸露给模型。
另一边,如果你给它太多原子工具:
get_user get_order get_order_items get_refund_policy get_payment_status get_shipping_statusAgent 可能要调用很多次才能完成一个简单问题。
上下文和延迟都会上升,更好的做法是按任务设计工具。
比如:
check_refund_eligibility它内部可以调用订单、支付、物流、政策服务;但对 Agent 来说,它只需要回答一个业务动作:
这个用户这笔订单能不能退款?工具粒度的原则是:
让工具接近 Agent 的决策动作,而不是内部系统的技术拆分。四、工具权限要分级
工具不只是能力,也是风险。
我建议至少分四级:
Read:只读查询 Draft:生成草稿,不产生外部副作用 Write:写入业务系统 Irreversible:不可逆或高风险动作比如:
search_orders Read draft_refund_response Draft create_refund_request Write issue_refund Irreversible不要把这些动作合成一个大工具。如果一个工具既能查询订单,又能直接退款,Agent 的风险边界就很难控制。高风险工具应该有更强约束:
必须人工确认 必须二次校验 必须写审计日志 必须有回滚或补偿方案 必须明确金额、对象和原因这不是保守,这是让 Agent 能进入真实业务系统的前提。
五、工具输出不要污染上下文
第六篇讲过,工具输出是上下文膨胀的主要来源。
工具设计时就要考虑输出层级。
可以分三层:
summary:给模型看的摘要 structured_fields:模型需要判断的关键字段 artifact_ref:原始输出的外部引用不要把所有原始日志都塞给模型。
例如测试工具可以返回:
{ "status": "failed", "failed_tests": 3, "top_failure": "AuthService should reject expired token", "artifact_ref": "artifacts/test-run-20260624.log", "suggested_next_step": "Inspect AuthService token expiry logic." }模型需要时,再读取 artifact。
这比一次性塞入几千行日志更稳。
六、工具也需要评测
很多团队会评测 prompt,但不评测工具。这是很大的盲区。同一个模型,同一个任务,只要工具定义不同,成功率就可能完全不同。
工具评测可以从三个层面做。
第一,工具选择评测。
给 Agent 一组任务,看它是否选对工具。
用户问订单状态 -> search_orders 用户要求退款 -> check_refund_eligibility 用户要求改邮箱 -> update_user_email第二,参数填充评测。
看它是否能从用户输入和上下文里抽出正确参数。
email order_id date range repository path test command第三,恢复能力评测。
故意让工具返回错误,看 Agent 能不能采取正确下一步。
missing identifier -> 追问用户 permission denied -> 升级人工 timeout -> 重试一次 not found -> 换查询条件这类评测很实用,因为它直接对应生产失败模式。
七、反模式清单
如果你正在设计工具,可以对照下面这份反模式。
1. 工具名像内部函数,不像行动语义 2. 描述只写 API 功能,不写使用场景 3. 参数照搬内部接口,过多过细 4. 输出原样返回大 JSON 或长日志 5. 错误信息不可恢复 6. 读写动作混在一个工具里 7. 高风险动作没有人工确认 8. 工具没有 artifact 引用,原始证据不可追踪 9. 工具没有版本管理 10. 没有评测工具选择和参数填充这些问题不一定会在 demo 里暴露,但会在真实用户、真实数据、真实权限里暴露。
八、一个实用设计模板
每个工具上线前,至少写清楚这些字段。
name: 工具名,动作 + 对象 purpose: 解决什么任务 when_to_use: 什么时候应该调用 when_not_to_use: 什么时候不要调用 inputs: 必填参数、可选参数、默认值 outputs: 摘要、关键字段、artifact 引用 side_effects: 是否写入系统,是否可逆 permissions: read / draft / write / irreversible failure_modes: 常见错误和恢复建议 eval_cases: 至少 5-10 个工具选择和参数填充样本这比直接写函数定义慢一点,但会让 Agent 稳很多。
九、最后
Agent 的能力,不只来自模型,也来自它能使用什么工具。一个坏工具会让强模型变笨。
一个好工具会让普通模型更稳定。
所以工具设计不是“把 API 暴露给模型”,而是把复杂系统包装成模型可理解、可调用、可验证、可恢复的行动接口。这也是 Harness Engineering 里最实际的一层。
下一篇,我们继续拆 Harness:
验证闭环。因为 Agent 说“我完成了”,并不代表它真的完成了。
参考资料:
- Anthropic: Writing Effective Tools for AI Agents
- OpenAI Agents SDK Documentation: Tools
- Model Context Protocol Documentation
- Anthropic: Building Effective Agents
- OpenAI: Practical Guide to Building Agents
参考文献:
工具设计,Agent 的手比大脑更容易出问题
