第三章 工具系统与 @Tool 注解:让 Agent 自主决定调用哪个 Ja va 方法
工具,说白了就是 Agent 与外部世界打交道的手段。有了工具,Agent 就能去查数据库、调 API、做计算、读写文件、搜索网络——这些事它自己原本干不了,但通过工具就能搞定。而且最关键的是,Agent 在推理过程中会自己判断要不要用工具、用哪个工具,而不是我们替它做决定。

3.1 什么是工具
在 AgentScope 里,工具(Tool)就是给 Agent 配的“外设能力”。通过给 Agent 配备工具,它可以做到:
- 查询数据库
- 调用外部 API
- 执行各种计算
- 读写文件
- 搜索网络
3.2 @Tool 注解
把一个普通的 Ja va 方法注册成 Agent 能用的工具,只需要加一个 @Tool 注解。来看这个例子:
import io.agentscope.core.tool.Tool;
import io.agentscope.core.tool.ToolParam;
public class MyTools {
@Tool(name = "get_current_time", description = "获取指定时区的当前时间")
public String getCurrentTime(
@ToolParam(name = "timezone", description = "时区名称,例如 'Asia/Shanghai'")
String timezone) {
// 实现逻辑
return "Current time in " + timezone + ": 2024-01-01 12:00:00";
}
}
这里有几个关键点需要留意:
@Tool的name属性是工具的唯一标识,Agent 调用时用的就是这个名称@Tool的description属性描述工具的功能,Agent 根据这段描述决定什么时候调用它@ToolParam标注在方法参数上,描述参数的含义- 方法的返回值会作为工具的执行结果返回给 Agent
另外值得一提的是,2.0 完全兼容 1.x 的 @Tool / @ToolParam 注解与 Toolkit.registerTool(...) 注册方式。HarnessAgent 在工作区模式下还支持通过 workspace/tools.json 声明 MCP server 和工具白名单——这部分详细内容我们放到第十五章展开。
3.3 注册工具
有了工具类之后,下一步就是把它注册到 Agent 里。先创建一个 Toolkit 实例,然后把工具对象注册进去:
Toolkit toolkit = new Toolkit();
toolkit.registerTool(new MyTools());
再把 Toolkit 传给 Agent 就行了:
import io.agentscope.core.ReActAgent;
import io.agentscope.core.tool.Toolkit;
// 纯 ReActAgent 场景
ReActAgent agent = ReActAgent.builder()
.name("Assistant")
.sysPrompt("你是一个可以使用工具的助手。")
.model(model)
.toolkit(toolkit)
.build();
// 或 HarnessAgent 场景
HarnessAgent agent = HarnessAgent.builder()
.name("Assistant")
.sysPrompt("你是一个可以使用工具的助手。")
.model(model)
.workspace(Path.of("./workspace"))
.toolkit(toolkit) // 工具集可以和工作区并存
.build();
3.4 完整示例:带工具的 Agent
下面是一个完整的可运行示例,你可以直接拿去测试:
package com.example;
import io.agentscope.core.ReActAgent;
import io.agentscope.core.agent.RuntimeContext;
import io.agentscope.core.formatter.openai.OpenAIChatFormatter;
import io.agentscope.core.message.UserMessage;
import io.agentscope.core.model.OpenAIChatModel;
import io.agentscope.core.tool.Tool;
import io.agentscope.core.tool.ToolParam;
import io.agentscope.core.tool.Toolkit;
import ja va.time.LocalDateTime;
import ja va.time.ZoneId;
import ja va.time.format.DateTimeFormatter;
public class ToolCallingExample {
public static void main(String[] args) {
String apiKey = System.getenv("DEEPSEEK_API_KEY");
// 创建工具集并注册工具
Toolkit toolkit = new Toolkit();
toolkit.registerTool(new SimpleTools());
ReActAgent agent = ReActAgent.builder()
.name("ToolAgent")
.sysPrompt("你是一个可以使用工具的助手。"
+ "在需要时使用工具来准确回答问题。"
+ "每次使用工具时请解释你在做什么。")
.model(OpenAIChatModel.builder()
.apiKey(apiKey)
.modelName("deepseek-reasoner")
.baseUrl("https://api.deepseek.com")
.stream(true)
.formatter(new OpenAIChatFormatter())
.build())
.toolkit(toolkit)
.build();
// 测试工具调用
String reply = agent
.call(new UserMessage("现在北京几点了?"), RuntimeContext.empty())
.block()
.getTextContent();
System.out.println(reply);
}
/**
* 工具类:每个带 @Tool 注解的方法都会被注册为一个工具
*/
public static class SimpleTools {
@Tool(name = "get_current_time",
description = "获取指定时区的当前时间")
public String getCurrentTime(
@ToolParam(name = "timezone",
description = "时区名称,例如 'Asia/Shanghai'、'America/New_York'")
String timezone) {
try {
ZoneId zoneId = ZoneId.of(timezone);
LocalDateTime now = LocalDateTime.now(zoneId);
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
return String.format("Current time in %s: %s", timezone, now.format(formatter));
} catch (Exception e) {
return "Error: Invalid timezone. Try 'Asia/Shanghai' or 'America/New_York'";
}
}
@Tool(name = "calculate",
description = "计算简单的数学表达式")
public String calculate(
@ToolParam(name = "expression",
description = "要计算的数学表达式,例如 '123 + 456'、'10 * 20'")
String expression) {
try {
expression = expression.replaceAll("\\s+", "");
double result;
if (expression.contains("+")) {
String[] parts = expression.split("\\+");
result = Double.parseDouble(parts[0]) + Double.parseDouble(parts[1]);
} else if (expression.contains("-")) {
String[] parts = expression.split("-");
result = Double.parseDouble(parts[0]) - Double.parseDouble(parts[1]);
} else if (expression.contains("*")) {
String[] parts = expression.split("\\*");
result = Double.parseDouble(parts[0]) * Double.parseDouble(parts[1]);
} else if (expression.contains("/")) {
String[] parts = expression.split("/");
result = Double.parseDouble(parts[0]) / Double.parseDouble(parts[1]);
} else {
return "Error: Unsupported operation. Use +, -, *, or /";
}
return String.format("%s = %.2f", expression, result);
} catch (Exception e) {
return "Error: Invalid expression. Example: '123 + 456'";
}
}
@Tool(name = "search",
description = "在网络上搜索信息")
public String search(
@ToolParam(name = "query", description = "搜索关键词")
String query) {
// 这里模拟搜索结果,实际项目中可以接入真实搜索 API
return "Search results for '" + query + "':"
+ "\n1. Result about " + query
+ "\n2. More information on " + query;
}
}
}
运行这个示例后,向 Agent 提问"现在北京几点了?",Agent 会执行一个完整的推理链条:首先判断需要获取北京时间,然后决定调用 get_current_time 工具并把参数设为 Asia/Shanghai,执行工具拿到结果后,再把结果整理成自然语言返回给用户。
3.5 工具的返回类型
工具方法的返回值会自动转换为字符串传给 Agent,支持以下几种返回类型:
// 1. 直接返回字符串
@Tool(name = "echo")
public String echo(String input) {
return "Echo: " + input;
}
// 2. 返回对象(自动序列化为 JSON)
@Tool(name = "get_user")
public Map getUser(String userId) {
return Map.of("id", userId, "name", "Alice", "age", 30);
}
// 3. 返回 void(Agent 会收到空结果)
@Tool(name = "log")
public void log(String message) {
System.out.println("[LOG] " + message);
}
// 4. 异步返回 Mono
@Tool(name = "async_task")
public Mono asyncTask(String input) {
return Mono.fromCallable(() -> ToolResultBlock.text("Async result: " + input));
}
3.6 工具分组
当工具数量多起来之后,分组管理就很有必要了。2.0 继续支持 1.x 的元工具机制:
Toolkit toolkit = new Toolkit();
// 注册工具到不同分组
toolkit.registration().tool(new WeatherTools()).group("weather").apply();
toolkit.registration().tool(new MathTools()).group("math").apply();
// 创建工具分组(默认全部激活)
toolkit.createToolGroup("weather", "天气工具", true);
toolkit.createToolGroup("math", "数学工具", true);
// 注册元工具,让 Agent 可以自主切换工具组
toolkit.registerMetaTool();
注册元工具之后,Agent 会获得一个 reset_equipped_tools 工具,可以自主激活或停用某些工具组。这个机制在工具数量特别多的时候非常实用,因为它能减少发送给 LLM 的工具描述数量,避免上下文被撑爆。
3.7 子 Agent 作为工具(1.x 风格)
下面这个是 1.x 风格的兼容写法,不推荐新代码使用:
// 1.x:把专家 Agent 注册为主 Agent 的工具
Toolkit mainToolkit = new Toolkit();
mainToolkit.registration().subAgent(() -> expertAgent).apply();
2.0 的推荐写法是通过工作区配置文件来定义子 Agent:
---
description: 数据分析专家。当用户要做统计分析、可视化、数据清洗时使用。
model: openai:gpt-4o-mini
---
你是一个数据分析专家。请按以下流程工作:
1. 先用 read_file / grep_files 收集数据
2. 做必要的统计与可视化
3. 给出业务结论
主 Agent 在推理时直接调用 agent_spawn agent_id="data-analyst" task="...",框架会自动加载并执行子 Agent,结果以 TOOL_RESULT 块回给主 Agent。这种方式更灵活,也更容易维护。
3.8 工具描述的重要性
这里需要特别强调一点:工具的 name 和 description 是 Agent 决定是否调用它的唯一依据。好的描述应该做到:
- 明确功能边界:说清楚工具能做什么、不能做什么
- 包含使用场景:告诉 Agent 什么时候应该调用这个工具
- 参数说明清晰:每个参数的含义、格式、取值范围都要讲明白
反面示例——这种描述等于没写:
@Tool(name = "do_stuff", description = "做事情")
public String doStuff(String input) { ... }
正面示例——明确的描述能让 Agent 精准决策:
@Tool(name = "get_weather",
description = "获取指定城市的当前天气信息。" +
"返回温度、湿度和天气状况。" +
"当用户询问某个地点的天气时使用此工具。")
public String getWeather(
@ToolParam(name = "city",
description = "城市英文名称,例如 'Beijing'、'New York'")
String city) { ... }
3.9 工具执行配置
实际生产环境中,工具调用可能会遇到超时或失败的情况。可以为工具执行配置超时时间和重试次数:
import io.agentscope.core.model.ExecutionConfig;
ReActAgent agent = ReActAgent.builder()
.name("Assistant")
.model(model)
.toolkit(toolkit)
.toolExecutionConfig(ExecutionConfig.builder()
.timeout(Duration.ofSeconds(30))
.maxRetries(2)
.build())
.build();
3.10 2.0 增量:工具在 Harness 中的可见性
HarnessAgent 默认会把 Toolkit 里的所有工具暴露给 LLM——这和 1.x 的行为一致。如果你需要做更精细的权限控制,2.0 提供了两条路径:
- workspace/tools.json:声明 MCP server + 工具粒度白名单/黑名单(推荐)
- Middleware:在
onActing/onModelCall钩子里改写工具列表
// workspace/tools.json
{
"mcpServers": [
{
"name": "github",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": {"GITHUB_TOKEN": "${env:GITHUB_TOKEN}" },
"enabled": true
}
],
"toolFilter": {
"mode": "allowlist",
"tools": ["read_file", "write_file", "grep_files", "github__*"]
}
}
RC2 新增了一个实用的特性:ToolCallParam.builder(original) 可以从已有参数创建副本并覆盖特定字段:
ToolCallParam param = ToolCallParam.builder(original).input(newInput).build();
这个功能在 Middleware 或 Hook 中需要拦截并修改工具调用参数时非常有用——不用从头构建整个 ToolCallParam。
3.11 实际应用场景
工具系统的应用场景非常广泛,基本上你想让 Agent 做什么,就能给它配什么工具:
| 场景 | 工具示例 |
|---|---|
| 信息查询 | 天气查询、股票查询、知识库搜索 |
| 数据处理 | 数据库查询、文件读写、格式转换 |
| 外部交互 | 发送邮件、创建日程、调用第三方 API |
| 计算推理 | 数学计算、统计分析、数据可视化 |
| 系统操作 | 创建文件、执行命令、管理进程 |
在实际项目中,工具往往是 Agent 与业务系统之间的桥梁。Agent 通过工具获取实时数据、执行具体操作,从而完成那些看起来挺复杂的任务。理解了工具系统的设计思路,后面构建真正可用的 Agent 应用就会顺手很多。
