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

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) 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). 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) 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:

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/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:

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/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:

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/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 for scanning logic, MethodTool.kt for the invocation wrapper, and 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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →