当前位置: 首页
AI教程
AgentScope Java新手村第三篇:工具系统教程

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

热心网友 时间:2026-06-12
转载

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

游乐网为非赢利性网站,所展示的游戏/软件/文章内容均来自于互联网或第三方用户上传分享,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系youleyoucom@outlook.com。

同类文章
更多
Obsidian Copilot新手入门安装保姆级教程:从下载到首次运行

Obsidian Copilot新手入门安装保姆级教程:从下载到首次运行

ObsidianCopilot是面向Obsidian的AI笔记插件,可实现问答、总结、改写和知识库检索。安装前需准备软件、插件权限和模型密钥,并注意隐私、费用与兼容性。

时间:2026-07-11 06:41
Obsidian Copilot安装失败?报错日志排查及升级回滚指南

Obsidian Copilot安装失败?报错日志排查及升级回滚指南

ObsidianCopilot安装失败多与插件源、版本兼容、网络请求、配置文件或模型密钥有关。排查时应先备份库,再检查插件目录、控制台日志、manifest信息与接口配置,必要时可手动安装、降级或回滚到稳定版本。

时间:2026-07-11 06:40
Obsidian Copilot本地模型部署与性能优化完整指南

Obsidian Copilot本地模型部署与性能优化完整指南

ObsidianCopilot可通过Ollama、LMStudio等方式接入本地模型,实现离线式笔记问答、摘要和写作辅助。配置重点在模型选择、保存路径、接口地址、上下文参数与硬件性能匹配。

时间:2026-07-11 06:40
Obsidian Copilot插件安装与配置完整指南

Obsidian Copilot插件安装与配置完整指南

ObsidianCopilot可为本地笔记加入AI问答、续写、总结和资料检索能力。安装前需准备最新版Obsidian、启用社区插件,并妥善配置模型服务、索引范围与权限,避免密钥泄露和笔记内容误传。

时间:2026-07-11 06:39
详细Obsidian Copilot API密钥配置教程:账号注册与国内网络设置

详细Obsidian Copilot API密钥配置教程:账号注册与国内网络设置

ObsidianCopilot需要先完成插件安装、模型服务账号注册与APIKey填写。配置时应关注密钥权限、接口地址、模型名称和国内网络连通性,并通过测试问答确认可用。

时间:2026-07-11 06:39
热门专题
更多
刀塔传奇破解版无限钻石下载大全 刀塔传奇破解版无限钻石下载大全
洛克王国正式正版手游下载安装大全 洛克王国正式正版手游下载安装大全
思美人手游下载专区 思美人手游下载专区
好玩的阿拉德之怒游戏下载合集 好玩的阿拉德之怒游戏下载合集
不思议迷宫手游下载合集 不思议迷宫手游下载合集
百宝袋汉化组游戏最新合集 百宝袋汉化组游戏最新合集
jsk游戏合集30款游戏大全 jsk游戏合集30款游戏大全
宾果消消消原版下载大全 宾果消消消原版下载大全
  • 热门数据榜