Comprehensive Guide to Spring AI Alibaba Tools: From Basics to Advanced

This article presents a complete, runnable Spring AI Alibaba Tools example (v1.1.2.0) that explains core concepts, three creation methods, registration techniques, advanced features such as JSON schema generation and custom result conversion, environment setup, testing commands, and common troubleshooting tips.

The Dominant Programmer
The Dominant Programmer
The Dominant Programmer
Comprehensive Guide to Spring AI Alibaba Tools: From Basics to Advanced

Introduction

Based on the official Spring AI Alibaba 1.1.2.0 documentation, the guide provides a complete, directly runnable Tools example project. It covers all common creation approaches, registration methods, advanced features, and fixes typical compilation errors such as the public record issue.

1. Tools Core Concepts

Tools are components that an Agent invokes to perform actions. They define clear inputs and outputs, allowing a large language model (LLM) to interact with the external world. Typical scenarios include:

Information retrieval – e.g., query weather, search a database, get the current time.

Operation execution – e.g., send email, create a record, set an alarm.

Workflow: User query → Model decides to call a Tool → Application executes the Tool → Result returns to Model → Model generates final reply.

1.1 Core Value of Tools

Tools extend the capabilities of the model by enabling it to fetch real‑time data or perform concrete tasks, addressing the inherent limitation that LLMs cannot directly access external APIs or execute operations.

1.2 Tool‑Calling Process

1. User asks a question → 2. Model decides to call a Tool → 3. Application executes the Tool
    ↓
4. Tool result returns → 5. Result sent back to Model → 6. Model produces the final response

Key security principle: The Model never directly accesses any API provided by a Tool; the actual execution is performed by the client application.

2. Ways to Create a Tool

2.1 Declarative – @Tool Annotation (Recommended)

Mark a method with @Tool to turn it into a Tool. This is the most concise approach.

import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import org.springframework.stereotype.Component;

@Component
public class DateTimeTools {
    @Tool(description = "Get the current date and time in the user's timezone")
    public String getCurrentDateTime() {
        return java.time.LocalDateTime.now()
            .atZone(org.springframework.context.i18n.LocaleContextHolder.getTimeZone().toZoneId())
            .toString();
    }

    @Tool(description = "Set a user alarm for the given time")
    public String setAlarm(@ToolParam(description = "Time in ISO‑8601 format, e.g., 2026-06-18T15:30:00") String time) {
        java.time.LocalDateTime alarmTime = java.time.LocalDateTime.parse(time, java.time.format.DateTimeFormatter.ISO_DATE_TIME);
        return "Alarm set for " + alarmTime;
    }
}
@Tool

annotation attributes: name – Tool name (defaults to method name, must be unique). description – Human‑readable description (strongly recommended). returnDirect – Whether the result is sent directly to the client (default false). resultConverter – Custom result converter class.

2.2 Programmatic – MethodToolCallback

Build a MethodToolCallback for fine‑grained control.

import org.springframework.ai.tool.ToolCallback;
import org.springframework.ai.tool.method.MethodToolCallback;
import org.springframework.ai.tool.ToolDefinitions;
import org.springframework.util.ReflectionUtils;

Method method = ReflectionUtils.findMethod(DateTimeTools.class, "getCurrentDateTime");
ToolCallback toolCallback = MethodToolCallback.builder()
    .toolDefinition(ToolDefinitions.builder(method)
        .description("Get the current date and time in the user's timezone")
        .build())
    .toolMethod(method)
    .toolObject(new DateTimeTools())
    .build();

For static methods, toolObject() can be omitted.

2.3 Functional – FunctionToolCallback

Wrap a Function, Supplier, Consumer or BiFunction as a Tool.

import org.springframework.ai.tool.ToolCallback;
import org.springframework.ai.tool.function.FunctionToolCallback;

record WeatherRequest(String location) {}
record WeatherResponse(double temp, String unit) {}

public class WeatherService implements java.util.function.Function<WeatherRequest, WeatherResponse> {
    @Override
    public WeatherResponse apply(WeatherRequest request) {
        return new WeatherResponse(25.0, "C");
    }
}

ToolCallback toolCallback = FunctionToolCallback.builder("currentWeather", new WeatherService())
    .description("Get the weather in location")
    .inputType(WeatherRequest.class)
    .build();

2.4 Dynamic Bean Definition – @Bean

Define a Tool as a Spring Bean; the framework resolves it at runtime.

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.function.Function;

