【AgentScope Java新手村系列】(13)工具分组

第十三章 工具分组:workspace/tools.json + toolFilter,按角色配置化限定工具

"我们希望"客服 agent"只能查订单和物流,"财务 agent"只能用支付相关工具。1.x 时代我们在 Java 端写一堆条件判断;2.0 用 workspace/tools.json + toolFilter 把这套规则配置化:新增一个角色只需要在 JSON 里加一段,不需要改 Java 代码。"

本章你将学到:如何写 workspace/tools.json、如何用 toolFilter 限定工具集、如何让 subagent 继承主 agent 的工具 / 屏蔽危险工具。

13.1 工具分组的两种方式

2.0 提供两套"工具分组"机制,可以单独用,也可以组合:

方式 配置位置 适用场景
Java 端 Toolkit HarnessAgent.builder().toolkit(toolkit) 代码定义工具,编译即固定
Java 端 toolsConfig HarnessAgent.builder().toolsConfig(config) 程序化 allow/deny,无需 JSON 文件
workspace tools.json workspace/tools.json 运行时可改,多环境差异化

生产建议 :基础工具集放 Java 端(Toolkit),角色级过滤放 toolsConfig(Java 端)或 tools.json(文件端)。

13.2 第一个 tools.json

复制代码
{
  "tools": [
    {
      "name": "query_order",
      "class": "demo.tools.OrderTool",
      "description": "查订单"
    },
    {
      "name": "query_logistics",
      "class": "demo.tools.LogisticsTool",
      "description": "查物流"
    },
    {
      "name": "refund_order",
      "class": "demo.tools.RefundTool",
      "description": "退款"
    }
  ]
}

HarnessAgent.builder().workspace(...) 启动时会自动扫描 workspace/tools.json 并把工具注册进 agent。但不传 classpath 时默认关闭------需要显式开:

复制代码
HarnessAgent agent = HarnessAgent.builder()
        ...
        .workspace(Path.of("./workspace"))
        .loadWorkspaceTools(true)        // 2.0 新增
        .build();

工具类的 class 必须在 classpath 里能找到(通过反射 Class.forName(...) 实例化),所以 demo.tools.OrderTool 这种业务类要保证在 agent 进程的 classpath 上。

13.3 角色级 toolFilter:allow / deny

一个 tools.json 可以同时定义工具列表和可见性规则:

复制代码
{
  "tools": [
    { "name": "query_order",     "class": "demo.tools.OrderTool" },
    { "name": "query_logistics", "class": "demo.tools.LogisticsTool" },
    { "name": "refund_order",    "class": "demo.tools.RefundTool" }
  ],
  "toolFilter": {
    "allow": ["query_order", "query_logistics"],
    "deny":  ["refund_order"]
  }
}

行为:

  • allow 白名单模式:只放行名字精确匹配的工具;其他一律不可见
  • deny 黑名单模式:名字匹配的工具被移除;其他都可见
  • allowdeny 同时存在:deny 优先(先 allow 再 deny)
  • 注意:使用精确工具名匹配,不支持通配符

HarnessAgent 启动时按 toolFilter 把工具列表过滤后再交给 LLM------LLM 看不到的工具连描述都不会出现在 prompt 里,避免"模型想调却调不到"。

13.4 给 subagent 单独配置 tools.json

每个 subagent 可以在 workspace/subagents/<id>.md 里声明自己的工具集:

复制代码
# customer_service.md
id: customer_service
description: 客服 agent;只能查订单和物流,不能退款
toolsFile: customer_service.tools.json
sysPrompt: |
  你是客服,只能查订单和物流,不能直接退款。
  如需退款,转人工。

workspace/subagents/customer_service.tools.json

复制代码
{
  "tools": [
    { "name": "query_order",     "class": "demo.tools.OrderTool" },
    { "name": "query_logistics", "class": "demo.tools.LogisticsTool" }
  ],
  "toolFilter": {
    "deny": ["refund_order"]
  }
}

13.5 在 Java 端过滤

不写 JSON 也行------直接用 ToolsConfig 对象:

复制代码
ToolsConfig config = new ToolsConfig();
config.setAllow(List.of("query_order", "query_logistics"));
config.setDeny(List.of("refund_order"));

HarnessAgent agent = HarnessAgent.builder()
        ...
        .toolsConfig(config)
        .build();

ToolsConfigtools.json 里的 toolFilter 字段语义完全一致。

