2026年9月26日

在 Agent 工具调用前放一道类型化护栏

让 Jev 判断一个拟议的工具调用是否符合用户意图、破坏性多大,然后由普通代码来决策。一次完整演练:状态组装、阈值与测试矩阵。

护栏要防的失败模式

Agent 的失败有一种特定形态:它发出的工具调用看起来局部合理,实际全局错误。把本该对 staging 表执行的 SELECT 打到了生产表上;本想 DELETE 却发出了 DROP TABLE。向聊天模型再问一句“这样安全吗?” — 慢、无结构,而且会把安全策略变成自由文本,下一次发版就可能被悄悄改掉。

类型化护栏改为问三个问题:这个调用是否符合用户要求(noul)、执行前是否需要人工确认(noul)、如果出错破坏性有多大(score)。三个问题放进同一个请求,每个答案都落在你定义的答案空间内 — 策略留在代码里,而不是文字里。

组装状态

把用户请求和拟议调用一起作为 state 发送。这里对象形式的状态比一整段字符串更清晰:每个事实都有名字,criteria 也能引用它们。

{
  "state": {
    "user_request": "Clean up the old test data in the staging database.",
    "proposed_tool_call": {
      "tool": "sql.execute",
      "args": { "query": "DROP TABLE customers;" },
      "environment": "production"
    }
  },
  "questions": {
    "matches_intent": {
      "type": "noul",
      "instructions": "Does `proposed_tool_call` match what `user_request` asked for?"
    },
    "needs_confirmation": {
      "type": "noul",
      "instructions": "Should a human confirm this tool call before it runs?",
      "criteria": {
        "true": "Touches live data, is destructive, or the environment does not match the request",
        "false": "Read-only, staging-only, and exactly what was asked"
      }
    },
    "risk": {
      "type": "score",
      "instructions": "How destructive is this action if it is wrong?",
      "criteria": ["Harmless", "Recoverable", "Irreversible data loss"]
    }
  }
}
护栏请求体

把策略留在你的代码里

Jev 只做判断,从不运行工具。由你的包装层把概率转成动作:

const a = answers;
const must_confirm =
  a.matches_intent.noul < 0.8 ||
  a.needs_confirmation.noul > 0.7 ||
  a.risk.score >= 1.5; // past "Recoverable", toward the red end

if (a.risk.score >= 2.0 && a.matches_intent.noul < 0.5) {
  blockOrFlag(a.request_id); // never execute, log for review
} else if (must_confirm) {
  confirmWith(a.request_id); // ask the human
} else {
  execute(call);
}
作为代码的策略

阈值由你定。0.8 是意图匹配的一个合理起始置信线;对不可逆工具(DROP、部署、发邮件)收紧,对只读工具放宽。用你自己流量里的十个真实例子跑一遍 — 不要用玩具例子 — 并且只在“你本来也会做出同样判断、而模型不一致”的地方调整阈值。

信任它之前的测试矩阵

  • 正确环境中的良性调用 — 预期低风险、意图匹配高、无需确认。
  • 意图正确、环境错误(要求 staging,调用却打到生产)— 预期意图匹配仍高而 needs_confirmation 上升。诚实地解读这种不一致:模型只能标记状态里可见的东西。
  • 意图错误、调用无害 — 预期 needs_confirmation 仍然很低;无害就是无害。
  • 意图错误、调用破坏性(即上例)— 预期所有信号都被触发。
  • 用户请求本身含糊 — 预期置信度低;转给人工,不要猜。

如果某个案例两次结果都错了,不要靠调阈值把它藏起来 — 调整 criteria 的措辞,或者把缺失的事实(比如调用方的环境)补进状态。

实际成本

这个三问题请求体约 350–500 token,从容落在 1 积分的最低消费之内,所以在 Starter 上给每次工具调用设闸的成本是 $0.0001,更大的积分包还能再摊薄。延迟增加一个往返:对线上网关的典型测量为 400–600 ms。这在大多数 Agent 循环里可以接受。如果你的工具调用是 100 ms 以内的事、每一毫秒都肉疼,那就只对破坏半径配得上这个开销的工具设闸 — 文件写入、SQL、支付、邮件 — 其余直接放行。

类型化护栏的局限

认清边界:状态里没有的东西,护栏模型就看不见。如果你不写明目标环境,它就无法发现环境错误。它也不会取代确定性控制 — 白名单、路径检查和网络作用域该干嘛干嘛,让类型化决策层负责语义问题:这个计划与请求匹配吗?

Jev 能自己执行工具调用吗?

不能。它只返回判断 — 对你所问问题的概率分布。执行或拒绝由你的代码决定。正是这种分离让审计记录掌握在你手里。

它能拦住所有危险调用吗?

任何模型都不是完整的安全体系。把护栏当作其中一层。它最强的场景是语义层面的危险(意图不匹配、环境搞混)。白名单和作用域继续交给确定性规则,不可逆路径上保留人工。

Playground 里有这个示例吗?

有 — /playground 页面上的 Tool-call risk gate 预设就是完全相同的请求结构,可以免费运行。

试试 Jev AI Playground ,读完这篇指南就发出你的第一次免费决策。