给 Agent 接一个 API,用不了太久。麻烦通常出现在第二天:参数偶尔不合法,接口超时后重复扣款,网页返回了一句“忽略之前的指令”,或者一个查询订单的工具顺手拿到了改订单的权限。
这类问题很少能靠提示词解决。工具层得像正常的公共接口一样设计。
先把工具说清楚
模型需要知道工具做什么、接收哪些字段、会不会改数据、失败后返回什么。get_order 可以这样定义:
{
"name": "get_order",
"description": "按订单号读取订单当前状态,不修改订单",
"input_schema": {
"type": "object",
"required": ["order_id"],
"properties": {"order_id": {"type": "string", "pattern": "^ORD-[0-9]+$"}}
},
"side_effect": "read",
"permission": "orders:read"
}
返回值也尽量结构化。直接返回 status、updated_at、items,比写一段“订单目前正在处理中”更好用。程序可以区分空数据和接口错误,模型也少猜一次。
工具名不用追求漂亮。refund_order 比 handle_customer_request 好,因为谁都看得出前者会动钱。
权限跟着动作走
通用的“数据库工具”很省开发时间,也把权限问题放大了。更稳妥的做法是拆成几个窄动作,并由运行时重新校验当前用户、订单归属和金额上限。模型选对了工具,不代表它有权执行。
我一般按后果处理:搜索和查询可以自动跑;创建草稿、生成工单这类动作要能撤销;退款、发邮件、删除数据,走到执行前停下来等人确认。
这些规则必须写在运行时里。提示词可以提醒模型,拦不住一次错误调用。
超时后到底能不能再来一次
查询接口超时,重试一两次通常没问题。创建资源就麻烦了:第一次请求可能已经成功,只是响应丢了。此时 Agent 再调一次,系统里会多出两张工单或两笔退款。
写操作要带幂等键,同一个任务重试时沿用同一个请求 ID。错误也别只扔异常文本,至少区分 TIMEOUT、VALIDATION_ERROR、PERMISSION_DENIED 和 CONFLICT。模型拿到明确错误后,才知道该改参数、换工具,还是直接告诉用户做不了。
工具结果也可能骗人
网页、上传文件和第三方接口都属于外部输入。里面出现“忽略系统指令”时,那句话仍然只是数据。工具返回值要和系统指令分开,限制长度,保留来源;浏览器类工具还要加域名和下载大小限制。
这一点容易被忽略,因为返回内容看起来是给模型读的。但 Agent 恰好会继续根据它做动作,风险也从这里传下去。
出错时要能回放
只存最终回答,排查不了一次 Agent 任务。日志里至少要有 trace_id、每轮选择的工具、脱敏参数、耗时、结果摘要、重试次数和权限判断。
用户说“刚才查错了”,你应该能找到那次查询用了什么条件、命中了哪个版本的数据。否则只能再跑一遍,然后期待这次别错。
任务本身也要有预算:最多调几次工具,最长跑多久,失败重试几次。预算用完就返回部分结果和当前状态,不要让循环在后台悄悄跑下去。
第一版做到这些就够了
先给已有工具补输入 schema 和错误码,再把只读与写入权限分开,把一次完整调用轨迹落下来。做到这里,很多“模型不听话”会露出原形:有的是参数定义含糊,有的是权限开得太大,还有的只是接口超时没有处理。
等这些问题都看得见,再考虑给 Agent 增加更多工具。