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

> Easily expose domain object methods as LLM tools in Embel. Learn how to use the @Tool annotation and SpringAiToolExtractor for seamless integration and enhanced AI capabilities.

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

---

**Annotate methods with `@Tool`, ensure the class is a Spring bean, and Embel's `SpringAiToolExtractor` automatically registers them with the LLM client at startup.**

Embel extends Spring AI's tool-calling capabilities to enable automatic discovery of LLM-callable methods. By placing Spring AI's `@Tool` annotation on methods within Spring-managed beans, you allow Embel to expose domain logic directly to LLMs like OpenAI, Anthropic, or Ollama without manual registry configuration.

## How the @Tool Annotation Works in Embel

Embel leverages the `@Tool` annotation provided by Spring AI to mark methods as invocable by an LLM. When your application starts, the `SpringAiToolExtractor` (located in [`embel-agent-api/src/main/kotlin/com/embel/agent/spi/support/springai/SpringAiToolExtractor.kt`](https://github.com/embabel/embabel-agent/blob/main/embel-agent-api/src/main/kotlin/com/embel/agent/spi/support/springai/SpringAiToolExtractor.kt)) scans the Spring `ApplicationContext` for beans containing `@Tool`-annotated methods.

For each discovered method, Embel creates a `MethodTool` instance (defined in [`embel-agent-api/src/main/kotlin/com/embel/agent/api/tool/MethodTool.kt`](https://github.com/embabel/embabel-agent/blob/main/embel-agent-api/src/main/kotlin/com/embel/agent/api/tool/MethodTool.kt)). This wrapper encapsulates the `java.lang.reflect.Method`, name, and description, handling the conversion of JSON arguments from the LLM into actual Java/Kotlin parameters and serializing return values back to JSON.

For explicit control over tool publication, Embel also supports the `SelfToolPublisher` interface ([`embel-agent-api/src/main/kotlin/com/embel/agent/api/common/support/SelfToolPublisher.kt`](https://github.com/embabel/embabel-agent/blob/main/embel-agent-api/src/main/kotlin/com/embel/agent/api/common/support/SelfToolPublisher.kt)) and the `@ToolGroup` annotation for organizing tools under specific roles.

## Step-by-Step Implementation

### 1. Annotate Methods with @Tool

Add the `@Tool` annotation to any public method you want the LLM to invoke. Provide a descriptive `description` parameter to guide the LLM on when to call the tool.

### 2. Register as a Spring Bean

Ensure the containing class is managed by Spring using `@Component`, `@Service`, `@EmbelComponent`, or `@Agent`. This registration makes the bean visible to Embel's classpath scanner.

### 3. Implement Optional Publishing Contracts

For advanced use cases, implement `SelfToolPublisher` or annotate the class with `@ToolGroup(role = "your-role")`. This gives you explicit control over which methods are published and allows logical grouping of related tools.

### 4. Ensure Serializable Parameters

Method parameters must be compatible with the configured `ObjectMapper`. Use primitive types, strings, or domain objects that support standard JSON serialization.

### 5. Runtime Registration

At startup, `SpringAiToolExtractor` automatically discovers your beans, instantiates `MethodTool` wrappers, and registers them with the active `ChatModel`. The LLM can then invoke these tools on-the-fly without additional wiring.

## Code Examples

### Simple Kotlin Service

This service exposes two tool methods that the LLM can call:

```kotlin
package com.example.demo.service

import com.embel.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 numbers")
    fun add(a: Int, b: Int): Int = a + b
}

```

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

### Java Domain Component

Java classes follow the same pattern using Spring's `@Component` annotation:

```java
package com.example.demo.vault;

import com.embel.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) {
        // Logic to look up secret
        return "CODE-1234";
    }
}

```

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

### Advanced Grouping with SelfToolPublisher

Use `SelfToolPublisher` when you need explicit control over tool registration:

```kotlin
package com.example.demo.tools

import com.embel.agent.api.common.support.SelfToolPublisher
import com.embel.agent.api.annotation.ToolGroup
import com.embel.agent.api.annotation.Tool
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 = 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/embel/embel-agent/blob/main/embel-agent-code/src/main/kotlin/com/embel/coding/tools/ci/CiTools.kt#L53)

## Behind the Scenes: Tool Registration Flow

The tool registration process operates in three distinct phases:

1. **Discovery**: `SpringAiToolExtractor` scans the bean definition registry for `@Tool` methods or classes implementing `SelfToolPublisher`.

2. **Adaptation**: For each method, Embel constructs a `MethodTool` instance containing the method's reflection metadata. This class handles JSON-to-parameter conversion and reflection-based invocation.

3. **Registration**: The `MethodTool` instances are passed to the underlying Spring AI `ChatModel`, which includes them in the tool registry sent with each prompt to the LLM provider.

Because this registration happens during application startup in `SpringAiToolExtractor`, the LLM can invoke these tools immediately without runtime configuration.

## Summary

- **Annotate** domain methods with `@Tool` from Spring AI, providing clear descriptions for the LLM to understand tool purpose and parameters.
- **Register** containing classes as Spring beans to enable automatic discovery by `SpringAiToolExtractor`.
- **Implement** `SelfToolPublisher` or use `@ToolGroup` when you need explicit control over tool publishing or logical organization.
- **Verify** that method parameters and return types are JSON-serializable to ensure proper argument marshalling by the `MethodTool` wrapper.
- **Reference** the core implementation in `embel-agent-api`: [`SpringAiToolExtractor.kt`](https://github.com/embabel/embabel-agent/blob/main/SpringAiToolExtractor.kt) for scanning logic, [`MethodTool.kt`](https://github.com/embabel/embabel-agent/blob/main/MethodTool.kt) for the invocation wrapper, and [`SelfToolPublisher.kt`](https://github.com/embabel/embabel-agent/blob/main/SelfToolPublisher.kt) for custom publishing contracts.

## Frequently Asked Questions

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

No. The `SpringAiToolExtractor` and `MethodTool` wrapper require public methods to construct the reflective call chain. Private methods are ignored during the discovery phase.

### How do I handle complex domain objects as parameters?

Ensure your parameter classes are standard POJOs or data classes with public properties. The `MethodTool` uses the configured `ObjectMapper` to deserialize JSON arguments into these objects, so they must follow standard Jackson serialization conventions.

### What happens if two tools have the same name?

Embel registers tools by name in the LLM client. Duplicate names may cause registration conflicts or overwrites depending on the underlying LLM provider implementation. Use the `name` attribute in `@Tool` to explicitly define unique names, or implement `SelfToolPublisher` to programmatically control tool naming.

### Do I need to implement SelfToolPublisher for simple use cases?

No. For most scenarios, simply annotating public methods with `@Tool` in Spring-managed beans suffices. Implement `SelfToolPublisher` only when you need dynamic tool selection, conditional registration based on runtime state, or explicit grouping beyond what the automatic scanner provides.