13.6 工具权限的"四层防线"

生产环境推荐工具分组 + Permission 双保险:

复制代码
第 1 层:工具注册(class 必须在 classpath)
第 2 层:toolFilter(allow / deny 精确匹配)
第 3 层:Permission rule(运行时再校验一次)
第 4 层:Middleware onModelCall / onActing(埋点 / 限流)

例:想让"退款"工具运行时必须经过人审批:

复制代码
PermissionContextState perms = PermissionContextState.builder()
        .mode(PermissionMode.ACCEPT_EDITS)
        .addAskRule("refund_order",
                new PermissionRule("refund_order", null, PermissionBehavior.ASK, "userSettings"))
        .build();

HarnessAgent agent = HarnessAgent.builder()
        .permissionContext(perms)
        .build();

即使 LLM 想调 refund_order,Permission 也会拦下来问用户。

13.7 完整可运行示例

这个例子在演示什么?

我们注册 3 个工具:query_order(查订单)、query_logistics(查物流)、refund_order(退款)。然后创建两个 agent:

  • 客服 agentdeny 屏蔽 refund_order → 只能查订单和物流,不能退款
  • 财务 agentallow 只放行 refund_order → 只能退款,看不到查询工具

同一套工具,两个角色两个视野。新增角色只需要改 ToolsConfig,不改 Java 代码。

复制代码
import io.agentscope.core.tool.Tool;
import io.agentscope.core.tool.Toolkit;
import io.agentscope.harness.agent.tools.ToolsConfig;
import io.agentscope.harness.HarnessAgent;

import java.util.List;

public class Chapter13_ToolFilter {

    // 业务工具类:@Tool 注解标记方法名即工具名
    public static class OrderTools {
        @Tool(name = "query_order", description = "查询订单状态和详情")
        public String queryOrder(String orderId) {
            return "订单 " + orderId + ":已发货。";
        }
    }

    public static class LogisticsTools {
        @Tool(name = "query_logistics", description = "查询物流轨迹")
        public String queryLogistics(String trackingId) {
            return "物流 " + trackingId + ":快件在北京分拣中心。";
        }
    }

    public static class RefundTools {
        @Tool(name = "refund_order", description = "处理订单退款")
        public String refundOrder(String orderId) {
            return "订单 " + orderId + " 退款已处理。";
        }
    }

    public static void main(String[] args) {
        // 1. 全部工具注册到同一个 Toolkit
        Toolkit baseToolkit = new Toolkit();
        baseToolkit.registerTool(new OrderTools());
        baseToolkit.registerTool(new LogisticsTools());
        baseToolkit.registerTool(new RefundTools());

        // 2. 客服 agent:deny "refund_order" --- 看不到退款工具
        ToolsConfig csConfig = new ToolsConfig();
        csConfig.setDeny(List.of("refund_order"));

        HarnessAgent customerService = HarnessAgent.builder()
                .name("customer_service")
                .sysPrompt("你是客服,只能查订单和物流。不能退款,如需退款请转人工。")
                .model(model())
                .workspace(Path.of("./workspace"))
                .toolkit(baseToolkit)
                .toolsConfig(csConfig)
                .build();

        // 3. 财务 agent:allow "refund_order" --- 只能看到退款工具
        ToolsConfig finConfig = new ToolsConfig();
        finConfig.setAllow(List.of("refund_order"));

        HarnessAgent finance = HarnessAgent.builder()
                .name("finance")
                .sysPrompt("你是财务,只处理退款。")
                .model(model())
                .workspace(Path.of("./workspace"))
                .toolkit(baseToolkit)
                .toolsConfig(finConfig)
                .build();

        // 问客服"订单 123 的状态"------能答
        // 问客服"退款"------会说"请转人工"
        // 问财务"订单 123 的状态"------看不到 query 工具
        // 问财务"退款"------正常处理
    }
}

关键ToolsConfigallow/denyHarnessAgent.build() 里被 ToolFilter.apply() 执行------从 Toolkit物理删除对应工具。LLM 的 prompt 里根本不会出现被屏蔽工具的描述,不存在"知道但不能用"的半吊子状态。

13.8 本章小结

  • workspace/tools.json 让工具配置离开 Java 代码,运行时可改。
  • toolFilterallow / deny + 精确工具名限定角色可调工具。
  • subagent 可声明自己的 toolsFile 单独配工具集。
  • 工具权限推荐四层防线:注册 → toolFilter → Permission → Middleware。