How to Expose Domain Object Methods as LLM Tools Using the @Tool Annotation in Embabel
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
SelfToolPublisherinterface - 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.
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/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.
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/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.
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/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) performs the registration:
-
Discovery: The extractor inspects the Spring
ApplicationContextfor beans containing@Toolannotated methods. If no annotations are found, it falls back to theSelfToolPublishercontract. -
Wrapping: For each discovered method, the extractor creates a
MethodToolinstance (defined inembabel-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
-
Registration: The
MethodToolobjects are registered with the active LLM client'sChatModel, 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
@Tooland provide clear descriptions for the LLM - Declare the containing class as a Spring bean using
@Component,@Service, or Embabel stereotypes - Optionally implement
SelfToolPublisheror use@ToolGroupfor explicit tool grouping and selective publishing - Ensure all parameters are JSON-serializable for automatic marshalling
- Rely on
SpringAiToolExtractorto 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 within the embabel-agent-api module. The actual runtime representation is MethodTool.kt in the same module, while the optional publishing contract is defined in SelfToolPublisher.kt under com.embabel.agent.api.common.support.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →