AgentScope

AgentScope Java特性:工具集成

我们最开始讲ASJ的时候的case中就演示了使用Toolkit来实现工具的集成,工具使用是Agent的必备技能。我们这一节再展开介绍下ASJ种的工具集成的相关能力和实现。 工具定义的方式 基于@Tool注解 在ASJ中,工具定义的方…

TL;DR

我们最开始讲ASJ的时候的case中就演示了使用Toolkit来实现工具的集成,工具使用是Agent的必备技能。我们这一节再展开介绍下ASJ种的工具集成的相关能力和实现。 工具定义的方式 基于@Tool注解 在ASJ中,工具定义的方…

我们最开始讲ASJ的时候的case中就演示了使用Toolkit来实现工具的集成,工具使用是Agent的必备技能。我们这一节再展开介绍下ASJ种的工具集成的相关能力和实现。

工具定义的方式

基于@Tool注解

在ASJ中,工具定义的方式有很多种,我们前面演示过最简单的基于注解的方式:

public class SimpleTools {
    @Tool(name = "get_time", description = "获取当前时间")
    public String getTime(
            @ToolParam(name = "zone", description = "时区,例如:北京") String zone) {
        return java.time.LocalDateTime.now()
                .format(java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
    }
}

使用@Tool和 @ToolParam组合,来定义工具。@Tool用来声明一个具体的工具,用在方发生,@ToolParam用来定义工具的参数。 方法返回值可以是 String、Mono、ToolResultBlock、Mono,或任意可 JSON 序列化的对象(框架自动用 DefaultToolResultConverter 转为 JSON)

实现 AgentTool 接口

除了基于注解外,我们还可以通过实现AgentTool 接口的方式来定义一个工具:

public interface AgentTool {


    String getName();


    String getDescription();


    Map<String, Object> getParameters();


    default Map<String, Object> getOutputSchema() {
        return null;
    }

    Mono<ToolResultBlock> callAsync(ToolCallParam param);
}

这几个方法看名字就知道是干嘛的了。需要注意的是getParameters这个方法,很多人会不知道该怎么写,我们可以通过内置的工具实现看看他如何定义,如ShellCommandTool中的实现。基本需要以下格式和字段:

