CtrlK
CommunityDocumentationLog inGet started
Tessl Logo

tessl/maven-com-embabel-agent--embabel-agent-starter

Base starter module for the Embabel Agent Framework providing core dependencies for building agentic flows on the JVM with Spring Boot integration and GOAP-based intelligent path finding.

Overview
Eval results
Files

guides-creating-tools.mddocs/

Creating Tools

Step-by-step guide for creating LLM tools, MatryoshkaTools for progressive disclosure, and tool groups.

1. Basic LLM Tool Creation

Java

import com.embabel.agent.api.annotation.LlmTool;
import com.embabel.agent.api.annotation.EmbabelComponent;

@EmbabelComponent
public class FileTools {

    @LlmTool(
        description = "Read the contents of a file from the filesystem",
        name = "read_file"
    )
    public String readFile(
        @LlmTool.Param(description = "Path to the file to read", required = true)
        String filePath
    ) {
        return Files.readString(Path.of(filePath));
    }

    @LlmTool(
        description = "Write content to a file on the filesystem",
        name = "write_file"
    )
    public void writeFile(
        @LlmTool.Param(description = "Path to the file to write", required = true)
        String filePath,
        @LlmTool.Param(description = "Content to write to the file", required = true)
        String content
    ) {
        Files.writeString(Path.of(filePath), content);
    }

    @LlmTool(
        description = "List files in a directory",
        name = "list_files"
    )
    public List<String> listFiles(
        @LlmTool.Param(description = "Directory path to list", required = true)
        String directoryPath
    ) {
        return Arrays.asList(new File(directoryPath).list());
    }
}

Kotlin

import com.embabel.agent.api.annotation.LlmTool
import com.embabel.agent.api.annotation.EmbabelComponent

@EmbabelComponent
class DatabaseTools {

    @LlmTool(
        description = "Execute a SQL query and return results",
        name = "sql_query"
    )
    fun executeQuery(
        @LlmTool.Param(description = "SQL query to execute", required = true)
        query: String
    ): List<Map<String, Any>> {
        return database.execute(query)
    }

    @LlmTool(
        description = "Get database schema information",
        name = "get_schema",
        returnDirect = true
    )
    fun getSchema(): String {
        return database.describeSchema()
    }

    @LlmTool(
        description = "Count records in a table",
        name = "count_records"
    )
    fun countRecords(
        @LlmTool.Param(description = "Table name", required = true)
        tableName: String
    ): Int {
        return database.count(tableName)
    }
}

2. LLM Tools with Optional Parameters

Java

@EmbabelComponent
public class SearchTools {

    @LlmTool(
        description = "Search for documents with optional filters",
        name = "search_documents"
    )
    public List<Document> searchDocuments(
        @LlmTool.Param(description = "Search query", required = true)
        String query,
        @LlmTool.Param(description = "Maximum number of results", required = false)
        Integer maxResults,
        @LlmTool.Param(description = "Sort field", required = false)
        String sortBy
    ) {
        int limit = maxResults != null ? maxResults : 10;
        String sort = sortBy != null ? sortBy : "relevance";
        return searchService.search(query, limit, sort);
    }
}

Kotlin

@EmbabelComponent
class ApiTools {

    @LlmTool(
        description = "Make HTTP request with optional headers",
        name = "http_request"
    )
    fun httpRequest(
        @LlmTool.Param(description = "URL to request", required = true)
        url: String,
        @LlmTool.Param(description = "HTTP method", required = false)
        method: String? = "GET",
        @LlmTool.Param(description = "Request headers", required = false)
        headers: Map<String, String>? = emptyMap()
    ): String {
        return httpClient.request(url, method ?: "GET", headers ?: emptyMap())
    }
}

3. LLM Tools with returnDirect

Use returnDirect = true to bypass LLM processing and return results directly.

Java

@EmbabelComponent
public class QuickAccessTools {

    @LlmTool(
        description = "Get current system time",
        name = "current_time",
        returnDirect = true  // Return directly without LLM processing
    )
    public String getCurrentTime() {
        return Instant.now().toString();
    }

    @LlmTool(
        description = "Get system environment variable",
        name = "get_env",
        returnDirect = true
    )
    public String getEnvironmentVariable(
        @LlmTool.Param(description = "Environment variable name", required = true)
        String name
    ) {
        return System.getenv(name);
    }
}

