作者凌晨3点被Agent的2800美元账单惊醒后,开发了AgentShield——API调用前执行的消费防火墙,基于Python标准库,<1ms延迟,7条可组合规则。
与 Jacopo(Agent-Devtools)联合撰写
凌晨 3:07,我的 agent 向一个高级 LLM 端点发起了 21 次 API 调用。每一次花费 133 美元。总耗时:60 秒。总账单:2,800 美元。
我当时在睡觉。预算告警邮件——我本来设置好防止这种情况发生的——在 4 分钟后到达。也是在我睡觉的时候。那是一封关于「钱已经没了」的信息丰富的邮件。
那一夜教会了我一件事,这件事花费了两个开源项目才彻底解决:
你需要一堵防火墙来止血。你需要一个调试器来看清它为何发生。二者缺一,都不算完整的安全系统。
在那晚之前,我按顺序尝试了标准防御措施:
预算告警邮件。它们在花费发生之后才触发。凌晨 3 点,唯一在读它们的是你的收件箱。
速率限制。它们限制的是速度,不是花费。一个 agent 可以用细水长流的方式在一小时内达到同样的账单。
人工监控。它恰好在你最需要它的时候失效——凌晨 3 点、周末、节假日、你忘记的那一晚。
所以我构建了 AgentShield:一个面向 AI agent 的执行前花费防火墙。每一次交易都会在你的规则下于 API 调用发出之前被评估。纯 Python 标准库,零依赖,每次评估 <1ms,7 条可组合规则,交易限额、日累计、速度限制、商户白名单、类别黑名单、会话预算、级联成本估算,外加一个紧急开关。APPROVED(批准)、BLOCKED(阻断)或 FLAGGED(标记),按优先级顺序,确定性执行。
那次凌晨 3 点的事故本不会逃过任何一条规则的拦截:transaction_limit: max_amount $100。
这解决了「我们现在应该拦截什么?」但它解决不了「为什么它会发生?」
拦截一个失控的 agent 就像拔掉一个着火的电器的电源。安全、必要。但你仍然不知道是代码里的 bug、库里的重试循环、中了毒的 tool 响应,还是一个糟糕的 prompt。没有这个答案,agent 明天还会再跑,而你只能玩打地鼠式的防火墙游戏。
这正是 Jacopo 构建的 Agent-Devtools 要解决的问题:一个本地优先的 agent 运行因果调试器。它用可视化回放、行为对比,以及对 prompt、上下文、记忆、检索和 tool 调用的完整可见性来回答「为什么我的 agent 会这样做」——正是那条导致了错误决策的完整执行时间线。
Jacopo 通过 LangChain issue 上的一条评论找到了 AgentShield,那个 issue 是关于失控 agent 循环的,他看到了和我一样的缺口,于是发了一个 issue:「你止出血,我展示循环。想整合吗?」我们都说好。
这个整合刻意做得非常简洁:一个共享的事件 schema、两个小模块、无托管服务:
from agentshield import SpendControlEngine, SpendEvaluationEmitter
from agent_devtools import TraceStore
from agent_devtools.adapters.agentshield import make_agentshield_callback
store = TraceStore()
cb = make_agentshield_callback(store)
engine = SpendControlEngine()
emitter = SpendEvaluationEmitter(engine)
# Every evaluation flows into your trace store automatically
emitter.emit(
transaction={"amount": "500.00", "merchant": "openai-api",
"category": "llm_inference"},
rules=[{"id": "r1", "type": "transaction_limit", "priority": 1,
"params": {"max_amount": "250.00"}, "action": "BLOCK"}],
trace_id="trace_42",
on_event=cb,
)
就这样。底层原理:
AgentShield 每次评估发出一个 agentshield.spend.evaluation 事件,以 NDJSON 格式输出到 stdout 或文件(便于 tail),或者是进程内回调(用于嵌入)。
trace_id 直接关联到 Agent-Devtools 的原生 run_id,所以花费决策会渲染在执行时间线内——被拦截的调用、触发的规则、实际值 vs 阈值数字,以及 agent 自身的推理,全在同一视图。
逐规则追踪可见性:每条规则报告 triggered、passed、skipped 或 not_reached。你看到的是险情而非仅有胜者。
金额保持精确:金额以 Decimal-safe 字符串在线上传输,绝不用浮点数。
最终实现了标题所承诺的闭环:防火墙在发生瞬间阻断花费,调试器向你展示 agent 到达那一步的回放。
这个整合好的地方在于,它几乎不需要什么仪式:
Jacopo 发起了 issue。我们在写代码之前就 schema 达成一致,合同就一个文档。
AgentShield 交付了 emitter(evaluate_with_trace() + SpendEvaluationEmitter),是增量添加,所以现有的 evaluate() 行为未改变。
Agent-Devtools 交付了适配器:一个解析器、一个回调、和 NDJSON 摄入。
然后是重要的一步:双方独立的 E2E 验证。我们各自用真实事件跑对方的代码,结果发现了真实的边界情况:trace_id 缺失导致宿主运行时崩溃(现在回退到未归因)、回调事件在运行列表中不可见(现在运行自动创建)、一条格式错误的 NDJSON 行导致文件其余内容丢失(现在是跳过并继续)、还有我们这边的 Decimal 精度 bug(现在是精确值)。
全部合并。两套 suite 全部 green。跨栈测试框架 37/37。
两个仓库,一个 issue 线程,从第一条评论到双方合并,不到一天。
每个 agent 团队最终都会有自己的凌晨 3 点事件。唯一的问题是,它是一场 2,800 美元的教训,还是一场 19 美元/月的非事件。
值得借鉴的模式不是代码,而是分工:执行前控制回答「这次应该跑吗?」;执行后可见性回答「为什么它跑坏了?」监控工具只告诉你已经发生了什么。预算告警只告诉你已经发生了什么。一个 agent 安全方案需要两部分,现在它们是一个整合。
AgentShield(执行前防火墙):github.com/kindrat86/agentshield,仅标准库,7 条规则,<1ms,紧急开关。
Agent-Devtools(执行后调试器):github.com/Jacopos311/Agent-Devtools,本地优先的 agent 运行可视化回放。
均为 MIT 许可。双方均已合并。给两个项目都点 star,把它们接起来,然后安心睡过下一个凌晨 3 点。