Agent Scope Java 2.x 系列【7】工具使用

目录

  • [AgentScope Tool](#AgentScope Tool)
    • [1. 概述](#1. 概述)
    • [2. 三个核心概念](#2. 三个核心概念)
    • [3. AgentTool / ToolBase 接口](#3. AgentTool / ToolBase 接口)
      • [3.1 向 Agent 与运行时描述 Tool 的属性](#3.1 向 Agent 与运行时描述 Tool 的属性)
      • [3.2 接入执行流程与权限系统的方法](#3.2 接入执行流程与权限系统的方法)
    • [4. 三种自定义 Tool 的方式](#4. 三种自定义 Tool 的方式)
      • [4.1 使用内置 Tool](#4.1 使用内置 Tool)
      • [4.2 注解式 @Tool(最轻量,推荐)](#4.2 注解式 @Tool(最轻量,推荐))
      • [4.3 继承 ToolBase(复杂场景)](#4.3 继承 ToolBase(复杂场景))
    • [5. 外部执行 Tool(HITL)](#5. 外部执行 Tool(HITL))
    • [6. Context 注入](#6. Context 注入)
      • [6.1 自动注入(@Tool 方法)](#6.1 自动注入(@Tool 方法))
      • [6.2 ToolBase.callAsync 中访问](#6.2 ToolBase.callAsync 中访问)
    • [7. Tool Group 与 Meta Tool](#7. Tool Group 与 Meta Tool)
      • [7.1 定义 Tool Group](#7.1 定义 Tool Group)
      • [7.2 Meta Tool 的运行时行为](#7.2 Meta Tool 的运行时行为)
    • [8. 最小可运行示例](#8. 最小可运行示例)

AgentScope Tool

本文档系统介绍 AgentScope 2.0 的 Tool 体系:Tool 是 Agent 与外部世界交互的方式------执行业务操作、调用 API、读写数据等。每个 Tool 通过 JSON Schema 暴露给 LLM,Agent 通过统一接口完成调用。

1. 概述

Tool 是 Agent 与外部世界交互的桥梁。当 LLM 推理出「需要调用某个能力」时,Agent 通过统一接口把参数交给 Tool,Tool 执行后把结果以 ToolResultBlock 的形式回灌给模型,形成「推理 → 调用 → 观察 → 再推理」的 ReAct 闭环。

为什么要用 Tool 而不是让模型直接输出?

  • LLM 本身不能联网、不能读写文件、不能调用内部 API
  • 把能力封装成带 JSON Schema 的 Tool,模型只需「填参数」,执行由代码完成
  • 执行前可以做权限检查、并发控制、只读标记,出问题可追溯

最小起步:

java 复制代码
import io.agentscope.core.tool.Toolkit;
import io.agentscope.core.tool.builtin.TodoTools;

Toolkit toolkit = new Toolkit();
toolkit.registerTool(new TodoTools());
toolkit.registerTool(new MyCustomTools());

只调用 registerTool(Object) 时,被注册对象上所有 @Tool 方法都会进入特殊的 "basic" 组------该组始终激活,不受 meta tool 影响。


2. 三个核心概念

AgentScope 把 Tool 相关的构件组织成三个概念:

text 复制代码
Toolkit(容器 / 分发器)
    │
    ├── Tool          任意实现 AgentTool 接口的对象
    │     ├── 继承 ToolBase 的显式 Tool
    │     └── @Tool 注解的普通方法(reflective function tool)
    │
    ├── MCP Client    外部 MCP 服务暴露的一组 Tool
    │
    ├── Skill         可加载的能力包
    │
    └── Tool Group    一组带名字的 Tool / MCP / Skill 集合
          ├── "basic"(保留名,始终激活)
          ├── "database"
          ├── "deployment"
          └── ...
概念 角色 关键行为
Tool 单个可调用能力 通过 JSON Schema 暴露参数;执行后返回 ToolResultBlock
Toolkit 容器 / 注册中心 注册 Tool、MCP、Skill、Group;向模型暴露 schema;把每次调用分发到对应 Tool
Tool Group 一组能力的集合 带名称、描述、作用域、初始激活态;可整体激活 / 停用,让上下文保持聚焦

#mermaid-svg-nrJPJHGaGo1KIXUq{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-nrJPJHGaGo1KIXUq .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-nrJPJHGaGo1KIXUq .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-nrJPJHGaGo1KIXUq .error-icon{fill:#552222;}#mermaid-svg-nrJPJHGaGo1KIXUq .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-nrJPJHGaGo1KIXUq .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-nrJPJHGaGo1KIXUq .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-nrJPJHGaGo1KIXUq .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-nrJPJHGaGo1KIXUq .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-nrJPJHGaGo1KIXUq .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-nrJPJHGaGo1KIXUq .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-nrJPJHGaGo1KIXUq .marker{fill:#333333;stroke:#333333;}#mermaid-svg-nrJPJHGaGo1KIXUq .marker.cross{stroke:#333333;}#mermaid-svg-nrJPJHGaGo1KIXUq svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-nrJPJHGaGo1KIXUq p{margin:0;}#mermaid-svg-nrJPJHGaGo1KIXUq .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-nrJPJHGaGo1KIXUq .cluster-label text{fill:#333;}#mermaid-svg-nrJPJHGaGo1KIXUq .cluster-label span{color:#333;}#mermaid-svg-nrJPJHGaGo1KIXUq .cluster-label span p{background-color:transparent;}#mermaid-svg-nrJPJHGaGo1KIXUq .label text,#mermaid-svg-nrJPJHGaGo1KIXUq span{fill:#333;color:#333;}#mermaid-svg-nrJPJHGaGo1KIXUq .node rect,#mermaid-svg-nrJPJHGaGo1KIXUq .node circle,#mermaid-svg-nrJPJHGaGo1KIXUq .node ellipse,#mermaid-svg-nrJPJHGaGo1KIXUq .node polygon,#mermaid-svg-nrJPJHGaGo1KIXUq .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-nrJPJHGaGo1KIXUq .rough-node .label text,#mermaid-svg-nrJPJHGaGo1KIXUq .node .label text,#mermaid-svg-nrJPJHGaGo1KIXUq .image-shape .label,#mermaid-svg-nrJPJHGaGo1KIXUq .icon-shape .label{text-anchor:middle;}#mermaid-svg-nrJPJHGaGo1KIXUq .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-nrJPJHGaGo1KIXUq .rough-node .label,#mermaid-svg-nrJPJHGaGo1KIXUq .node .label,#mermaid-svg-nrJPJHGaGo1KIXUq .image-shape .label,#mermaid-svg-nrJPJHGaGo1KIXUq .icon-shape .label{text-align:center;}#mermaid-svg-nrJPJHGaGo1KIXUq .node.clickable{cursor:pointer;}#mermaid-svg-nrJPJHGaGo1KIXUq .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-nrJPJHGaGo1KIXUq .arrowheadPath{fill:#333333;}#mermaid-svg-nrJPJHGaGo1KIXUq .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-nrJPJHGaGo1KIXUq .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-nrJPJHGaGo1KIXUq .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-nrJPJHGaGo1KIXUq .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-nrJPJHGaGo1KIXUq .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-nrJPJHGaGo1KIXUq .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-nrJPJHGaGo1KIXUq .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-nrJPJHGaGo1KIXUq .cluster text{fill:#333;}#mermaid-svg-nrJPJHGaGo1KIXUq .cluster span{color:#333;}#mermaid-svg-nrJPJHGaGo1KIXUq 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-nrJPJHGaGo1KIXUq .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-nrJPJHGaGo1KIXUq rect.text{fill:none;stroke-width:0;}#mermaid-svg-nrJPJHGaGo1KIXUq .icon-shape,#mermaid-svg-nrJPJHGaGo1KIXUq .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-nrJPJHGaGo1KIXUq .icon-shape p,#mermaid-svg-nrJPJHGaGo1KIXUq .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-nrJPJHGaGo1KIXUq .icon-shape .label rect,#mermaid-svg-nrJPJHGaGo1KIXUq .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-nrJPJHGaGo1KIXUq .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-nrJPJHGaGo1KIXUq .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-nrJPJHGaGo1KIXUq :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-nrJPJHGaGo1KIXUq .llmNode>*{fill:#FFF4E6!important;stroke:#E67E22!important;stroke-width:2px!important;color:#333!important;}#mermaid-svg-nrJPJHGaGo1KIXUq .llmNode span{fill:#FFF4E6!important;stroke:#E67E22!important;stroke-width:2px!important;color:#333!important;}#mermaid-svg-nrJPJHGaGo1KIXUq .llmNode tspan{fill:#333!important;}#mermaid-svg-nrJPJHGaGo1KIXUq .tkNode>*{fill:#E8F4FD!important;stroke:#2980B9!important;stroke-width:2px!important;color:#333!important;}#mermaid-svg-nrJPJHGaGo1KIXUq .tkNode span{fill:#E8F4FD!important;stroke:#2980B9!important;stroke-width:2px!important;color:#333!important;}#mermaid-svg-nrJPJHGaGo1KIXUq .tkNode tspan{fill:#333!important;}#mermaid-svg-nrJPJHGaGo1KIXUq .grpNode>*{fill:#E8F8E9!important;stroke:#27AE60!important;stroke-width:1px!important;color:#333!important;}#mermaid-svg-nrJPJHGaGo1KIXUq .grpNode span{fill:#E8F8E9!important;stroke:#27AE60!important;stroke-width:1px!important;color:#333!important;}#mermaid-svg-nrJPJHGaGo1KIXUq .grpNode tspan{fill:#333!important;} Tool Groups
调用请求

toolCallId + 参数
只暴露激活组的 schema
LLM

只看到激活 Tool 的 JSON Schema
Toolkit

注册 + 分发
basic 组

始终激活
database 组

默认停用
deployment 组

默认停用


3. AgentTool / ToolBase 接口

ToolBase 是 AgentTool 的抽象实现。绝大多数业务 Tool 二选一:继承 ToolBase (显式建模)或 在普通类方法上标 @Tool(反射注册,由框架自动包成 AgentTool)。

text 复制代码
AgentTool (interface)
    │
    └── ToolBase (abstract)            ← 显式建模带参数 schema 的 Tool
          │
          ├── TodoTools(内置)
          ├── WebSearchTool(继承示例)
          ├── HumanApprovalTool(外部执行)
          └── ...

reflective function tool             ← @Tool 注解的普通方法
    (由 Toolkit#registerTool(Object) 反射包装实现 AgentTool)

3.1 向 Agent 与运行时描述 Tool 的属性

方法 类型 说明
getName() String 暴露给 Agent 的 Tool 名称
getDescription() String 面向 Agent 的功能描述
getParameters() Map<String, Object> 定义参数的 JSON Schema
isConcurrencySafe() boolean 是否可并发调用
isReadOnly() boolean 是否只读、不产生副作用
isExternalTool() boolean 为 true 时执行委派给外部(见 [第 5 节](#方法 类型 说明 getName() String 暴露给 Agent 的 Tool 名称 getDescription() String 面向 Agent 的功能描述 getParameters() Map<String, Object> 定义参数的 JSON Schema isConcurrencySafe() boolean 是否可并发调用 isReadOnly() boolean 是否只读、不产生副作用 isExternalTool() boolean 为 true 时执行委派给外部(见 第 5 节) isStateInjected() boolean 为 true 时框架注入 AgentState 参数 isMcp() boolean 是否来自 MCP 服务 getMcpName() String isMcp() 为 true 时所属 MCP 服务名))
isStateInjected() boolean 为 true 时框架注入 AgentState 参数
isMcp() boolean 是否来自 MCP 服务
getMcpName() String isMcp() 为 true 时所属 MCP 服务名

3.2 接入执行流程与权限系统的方法

方法 必需 说明
checkPermissions(toolInput, context) 是 执行前的运行时权限检查;返回 Mono<PermissionDecision>
matchRule(ruleContent, toolInput) 可选 权限系统中的自定义规则匹配;返回 boolean
generateSuggestions(toolInput) 可选 基于本次调用生成建议规则;返回 List<PermissionRule>
callAsync(param) 可选 Tool 的执行逻辑;返回 Mono<ToolResultBlock>。外部执行 Tool 不需要实现

4. 三种自定义 Tool 的方式

4.1 使用内置 Tool

AgentScope 内置了开箱即用的 Tool:

Tool 说明 只读
TodoTools.todoWrite 维护当前会话的结构化任务列表(全列表替换语义) 否
java 复制代码
Toolkit toolkit = new Toolkit();
toolkit.registerTool(new io.agentscope.core.tool.builtin.TodoTools());

当 Toolkit 出现额外的 Tool Group 或 Skill 时,会自动注册 reset_tools meta tool 与 skill 查看器 load_skill_through_path,开发者无需手动实例化。

4.2 注解式 @Tool(最轻量,推荐)

在普通类的方法上标注 @Tool 与 @ToolParam,然后通过 Toolkit#registerTool(Object) 反射注册。框架自动从 Java 类型推导 JSON Schema,从 description 取面向 Agent 的说明。

java 复制代码
import io.agentscope.core.tool.Tool;
import io.agentscope.core.tool.ToolParam;
import io.agentscope.core.tool.Toolkit;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;

public class SimpleTools {

    @Tool(
        name = "get_current_time",
        description = "Returns the current time in a given IANA timezone.",
        readOnly = true,
        concurrencySafe = true)
    public String getCurrentTime(
            @ToolParam(name = "timezone",
                       description = "IANA timezone, e.g. Asia/Shanghai")
            String timezone) {
        return LocalDateTime.now(ZoneId.of(timezone))
                .format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
    }
}

Toolkit toolkit = new Toolkit();
toolkit.registerTool(new SimpleTools());

@Tool 常用属性:

属性 类型 说明
name String Tool 名(默认取方法名)
description String 面向 Agent 的描述
readOnly boolean 是否只读(默认 false)
concurrencySafe boolean 是否可并发调用(默认 false)
stateInjected boolean 调用时是否注入 AgentState 作为额外参数(默认 false)
dangerousFiles / dangerousDirectories String[] 追加自定义危险路径列表
converter Class<? extends ToolResultConverter> 自定义返回值到 ToolResultBlock 的转换器

4.3 继承 ToolBase(复杂场景)

需要自定义权限策略、外部执行或更复杂的 Schema 时,继承 ToolBase:

java 复制代码
import io.agentscope.core.message.TextBlock;
import io.agentscope.core.message.ToolResultBlock;
import io.agentscope.core.permission.PermissionContextState;
import io.agentscope.core.permission.PermissionDecision;
import io.agentscope.core.tool.ToolBase;
import io.agentscope.core.tool.ToolCallParam;
import reactor.core.publisher.Mono;

import java.util.List;
import java.util.Map;

public class WebSearchTool extends ToolBase {
    protected WebSearchTool(Builder builder) {
        super(
                ToolBase.builder()
                        .name("WebSearch")
                        .description("Search the web for information on a given query.")
                        .inputSchema(Map.of(
                                "type", "object",
                                "properties", Map.of(
                                        "query", Map.of(
                                                "type", "string",
                                                "description", "The search query.")),
                                "required", List.of("query")))
                        .readOnly(true)
                        .concurrencySafe(true));
    }

    @Override
    public Mono<PermissionDecision> checkPermissions(
            Map<String, Object> toolInput, PermissionContextState context) {
        return Mono.just(PermissionDecision.allow("Web search is read-only."));
    }

    @Override
    public Mono<ToolResultBlock> callAsync(ToolCallParam param) {
        String query = (String) param.getInput().get("query");
        String callId = param.getToolUseBlock() != null ? param.getToolUseBlock().getId() : null;
        String result = String.format("Web search result: %s", query);
        ToolResultBlock build = ToolResultBlock.builder()
                .id(callId)
                .name(getName())
                .output(List.of(TextBlock.builder().text(result).build()))
                .build();
        return Mono.just(build);

    }

}

5. 外部执行 Tool(HITL)

外部执行 Tool 把实际执行委派给 Agent 运行时之外------通常是人工操作员或外部系统 。Agent 调用此类 Tool 时会发出 RequireExternalExecutionEvent 并暂停;下一次调用回传匹配的 ToolResultBlock 后,Agent 发出带相同 replyId 的 ExternalExecutionResultEvent,然后继续执行。

这种模式是 Agent 工作流的基础------某些动作需要人工确认或人工执行。创建外部执行 Tool 只需把 externalTool 设为 true,不必实现 callAsync:

java 复制代码
public class HumanApprovalTool extends ToolBase {
    public HumanApprovalTool() {
        super(ToolBase.builder()
            .name("HumanApproval")
            .description("Request human approval for a sensitive operation.")
            .inputSchema(Map.of(
                "type", "object",
                "properties", Map.of(
                    "action",  Map.of("type", "string"),
                    "reason",  Map.of("type", "string")),
                "required", List.of("action", "reason")))
            .readOnly(false)
            .concurrencySafe(true)
            .externalTool(true));
    }

    @Override
    public Mono<PermissionDecision> checkPermissions(
            Map<String, Object> toolInput, PermissionContextState context) {
        return Mono.just(PermissionDecision.allow("External tool dispatch is always allowed."));
    }
}

Client / 外部系统 Agent Client / 外部系统 Agent #mermaid-svg-kcEBGbcrSg0ctK88{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-kcEBGbcrSg0ctK88 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-kcEBGbcrSg0ctK88 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-kcEBGbcrSg0ctK88 .error-icon{fill:#552222;}#mermaid-svg-kcEBGbcrSg0ctK88 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-kcEBGbcrSg0ctK88 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-kcEBGbcrSg0ctK88 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-kcEBGbcrSg0ctK88 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-kcEBGbcrSg0ctK88 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-kcEBGbcrSg0ctK88 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-kcEBGbcrSg0ctK88 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-kcEBGbcrSg0ctK88 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-kcEBGbcrSg0ctK88 .marker.cross{stroke:#333333;}#mermaid-svg-kcEBGbcrSg0ctK88 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-kcEBGbcrSg0ctK88 p{margin:0;}#mermaid-svg-kcEBGbcrSg0ctK88 .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-kcEBGbcrSg0ctK88 text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-kcEBGbcrSg0ctK88 .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-kcEBGbcrSg0ctK88 .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-kcEBGbcrSg0ctK88 .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-kcEBGbcrSg0ctK88 .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-kcEBGbcrSg0ctK88 #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-kcEBGbcrSg0ctK88 .sequenceNumber{fill:white;}#mermaid-svg-kcEBGbcrSg0ctK88 #sequencenumber{fill:#333;}#mermaid-svg-kcEBGbcrSg0ctK88 #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-kcEBGbcrSg0ctK88 .messageText{fill:#333;stroke:none;}#mermaid-svg-kcEBGbcrSg0ctK88 .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-kcEBGbcrSg0ctK88 .labelText,#mermaid-svg-kcEBGbcrSg0ctK88 .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-kcEBGbcrSg0ctK88 .loopText,#mermaid-svg-kcEBGbcrSg0ctK88 .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-kcEBGbcrSg0ctK88 .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-kcEBGbcrSg0ctK88 .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-kcEBGbcrSg0ctK88 .noteText,#mermaid-svg-kcEBGbcrSg0ctK88 .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-kcEBGbcrSg0ctK88 .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-kcEBGbcrSg0ctK88 .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-kcEBGbcrSg0ctK88 .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-kcEBGbcrSg0ctK88 .actorPopupMenu{position:absolute;}#mermaid-svg-kcEBGbcrSg0ctK88 .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-kcEBGbcrSg0ctK88 .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-kcEBGbcrSg0ctK88 .actor-man circle,#mermaid-svg-kcEBGbcrSg0ctK88 line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-kcEBGbcrSg0ctK88 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Agent 暂停,等待外部执行结果 Agent 带着结果继续推理 RequireExternalExecutionEvent (toolCallId, 参数) 1 人工审批 / 外部系统执行 2 回传匹配的 ToolResultBlock (同 toolCallId) 3 ExternalExecutionResultEvent (同 replyId) 4


6. Context 注入

每次 agent.call(msgs, runtimeContext) 传入的 RuntimeContext 会自动透传到所在 reply 内的每一次工具调用。Tool 拿 Context 有两种方式:

  • 注解式 Tool:按类型自动注入
  • 继承 ToolBase :从 ToolCallParam 里取

6.1 自动注入(@Tool 方法)

@Tool 方法签名里,没有标注 @ToolParam 的参数会被框架视为「需要从框架注入」,按以下优先级解析:

参数类型 注入来源
ToolEmitter 流式中间产物 emitter(无配置时为 no-op)
Agent 当前 Agent 实例
AgentState 当前 call 的 per-session 状态(通过 RuntimeContext.getAgentState() 获取)
RuntimeContext 当前 per-call 上下文
ToolExecutionContext runtimeContext.asToolExecutionContext()(兼容层,已 deprecated)
其它用户自定义 POJO runtimeContext.get(ParamType.class) ------ 调用方在 RuntimeContext.builder().put(...) 注册的对象

「用户自定义 POJO」的判定:参数没有 @ToolParam、不是基本类型、不是 ContentBlock / Msg、不在 java.* / javax.* 包下。其余参数(带 @ToolParam 或属于上述兜底类型)从 LLM 提供的 JSON 输入按名称取值。

java 复制代码
public record UserContext(String username, String locale) {}

public class PersonalizedTools {

    @Tool(name = "greet", description = "Greet the user with a custom greeting")
    public String greet(
            @ToolParam(name = "greeting",
                        description = "Greeting word, e.g. 'Hello'")
            String greeting,              // ← 由模型提供
            UserContext userCtx) {        // ← 由框架自动注入
        return greeting + ", "
            + (userCtx == null ? "unknown" : userCtx.username()) + "!";
    }
}

调用方按类型注册同款 POJO:

java 复制代码
RuntimeContext ctx = RuntimeContext.builder()
    .put(UserContext.class, new UserContext("alice", "en"))
    .userId("alice")
    .build();

agent.call(List.of(new UserMessage("Greet me.")), ctx).block();

模型不需要把 userCtx 写进 JSON 参数------schema 里也不会出现它。

6.2 ToolBase.callAsync 中访问

继承 ToolBase 的 Tool 通过 ToolCallParam 取 Context:

java 复制代码
@Override
public Mono<ToolResultBlock> callAsync(ToolCallParam param) {
    RuntimeContext rc = param.getRuntimeContext();
    String tenantId = rc != null ? rc.getUserId() : null;
    TenantConfig cfg = rc != null ? rc.get(TenantConfig.class) : null;
    // ... 用 tenantId / cfg 执行业务 ...
}

ToolCallParam 同时暴露 getAgent()、getInput()、getEmitter()、getToolUseBlock() 以及(已 deprecated 的)getContext()。新代码使用 getRuntimeContext()。

中间件与 Tool 通信 :RuntimeContext 的 string 层(put(String, Object) / get(String))是同一次 call 内 middleware 与 Tool 之间的临时通信通道------middleware 在 onActing / onReasoning 等位置写入,Tool 通过注入 RuntimeContext 参数读取;调用结束后该实例与 hook 一并解绑。


7. Tool Group 与 Meta Tool

内置 meta tool reset_tools 让 Agent 在运行时自我管理哪些 Tool Group 处于激活状态,从而保持上下文聚焦------只有与当前任务相关的 Tool 暴露给模型。

7.1 定义 Tool Group

ToolGroup 是带名称的 Tool / MCP / Skill 集合。把 group 注册到 Toolkit 后,再用 builder 启用 meta tool:

java 复制代码
Toolkit toolkit = new Toolkit();
toolkit.registerTool(new BasicTools());

ToolGroup database = new ToolGroup(
    "database",
    "Tools for database operations.",
    ToolGroupScope.SESSION,
    /* active = */ false);
database.addTool("db_query");
database.addTool("db_migrate");
toolkit.registerTool(new DatabaseTools());
toolkit.registerToolGroup(database);

ToolGroup deployment = new ToolGroup(
    "deployment",
    "Tools for deploying services.",
    ToolGroupScope.SESSION,
    /* active = */ false);
deployment.addTool("deploy");
deployment.addTool("rollback");
toolkit.registerTool(new DeploymentTools());
toolkit.registerToolGroup(deployment);

ReActAgent agent = ReActAgent.builder()
    .name("router")
    .toolkit(toolkit)
    .enableMetaTool(true)
    .build();

ToolGroup 接收名称、描述、作用域(ToolGroupScope)以及初始激活态。保留名 "basic" 由 Toolkit#registerTool(Object) 自动构成,且始终激活。

7.2 Meta Tool 的运行时行为

只要存在至少一个非 basic 的 Tool Group,并通过 enableMetaTool(true) 打开开关,Toolkit 就会自动注册 reset_tools 并把其 schema 暴露给 Agent。每个非 basic group 在 schema 中表示为一个布尔字段,Agent 调用 meta tool 时声明期望的最终状态。

  • "basic" 组中的 Tool 始终暴露,meta tool 不会影响它们
  • 每次调用 reset_tools 都会整体覆盖 激活集合------任何未显式置为 true 的非 basic group 都会被停用,无论之前的状态
  • 对每个本次切换为激活的 group,其 description 与(若提供的)使用说明会被拼接进 meta tool 的返回值,告诉 Agent 如何正确使用该组
  • 未激活 group 中的 Tool 不会出现在 Agent 的工具 schema 中,从而把上下文留给当前激活的工具集

#mermaid-svg-ZiFFmNVdKQa5jxFl{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-ZiFFmNVdKQa5jxFl .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ZiFFmNVdKQa5jxFl .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ZiFFmNVdKQa5jxFl .error-icon{fill:#552222;}#mermaid-svg-ZiFFmNVdKQa5jxFl .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ZiFFmNVdKQa5jxFl .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ZiFFmNVdKQa5jxFl .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ZiFFmNVdKQa5jxFl .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ZiFFmNVdKQa5jxFl .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ZiFFmNVdKQa5jxFl .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ZiFFmNVdKQa5jxFl .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ZiFFmNVdKQa5jxFl .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ZiFFmNVdKQa5jxFl .marker.cross{stroke:#333333;}#mermaid-svg-ZiFFmNVdKQa5jxFl svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ZiFFmNVdKQa5jxFl p{margin:0;}#mermaid-svg-ZiFFmNVdKQa5jxFl .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-ZiFFmNVdKQa5jxFl .cluster-label text{fill:#333;}#mermaid-svg-ZiFFmNVdKQa5jxFl .cluster-label span{color:#333;}#mermaid-svg-ZiFFmNVdKQa5jxFl .cluster-label span p{background-color:transparent;}#mermaid-svg-ZiFFmNVdKQa5jxFl .label text,#mermaid-svg-ZiFFmNVdKQa5jxFl span{fill:#333;color:#333;}#mermaid-svg-ZiFFmNVdKQa5jxFl .node rect,#mermaid-svg-ZiFFmNVdKQa5jxFl .node circle,#mermaid-svg-ZiFFmNVdKQa5jxFl .node ellipse,#mermaid-svg-ZiFFmNVdKQa5jxFl .node polygon,#mermaid-svg-ZiFFmNVdKQa5jxFl .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ZiFFmNVdKQa5jxFl .rough-node .label text,#mermaid-svg-ZiFFmNVdKQa5jxFl .node .label text,#mermaid-svg-ZiFFmNVdKQa5jxFl .image-shape .label,#mermaid-svg-ZiFFmNVdKQa5jxFl .icon-shape .label{text-anchor:middle;}#mermaid-svg-ZiFFmNVdKQa5jxFl .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-ZiFFmNVdKQa5jxFl .rough-node .label,#mermaid-svg-ZiFFmNVdKQa5jxFl .node .label,#mermaid-svg-ZiFFmNVdKQa5jxFl .image-shape .label,#mermaid-svg-ZiFFmNVdKQa5jxFl .icon-shape .label{text-align:center;}#mermaid-svg-ZiFFmNVdKQa5jxFl .node.clickable{cursor:pointer;}#mermaid-svg-ZiFFmNVdKQa5jxFl .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-ZiFFmNVdKQa5jxFl .arrowheadPath{fill:#333333;}#mermaid-svg-ZiFFmNVdKQa5jxFl .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-ZiFFmNVdKQa5jxFl .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-ZiFFmNVdKQa5jxFl .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ZiFFmNVdKQa5jxFl .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-ZiFFmNVdKQa5jxFl .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ZiFFmNVdKQa5jxFl .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-ZiFFmNVdKQa5jxFl .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-ZiFFmNVdKQa5jxFl .cluster text{fill:#333;}#mermaid-svg-ZiFFmNVdKQa5jxFl .cluster span{color:#333;}#mermaid-svg-ZiFFmNVdKQa5jxFl 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-ZiFFmNVdKQa5jxFl .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-ZiFFmNVdKQa5jxFl rect.text{fill:none;stroke-width:0;}#mermaid-svg-ZiFFmNVdKQa5jxFl .icon-shape,#mermaid-svg-ZiFFmNVdKQa5jxFl .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ZiFFmNVdKQa5jxFl .icon-shape p,#mermaid-svg-ZiFFmNVdKQa5jxFl .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-ZiFFmNVdKQa5jxFl .icon-shape .label rect,#mermaid-svg-ZiFFmNVdKQa5jxFl .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ZiFFmNVdKQa5jxFl .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-ZiFFmNVdKQa5jxFl .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-ZiFFmNVdKQa5jxFl :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-ZiFFmNVdKQa5jxFl .startNode>*{fill:#FFF4E6!important;stroke:#E67E22!important;stroke-width:2px!important;color:#333!important;}#mermaid-svg-ZiFFmNVdKQa5jxFl .startNode span{fill:#FFF4E6!important;stroke:#E67E22!important;stroke-width:2px!important;color:#333!important;}#mermaid-svg-ZiFFmNVdKQa5jxFl .startNode tspan{fill:#333!important;}#mermaid-svg-ZiFFmNVdKQa5jxFl .procNode>*{fill:#E8F4FD!important;stroke:#2980B9!important;stroke-width:1px!important;color:#333!important;}#mermaid-svg-ZiFFmNVdKQa5jxFl .procNode span{fill:#E8F4FD!important;stroke:#2980B9!important;stroke-width:1px!important;color:#333!important;}#mermaid-svg-ZiFFmNVdKQa5jxFl .procNode tspan{fill:#333!important;}#mermaid-svg-ZiFFmNVdKQa5jxFl .decNode>*{fill:#FFF8DC!important;stroke:#B8860B!important;stroke-width:1px!important;color:#333!important;}#mermaid-svg-ZiFFmNVdKQa5jxFl .decNode span{fill:#FFF8DC!important;stroke:#B8860B!important;stroke-width:1px!important;color:#333!important;}#mermaid-svg-ZiFFmNVdKQa5jxFl .decNode tspan{fill:#333!important;}#mermaid-svg-ZiFFmNVdKQa5jxFl .doneNode>*{fill:#E8F8E9!important;stroke:#27AE60!important;stroke-width:2px!important;color:#333!important;}#mermaid-svg-ZiFFmNVdKQa5jxFl .doneNode span{fill:#E8F8E9!important;stroke:#27AE60!important;stroke-width:2px!important;color:#333!important;}#mermaid-svg-ZiFFmNVdKQa5jxFl .doneNode tspan{fill:#333!important;} 不需要
需要
Agent 接到任务
Toolkit 暴露 schema:

basic 组(始终)+ 当前激活 group + reset_tools
Agent 决定是否需要

切换 group?
调用 reset_tools

声明各 group 最终 true/false
Toolkit 整体覆盖激活集合

把新激活 group 的说明返回给 Agent
Agent 在激活的 Tool 集合上

继续推理 / 调用
任务完成

注意 :meta tool 的输入表示所有 group 的最终状态 而非增量。任何未显式置为 true 的 group 都会被停用,无论之前的状态。


8. 最小可运行示例

把上面三块拼起来------一个带注解式 Tool、一个外部审批 Tool、一个 Tool Group 的完整装配:

java 复制代码
import io.agentscope.core.ReActAgent;
import io.agentscope.core.message.UserMessage;
import io.agentscope.core.tool.ToolGroup;
import io.agentscope.core.tool.ToolGroupScope;
import io.agentscope.core.tool.Toolkit;

public class ToolSystemQuickstart {

    public static void main(String[] args) {
        Toolkit toolkit = new Toolkit();

        // 1) basic 组:始终激活
        toolkit.registerTool(new SimpleTools());          // get_current_time
        toolkit.registerTool(new TodoTools());

        // 2) 非 basic 组:默认停用,通过 meta tool 切换
        ToolGroup database = new ToolGroup(
            "database", "DB query & migrate",
            ToolGroupScope.SESSION, false);
        database.addTool("db_query");
        toolkit.registerTool(new DatabaseTools());
        toolkit.registerToolGroup(database);

        // 3) 外部执行 Tool:敏感动作走人工审批
        toolkit.registerTool(new HumanApprovalTool());

        ReActAgent agent = ReActAgent.builder()
            .name("assistant")
            .toolkit(toolkit)
            .enableMetaTool(true)        // 自动注册 reset_tools
            .build();

        RuntimeContext ctx = RuntimeContext.builder()
            .userId("alice")
            .build();

        agent.call(
            List.of(new UserMessage("现在上海几点?")),
            ctx).block();
    }
}
相关推荐
旺仔学长 哈哈1 小时前
springboot钓鱼爱好者交流平台APP设计与实现
java·spring boot·mysql·充电桩管理系统
运维开发王义杰1 小时前
从 Completions 到 Responses:OpenAI 接口规范演进与开源兼容真相
ai
Spcarrydoinb1 小时前
【无标题】
ai·agent
茉莉玫瑰花茶2 小时前
GO [ 方法 ]
开发语言·后端·golang
xcLeigh2 小时前
AI 编程的未来趋势:2025-2026 年你必须关注的六大技术方向
人工智能·ai·ai编程
小燕子~~2 小时前
Photoshop2026新版 AI 功能实测,看这一篇就够了
图像处理·人工智能·ai·aigc·photoshop
狗凯之家源码网2 小时前
微博图床 PHP 系统搭建与实战应用指南
android·开发语言·php
自强的小白3 小时前
核心功能(Service接口)
java·mybatis