# How to Expose Domain Object Methods as LLM Tools Using the @Tool Annotation in Embabel

> Easily expose domain object methods as LLM tools in Embabel. Use the @Tool annotation and Spring beans for automatic discovery and registration with SpringAiToolExtractor. Learn more now!

- Repository: [Embabel/embabel-agent](https://github.com/embabel/embabel-agent)
- Tags: how-to-guide
- Published: 2026-08-08

---

**Annotate any public method with `@Tool`, ensure the containing class is a Spring bean, and Embabel automatically discovers and registers the method as a callable LLM tool through the `SpringAiToolExtractor`.**

Exposing domain logic to large language models (LLMs) is a core capability of the **embabel/embabel-agent** framework. By leveraging Spring AI's `@Tool` annotation, developers can expose domain object methods as LLM tools without boilerplate wiring. Embabel extends this with automatic discovery via classpath scanning and optional explicit publishing contracts.

## Prerequisites and Core Concepts

Embabel builds on Spring AI's **tool-calling** infrastructure. The `@Tool` annotation marks methods as invocable functions that LLMs can call during conversation turns. Embabel adds a lightweight publishing layer that discovers these methods and adapts them to the underlying LLM client (OpenAI, Anthropic, Ollama, etc.).

The framework scans for `@Tool` methods on:
- Spring beans (`@Component`, `@Service`, `@EmbabelComponent`, or `@Agent`)
- Classes implementing the `SelfToolPublisher` interface
- Classes annotated with `@ToolGroup`

## Step-by-Step Implementation

### Annotate Methods with @Tool

Add the `@Tool` annotation to any public method you want the LLM to invoke. Include a descriptive string to help the model understand when to use the tool.

```kotlin
import com.embabel.agent.api.annotation.Tool
import org.springframework.stereotype.Service

@Service
class GreetingService {

    @Tool(description = "Greets a person by name")
    fun sayHello(name: String): String {
        return "Hello, $name!"
    }

    @Tool(description = "Adds two integers and returns the sum")
    fun add(a: Int, b: Int): Int = a + b
}

```

*Source:* [[`testTypes.kt`](https://github.com/embabel/embabel-agent/blob/main/testTypes.kt)](https://github.com/embabel/embabel-agent/blob/main/embabel-agent-test-support/embabel-agent-test-internal/src/main/kotlin/com/embabel/agent/test/type/testTypes.kt#L586)

### Register as Spring Beans

The containing class must be a Spring-managed bean. Use standard stereotypes like `@Component` or `@Service`, or Embabel-specific annotations like `@Agent`.

```java
import com.embabel.agent.api.annotation.Tool;
import org.springframework.stereotype.Component;

@Component
public class VaultAccess {

    @Tool(description = "Returns the current access code for a named vault")
    public String getAccessCode(String vaultName) {
        // Business logic to retrieve secret
        return "CODE-1234";
    }
}

```

*Source:* [[`OpenAiResponsesAdapterIT.kt`](https://github.com/embabel/embabel-agent/blob/main/OpenAiResponsesAdapterIT.kt)](https://github.com/embabel/embabel-agent/blob/main/embabel-agent-autoconfigure/models/embabel-agent-openai-autoconfigure/src/test/kotlin/com/embabel/agent/config/models/openai/OpenAiResponsesAdapterIT.kt#L56)

### Optional - Implement SelfToolPublisher or Use @ToolGroup

For explicit control over which methods are published or to group tools under a specific role, implement the `SelfToolPublisher` interface or annotate the class with `@ToolGroup`.

```kotlin
import com.embabel.agent.api.common.support.SelfToolPublisher
import com.embabel.agent.api.annotation.Tool
import com.embabel.agent.api.annotation.ToolGroup
import org.springframework.stereotype.Component

@ToolGroup(role = "math")
@Component
class MathTools : SelfToolPublisher {

    @Tool(description = "Computes the factorial of a non-negative integer")
    fun factorial(n: Int): Long = (1..n).fold(1L) { acc, i -> acc * i }

    @Tool(description = "Checks if a number is prime")
    fun isPrime(num: Int): Boolean {
        // Prime checking logic
        return num > 1 && (2 until num).none { num % it == 0 }
    }
}

```

*Source:* [[`CiTools.kt`](https://github.com/embabel/embabel-agent/blob/main/CiTools.kt)](https://github.com/embabel/embabel-agent/blob/main/embabel-agent-code/src/main/kotlin/com/embabel/coding/tools/ci/CiTools.kt#L53)

### Ensure Serializable Parameters

Method parameters must be serializable types that the application's `ObjectMapper` can handle—typically primitives, Strings, or simple domain objects. The tool-calling bridge marshals LLM-provided JSON arguments into these types via reflection.

## How Embabel Registers Tools at Runtime

Under the hood, Embabel's `SpringAiToolExtractor` (located in [`embabel-agent-api/src/main/kotlin/com/embabel/agent/spi/support/springai/SpringAiToolExtractor.kt`](https://github.com/embabel/embabel-agent/blob/main/embabel-agent-api/src/main/kotlin/com/embabel/agent/spi/support/springai/SpringAiToolExtractor.kt)) performs the registration:

1. **Discovery**: The extractor inspects the Spring `ApplicationContext` for beans containing `@Tool` annotated methods. If no annotations are found, it falls back to the `SelfToolPublisher` contract.

2. **Wrapping**: For each discovered method, the extractor creates a **`MethodTool`** instance (defined in [`embabel-agent-api/src/main/kotlin/com/embabel/agent/api/tool/MethodTool.kt`](https://github.com/embabel/embabel-agent/blob/main/embabel-agent-api/src/main/kotlin/com/embabel/agent/api/tool/MethodTool.kt)). This wrapper handles:
   - Converting JSON arguments to method parameters
   - Invoking the method via reflection
   - Serializing return values back to JSON

3. **Registration**: The `MethodTool` objects are registered with the active LLM client's `ChatModel`, making them available in the tool registry sent with each prompt.

Because registration occurs at startup, the LLM can call these tools on-the-fly without additional runtime wiring.

## Summary

- **Annotate** domain methods with `@Tool` and provide clear descriptions for the LLM
- **Declare** the containing class as a Spring bean using `@Component`, `@Service`, or Embabel stereotypes
- **Optionally** implement `SelfToolPublisher` or use `@ToolGroup` for explicit tool grouping and selective publishing
- **Ensure** all parameters are JSON-serializable for automatic marshalling
- **Rely** on `SpringAiToolExtractor` to automatically discover and register tools at application startup

## Frequently Asked Questions

### What is the difference between @Tool and @ToolGroup?

The `@Tool` annotation (from Spring AI) marks individual methods as callable by the LLM. The `@ToolGroup` annotation (Embabel-specific) categorizes an entire class under a specific role, allowing you to organize related tools (e.g., "math", "vault", "ci") for better LLM context management.

### Can I use @Tool with private methods?

No. Embabel's `SpringAiToolExtractor` scans for public methods only. The underlying `MethodTool` wrapper uses reflection to invoke methods, and the discovery mechanism specifically targets public APIs that the LLM can reasonably access. Private methods are ignored during classpath scanning.

### How does Embabel handle method parameters?

Embabel uses Jackson's `ObjectMapper` to deserialize JSON arguments provided by the LLM into the method's parameter types. Primitive types, Strings, and custom domain objects with appropriate constructors or getter/setter pairs work automatically. Complex nested objects are supported as long as they are properly annotated for Jackson serialization.

### Where are the tools registered in the codebase?

Tool registration logic resides in [`SpringAiToolExtractor.kt`](https://github.com/embabel/embabel-agent/blob/main/SpringAiToolExtractor.kt) within the `embabel-agent-api` module. The actual runtime representation is [`MethodTool.kt`](https://github.com/embabel/embabel-agent/blob/main/MethodTool.kt) in the same module, while the optional publishing contract is defined in [`SelfToolPublisher.kt`](https://github.com/embabel/embabel-agent/blob/main/SelfToolPublisher.kt) under `com.embabel.agent.api.common.support`.