Kotlin

@EmbabelComponent
class SystemTools {

    @LlmTool(
        description = "Get system memory info",
        name = "memory_info",
        returnDirect = true
    )
    fun getMemoryInfo(): Map<String, Long> {
        val runtime = Runtime.getRuntime()
        return mapOf(
            "total" to runtime.totalMemory(),
            "free" to runtime.freeMemory(),
            "used" to (runtime.totalMemory() - runtime.freeMemory())
        )
    }
}

4. MatryoshkaTools - Progressive Disclosure

MatryoshkaTools present a single facade tool initially, then reveal specific tools based on category selection.

Java

import com.embabel.agent.api.annotation.MatryoshkaTools;
import com.embabel.agent.api.annotation.LlmTool;
import com.embabel.agent.api.annotation.EmbabelComponent;

@EmbabelComponent
@MatryoshkaTools(
    name = "database_operations",
    description = "Database tools - select category: query, insert, update, or delete",
    removeOnInvoke = true,
    categoryParameter = "category"
)
public class DatabaseOperationsTools {

    @LlmTool(
        description = "Execute SELECT query",
        category = "query"
    )
    public List<Map<String, Object>> select(
        @LlmTool.Param(description = "SQL SELECT statement", required = true)
        String sql
    ) {
        return database.executeQuery(sql);
    }

    @LlmTool(
        description = "Insert new record",
        category = "insert"
    )
    public int insert(
        @LlmTool.Param(description = "Table name", required = true)
        String table,
        @LlmTool.Param(description = "Record data", required = true)
        Map<String, Object> data
    ) {
        return database.insert(table, data);
    }

    @LlmTool(
        description = "Update existing record",
        category = "update"
    )
    public int update(
        @LlmTool.Param(description = "Table name", required = true)
        String table,
        @LlmTool.Param(description = "WHERE clause", required = true)
        String where,
        @LlmTool.Param(description = "Updated data", required = true)
        Map<String, Object> data
    ) {
        return database.update(table, where, data);
    }

    @LlmTool(
        description = "Delete record",
        category = "delete"
    )
    public int delete(
        @LlmTool.Param(description = "Table name", required = true)
        String table,
        @LlmTool.Param(description = "WHERE clause", required = true)
        String where
    ) {
        return database.delete(table, where);
    }
}

Kotlin

import com.embabel.agent.api.annotation.MatryoshkaTools
import com.embabel.agent.api.annotation.LlmTool
import com.embabel.agent.api.annotation.EmbabelComponent

@EmbabelComponent
@MatryoshkaTools(
    name = "file_operations",
    description = "File system tools - choose: basic, advanced, or search",
    removeOnInvoke = true
)
class FileOperationsTools {

    @LlmTool(
        description = "List files in directory",
        category = "basic"
    )
    fun listFiles(
        @LlmTool.Param(description = "Directory path", required = true)
        path: String
    ): List<String> {
        return fileService.list(path)
    }

    @LlmTool(
        description = "Read file contents",
        category = "basic"
    )
    fun readFile(
        @LlmTool.Param(description = "File path", required = true)
        path: String
    ): String {
        return fileService.read(path)
    }

    @LlmTool(
        description = "Copy file with options",
        category = "advanced"
    )
    fun copyFile(
        @LlmTool.Param(description = "Source path", required = true)
        source: String,
        @LlmTool.Param(description = "Destination path", required = true)
        dest: String
    ) {
        fileService.copy(source, dest)
    }

    @LlmTool(
        description = "Move file",
        category = "advanced"
    )
    fun moveFile(
        @LlmTool.Param(description = "Source path", required = true)
        source: String,
        @LlmTool.Param(description = "Destination path", required = true)
        dest: String
    ) {
        fileService.move(source, dest)
    }

    @LlmTool(
        description = "Search files by pattern",
        category = "search"
    )
    fun searchFiles(
        @LlmTool.Param(description = "Search pattern", required = true)
        pattern: String
    ): List<String> {
        return fileService.search(pattern)
    }

    @LlmTool(
        description = "Find files by content",
        category = "search"
    )
    fun findByContent(
        @LlmTool.Param(description = "Search text", required = true)
        text: String
    ): List<String> {
        return fileService.findByContent(text)
    }
}

