The tool system enables agents to perform external operations such as API calls, database queries, file operations, etc.
- Annotation-Based: Quickly define tools using
@Tooland@ToolParam - Reactive Programming: Native support for
Mono/Fluxasync execution - Auto Schema: Automatically generate JSON Schema for LLM understanding
- Tool Groups: Dynamically activate/deactivate tool collections
- Preset Parameters: Hide sensitive parameters (e.g., API Keys)
- Parallel Execution: Support parallel invocation of multiple tools
public class WeatherService {
@Tool(description = "Get weather for a specified city")
public String getWeather(
@ToolParam(name = "city", description = "City name") String city) {
return city + " weather: Sunny, 25°C";
}
}Note: The
nameattribute of@ToolParamis required because Java doesn't preserve parameter names by default.
Toolkit toolkit = new Toolkit();
toolkit.registerTool(new WeatherService());
ReActAgent agent = ReActAgent.builder()
.name("Assistant")
.model(model)
.toolkit(toolkit)
.build();Return results directly, suitable for quick operations:
@Tool(description = "Calculate sum of two numbers")
public int add(
@ToolParam(name = "a", description = "First number") int a,
@ToolParam(name = "b", description = "Second number") int b) {
return a + b;
}Return Mono<T> or Flux<T>, suitable for I/O operations:
@Tool(description = "Async search")
public Mono<String> search(
@ToolParam(name = "query", description = "Search query") String query) {
return webClient.get()
.uri("/search?q=" + query)
.retrieve()
.bodyToMono(String.class);
}Use ToolEmitter to send intermediate progress, suitable for long-running tasks (progress is only visible to Hooks, not sent to LLM):
@Tool(description = "Generate data")
public ToolResultBlock generate(
@ToolParam(name = "count") int count,
ToolEmitter emitter) { // Auto-injected, no @ToolParam needed
for (int i = 0; i < count; i++) {
emitter.emit(ToolResultBlock.text("Progress " + i));
}
return ToolResultBlock.text("Completed");
}| Return Type | Description |
|---|---|
String, int, Object, etc. |
Sync execution, auto-converted to ToolResultBlock |
Mono<T> |
Async execution |
Flux<T> |
Streaming execution |
ToolResultBlock |
Direct control over return format (text, image, error, etc.) |
Manage tools by scenario with dynamic activation/deactivation:
// Create tool groups
toolkit.createToolGroup("basic", "Basic Tools", true); // Active by default
toolkit.createToolGroup("admin", "Admin Tools", false); // Inactive by default
// Register to tool groups
toolkit.registration()
.tool(new BasicTools())
.group("basic")
.apply();
// Dynamic switching
toolkit.updateToolGroups(List.of("admin"), true); // Activate
toolkit.updateToolGroups(List.of("basic"), false); // DeactivateUse Cases:
- Permission control: Activate different tools based on user roles
- Scenario switching: Use different tool sets at different conversation stages
- Performance optimization: Reduce the number of tools visible to LLM
Hide sensitive parameters (e.g., API Key) from LLM:
public class EmailService {
@Tool(description = "Send email")
public String send(
@ToolParam(name = "to") String to,
@ToolParam(name = "subject") String subject,
@ToolParam(name = "apiKey") String apiKey) { // Preset, invisible to LLM
return "Sent";
}
}
toolkit.registration()
.tool(new EmailService())
.presetParameters(Map.of(
"send", Map.of("apiKey", System.getenv("EMAIL_API_KEY"))
))
.apply();Effect: LLM only sees to and subject parameters; apiKey is auto-injected.
Pass business objects (e.g., user info) to tools without exposing to LLM:
// 1. Define context class
public class UserContext {
private final String userId;
public UserContext(String userId) { this.userId = userId; }
public String getUserId() { return userId; }
}
// 2. Register to Agent
ToolExecutionContext context = ToolExecutionContext.builder()
.register(new UserContext("user-123"))
.build();
ReActAgent agent = ReActAgent.builder()
.name("Assistant")
.model(model)
.toolkit(toolkit)
.toolExecutionContext(context)
.build();
// 3. Use in tools (auto-injected)
@Tool(description = "Query user data")
public String query(
@ToolParam(name = "sql") String sql,
UserContext ctx) { // Auto-injected, no @ToolParam needed
return "Data for user " + ctx.getUserId();
}See Agent documentation for detailed configuration.
import io.agentscope.core.tool.file.ReadFileTool;
import io.agentscope.core.tool.file.WriteFileTool;
// Basic registration
toolkit.registerTool(new ReadFileTool());
toolkit.registerTool(new WriteFileTool());
// Secure mode (recommended): Restrict file access scope
toolkit.registerTool(new ReadFileTool("/safe/workspace"));
toolkit.registerTool(new WriteFileTool("/safe/workspace"));| Tool | Method | Description |
|---|---|---|
ReadFileTool |
view_text_file |
View files by line range |
ReadFileTool |
list_directory |
List files and folders in a directory |
WriteFileTool |
write_text_file |
Create/overwrite/replace file content |
WriteFileTool |
insert_text_file |
Insert content at specified line |
| Tool | Features |
|---|---|
ShellCommandTool |
Execute shell commands with whitelist, callback approval, and timeout support |
Quick Start:
import io.agentscope.core.tool.coding.ShellCommandTool;
Function<String, Boolean> callback = cmd -> askUserForApproval(cmd);
toolkit.registerTool(new ShellCommandTool(allowedCommands, callback));import io.agentscope.core.tool.multimodal.DashScopeMultiModalTool;
import io.agentscope.core.tool.multimodal.OpenAIMultiModalTool;
toolkit.registerTool(new DashScopeMultiModalTool(System.getenv("DASHSCOPE_API_KEY")));
toolkit.registerTool(new OpenAIMultiModalTool(System.getenv("OPENAI_API_KEY")));| Tool | Capabilities |
|---|---|
DashScopeMultiModalTool |
Text-to-image, image-to-text, text-to-speech, speech-to-text, text-to-video, image-to-video, first-last-frame-to-video, video understanding |
OpenAIMultiModalTool |
Text-to-image, image-to-text, text-to-speech, speech-to-text |
Agents can be registered as tools for other agents to call. See Agent as Tool for details.
For fine-grained control, implement the interface directly:
public class CustomTool implements AgentTool {
@Override
public String getName() { return "custom_tool"; }
@Override
public String getDescription() { return "Custom tool"; }
@Override
public Map<String, Object> getParameters() {
return Map.of(
"type", "object",
"properties", Map.of(
"query", Map.of("type", "string", "description", "Query")
),
"required", List.of("query")
);
}
@Override
public Mono<ToolResultBlock> callAsync(ToolCallParam param) {
String query = (String) param.getInput().get("query");
return Mono.just(ToolResultBlock.text("Result: " + query));
}
}Toolkit toolkit = new Toolkit(ToolkitConfig.builder()
.parallel(true) // Parallel execution of multiple tools
.allowToolDeletion(false) // Prevent tool deletion
.executionConfig(ExecutionConfig.builder()
.timeout(Duration.ofSeconds(30))
.build())
.build());| Option | Description | Default |
|---|---|---|
parallel |
Whether to execute multiple tools in parallel | false |
allowToolDeletion |
Whether to allow tool deletion | true |
executionConfig.timeout |
Tool execution timeout | 5 minutes |
Allow agents to autonomously manage tool groups:
toolkit.registerMetaTool();
// Agent can call "reset_equipped_tools" to activate (reset to specified set) tool groupsWhen there are many tool groups, agents can autonomously choose which groups to activate based on task requirements.
When a tool throws ToolSuspendException, the Agent execution pauses and returns to the caller, allowing external systems to perform the actual execution before resuming.
Use Cases:
- Tool requires external system execution (e.g., remote API, user manual operation)
- Need to asynchronously wait for external results
Usage:
@Tool(name = "external_api", description = "Call external API")
public ToolResultBlock callExternalApi(
@ToolParam(name = "url") String url) {
// Throw exception to suspend execution
throw new ToolSuspendException("Awaiting external API response: " + url);
}Resume Execution:
Msg response = agent.call(userMsg).block();
// Check if suspended
if (response.getGenerateReason() == GenerateReason.TOOL_SUSPENDED) {
// Get pending tool calls
List<ToolUseBlock> pendingTools = response.getContentBlocks(ToolUseBlock.class);
// After external execution, provide result
for (ToolUseBlock toolUse : pendingTools) {
Msg toolResult = Msg.builder()
.role(MsgRole.TOOL)
.content(ToolResultBlock.of(toolUse.getId(), toolUse.getName(),
TextBlock.builder().text("External execution result").build()))
.build();
// Resume execution
response = agent.call(toolResult).block();
}
}Register only the tool's schema (name, description, parameters) without execution logic. When LLM calls this tool, the framework automatically triggers suspension and returns to the caller for execution.
Use Cases:
- Tool implemented by external systems (e.g., frontend, other services)
- Dynamically register third-party tools
Usage:
// Method 1: Using ToolSchema
ToolSchema schema = ToolSchema.builder()
.name("query_database")
.description("Query external database")
.parameters(Map.of(
"type", "object",
"properties", Map.of("sql", Map.of("type", "string")),
"required", List.of("sql")
))
.build();
toolkit.registerSchema(schema);
// Method 2: Batch registration
toolkit.registerSchemas(List.of(schema1, schema2));
// Check if it's an external tool
boolean isExternal = toolkit.isExternalTool("query_database"); // trueThe call flow is the same as Tool Suspend: LLM calls → returns TOOL_SUSPENDED → external execution → provide result to resume.
- Tool Call Example: ToolCallingExample.java
- Tool Group Example: ToolGroupExample.java
- MultiModal Tool Example: MultiModalToolExample.java