Enterprise Java

LLM Tool Call Reasoning with Embabel AI

Large language models become significantly more useful when they can interact with external tools instead of only generating text. A model can decide that it needs information from a database, an API, a calculation service, or another business capability and then invoke the appropriate tool. However, tool calling by itself does not necessarily make an application agentic. A production agent often needs to determine which tool to use, in what order, how to interpret the result, and whether another action is required. This is where an agentic framework such as Embabel becomes useful. Embabel is a JVM agent framework designed to combine LLM-driven interactions with normal Java code, domain objects, and tools. It is built on Spring and Spring AI and supports agent actions, typed outputs, tool usage, planning, and multiple LLM configurations. This article demonstrates a small Spring Boot application in which an Embabel agent uses Spring AI tools to answer a flight-status question. The example intentionally keeps the architecture simple: one agent, one tool, a system prompt, and an LLM that decides when the tool call is necessary.

1. Introduction

1.1 Understanding LLM Tool Calling

Consider a user asking whether flight AI101 is delayed and, if so, how long the delay is. A normal LLM cannot provide reliable current flight information unless that information is already available in its context. A tool-enabled LLM, however, can reason about the request, recognize that it needs real-time flight information, invoke an appropriate flight-status tool, interpret the returned information, and then generate a final response for the user. The key advantage is that the application does not need to explicitly define every possible decision or tool invocation in advance. Instead, the model is provided with descriptions of the available tools and can determine which capability is relevant to the user’s request based on the context and instructions.

1.2 Why Use an Agentic AI Framework?

Simple Spring AI tool calling is often sufficient for applications that involve a single LLM interaction and a small set of tools. However, as workflows become more complex, an agent framework such as Embabel can provide additional orchestration capabilities. Embabel models an agent as a component that combines actions and domain capabilities, where actions can work with typed inputs and outputs to determine how different capabilities can be combined to achieve a specific goal. In this architecture, Spring Boot provides the application configuration and dependency injection, Spring AI provides the LLM and tool-calling abstractions, and Embabel provides the agent-oriented orchestration layer for coordinating LLM interactions, actions, tools, and goals.

1.3 Understanding Embabel Agentic AI Framework

Embabel is a JVM-based agentic AI framework designed for building LLM-powered applications using familiar Java and Spring programming models. It allows developers to define agents and actions using annotations and combine them with typed Java objects, application services, and LLM-accessible tools. Embabel builds on Spring and Spring AI, allowing the application to use Spring’s dependency injection and Spring AI’s model and tool-calling abstractions while adding an agent-oriented programming model for coordinating actions and goals. Instead of implementing every possible workflow as a fixed sequence of application code, developers can expose capabilities to an agent and allow the LLM to determine which capabilities are required to fulfill a user’s request. This makes Embabel useful for applications that need dynamic tool selection, multi-step reasoning, conditional execution, typed intermediate results, and more sophisticated agent orchestration.

1.4 LLM-Driven Tool Selection vs. Hard-Coded Orchestration

There is an important distinction between hard-coded orchestration and LLM-driven tool selection. With hard-coded orchestration, the application explicitly defines the sequence of operations and conditions that determine which service or capability should be invoked, making the workflow deterministic and predictable. In contrast, with LLM-driven tool selection, the model receives a user request along with descriptions of the available capabilities and determines which tools are relevant to fulfill that request. Embabel becomes particularly useful when these dynamic tool-selection decisions need to be combined with larger agentic workflows involving multiple actions, intermediate results, typed state, and goals.

1.5 Understanding the Tool Calling Workflow

Tool call reasoning can be understood as an interaction between the user, the LLM, the available tools, and the application. The user provides a natural-language request, and the LLM receives that request together with the system instructions and descriptions of the available tools. If the model determines that external information is required, it generates a tool call with the necessary arguments. The application then executes the requested tool, such as getFlightStatus(), which may communicate with an external API and return the requested information. The tool result is provided back to the LLM, which interprets the returned data and either generates the final response or determines that another tool call is necessary. This creates a reasoning loop in which intermediate tool results can influence the model’s next action, allowing the agent to dynamically select and combine capabilities instead of following a completely hard-coded sequence.

