演示里的多智能体总是停在最精彩的地方:主管节点负责路由,工作节点调用工具,工具"做了点什么"。演示环境里这个工具是 lookup_order,到了生产环境就变成 send_email 或者 issue_refund。问题随之而来——谁有权按下这个按钮,客户数据在流转途中又被谁看见了。
围绕工具调用的智能体,有三道护栏值得默认装上:默认拒绝的工具策略、会暂停运行的审批闸门、以及不破坏数据的 PII 脱敏。这三件事都能在没有 API key 的情况下测起来。
策略:默认拒绝,且不由工具自己决定
第一条规则是权限住在工具之外,也住在提示词之外。一个有 bug 或被注入的提示词,不能自己给自己扩权。
策略评估的逻辑很直接:工具不在注册表里,拒绝;工具不在该租户的允许清单里,拒绝。一个 allowed_tools 为空的租户什么都调不了——忘记填的允许清单会安全失败,而忘记补的拒绝清单会开放失败。
审批则是两个来源的"或":工具作者可以给有副作用的工具标上 requires_approval=True,每个租户也能往自己的 approval_tools 里追加。注册一个受控工具只需要一个装饰器,比如给 send_email 打上需要审批的标记。
审批闸门:暂停、持久化、恢复
"问一下人"听起来简单,直到你意识到这次运行必须停下来、扛过一次进程重启、再从正确的位置继续。这里有两个设计决定。
第一,把闸门做成独立的图节点。中断式的暂停在恢复时会从被中断节点的第一行重新执行;如果暂停逻辑藏在工作节点内部,工作节点那次 LLM 调用就要付两遍钱。单独的 tool_gate 节点意味着被重放的只是一次廉价策略检查。
第二,状态保持纯 JSON,并在每个节点之后做快照。运行时自带一个纯 Python 执行器,遵循与 LangGraph 相同的 interrupt() 契约:遇到受控工具就抛出暂停,把状态、下一节点和中断信息写进检查点,然后返回审批请求。恢复时加载快照,带着人的决定重新进入那个节点。LangGraph 和 Postgres 都是可选项,默认安装用内置执行器配 SQLite。
从调用方看,流程是:启动运行,事件流最后一个事件就是暂停的审批请求;带着 approved=True 恢复,继续消费事件。走 HTTP 则是向审批接口提交 {"approved": true}。暂停载荷里带着运行 id、工具名、参数和原因,审批人能看到自己到底在批什么。
PII 脱敏:别把订单号一起吃掉
朴素做法是用正则匹配"13 到 19 位数字"来抓卡号。它同样会脱掉订单 ID、时间戳和物流单号,智能体直接变废。正确做法是校验匹配:卡号过 Luhn 校验,IBAN 过结构和校验位检查,通过才脱敏。
脱敏后原文被替换成占位符,比如把邮箱换成 [EMAIL_1],同时保留一份映射表。映射表才是敏感的部分,因为它装着原始值,要当作静态数据对待。还原只能在信任边界内做。
脱敏也不是保证:基于正则的检测器会漏东西,比如人名和街道地址。它是一层防护,不是合规声明。
用黄金用例测护栏
没测过的护栏不可信。把用例声明出来,对运行的最终状态和输出做断言:
- 发邮件任务,租户只允许 send_email,期望状态为等待审批;
- 查询订单任务,期望完成且输出不含"已发货";
- 提示词注入任务,期望被拦截;
- 含邮箱的任务,期望完成且输出不含该邮箱;
- 令牌预算设为 5 的任务,期望因超预算失败。
因为运行时接受确定性的 mock LLM,这些用例能在 CI 里跑,不需要网络也不需要密钥。一次悄悄放行未审批邮件的提示词改动,会让构建变红,而不是让客户生气。
顺序比工具本身更值得记住:默认拒绝,副作用之前先暂停,模型看到之前先脱敏,事故之前先写测试。
特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.