How to Expose Domain Object Methods as LLM Tools in Embabel

You expose domain object methods as LLM tools by annotating them with @Tool and letting the ToolRegistry automatically bundle them into ToolGroupMetadata that the Embabel runtime surfaces to language models.

The embabel/embabel-agent framework provides a type-safe bridge between your Java domain layer and Large Language Models (LLMs). By leveraging the @Tool annotation and Spring bean registration, you can expose domain object methods as LLM tools without boilerplate code.

The Three-Step Process

Annotate Methods with @Tool

Mark any public method you want the LLM to invoke with the @Tool annotation from com.embabel.agent.api.annotation.Tool. This annotation signals Embabel to generate a ToolDefinition containing the method name, parameter schema, and return type.

The annotation resides in embabel-agent-api/src/main/java/com/embabel/agent/api/annotation/Tool.java. When the Spring context starts, Embabel scans for beans containing methods annotated with @Tool and creates metadata objects describing each method's signature.

Register the Domain Bean

Embabel automatically registers any Spring bean containing @Tool methods with the ToolRegistry located in embabel-agent-core/src/main/java/com/embabel/agent/core/ToolRegistry.java. The registry scans the application context during startup, detects annotated methods, and prepares them for LLM invocation.

No explicit registration code is required if you use Spring's @Component or @Service stereotypes. The ToolRegistry discovers the bean and extracts its tool definitions automatically.

Tool Group Metadata Generation

The ToolRegistry bundles all ToolDefinition instances from a single bean into a ToolGroupMetadata object. This class, found in embabel-agent-observability/src/main/java/com/embabel/agent/core/ToolGroupMetadata.java, holds the bean reference, list of tool definitions, and runtime metadata such as version and description.

During chat processing, Embabel attaches this metadata to the LLM request payload under the tool_groups field, enabling the model to discover and invoke available methods.

How Tool Invocation Works Under the Hood

When the LLM decides to call a tool, Embabel executes the following flow:

  1. Request Parsing – The LLM sends a tool call request containing the tool name and JSON arguments. Embabel receives this via the chat API.
  2. Definition Lookup – The runtime queries the ToolGroupMetadata to locate the matching ToolDefinition by tool name.
  3. Parameter Deserialization – Embabel deserializes the input JSON into Java objects using the parameter schema derived from the method signature (or the explicit inputSchema provided in the @Tool annotation).
  4. Method Execution – The framework invokes the original Java method on the registered Spring bean with the deserialized arguments.
  5. Response Serialization – The return value is serialized to JSON according to the return type schema and sent back to the LLM as a tool result.

Throughout this process, the observability layer emits events via ToolCallRequestEvent and ToolCallResponseEvent, defined in embabel-agent-observability/src/main/java/com/embabel/agent/api/event/. These events enable tracing and metrics collection through Micrometer or OpenTelemetry.

Code Examples

Basic Domain Service with @Tool

Create a Spring bean and annotate methods you want to expose:

package com.example.customer;

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

@Service
public class CustomerService {

    @Tool(description = "Find a customer by its unique identifier")
    public Customer findById(Long id) {
        // Domain logic to retrieve customer
        return customerRepository.findById(id)
            .orElseThrow(() -> new CustomerNotFoundException(id));
    }

    @Tool(description = "Create a new customer record")
    public Customer create(String name, String email) {
        Customer customer = new Customer(name, email);
        return customerRepository.save(customer);
    }
}

The @Tool annotation creates a ToolDefinition for each method, defaulting the tool name to <ClassName>.<methodName>.

Customizing Tool Names and Schemas

Override default behavior using annotation attributes:

@Tool(
    name = "searchCustomers",
    description = "Search customers using a free-text query",
    inputSchema = """
        {
          "type": "object",
          "properties": {
            "query": {"type": "string", "description": "Search terms"},
            "limit": {"type": "integer", "default": 10}
          },
          "required": ["query"]
        }
        """
)
public List<Customer> search(String query, int limit) {
    return customerRepository.search(query, limit);
}

Explicit inputSchema overrides the automatically generated JSON Schema, useful for complex validation rules or documentation.

Handling Tool Calls in Chat Sessions

The Embabel runtime automatically handles tool invocations during chat processing:

ChatRequest request = ChatRequest.builder()
    .message("Find the customer with ID 42 and update their email")
    .build();

ChatResponse response = embabelAgent.chat(request);
// Embabel automatically invokes CustomerService.findById(42)
// and any subsequent tool calls required to complete the request

You do not manually call the tool methods; the LLM requests them by name, and Embabel routes the invocation through the ToolRegistry to your domain bean.

Observability and Tool Events

Every tool invocation generates observability events defined in the embabel-agent-observability module:

These events integrate with EmbabelToolLoopObservationConvention to produce metrics such as embabel.tool.calls.total and embabel.tool.duration. You can consume these events to build dashboards monitoring which domain methods your LLM invokes most frequently.

Summary

  • Annotate domain methods with @Tool from com.embabel.agent.api.annotation.Tool to mark them as LLM-callable.
  • Register the containing class as a Spring bean; ToolRegistry automatically discovers and indexes annotated methods.
  • Metadata is stored in ToolGroupMetadata objects that bundle ToolDefinition instances for runtime lookup.
  • Invocation flows through Embabel's tool-calling API, which handles JSON serialization, method routing, and response formatting.
  • Monitor tool usage via ToolCallRequestEvent and ToolCallResponseEvent for complete observability of LLM-domain interactions.

Frequently Asked Questions

What annotation is used to expose methods as LLM tools in Embabel?

Use the @Tool annotation from the embabel-agent-api module. Apply it to any public method in a Spring bean to generate a ToolDefinition. The annotation supports attributes like name, description, and inputSchema to customize how the LLM perceives the tool.

How does Embabel generate JSON schemas for tool parameters?

By default, Embabel introspects the method signature at runtime to derive parameter types and constructs a JSON Schema automatically. You can override this by providing an explicit inputSchema string in the @Tool annotation, which is useful for adding descriptions, validation patterns, or default values.

Where are tool definitions stored during runtime?

Tool definitions are stored in ToolGroupMetadata instances managed by the ToolRegistry. Each domain bean with @Tool methods gets one ToolGroupMetadata object containing all its ToolDefinition instances, stored in embabel-agent-observability/src/main/java/com/embabel/agent/core/ToolGroupMetadata.java.

How can I monitor tool usage in Embabel?

Embabel emits ToolCallRequestEvent and ToolCallResponseEvent events before and after each tool invocation. You can listen for these events or use the built-in Micrometer integration via EmbabelToolLoopObservationConvention to record metrics like call counts and execution duration.

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 →