2. Building an Embabel Agent with Spring AI

Let’s build a small Spring Boot application. The example uses an external flight-status REST API so that the focus remains on agentic tool calling rather than hard-coded flight data.

2.1 Maven Configuration

The following Maven configuration sets Java 21 as the project version and adds the Embabel Agent Spring Boot starter required to build the agent application.

<properties>
    <java.version>latest_stable_jar_version</java.version>
    <embabel-agent.version>latest_stable_jar_version</embabel-agent.version>
</properties>

<dependencies>

    <dependency>
        <groupId>com.embabel.agent</groupId>
        <artifactId>embabel-agent-starter</artifactId>
        <version>${embabel-agent.version}</version>
    </dependency>

</dependencies>

2.2 Spring Boot Application Class

The following class bootstraps the Spring Boot application and enables Embabel agent support through @EnableAgents. It serves as the entry point for the application and initializes the Spring context when the application starts.

package com.example.flightagent;

import com.embabel.agent.config.annotation.EnableAgents;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@EnableAgents
public class Application {

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

The Application class is the entry point of the Spring Boot application. The @SpringBootApplication annotation enables Spring Boot auto-configuration and component scanning, while @EnableAgents enables Embabel’s agent functionality.

2.3 User Question Model

The following record defines a simple typed model for representing the user’s question. It keeps the input immutable and allows the question to be passed directly to the Embabel agent action.

package com.example.flightagent;

public record UserQuestion(String question) {
}

The UserQuestion record represents the user’s input to the agent. It contains a single question field and provides a concise immutable data structure that can be passed to the Embabel agent action.

2.4 Flight Status Model

The following record defines the structured flight information returned by the flight API and passed from the tool back to the LLM. It contains the flight number, current status, and delay duration.

package com.example.flightagent;

public record FlightStatus(
        String flightNumber,
        String status,
        int delayMinutes) {
}

The FlightStatus record represents the structured flight-status data returned by the API and passed back through the tool to the LLM. It contains the flight number, current status, and delay duration.

2.5 Flight API Client

The following service uses Spring’s RestClient to call the external flight-status API. It accepts a flight number, sends a GET request to the corresponding endpoint, and maps the API response to the FlightStatus model.

package com.example.flightagent;

import org.springframework.stereotype.Service;
import org.springframework.web.client.RestClient;

@Service
public class FlightApiClient {

    private final RestClient restClient;

    public FlightApiClient(RestClient.Builder builder) {
        this.restClient = builder
                .baseUrl("https://api.example.com")
                .build();
    }

    public FlightStatus getFlightStatus(String flightNumber) {
        return restClient.get()
                .uri("/flights/{flightNumber}", flightNumber)
                .retrieve()
                .body(FlightStatus.class);
    }
}

The FlightApiClient is responsible for communicating with the external flight-status service. It uses Spring’s RestClient to send an HTTP GET request containing the flight number and converts the API response into the FlightStatus record. The URL shown here is a placeholder for an actual flight-data provider API.

2.6 Defining the LLM Tool

The following component exposes the flight-status operation as an LLM-callable tool using Spring AI’s @Tool annotation. When the LLM selects this tool, the method delegates the request to FlightApiClient and returns the resulting FlightStatus.

package com.example.flightagent;

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

@Component
public class FlightTools {

    private final FlightApiClient flightApiClient;

    public FlightTools(FlightApiClient flightApiClient) {
        this.flightApiClient = flightApiClient;
    }

    @Tool(description = "Get the current status of a flight")
    public FlightStatus getFlightStatus(String flightNumber) {
        return flightApiClient.getFlightStatus(flightNumber);
    }
}

The FlightTools class exposes the external flight-status operation as an LLM-accessible tool. The @Tool annotation tells Spring AI that the getFlightStatus() method can be invoked by the LLM, while its description helps the model understand when the tool should be used. When the model requests the tool, the method delegates the actual API call to FlightApiClient.

2.7 Implementing the Embabel Agent

The following class defines the Embabel agent and its main action for answering flight-status questions. It provides the system prompt, makes the Spring AI tool available to the LLM through withToolObject(), and uses the configured LLM to process the user’s question and generate the final response.

package com.example.flightagent;

import com.embabel.agent.api.OperationContext;
import com.embabel.agent.api.annotation.Action;
import com.embabel.agent.api.annotation.Agent;
import org.springframework.stereotype.Component;

@Agent(description = "Answers questions about current flight status")
@Component
public class FlightAgent {

    private static final String SYSTEM_PROMPT = """
            You are a flight information assistant.

            Your job is to answer questions about flight status.

            Rules:
            1. Use the available flight tool for current flight information.
            2. Never invent flight status or delay information.
            3. If the tool does not return information,
               clearly state that the flight status is unavailable.
            4. Give the user a concise final answer.
            """;

    private final FlightTools flightTools;

    public FlightAgent(FlightTools flightTools) {
        this.flightTools = flightTools;
    }

    @Action
    public String answer(
            UserQuestion question,
            OperationContext context) {

        return context.ai()
                .withDefaultLlm()
                .withToolObject(flightTools)
                .generateText(
                        SYSTEM_PROMPT
                                + "\n\nUser question:\n"
                                + question.question()
                );
    }
}

The FlightAgent class defines the Embabel agent responsible for answering flight-status questions. The @Agent annotation identifies the class as an Embabel agent, while @Action identifies the method that processes the user’s question. The system prompt instructs the LLM to use the flight tool whenever current flight information is required and prevents it from inventing operational information. The withToolObject(flightTools) call exposes the @Tool-annotated method to the LLM. The model can then determine whether the tool is required, request the tool with the appropriate flight number, and use the returned FlightStatus to generate the final answer.

2.8 Running the Application and Tool Call Output

When the application is running locally, expose the agent through a REST endpoint such as POST /api/flights/status. The complete endpoint is http://localhost:8080/api/flights/status. A client can send a request containing the user’s question, which is passed to the Embabel agent for processing. For example, the LLM can determine that current flight information is required, invoke the getFlightStatus tool with AI101, and use the returned result to generate the final response.

POST http://localhost:8080/api/flights/status
Content-Type: application/json

{
    "question": "Is AI101 delayed?"
}

Tool call:
getFlightStatus("AI101")

External API response:
{
    "flightNumber": "AI101",
    "status": "DELAYED",
    "delayMinutes": 45
}

Response:
AI101 is currently delayed by 45 minutes.

This endpoint provides the application-facing entry point for the agentic workflow: the client sends a natural-language question to localhost:8080, the Embabel agent invokes the configured LLM and tool when necessary, the tool retrieves flight information from the external API, and the LLM returns the final natural-language response.

3. Conclusion

LLM tool calling is one of the most practical building blocks for agentic AI applications. In this example, Spring Boot provides the application infrastructure, Embabel provides the agent and action programming model, and Spring AI provides the LLM and tool-calling abstractions. The @Tool annotation exposes the FlightTools capability to the LLM, while the underlying FlightApiClient communicates with an external flight-status API to retrieve current information. The key idea is that the flight-status operation is exposed as a capability rather than implemented as a hard-coded decision in the application. The LLM receives the system prompt and available tool, determines whether current flight information is required, invokes the appropriate tool, interprets the returned FlightStatus, and generates the final response. The example intentionally keeps the architecture small, but the same approach can be extended to multiple tools and more complex agentic workflows. For simple applications, Spring AI’s native tool-calling capabilities may be sufficient, while Embabel becomes more valuable when workflows grow to include multiple actions, typed intermediate state, planning, conditional execution, and more sophisticated agent orchestration.

Yatin Batra

An experience full-stack engineer well versed with Core Java, Spring/Springboot, MVC, Security, AOP, Frontend (Angular & React), and cloud technologies (such as AWS, GCP, Jenkins, Docker, K8).
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Back to top button