目录
- [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_toolsmeta 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();
}
}