Java Agent 工具调用的 allowlist、参数校验与调用预算

Agent 最危险的误解是"模型说要执行,所以系统就执行"。函数调用(Function Calling)只是一种让模型提出工具与参数建议的协议;真正调用数据库、发货接口或邮件服务的权限仍在你的 Java 程序手里。本文不绑定某一家 SDK,而是先实现所有 Agent 都该有的执行门:工具白名单、参数格式、次数预算和默认拒绝。它能在你接上 Gemini、其他模型或 MCP(Model Context Protocol,模型上下文协议)工具前先守住边界。

官方 Gemini API 文档将 Function Calling 描述为把 Agent 工作流连接到外部 API 的能力。关键架构是双回合:模型返回 function call 候选;宿主验证后执行;再把工具结果送回模型。模型永远不能越过宿主直接产生副作用。
#mermaid-svg-fX869qtbjlzhJ6x0{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-fX869qtbjlzhJ6x0 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-fX869qtbjlzhJ6x0 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-fX869qtbjlzhJ6x0 .error-icon{fill:#552222;}#mermaid-svg-fX869qtbjlzhJ6x0 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-fX869qtbjlzhJ6x0 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-fX869qtbjlzhJ6x0 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-fX869qtbjlzhJ6x0 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-fX869qtbjlzhJ6x0 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-fX869qtbjlzhJ6x0 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-fX869qtbjlzhJ6x0 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-fX869qtbjlzhJ6x0 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-fX869qtbjlzhJ6x0 .marker.cross{stroke:#333333;}#mermaid-svg-fX869qtbjlzhJ6x0 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-fX869qtbjlzhJ6x0 p{margin:0;}#mermaid-svg-fX869qtbjlzhJ6x0 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-fX869qtbjlzhJ6x0 .cluster-label text{fill:#333;}#mermaid-svg-fX869qtbjlzhJ6x0 .cluster-label span{color:#333;}#mermaid-svg-fX869qtbjlzhJ6x0 .cluster-label span p{background-color:transparent;}#mermaid-svg-fX869qtbjlzhJ6x0 .label text,#mermaid-svg-fX869qtbjlzhJ6x0 span{fill:#333;color:#333;}#mermaid-svg-fX869qtbjlzhJ6x0 .node rect,#mermaid-svg-fX869qtbjlzhJ6x0 .node circle,#mermaid-svg-fX869qtbjlzhJ6x0 .node ellipse,#mermaid-svg-fX869qtbjlzhJ6x0 .node polygon,#mermaid-svg-fX869qtbjlzhJ6x0 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-fX869qtbjlzhJ6x0 .rough-node .label text,#mermaid-svg-fX869qtbjlzhJ6x0 .node .label text,#mermaid-svg-fX869qtbjlzhJ6x0 .image-shape .label,#mermaid-svg-fX869qtbjlzhJ6x0 .icon-shape .label{text-anchor:middle;}#mermaid-svg-fX869qtbjlzhJ6x0 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-fX869qtbjlzhJ6x0 .rough-node .label,#mermaid-svg-fX869qtbjlzhJ6x0 .node .label,#mermaid-svg-fX869qtbjlzhJ6x0 .image-shape .label,#mermaid-svg-fX869qtbjlzhJ6x0 .icon-shape .label{text-align:center;}#mermaid-svg-fX869qtbjlzhJ6x0 .node.clickable{cursor:pointer;}#mermaid-svg-fX869qtbjlzhJ6x0 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-fX869qtbjlzhJ6x0 .arrowheadPath{fill:#333333;}#mermaid-svg-fX869qtbjlzhJ6x0 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-fX869qtbjlzhJ6x0 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-fX869qtbjlzhJ6x0 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-fX869qtbjlzhJ6x0 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-fX869qtbjlzhJ6x0 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-fX869qtbjlzhJ6x0 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-fX869qtbjlzhJ6x0 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-fX869qtbjlzhJ6x0 .cluster text{fill:#333;}#mermaid-svg-fX869qtbjlzhJ6x0 .cluster span{color:#333;}#mermaid-svg-fX869qtbjlzhJ6x0 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-fX869qtbjlzhJ6x0 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-fX869qtbjlzhJ6x0 rect.text{fill:none;stroke-width:0;}#mermaid-svg-fX869qtbjlzhJ6x0 .icon-shape,#mermaid-svg-fX869qtbjlzhJ6x0 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-fX869qtbjlzhJ6x0 .icon-shape p,#mermaid-svg-fX869qtbjlzhJ6x0 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-fX869qtbjlzhJ6x0 .icon-shape .label rect,#mermaid-svg-fX869qtbjlzhJ6x0 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-fX869qtbjlzhJ6x0 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-fX869qtbjlzhJ6x0 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-fX869qtbjlzhJ6x0 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 拒绝
允许
模型建议 tool + args
Java 解析候选
白名单/参数/预算
记录拒绝原因并要求澄清
受限工具实现
工具结果

运行前提

Java 17+ 即可,本例没有网络和密钥,因为安全门应能脱离模型独立测试。模型侧必须按官方 Function Calling 文档声明同名工具 schema;这里的 ToolCall 代表解析后的候选,而不是替代官方 API 请求。

