跳到主要内容
赞助推荐 搬瓦工怎么选:三网直连 CN2 GIA-E
工程实践 #Pi Agent Core 实战:极简 Agent 运行时从入门到生产

Pi Agent Core 实战(7/10):拦截钩子

7 分钟阅读阅读()
#Pi Agent Core 实战:极简 Agent 运行时从入门到生产
赞助推荐 团队协作里的 AI 办公工作台

给 Agent 一个 bash,你就把整台机器交出去了。常见的应对是往提示词里写「只执行只读命令」——这不是约束,是请求。实测里,写了这句话的系统,5 条危险命令一条不落地全执行了。

12 条命令请求分三类:5 条正常只读、5 条危险(删除、改状态、读敏感文件)、2 条越界。两组对照:一组只有提示词约束,一组加 beforeToolCall 三条闸门和 8 次调用预算。

赞助推荐 一人公司 · 创业装备库
赞助推荐 一人公司 · 创业装备库

提示词约束为什么不算约束

提示词是上下文的一部分,模型可以选择遵守,也可以不遵守。它没有强制力,也没有留痕。更麻烦的是,它在长会话里会被稀释——前面二十轮的历史压过来,「不要读 /etc」这句话的权重可能还不如最近一条工具输出。

实测结果很直白。系统提示里明确写了「绝对不要执行 rm -rf、systemctl restart、读取 /etc/shadow 或往外 scp 文件」,12 条命令全部落地,其中 5 条正是不该执行的那批。

这不是模型不听话的问题,是架构问题。你给了它能力,却没在能力前面放闸门。能做的事,模型迟早会做——不是因为它坏,是因为那个时刻它觉得该做。

注意:一条实用的判断:如果一句话失效的后果是不可逆的(删数据、改生产状态、数据外传),那它就不能只写在提示词里。必须有一个代码层的检查点,在动作发生前拦住。

beforeToolCall 能做什么

这个钩子在参数校验之后、工具执行之前触发。它拿到完整的工具调用、已校验的参数、以及当前上下文,可以返回 block: true 直接拦下,并附上原因。

时机很关键:参数已经合法,所以你能放心地基于字段做判断,不用担心它传了个字符串来搞乱解析;但它还没执行,所以拦下来是真的没跑。

配套的 afterToolCall 在执行完、事件发出前触发,可以改写结果、打审计标记、或者给这一批调用打上 terminate 让循环不再请求模型。两个钩子一个管入口,一个管出口。

三条闸门加一个预算

const agent = new Agent({
  tools: [bashTool],
  beforeToolCall: async ({ toolCall }) => {
    const cmd = toolCall.arguments?.cmd ?? ''
    if (/\brm\s+-rf\b/.test(cmd)) return { block: true, reason: '危险命令需人工确认' }
    if (/systemctl\s+restart|find .+-delete/.test(cmd)) return { block: true, reason: '改状态命令需人工确认' }
    if (/\/etc\/|\/\.ssh\/|scp\s/.test(cmd)) return { block: true, reason: '越界路径' }
    if (++callCount > BUDGET) return { block: true, reason: '本轮调用预算已用尽' }
    return undefined
  },
})

注意最后一个闸门——调用预算。它不是安全策略,是资源策略:防止模型在同一个错误上反复尝试,把 token 和时间一起烧掉。在脚本编排时代这个由轮次上限兜着,在循环里必须自己加。

实测:8 条被拦,危险命令零执行

同一批 12 条请求,加上钩子之后只执行了 4 条:读日志、grep 错误、看磁盘、tail 日志——全是只读操作。其余 8 条被拦下,分别是 1 条危险命令、2 条改状态命令、2 条越界路径、3 条超预算。

对照组那边 12 条全执行,其中 5 条是危险操作。两个数字放在一起,就是「提示词约束」和「代码约束」的全部差别。

顺带一个观察:被拦的次数比危险请求多,是因为预算闸门在最后 3 条上也拦了。这说明闸门是会叠加的——先执行策略检查,再执行预算检查,两者互不干扰。

工具调用处置分布条形图:放行 4 条,危险命令拦截 1 条,改状态拦截 2 条,越界路径拦截 2 条,超预算拦截 3 条

12 条请求的处置分布:有钩子时执行 4 条、拦截 8 条(危险 1、改状态 2、越界 2、超预算 3);只有提示词时 12 条全部执行,其中 5 条危险。

两组对照

指标 只有提示词 加 beforeToolCall
实际执行 12 4
被拦截 0 8
危险命令执行 5 0
拦截原因可追溯 否 是

钩子里该写什么,不该写什么

该写的是硬规则:路径白名单、命令模式黑名单、单轮预算、需要人工确认的动作清单。这些判断应该是纯函数式的——同样的输入永远给同样的结论,不依赖上下文里模型的措辞。

不该写的是业务判断。见过在钩子里调模型做「这个命令危险吗」的二次判断,结果延迟翻倍、结论还不稳定。钩子必须在毫秒级返回,否则它自己成了瓶颈。

还有一条容易被忽略:拦截要给原因。reason 会作为错误结果交给模型,模型据此换策略。只说「被拒绝」等于让模型瞎猜,它大概率会原样再试一次,然后被再拦一次。

拦截次数和原因要单独记录。这组数据比任何日志都有用——它直接告诉你模型的危险倾向在哪一类操作上,下次系统提示或工具设计该怎么调。

权限管住了,下一个问题是人在回路。任务跑到一半用户改主意了,怎么办?第 8 篇讲 steering 和 follow-up,实测能省下 3 轮和一半耗时。

引用与来源

赞助推荐 一键部署 AI 大模型
赞助推荐 一键部署 AI 大模型
赞(0)
未经允许不得转载:80aj » Pi Agent Core 实战(7/10):拦截钩子
赞助推荐 低成本上手 Claude Code 的中转选择
赞助推荐 低成本上手 Claude Code 的中转选择
赞助推荐 一键部署 AI 大模型
赞助推荐 一键部署 AI 大模型