@Configuration(proxyBeanMethods = false)
class WeatherTools {
    public static final String CURRENT_WEATHER_TOOL = "currentWeather";

    @Bean(CURRENT_WEATHER_TOOL)
    @org.springframework.context.annotation.Description("Get the weather in location")
    Function<WeatherRequest, WeatherResponse> currentWeather() {
        return request -> {
            // mock data or call external service
            return new WeatherResponse(25.0, "C");
        };
    }
}

2.5 Method Tool Limitations

The following types are not supported as Tool method parameters or return types: Optional Asynchronous types: CompletableFuture, Future Reactive types: Flow, Mono, Flux Functional types: Function, Supplier, Consumer (supported only via FunctionToolCallback)

3. Registering and Using Tools

3.1 In ChatClient

ChatModel chatModel = ...;
String response = ChatClient.create(chatModel)
        .prompt("What day is tomorrow?")
        .tools(new DateTimeTools())
        .call()
        .content();

3.2 In a ReactAgent

Convert @Tool -annotated objects to ToolCallback array and pass it to the agent.

@Bean
public ReactAgent assistantAgent(ChatModel chatModel, DateTimeTools dateTimeTools,
        CustomerTools customerTools, WeatherTools weatherTools) {
    ToolCallback[] methodCallbacks = MethodToolCallbackProvider.builder()
            .toolObjects(dateTimeTools, customerTools)
            .build()
            .getToolCallbacks();
    ToolCallback weatherCallback = weatherTools.getWeatherTool();
    java.util.List<ToolCallback> all = new java.util.ArrayList<>();
    all.addAll(java.util.List.of(methodCallbacks));
    all.add(weatherCallback);
    return ReactAgent.builder()
            .name("assistant_agent")
            .model(chatModel)
            .tools(all.toArray(new ToolCallback[0]))
            .systemPrompt("""
                You are an intelligent assistant capable of performing various tasks.
                Available abilities:
                1. Get current date and time (getCurrentDateTime)
                2. Set an alarm (setAlarm)
                3. Query city weather (getWeather)
                4. Query customer info (getCustomerInfo) – result returned directly
                5. Update customer info (updateCustomerInfo)
                Choose the appropriate tool based on the user query.
                If no tool is needed, answer directly.
                """)
            .hooks(ModelCallLimitHook.builder().runLimit(5).build())
            .saver(new MemorySaver())
            .build();
}

3.3 Adding Default Tools to ChatClient.Builder

ChatClient chatClient = ChatClient.builder(chatModel)
        .defaultToolCallbacks(toolCallback)
        .build();

3.4 Dynamic Tool Name Resolution

ChatClient.create(chatModel)
        .prompt("What's the weather like in Copenhagen?")
        .toolNames("currentWeather")
        .call()
        .content();

4. Advanced Features

4.1 JSON Schema & Parameter Description

Spring AI automatically generates a JSON Schema for @Tool method parameters. Customization annotations include: @ToolParam(description = "...") – Spring AI native. @JsonClassDescription, @JsonPropertyDescription – Jackson. @Schema(description = "...") – Swagger.

All parameters are required by default; use @ToolParam(required = false) to make them optional.

4.2 Custom Result Conversion

By default, Tool results are serialized to JSON via Jackson. Override with a custom ToolCallResultConverter:

class CustomerTools {
    @Tool(description = "Retrieve customer information",
          resultConverter = CustomToolCallResultConverter.class)
    Customer getCustomerInfo(Long id) { ... }
}

4.3 Return Direct

Setting returnDirect = true sends the Tool result straight to the caller, useful for RAG scenarios or when the Tool should end the Agent reasoning loop.

@Tool(description = "Retrieve customer information", returnDirect = true)
Customer getCustomerInfo(Long id) { ... }

4.4 Custom ToolCallingManager

@Bean
ToolCallingManager toolCallingManager() {
    return ToolCallingManager.builder().build();
}

5. Environment Setup

Key pom.xml dependencies (Java 17, Spring Boot 3.2.5, Spring AI Alibaba 1.1.2.0, Jackson 2.16.2, DashScope model):

<project xmlns="http://maven.apache.org/POM/4.0.0" ...>
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.5</version>
    </parent>
    <properties>
        <java.version>17</java.version>
        <spring-ai-alibaba.version>1.1.2.0</spring-ai-alibaba.version>
        <jackson.version>2.16.2</jackson.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud.ai</groupId>
            <artifactId>spring-ai-alibaba-agent-framework</artifactId>
            <version>${spring-ai-alibaba.version}</version>
        </dependency>
        <!-- Force Jackson version to avoid NoSuchMethodError -->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>${jackson.version}</version>
        </dependency>
    </dependencies>
    <properties>
        <dashscope.api-key>${DASHSCOPE_API_KEY}</dashscope.api-key>
    </properties>