bash 复制代码
javac AgentGate.java && java AgentGate

完整代码

代码只允许查询订单和创建草稿;拒绝取消订单等副作用工具。每轮最多两次工具调用,并把订单号限制为十位大写字母数字。execute 是可替换的受限适配器,真实实现中再接数据库或 HTTP 客户端,并分别设置连接与请求超时。

java 复制代码
import java.util.*;
import java.util.regex.Pattern;

public class AgentGate {
  record ToolCall(String name, Map<String, String> args) {}
  static final Set<String> ALLOWED = Set.of("get_order", "create_reply_draft");
  static final Pattern ORDER = Pattern.compile("[A-Z0-9]{10}");
  static final int MAX_CALLS = 2;

  static String authorize(ToolCall call, int used) {
    if (used >= MAX_CALLS) return "DENY: 调用预算已用尽";
    if (!ALLOWED.contains(call.name())) return "DENY: 工具不在白名单";
    if (call.name().equals("get_order")) {
      String id = call.args().get("order_id");
      if (id == null || !ORDER.matcher(id).matches()) return "DENY: 非法订单号";
    }
    if (call.name().equals("create_reply_draft")) {
      String text = call.args().get("text");
      if (text == null || text.isBlank() || text.length() > 500) return "DENY: 草稿长度不合法";
    }
    return "ALLOW";
  }
  static String execute(ToolCall call) {
    return switch (call.name()) {
      case "get_order" -> "ORDER_FOUND: delivery=processing";
      case "create_reply_draft" -> "DRAFT_CREATED: no external message sent";
      default -> throw new IllegalArgumentException("unreachable");
    };
  }
  public static void main(String[] args) {
    List<ToolCall> suggestions = List.of(
        new ToolCall("get_order", Map.of("order_id", "AB12CD34EF")),
        new ToolCall("cancel_order", Map.of("order_id", "AB12CD34EF")),
        new ToolCall("create_reply_draft", Map.of("text", "您的订单正在处理中。")));
    int used = 0;
    for (ToolCall call : suggestions) {
      String verdict = authorize(call, used);
      if (verdict.equals("ALLOW")) { System.out.println(execute(call)); used++; }
      else System.out.println(verdict + " -> " + call.name());
    }
  }
}

预期输出先返回订单状态,再拒绝 cancel_order,最后仅创建一份草稿。这个次序说明:模型可以提出危险工具,但宿主拒绝不在白名单的名称。此次已用 javac AgentGate.java 编译并实际运行,输出符合预期;没有调用任何模型、数据库或线上工具。

不要跳过的三层判断

第一层是工具种类 :默认拒绝、按业务最小授权;第二层是参数 :枚举、长度、正则、金额范围和权限主体逐项校验;第三层是资源预算 :限制轮数、调用次数、并发和总耗时。只做其中一层不够。例如允许 get_order 但不验证订单归属,仍可能泄露数据;限制两次调用但允许 delete_all 同样危险。

常见错误有三类:一,把模型输出当可信 JSON 而不进行 schema 验证;二,把"创建草稿"和"发送消息"共用同一工具;三,只记录成功调用而不记录拒绝原因。工程化应为每次决定保存工具、参数摘要、授权主体、规则版本、耗时和结果哈希;日志不可写入完整敏感参数。涉及支付、删除、外发的工具还应要求人类二次确认和幂等键。

适用范围与练习

适合客服助手、内部知识查询、代码审阅的只读工具与有人工确认的工作流;不适合让 Agent 自主执行转账、删库、发布生产配置。五分钟练习:加入 refund_preview,只允许金额 0--100 且不产生退款;再写一个测试证明 refund_execute 一定被拒绝。

你的 Agent 最先该限制的是工具种类、参数范围还是调用次数?

关注「蜗牛聊AI」,一起看懂技术变化背后的真正机会。


本文首发于 java4u.cn,转载请注明出处。

相关推荐
用户813267933251 小时前
行情数据晚到几秒,会让量化策略失去优势吗?从信号时间到回测偏差
后端·github·api
大文说跨境1 小时前
多账号环境隔离方案技术选型:指纹浏览器、VPS 与云手机的三种架构对比
java·开发语言·前端
九零HTTP1 小时前
一次 TCP 连接的一生:从三次握手到四次挥手
后端
MicrosoftReactor1 小时前
技术速递|使用 GitHub Security Lab Taskflow Agent 实现 AI 驱动的模糊测试
人工智能·ai·github·copilot·agent·模糊测试·ai-agent
promanz1 小时前
关于RAG文件格式转换
人工智能
chenbingjie_c1 小时前
手撕 C++ priority_queue:二叉堆 + 模板仿函数 + 模板特化(保姆级逐函数拆解)
开发语言·c++
桃西西呀1 小时前
Spring AI Alibaba 之五:我把五个子智能体拼成客服流水线,才看懂内置 flow 编排和 A2A 远程调用
人工智能
翻滚的芋头1 小时前
神经网络基础——前馈神经网络FNN
人工智能
ZOnePieceC1 小时前
消息队列之Kafka
后端