5. Tool Groups

Organize related tools into groups that can be required by actions.

Java

import com.embabel.agent.api.annotation.ToolGroup;
import com.embabel.agent.api.Tool;

@EmbabelComponent
public class AnalysisToolGroups {

    @ToolGroup(role = "statistical-analysis")
    public List<Tool> getStatisticalTools() {
        return List.of(
            Tool.create("mean", "Calculate mean", this::calculateMean),
            Tool.create("median", "Calculate median", this::calculateMedian),
            Tool.create("std_dev", "Calculate standard deviation", this::calculateStdDev),
            Tool.create("correlation", "Calculate correlation", this::calculateCorrelation)
        );
    }

    @ToolGroup(role = "data-visualization")
    public List<Tool> getVisualizationTools() {
        return List.of(
            Tool.create("scatter_plot", "Create scatter plot", this::createScatterPlot),
            Tool.create("bar_chart", "Create bar chart", this::createBarChart),
            Tool.create("line_chart", "Create line chart", this::createLineChart),
            Tool.create("histogram", "Create histogram", this::createHistogram)
        );
    }

    private double calculateMean(Map<String, Object> args) {
        List<Double> values = (List<Double>) args.get("values");
        return values.stream().mapToDouble(Double::doubleValue).average().orElse(0.0);
    }

    private double calculateMedian(Map<String, Object> args) {
        List<Double> values = (List<Double>) args.get("values");
        Collections.sort(values);
        return values.get(values.size() / 2);
    }

    private double calculateStdDev(Map<String, Object> args) {
        // Implementation
        return 0.0;
    }

    private double calculateCorrelation(Map<String, Object> args) {
        // Implementation
        return 0.0;
    }

    private String createScatterPlot(Map<String, Object> args) {
        // Implementation
        return "scatter_plot.png";
    }

    private String createBarChart(Map<String, Object> args) {
        // Implementation
        return "bar_chart.png";
    }

    private String createLineChart(Map<String, Object> args) {
        // Implementation
        return "line_chart.png";
    }

    private String createHistogram(Map<String, Object> args) {
        // Implementation
        return "histogram.png";
    }
}

Kotlin

import com.embabel.agent.api.annotation.ToolGroup
import com.embabel.agent.api.Tool

@EmbabelComponent
class DataToolGroups {

    @ToolGroup(role = "data-cleaning")
    fun getCleaningTools(): List<Tool> {
        return listOf(
            Tool.create("remove_nulls", "Remove null values", ::removeNulls),
            Tool.create("remove_duplicates", "Remove duplicate rows", ::removeDuplicates),
            Tool.create("normalize", "Normalize data values", ::normalizeData),
            Tool.create("handle_outliers", "Handle outlier values", ::handleOutliers)
        )
    }

    @ToolGroup(role = "data-transformation")
    fun getTransformationTools(): List<Tool> {
        return listOf(
            Tool.create("pivot", "Pivot table data", ::pivotData),
            Tool.create("aggregate", "Aggregate data", ::aggregateData),
            Tool.create("join", "Join datasets", ::joinDatasets),
            Tool.create("filter", "Filter data by criteria", ::filterData)
        )
    }

    private fun removeNulls(args: Map<String, Any>): Dataset {
        // Implementation
        return Dataset()
    }

    private fun removeDuplicates(args: Map<String, Any>): Dataset {
        // Implementation
        return Dataset()
    }

    private fun normalizeData(args: Map<String, Any>): Dataset {
        // Implementation
        return Dataset()
    }

    private fun handleOutliers(args: Map<String, Any>): Dataset {
        // Implementation
        return Dataset()
    }

    private fun pivotData(args: Map<String, Any>): Dataset {
        // Implementation
        return Dataset()
    }

    private fun aggregateData(args: Map<String, Any>): Dataset {
        // Implementation
        return Dataset()
    }

    private fun joinDatasets(args: Map<String, Any>): Dataset {
        // Implementation
        return Dataset()
    }

    private fun filterData(args: Map<String, Any>): Dataset {
        // Implementation
        return Dataset()
    }
}

6. Using Tool Groups in Actions

Java

@Agent(description = "Data analyst")
public class DataAnalystAgent {