</project>

6. Complete Runnable Code

6.1 Project Structure

src/main/java/com/example/ai/
├── SpringAiDemoApplication.java          # Main class
├── config/
│   └── AgentConfig.java                    # Agent configuration
├── tools/
│   ├── DateTimeTools.java                   # Declarative @Tool
│   ├── WeatherTools.java                    # Functional FunctionToolCallback
│   └── CustomerTools.java                  # Advanced: returnDirect & optional params
├── service/
│   └── AgentService.java                   # Service layer
└── controller/
    └── AgentController.java                # REST controller

6.2 Main Application

package com.example.ai;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringAiDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringAiDemoApplication.class, args);
    }
}

6.3 Tool Classes

DateTimeTools.java (Declarative @Tool)

package com.example.ai.tools;

import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

@Component
public class DateTimeTools {
    @Tool(description = "Get the current date and time (user timezone)")
    public String getCurrentDateTime() {
        return LocalDateTime.now()
            .atZone(LocaleContextHolder.getTimeZone().toZoneId())
            .toString();
    }

    @Tool(description = "Set an alarm (ISO‑8601 format time)")
    public String setAlarm(@ToolParam(description = "Time, format: yyyy-MM-ddTHH:mm:ss, e.g., 2026-06-18T15:30:00") String time) {
        LocalDateTime alarmTime = LocalDateTime.parse(time, DateTimeFormatter.ISO_DATE_TIME);
        return "Alarm set for " + alarmTime;
    }
}

WeatherTools.java (Functional)

Fix note: The public record definitions are changed to package‑private to avoid compilation errors.

package com.example.ai.tools;

import org.springframework.ai.tool.ToolCallback;
import org.springframework.ai.tool.function.FunctionToolCallback;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;

record WeatherRequest(String location) {}
record WeatherResponse(String location, String weather, double temperature, String unit) {}

@Component
public class WeatherTools {
    private static final Map<String, WeatherResponse> WEATHER_DATA = new HashMap<>();
    static {
        WEATHER_DATA.put("Beijing", new WeatherResponse("Beijing", "Sunny", 25.0, "°C"));
        WEATHER_DATA.put("Shanghai", new WeatherResponse("Shanghai", "Cloudy", 28.0, "°C"));
        WEATHER_DATA.put("Hangzhou", new WeatherResponse("Hangzhou", "Light rain", 22.0, "°C"));
        WEATHER_DATA.put("Shenzhen", new WeatherResponse("Shenzhen", "Sunny", 30.0, "°C"));
    }

    public ToolCallback getWeatherTool() {
        return FunctionToolCallback.builder("getWeather", (Function<WeatherRequest, WeatherResponse>) request -> {
            String location = request.location();
            return WEATHER_DATA.getOrDefault(location,
                new WeatherResponse(location, "Unknown", 0.0, "°C"));
        })
        .description("Get current weather for a specified city")
        .inputType(WeatherRequest.class)
        .build();
    }
}

CustomerTools.java (Advanced Features)

package com.example.ai.tools;

import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;

@Component
public class CustomerTools {
    private final Map<Long, String> customerDatabase = new HashMap<>();

    public CustomerTools() {
        customerDatabase.put(1L, "Zhang San - VIP");
        customerDatabase.put(2L, "Li Si - Regular");
    }

    @Tool(description = "Query customer info by ID", returnDirect = true)
    public String getCustomerInfo(@ToolParam(description = "Customer ID") Long id) {
        return customerDatabase.getOrDefault(id, "Customer not found");
    }

    @Tool(description = "Update customer info (name required, email optional)")
    public String updateCustomerInfo(@ToolParam(description = "Customer ID") Long id,
                                     @ToolParam(description = "New name") String name,
                                     @ToolParam(required = false, description = "New email") String email) {
        customerDatabase.put(id, name + (email != null ? " (" + email + ")" : ""));
        return "Customer " + id + " updated successfully";
    }
}

6.4 Agent Configuration

package com.example.ai.config;

