让 Agent 真的能干活:把操作封成 webhook,而不是给它更长的提示词
很多人第一次把 Agent 接进业务,都会撞上同一堵墙:它能把方案讲得头头是道,但真要它去发一封邮件、建一张工单、改一条数据库记录,就开始原地打转——要么编造一个「已经完成」,要么把参数拼错三次。
问题很少出在模型身上。出在你只给了它嘴,没给它手。
先分清两种「工具」
给 Agent 接能力,本质上有两条路:
- 让它直接碰你的系统:把数据库账号、API 密钥塞进上下文,让它自己写 SQL、自己拼请求。灵活,但出事就是大事。
- 你先把操作封好,只给它一个入口:每个动作是一个独立的 HTTP 端点,输入有 schema,输出有结构,权限你在服务端控制。
第一种适合玩具,第二种才能上线。差别在于:第二种的每个动作都是可命名、可鉴权、可限流、可回滚的,而第一种出问题时你连日志都看不懂。
动作层长什么样
一个能被 Agent 稳定调用的动作,至少要有这四样东西:
1. 一个动词化的名字
create_refund_ticket 比 handle_request 好。模型是靠名字和描述来选工具的,含糊的命名会直接导致选错。
2. 一份输入输出 schema
别让模型自由发挥。写清楚字段、类型、必填、枚举值。返回也一样——永远返回结构化结果,哪怕出错也返回 {ok: false, reason: "..."},而不是抛一个 500。
3. 一次只做一件事
我吃过亏:一个「处理客户请求」的端点内部会判断意图、发邮件、改状态,Agent 一次误调用就群发了。拆成 send_email、update_ticket_status、create_task 三个之后,误操作的爆炸半径小了一个量级。
4. 写在服务端的权限与限额 Webhook 只认一个专用 token,能做什么在服务端写死。Agent 就算被提示注入骗了,攻击者也只能在限额内调这几个端点。
写操作要有两步
只读动作(查订单、搜文档、读报表)可以直接让 Agent 调。写动作(发钱、发信、改数据)我一律加一层确认:
- Agent 调用带
dry_run: true的动作,服务端返回「我准备做什么」的预览,不落库; - 人(或一条规则)确认后,Agent 再带同一个
request_id调一次真正执行。
request_id 在这里是关键:服务端用它做幂等,重复调用同一 id 只会执行一次。这条规则救过我两次——Agent 超时重试,如果没有幂等,客户会收到两封一样的邮件。
一个能直接抄的结构
我现在的默认做法是:n8n(或任何工作流引擎)负责把内部系统拼成动作,每个动作暴露成一个 webhook,Agent 侧只维护一份工具清单:
意图层 模型决定「该做什么」
↓ 传入结构化参数
动作层 webhook:校验 → 幂等检查 → 执行 → 返回结构化结果
↓
审计层 落一条日志:谁触发的、参数是什么、改了什么、能不能回滚
三层分开的好处是,任何一层都能单独换。模型换成更强的,动作层不动;动作层从 n8n 换成自己写的服务,模型侧也不用改。
什么时候别这么干
- 动作只有一两个、而且不会变:直接写个脚本让 Agent 调就够了,上工作流引擎是给自己找事。
- 延迟敏感:多一跳 HTTP 就多几十毫秒,实时对话里能感觉到。
- 你需要事务性:HTTP 调用之间没有事务,做不到「要么全成功要么全回滚」。这种场景老实写成一个服务端函数,让 Agent 一次调完。
最后一句
Agent 的能力上限,是被你提供的动作层的质量锁死的。与其花时间把提示词从 300 字改到 800 字,不如把三个动作的接口写清楚——前者的收益会迅速饱和,后者的收益是线性的。