    @Action(
        description = "Perform statistical analysis",
        toolGroupRequirements = {"statistical-analysis"}
    )
    public StatisticalReport analyzeData(Dataset data, @Provided Ai ai) {
        // This action requires statistical-analysis tools
        return ai.withLlm(OpenAiModels.GPT_4_TURBO)
                 .createObject("Analyze this dataset: " + data);
    }

    @Action(
        description = "Create visualization",
        toolGroupRequirements = {"data-visualization"}
    )
    public Visualization visualizeData(Dataset data, @Provided Ai ai) {
        // This action requires data-visualization tools
        return ai.withLlm(OpenAiModels.GPT_4_TURBO)
                 .createObject("Create a visualization for: " + data);
    }

    @Action(
        description = "Full analysis with charts",
        toolGroupRequirements = {"statistical-analysis", "data-visualization"}
    )
    public FullReport fullAnalysis(Dataset data, @Provided Ai ai) {
        // This action requires both tool groups
        return ai.withLlm(OpenAiModels.GPT_4_TURBO)
                 .createObject("Perform full analysis with charts: " + data);
    }
}

Kotlin

@Agent(description = "Data processor")
class DataProcessorAgent {

    @Action(
        description = "Clean and transform data",
        toolGroupRequirements = ["data-cleaning", "data-transformation"]
    )
    fun processData(raw: RawData, @Provided ai: Ai): ProcessedData {
        return ai.withLlm(GeminiModels.GEMINI_2_5_PRO)
                 .createObject("Clean and transform: $raw")
    }
}

7. Programmatic Tool Creation

Create tools programmatically without annotations.

Java

import com.embabel.agent.api.Tool;

@EmbabelComponent
public class CalculatorTools {

    public List<Tool> createCalculatorTools() {
        return List.of(
            Tool.create("add", "Add two numbers", (args) -> {
                double a = (Double) args.get("a");
                double b = (Double) args.get("b");
                return a + b;
            }),

            Tool.create("subtract", "Subtract two numbers", (args) -> {
                double a = (Double) args.get("a");
                double b = (Double) args.get("b");
                return a - b;
            }),

            Tool.create("multiply", "Multiply two numbers", (args) -> {
                double a = (Double) args.get("a");
                double b = (Double) args.get("b");
                return a * b;
            }),

            Tool.create("divide", "Divide two numbers", (args) -> {
                double a = (Double) args.get("a");
                double b = (Double) args.get("b");
                if (b == 0) throw new IllegalArgumentException("Division by zero");
                return a / b;
            })
        );
    }
}

Kotlin

import com.embabel.agent.api.Tool

@EmbabelComponent
class StringTools {

    fun createStringTools(): List<Tool> {
        return listOf(
            Tool.create("uppercase", "Convert string to uppercase") { args ->
                (args["text"] as String).uppercase()
            },

            Tool.create("lowercase", "Convert string to lowercase") { args ->
                (args["text"] as String).lowercase()
            },

            Tool.create("reverse", "Reverse a string") { args ->
                (args["text"] as String).reversed()
            },

            Tool.create("length", "Get string length") { args ->
                (args["text"] as String).length
            }
        )
    }
}

8. AgenticTools - Wrapping Agents as Tools

Wrap agents as tools for hierarchical agent composition.

Java

import com.embabel.agent.api.AgenticTool;

@EmbabelComponent
public class AgentToolProvider {

    public List<Tool> createAgenticTools() {
        return List.of(
            // Wrap agents as tools
            AgenticTool.fromAgent(DataAnalyzerAgent.class),
            AgenticTool.fromAgent("ReportGeneratorAgent"),
            AgenticTool.fromAgent(summarizerAgentInstance)
        );
    }

    @Action(description = "Use agentic tools for complex processing")
    public Result processWithAgenticTools(
        Input input,
        @Provided Ai ai
    ) {
        // Agents are exposed as tools to the LLM
        List<Tool> tools = createAgenticTools();

        return ai.withLlm(OpenAiModels.GPT_4_TURBO)
                 .withTools(tools)
                 .createObject("Process this input: " + input);
    }
}

Kotlin

import com.embabel.agent.api.AgenticTool

@EmbabelComponent
class AgentToolProvider {