import com.alibaba.cloud.ai.dashscope.api.DashScopeApi;
import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel;
import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatOptions;
import com.alibaba.cloud.ai.graph.agent.ReactAgent;
import com.alibaba.cloud.ai.graph.agent.hook.modelcalllimit.ModelCallLimitHook;
import com.alibaba.cloud.ai.graph.checkpoint.savers.MemorySaver;
import com.example.ai.tools.CustomerTools;
import com.example.ai.tools.DateTimeTools;
import com.example.ai.tools.WeatherTools;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.tool.ToolCallback;
import org.springframework.ai.tool.method.MethodToolCallbackProvider;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.ArrayList;
import java.util.List;

@Configuration
public class AgentConfig {
    @Value("${spring.ai.dashscope.api-key}")
    private String apiKey;

    @Bean
    public ChatModel chatModel() {
        DashScopeApi dashScopeApi = DashScopeApi.builder()
                .apiKey(apiKey)
                .build();
        return DashScopeChatModel.builder()
                .dashScopeApi(dashScopeApi)
                .defaultOptions(DashScopeChatOptions.builder()
                        .withModel("deepseek-v4-flash")
                        .withTemperature(0.7)
                        .withMaxToken(2000)
                        .build())
                .build();
    }

    @Bean
    public ReactAgent assistantAgent(ChatModel chatModel,
                                    DateTimeTools dateTimeTools,
                                    CustomerTools customerTools,
                                    WeatherTools weatherTools) {
        ToolCallback[] methodCallbacks = MethodToolCallbackProvider.builder()
                .toolObjects(dateTimeTools, customerTools)
                .build()
                .getToolCallbacks();
        ToolCallback weatherCallback = weatherTools.getWeatherTool();
        List<ToolCallback> all = new ArrayList<>();
        all.addAll(List.of(methodCallbacks));
        all.add(weatherCallback);
        return ReactAgent.builder()
                .name("assistant_agent")
                .model(chatModel)
                .tools(all.toArray(new ToolCallback[0]))
                .systemPrompt("""
                    You are an intelligent assistant that can help users complete various tasks.
                    Available abilities:
                    1. Get current date and time (getCurrentDateTime)
                    2. Set an alarm (setAlarm)
                    3. Query city weather (getWeather)
                    4. Query customer info (getCustomerInfo) – result returned directly
                    5. Update customer info (updateCustomerInfo)
                    Choose the appropriate tool based on the user's question.
                    If no tool is needed, answer directly.
                    """)
                .hooks(ModelCallLimitHook.builder().runLimit(5).build())
                .saver(new MemorySaver())
                .build();
    }
}

6.5 Service Layer

package com.example.ai.service;

import com.alibaba.cloud.ai.graph.RunnableConfig;
import com.alibaba.cloud.ai.graph.agent.ReactAgent;
import com.alibaba.cloud.ai.graph.exception.GraphRunnerException;
import org.springframework.ai.chat.messages.AssistantMessage;
import org.springframework.stereotype.Service;

@Service
public class AgentService {
    private final ReactAgent agent;

    public AgentService(ReactAgent agent) {
        this.agent = agent;
    }

    public String chat(String userMessage) throws GraphRunnerException {
        AssistantMessage response = agent.call(userMessage);
        return response.getText();
    }

    public String chatWithMemory(String userMessage, String sessionId) throws GraphRunnerException {
        RunnableConfig config = RunnableConfig.builder()
                .threadId(sessionId)
                .build();
        AssistantMessage response = agent.call(userMessage, config);
        return response.getText();
    }
}

6.6 Controller Layer

package com.example.ai.controller;

import com.alibaba.cloud.ai.graph.exception.GraphRunnerException;
import com.example.ai.service.AgentService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
import java.util.UUID;

@RestController
@RequestMapping("/api/agent")
public class AgentController {
    private static final Logger log = LoggerFactory.getLogger(AgentController.class);
    private final AgentService agentService;

    public AgentController(AgentService agentService) {
        this.agentService = agentService;
    }

    @PostMapping("/chat")
    public Map<String, Object> chat(@RequestBody Map<String, String> request) {
        String message = request.get("message");
        try {
            String response = agentService.chat(message);
            return Map.of("success", true, "response", response);
        } catch (GraphRunnerException e) {
            log.error("Agent execution failed", e);
            return Map.of("success", false, "error", "Agent execution failed: " + e.getMessage());
        }
    }

    @PostMapping("/chat/session")
    public Map<String, Object> chatWithSession(@RequestBody Map<String, String> request) {
        String message = request.get("message");
        String sessionId = request.getOrDefault("sessionId", UUID.randomUUID().toString());
        try {
            String response = agentService.chatWithMemory(message, sessionId);
            return Map.of("success", true, "sessionId", sessionId, "response", response);
        } catch (GraphRunnerException e) {
            log.error("Agent execution failed", e);
            return Map.of("success", false, "error", "Agent execution failed: " + e.getMessage());
        }
    }
}