    @Override
    public Map<String, Object> getParameters() {
        return Map.of(
            "type", "object",
            "properties", Map.of(
                "command", Map.of("type", "string", "description", "The shell command to execute")
            ),
            "required", List.of("sql")
        );
    }

这个 Map 最终序列化为 JSON 放入 LLM API 请求的 tools[].function.parameters。模型会据此约束自己生成的参数。如果使用 strict = true,模型严格按照 schema 生成(无多余字段、类型完全匹配)。 和 @Tool 注解方式的对比:注解式由框架通过反射+@ToolParam 自动生成这个 Map;接口式需要你手动构造,但获得了完全的自由度——你可以做动态 schema(比如根据运行时状态决定有哪些参数)。 还有getOutputSchema()这个方法,用来定义输出的schema的,大多数工具不需要定义输出 schema。这个方法主要为 MCP 工具设计——MCP 协议允许 server 声明工具的输出结构。框架里 McpTool 覆写了此方法来暴露 MCP server 提供的 outputSchema。 callAsync(ToolCallParam param)这个方法就是执行的入口了。这个是天然异步的接口。

内置的工具

ASJ中内置了一些工具,可以供开发者直接使用,这些工具的定义也分别使用了上面的工具定义的方式。有使用注解的,也有实现AgentTool接口的。这些工具定义在io.agentscope.core.tool下面。 - ReadFileTool/WriteFileTool - 通过注解实现,实现文件的读写功能。 - ShellCommandTool - 基于AgentTool接口实现,实现shell命令的执行。 - SubAgentTool - 基于AgentTool接口实现,提供把子agent当做tool的能力 - OpenAiMultiModalTool/DashScopeMultiModalTool - 通过注解实现,提供多模态工具,提供文生图、图生文、文生视频、文本转语音、语音转文本、视频理解等能力。 - McpTool - 基于AgentTool接口实现,作为MCP的协议桥接,通过该工具调用远程的服务。 - SchemaOnlyTool - 基于AgentTool接口实现。callAsync直接抛ToolSuspendException异常,让模型知道某个工具的存在(从而可以决定调用它),但工具的实际执行不在框架内发生。用来实现HIL、外部审批等。 (另外,还有一些工具不在这个tool包下,但是也算默认实现,比如后面我们RAG这里需要用到的KnowledgeRetrievalTools) AgentTool这种形式更加灵活,比如用在Schema不确定的场景,如McpTool 的参数来自远程 MCP Server——你根本没法在编译时写注解,因为参数是什么取决于对面那个进程。所以它必须自己实现 getParameters(),把远端拿到的 inputSchema 原样返回。

注册工具到 Toolkit

有了工具定义之后,需要通过ToolKit来包装工具,然后把ToolKit传给Agent

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

ReActAgent jarvis = ReActAgent.builder()
                .toolkit(toolkit)
                .build();

工具组

工具多了之后,一次性全塞给模型会导致 schema 过长、模型选错工具。Tool Group 的设计是:把工具按功能分组,只有active 状态的组才对模型可见。利用工具组可以实现: - 权限控制:根据用户角色激活不同工具 - 场景切换:不同对话阶段使用不同工具集 - 性能优化:减少 LLM 可见的工具数量

// 创建分组(默认 active=true)
toolkit.createToolGroup("file_ops", "File system operations", false);  // 初始不激活
toolkit.createToolGroup("math_ops", "Math calculations", false);

// 注册工具到分组
toolkit.registration().tool(new FileTools()).group("file_ops").apply();
toolkit.registration().tool(new MathTools()).group("math_ops").apply();

// 运行时激活/停用
toolkit.updateToolGroups(List.of("file_ops"), true);   // 激活
toolkit.updateToolGroups(List.of("math_ops"), false);  // 停用

工具执行上下文 —— ToolExecutionContext

如果在调用工具的时候,有一些参数想要直接传给工具,而如果不需要模型来决策参数传递的话,可以用ToolExecutionContext。典型用途是:注入当前用户信息、数据库连接、Session 上下文。

// 定义上下文对象
public class UserContext {
    private String userId;
    private String role;
    // getters...
}

// 注册上下文
ToolExecutionContext context = ToolExecutionContext.builder()
    .register(new UserContext("user_123", "admin"))
    .build();

// 绑定到 toolkit
Toolkit toolkit = new Toolkit(ToolkitConfig.builder()
    .defaultContext(context)
    .build());

// 工具方法中通过类型自动注入
@Tool(name = "get_profile")
public String getProfile(UserContext ctx) {  // ★ 框架自动注入,不在 schema 里
    return "User: " + ctx.getUserId() + ", Role: " + ctx.getRole();
}

工具流式进度 —— ToolEmitter

长如果遇到耗时工具可以在执行过程中发射中间进度,前端 UI 或监控 Hook 可以实时展示。注意:emit 的内容不进入模型上下文,只有最终 return 值才喂给 LLM。

@Tool(name = "analyze_data", description = "Analyze large dataset")
public String analyzeData(
        @ToolParam(name = "dataset") String dataset,
        ToolEmitter emitter) {   // ★ 框架自动注入,不需要 @ToolParam

    emitter.emit(ToolResultBlock.text("Loading dataset..."));
    loadData(dataset);

    emitter.emit(ToolResultBlock.text("Processing 50%..."));
    processHalf();

    emitter.emit(ToolResultBlock.text("Processing 100%..."));
    processAll();

    return "Analysis complete: 1000 records processed, 3 anomalies found.";
}

工具调用流程

用户消息 → ReActAgent.call()
    │
    ├─ 1. 构造 messages + tools schema → 送模型
    │
    ├─ 2. 模型返回 ToolUseBlock(可能多个)
    │       {name: "get_weather", input: {city: "Beijing"}}
    │
    ├─ 3. Toolkit.callTools(toolUseBlocks, config, agent, context)
    │       │
    │       ├─ ToolExecutor.executeAll()
    │       │   ├─ 并行/串行分发
    │       │   ├─ 每个 tool: 合并预设参数 → 注入上下文 → callAsync()
    │       │   ├─ 超时/重试由 ExecutionConfig 控制
    │       │   └─ 返回 List<ToolResultBlock>
    │       │
    │       └─ ToolResultBlock 回填到 memory(作为 ToolResultBlock 消息)
    │
    ├─ 4. 继续推理(带工具结果的 messages → 模型)
    │
    └─ 5. 模型生成最终回复 或 继续调用更多工具(循环,受 maxIters 限制)
版本提示

模型、框架与接口会持续变化。涉及版本号、参数与生产配置时,请在实践前对照对应官方文档。

LLMentor系统化学习大模型应用工程

内容来自个人课程知识库备份,并经过结构化整理。技术版本持续演进,生产使用前请结合官方文档验证。