    fun createAgenticTools(): List<Tool> {
        return listOf(
            AgenticTool.fromAgent(DataAnalyzerAgent::class.java),
            AgenticTool.fromAgent("ReportGeneratorAgent"),
            AgenticTool.fromAgent(summarizerAgentInstance)
        )
    }

    @Action(description = "Complex processing with agent tools")
    fun processWithAgenticTools(
        input: Input,
        @Provided ai: Ai
    ): Result {
        val tools = createAgenticTools()

        return ai.withLlm(OpenAiModels.GPT_4_TURBO)
                 .withTools(tools)
                 .createObject("Process: $input")
    }
}

9. Complete Working Example

Java - Comprehensive Tool Suite

import com.embabel.agent.api.*;
import com.embabel.agent.api.annotation.*;

@Agent(description = "Data processing agent with custom tools")
public class DataProcessingAgent {

    @Action(
        description = "Process data with custom tools",
        toolGroups = {"data-tools"}
    )
    public ProcessedData processData(
        RawData data,
        @Provided Ai ai,
        @Provided ActionContext context
    ) {
        // Create custom tools
        List<Tool> tools = createDataTools();

        context.updateProgress("Processing data with " + tools.size() + " tools");

        // Use tools with LLM
        return ai.withLlm(OpenAiModels.GPT_4_TURBO)
                 .withTools(tools)
                 .createObject("Process and transform: " + data);
    }

    @ToolGroup(role = "data-tools")
    private List<Tool> createDataTools() {
        return List.of(
            // Simple lambda tools
            Tool.create("validate", "Validate data format", this::validateData),
            Tool.create("normalize", "Normalize data values", this::normalizeData),
            Tool.create("aggregate", "Aggregate data points", this::aggregateData),

            // MatryoshkaTool for complex operations
            MatryoshkaTool.fromInstance(new DataTransformTools()),

            // AgenticTool for delegating to specialized agents
            AgenticTool.fromAgent(ValidationAgent.class),
            AgenticTool.fromAgent("EnrichmentAgent")
        );
    }

    private ValidationResult validateData(Map<String, Object> args) {
        RawData data = (RawData) args.get("data");
        return validator.validate(data);
    }

    private NormalizedData normalizeData(Map<String, Object> args) {
        RawData data = (RawData) args.get("data");
        return normalizer.normalize(data);
    }

    private AggregatedData aggregateData(Map<String, Object> args) {
        RawData data = (RawData) args.get("data");
        String method = (String) args.get("method");
        return aggregator.aggregate(data, method);
    }
}

@MatryoshkaTools(
    name = "data_transforms",
    description = "Data transformation operations - select: convert, reshape, or filter"
)
class DataTransformTools {

    @LlmTool(description = "Convert data format", category = "convert")
    public ConvertedData convert(
        @LlmTool.Param(description = "Source data") RawData data,
        @LlmTool.Param(description = "Target format") String format
    ) {
        return converter.convert(data, format);
    }

    @LlmTool(description = "Reshape data structure", category = "reshape")
    public ReshapedData reshape(
        @LlmTool.Param(description = "Source data") RawData data,
        @LlmTool.Param(description = "Target shape") String shape
    ) {
        return reshaper.reshape(data, shape);
    }

    @LlmTool(description = "Filter data by criteria", category = "filter")
    public FilteredData filter(
        @LlmTool.Param(description = "Source data") RawData data,
        @LlmTool.Param(description = "Filter expression") String expression
    ) {
        return filterer.filter(data, expression);
    }
}

Kotlin - Analysis Tool Suite

import com.embabel.agent.api.*
import com.embabel.agent.api.annotation.*

@Agent(description = "Analysis agent with tool suite")
class AnalysisAgent {

    @Action(
        description = "Analyze data with specialized tools",
        toolGroups = ["analysis-tools"]
    )
    fun analyzeData(
        data: Dataset,
        @Provided ai: Ai,
        @Provided context: ActionContext
    ): AnalysisReport {
        // Create tool suite
        val tools = createAnalysisTools()

        context.updateProgress("Analyzing with ${tools.size} specialized tools")

        // Execute with LLM
        return ai.withLlm(GeminiModels.GEMINI_2_5_PRO)
                 .withTools(tools)
                 .createObject("Analyze: $data")
    }