7. Testing and Verification

7.1 Start the Application

Set the environment variable DASHSCOPE_API_KEY and run SpringAiDemoApplication.

7.2 Test Commands

# 1. Get current time
curl -X POST http://localhost:885/api/agent/chat \
  -H "Content-Type: application/json" \
  -d '{"message": "现在几点了?"}'

# 2. Query weather
curl -X POST http://localhost:885/api/agent/chat \
  -H "Content-Type: application/json" \
  -d '{"message": "北京今天天气怎么样?"}'

# 3. Set alarm
curl -X POST http://localhost:885/api/agent/chat \
  -H "Content-Type: application/json" \
  -d '{"message": "请帮我设置一个10分钟后的闹钟"}'

# 4. Query customer info (returnDirect example)
curl -X POST http://localhost:885/api/agent/chat \
  -H "Content-Type: application/json" \
  -d '{"message": "查询客户ID为1的信息"}'

# 5. Update customer info
curl -X POST http://localhost:885/api/agent/chat \
  -H "Content-Type: application/json" \
  -d '{"message": "更新客户ID为2的名字为王五"}'

# 6. Multi‑turn conversation with memory
curl -X POST http://localhost:885/api/agent/chat/session \
  -H "Content-Type: application/json" \
  -d '{"message": "我叫张三", "sessionId": "test-001"}'
curl -X POST http://localhost:885/api/agent/chat/session \
  -H "Content-Type: application/json" \
  -d '{"message": "我叫什么名字?", "sessionId": "test-001"}'

7.3 Expected Output Example

For the query “北京今天天气怎么样?”:

{
  "success": true,
  "response": "北京今天的天气是晴,温度25.0°C。"
}

8. Common Issues and Solutions

8.1 "Cannot resolve method 'tools(AgentTools)'"

Cause: ReactAgent.builder().tools() expects ToolCallback... arguments.

Solution: Convert agent tools using MethodToolCallbackProvider:

ToolCallback[] toolCallbacks = MethodToolCallbackProvider.builder()
        .toolObjects(agentTools)
        .build()
        .getToolCallbacks();
ReactAgent.builder()
        .tools(toolCallbacks)
        .build();

8.2 "public record" Compilation Error

Cause: Only one top‑level public class/record is allowed per .java file.

Solution: Change the record to package‑private (remove public) or move it to its own file.

// ✅ Correct: package‑private record
record WeatherRequest(String location) {}
record WeatherResponse(String location, String weather, double temperature, String unit) {}

8.3 Model Does Not Call Tool

Check that the @Tool description is clear, list all tools in the system prompt, and ensure method parameter types match the model's expected input.

8.4 Parameter Parsing Errors

Provide explicit descriptions with @ToolParam(description = "...") or use @Schema / @JsonProperty annotations. Avoid unsupported types such as Optional, CompletableFuture, etc.

8.5 returnDirect with Multiple Tools

When multiple tools are invoked in the same request, the returnDirect flag must be consistent across all involved tools (all true or all false).

8.6 GraphRunnerException Handling

Declare throws GraphRunnerException on service methods and catch it uniformly in the controller.

public String chat(String userMessage) throws GraphRunnerException {
    AssistantMessage response = agent.call(userMessage);
    return response.getText();
}

9. Summary

The guide delivers a complete, runnable Spring AI Alibaba Tools example, illustrating:

Declarative @Tool usage with @ToolParam.

Programmatic MethodToolCallback creation.

Functional FunctionToolCallback approach.

Tool registration via MethodToolCallbackProvider and bean definitions.

Advanced features such as returnDirect, optional parameters, custom result conversion, and custom ToolCallingManager.

Memory‑enabled multi‑turn conversations using MemorySaver.

Exception handling with GraphRunnerException.

Developers can quickly assemble their own Tool collections and extend the capabilities of AI agents.

Reference resources: Spring AI Alibaba GitHub | Official documentation | Original Tools tutorial.
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

AlibabaJavaJSON SchemaSpring BootAI AgentSpring AItoolsTool Calling
The Dominant Programmer
Written by

The Dominant Programmer

Resources and tutorials for programmers' advanced learning journey. Advanced tracks in Java, Python, and C#. Blog: https://blog.csdn.net/badao_liumang_qizhi

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.