游乐游手机版
首页/AI教程/文章详情

AgentScope Java新手村第三篇:工具系统教程

时间:2026-06-12 15:46
AgentScopeJava工具系统利用@Tool注解将普通Java方法动态注册为Agent扩展,实现数据库查询、API调用等操作。Agent可自主判断调用时机,支持多种返回类型与工具分组管理,有效提升推理灵活性与开发效率。

第三章 工具系统与 @Tool 注解:让 Agent 自主决定调用哪个 Ja va 方法

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

【AgentScope Ja va新手村系列】(3)工具系统

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";
    }
}

这里有几个关键点需要留意:

  • @Toolname 属性是工具的唯一标识,Agent 调用时用的就是这个名称
  • @Tooldescription 属性描述工具的功能,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 工具描述的重要性

这里需要特别强调一点:工具的 namedescription 是 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 应用就会顺手很多。

来源:https://developer.aliyun.com/article/1740846
上一篇AI搜索时代内容革命:从SEO到GEO优化的全面解析 下一篇Go slices.Move新提案:一次移动胜过两次删除插入
本站内容用于信息整理与展示,如有侵权或内容问题请及时联系处理。

相关推荐

补充同频道和同主题内容,方便继续浏览更多相关内容。

同类最新

继续查看同栏目最近更新的文章。

更多
Windows Docker Desktop RabbitMQ生产级部署完整指南
AI教程 · 2026-06-29

Windows Docker Desktop RabbitMQ生产级部署完整指南

前言 在 Windows 本地开发环境中,直接安装 RabbitMQ 确实颇为周折:需要单独配置 Erlang 运行环境、手动管理环境变量、服务启停全凭手工操作。更令人困扰的是,版本兼容冲突、端口占用、环境不一致等问题层出不穷。笔者见过不少开发者为搭建环境就得耗费整整半天时间。 相比之下,借助 Do

AI搜索重构制造业采购逻辑的阿里云企业级GEOCMS优化实践
AI教程 · 2026-06-29

AI搜索重构制造业采购逻辑的阿里云企业级GEOCMS优化实践

先分享一个切实感受。过去两年,我们与福建制造企业合作较为频繁,发现一个非常突出的现象:超过80%的企业官网,产品参数仍然存放在PDF或图片中。AI爬虫?根本无法抓取。这些企业技术实力不弱、资质证照齐全、应用案例也丰富,但在AI搜索这一全新战场上,它们几乎处于隐身状态。 一、一个正在发生的行业变化 A

阿里云Token Plan团队版功能价格与省钱购买指南
AI教程 · 2026-06-29

阿里云Token Plan团队版功能价格与省钱购买指南

阿里云百炼近期推出了名为“Token Plan 团队版”的全新服务,这一服务专为企业与开发者量身打造,定位为AI大模型订阅平台。通过引入Credits作为统一计量单位,将文本生成、图像生成等多模态AI能力纳入单一计费体系,同时无缝兼容主流AI编程工具及智能体(Agent)生态系统。其核心亮点包括:全

阿里云物联网.NET Core客户端位置信息上报
AI教程 · 2026-06-29

阿里云物联网.NET Core客户端位置信息上报

阿里云物联网平台的位置服务并非一个完全独立的功能模块。位置信息可包含二维坐标与三维坐标,而位置数据的来源本质上是借助设备属性进行上传。换言之,若要让设备上报位置,您需先将其视为一个普通属性进行处理。 1)添加二维位置数据 操作过程十分简洁。进入数据分析 → 空间数据可视化 → 二维数据,点击添加,将

年阿里云服务器选型配置与网站部署全攻略
AI教程 · 2026-06-29

年阿里云服务器选型配置与网站部署全攻略

2026年,阿里云服务器生态已高度成熟,形成了清晰的轻量应用服务器与ECS云服务器两大产品阵营。无论你是计划搭建个人博客、企业官网,还是运营电商平台、进行应用开发,基本都能找到理想的解决方案。本指南将从服务器选型、配置选择、部署流程到安全运维,系统梳理2026年最实用的操作要点,帮助你少走弯路,让网