    @ToolGroup(role = "analysis-tools")
    fun createAnalysisTools(): List<Tool> {
        return listOf(
            // Function reference tools
            Tool.create("summarize", "Summarize data", ::summarizeData),
            Tool.create("findOutliers", "Find outliers", ::findOutliers),
            Tool.create("detectTrends", "Detect trends", ::detectTrends),

            // MatryoshkaTool for progressive disclosure
            MatryoshkaTool.fromInstance(StatisticalTools()),

            // AgenticTool for complex analysis
            AgenticTool.fromAgent(DeepAnalysisAgent::class.java)
        )
    }

    private fun summarizeData(args: Map<String, Any>): Summary {
        val data = args["data"] as Dataset
        return summarizer.summarize(data)
    }

    private fun findOutliers(args: Map<String, Any>): List<Outlier> {
        val data = args["data"] as Dataset
        return outlierDetector.detect(data)
    }

    private fun detectTrends(args: Map<String, Any>): List<Trend> {
        val data = args["data"] as Dataset
        return trendDetector.detect(data)
    }
}

@MatryoshkaTools(
    name = "statistical_tools",
    description = "Statistical analysis - choose: descriptive, inferential, or advanced"
)
class StatisticalTools {

    @LlmTool(description = "Calculate descriptive statistics", category = "descriptive")
    fun descriptiveStats(
        @LlmTool.Param(description = "Data values") values: List<Double>
    ): DescriptiveStats {
        return statistician.descriptive(values)
    }

    @LlmTool(description = "Perform hypothesis testing", category = "inferential")
    fun hypothesisTest(
        @LlmTool.Param(description = "Sample data") sample: List<Double>,
        @LlmTool.Param(description = "Test type") testType: String
    ): TestResult {
        return statistician.test(sample, testType)
    }

    @LlmTool(description = "Regression analysis", category = "advanced")
    fun regression(
        @LlmTool.Param(description = "Independent variables") x: List<List<Double>>,
        @LlmTool.Param(description = "Dependent variable") y: List<Double>
    ): RegressionModel {
        return statistician.regress(x, y)
    }
}

Key Annotation Attributes

@LlmTool

  • description (required) - Description shown to LLM
  • name - Override tool name (default: method name)
  • returnDirect - Skip LLM processing (default: false)
  • category - Category for MatryoshkaTools (default: empty)

@LlmTool.Param

  • description (required) - Parameter description for LLM
  • required - Whether parameter is required (default: true)

@MatryoshkaTools

  • name (required) - Name of facade tool
  • description (required) - Description shown to LLM
  • removeOnInvoke - Remove facade after first use (default: true)
  • categoryParameter - Parameter name for category selection (default: "category")

@ToolGroup

  • role (required) - Role/name of the tool group

Best Practices

  1. Clear Descriptions - Write clear, concise descriptions for LLM understanding
  2. Document Parameters - Use @LlmTool.Param for all parameters
  3. Use MatryoshkaTools - Reduce tool clutter with progressive disclosure
  4. Organize Tool Groups - Group related tools for modular management
  5. Use returnDirect - Skip LLM for deterministic operations
  6. Validate Inputs - Always validate tool inputs before processing
  7. Handle Errors Gracefully - Return meaningful error messages
  8. Type Safety - Use strong typing for parameters and return values
  9. Test Independently - Unit test tools separately from agent logic
  10. Use AgenticTools - Wrap agents as tools for hierarchical composition

See Also

  • Defining Actions - Define actions that use tools
  • Creating Agents - Create agents with planning
  • Goal Achievement - Define goal-achieving actions
  • Multimodal - Use multimodal content in tools
tessl i tessl/maven-com-embabel-agent--embabel-agent-starter@0.3.1

docs

api-annotations.md

api-domain-model.md

api-invocation.md

api-tools.md

concepts-actions.md

concepts-agents.md

concepts-goals.md

concepts-invocation.md

concepts-tools.md

guides-creating-agents.md

guides-creating-tools.md

guides-defining-actions.md

guides-goal-achievement.md

guides-human-in-loop.md

guides-multimodal.md

index.md

integration-mcp.md

integration-model-providers.md

integration-spring-boot.md

LlmTool.md

quickstart.md

reference-component-scanning.md

reference-configuration-properties.md

reference-installation.md

reference-logging.md

reference-resilience.md

reference-streaming.md

tile.json