How to Create and Integrate Custom Tools with ToolGroup in Embabel Agents
Create custom tools in Embabel by defining a group enum, annotating your tool class with @ToolGroup, marking callable methods with @Action, and registering the class as a Spring component for automatic discovery by the ToolGroupResolver.
Embabel agents organize capabilities into logical tool groups that planners use to select specific function sets such as Web, File-System, or MCP. To extend an agent's functionality, you must create custom tools and assign them to groups using the framework's annotation-based registration system. This guide walks through the exact implementation pattern used in the embabel/embabel-agent repository.
Understanding ToolGroup Architecture
Embabel uses the ToolGroup abstraction to categorize related capabilities. A tool group is defined by the @ToolGroup annotation and resolved at runtime by the ToolGroupResolver class. According to the source code in embabel-agent-api/src/main/kotlin/com/embabel/agent/spi/config/spring/ToolGroupsConfiguration.kt, the resolver scans all Spring beans carrying the @ToolGroup annotation and builds a mapping from role strings to lists of executable tools.
The annotation definitions reside in embabel-agent-api/src/main/kotlin/com/embabel/agent/api/annotation/annotations.kt (lines 43-45), where @ToolGroup accepts a role parameter that serves as the group's unique identifier. This role string links your custom implementation to agent actions that request specific tool sets.
Step-by-Step Implementation Guide
Follow this exact sequence to register a custom tool with a new group in Embabel.
1. Define the Tool Group Enum
Create a Kotlin enum that holds logical group names. These values reference the role attribute in subsequent annotations.
package com.example.agent.tools
/** Logical groups referenced by @ToolGroup and @Action annotations */
enum class MyToolGroups(val role: String) {
DATABASE("db"),
CLOUD("cloud"),
ANALYTICS("analytics")
}
2. Annotate the Tool Class with @ToolGroup
Apply @ToolGroup to the class implementation, specifying the role string that identifies the group. The class must also carry Spring's @Component annotation (or @EmbabelComponent) to enable classpath scanning.
package com.example.agent.tools
import com.embabel.agent.api.annotation.ToolGroup
import com.embabel.agent.api.annotation.Action
import org.springframework.stereotype.Component
@ToolGroup(role = MyToolGroups.CLOUD.role)
@Component
class CloudTool {
// Tool methods will be defined here
}
3. Expose Callable Methods with @Action
Each public function that the LLM can invoke requires the @Action annotation. You can optionally reinforce group membership using the toolGroups attribute, or override the class-level group for specific methods.
@Action(
description = "Starts a VM in the cloud",
toolGroups = [MyToolGroups.CLOUD.role]
)
fun startVm(instanceId: String): String {
// Implementation logic
return "VM $instanceId started"
}
@Action(
description = "Stops a VM in the cloud"
)
fun stopVm(instanceId: String): String {
return "VM $instanceId stopped"
}
4. Register with Spring Context
Ensure your class is discoverable by Spring's component scan. The framework automatically detects beans annotated with both @Component and @ToolGroup, as implemented in ToolGroupsConfiguration.kt.
5. Reference the Group in Agent Definitions
Actions or agents request tool groups via the toolGroups attribute. When the planner generates execution plans, it filters available tools to include only those belonging to the specified groups.
package com.example.agent
import com.embabel.agent.api.annotation.Agent
import com.embabel.agent.api.annotation.Action
import com.example.agent.tools.MyToolGroups
@Agent(
name = "cloud-agent",
description = "Agent that manages cloud resources",
version = "1.0.0"
)
class CloudAgent {
@Action(
description = "Provision a new VM",
toolGroups = [MyToolGroups.CLOUD.role]
)
fun provisionVm(size: String): String {
// Planner will select tools from the CLOUD group
return "VM provisioned with size $size"
}
}
Testing Custom Tool Group Integration
Verify that your custom group resolves correctly by writing unit tests against the ToolGroupResolver. The test suite in embabel-agent-api/src/test/kotlin/com/embabel/agent/spi/config/spring/ToolGroupsConfigurationTest.kt demonstrates the validation pattern for built-in groups.
package com.example.agent.tests
import com.embabel.agent.spi.ToolGroupResolver
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import kotlin.test.assertTrue
class CloudToolGroupTest @Autowired constructor(
private val resolver: ToolGroupResolver
) {
@Test
fun `cloud group contains CloudTool methods`() {
val tools = resolver.resolveTools("cloud")
assertTrue(tools.any { it.name == "startVm" })
assertTrue(tools.any { it.name == "stopVm" })
}
}
Summary
- Tool groups organize related capabilities using the
@ToolGroup(role = "group-name")annotation defined inannotations.kt(lines 43-45). - Custom tools must be Spring components (
@Componentor@EmbabelComponent) to enable automatic discovery byToolGroupsConfiguration. - Method exposure requires
@Actionannotations on public functions, with optionaltoolGroupsattributes to specify membership. - Runtime resolution is handled by
ToolGroupResolver, which maps role strings to executable tool instances. - Testing should verify group registration using the resolver interface, following patterns in
ToolGroupsConfigurationTest.kt.
Frequently Asked Questions
What is the difference between @ToolGroup and the toolGroups attribute in @Action?
@ToolGroup is a class-level annotation that assigns an entire tool class to a specific group, as defined in embabel-agent-api/src/main/kotlin/com/embabel/agent/api/annotation/annotations.kt. The toolGroups attribute within @Action allows method-level override or reinforcement of group membership. Use the class-level annotation for cohesion, and the method attribute when specific actions need to declare or restrict group access.
Can a single tool class belong to multiple tool groups?
No, a class can only carry one @ToolGroup annotation with a single role. To expose the same functionality across multiple groups, create separate wrapper classes for each group, each annotated with their respective @ToolGroup role, or use the toolGroups attribute in individual @Action methods to override the default group assignment.
How does the ToolGroupResolver discover custom tools at runtime?
The resolver scans the Spring application context for beans annotated with @ToolGroup, as configured in embabel-agent-api/src/main/kotlin/com/embabel/agent/spi/config/spring/ToolGroupsConfiguration.kt. It extracts the role string from each bean's annotation and populates a map that the planner queries when resolving tool requests for specific groups.
Where are built-in tool groups like WEB or FILE_SYSTEM defined?
Built-in groups are typically defined in enums such as CoreToolGroups, referenced in the README.md examples showing usage like @Action(toolGroups = {CoreToolGroups.WEB}). These constants map to role strings that the ToolGroupResolver recognizes, allowing consistent group references across the embabel/embabel-agent codebase.
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 →