Embabel (Em-BAY-bel) is a framework for authoring agentic flows on the JVM that seamlessly mix LLM-prompted interactions with code and domain models. Supports intelligent path finding towards goals. Written in Kotlin but offers a natural usage model from Java. From the creator of Spring.
Models agentic flows in terms of:
Application developers don't usually have to deal with these concepts directly, as most conditions result from data flow defined in code, allowing the system to infer pre and post conditions.
These concepts underpin these differentiators versus other agent frameworks:
Other benefits:
Flows can be authored in one of two ways:
@Agent
, using @Goal
, @Condition
and @Action
methods.agent {
and action {
blocks.Either way, flows are backed by a domain model of objects that can have rich behavior.
We are working toward allowing natural language actions and goals to be deployed.
The planning step is pluggable.
The default planning approach is Goal Oriented Action Planning. GOAP is a popular AI planning algorithm used in gaming. It allows for dynamic decision-making and action selection based on the current state of the world and the goals of the agent.
Goals, actions and plans are independent of GOAP. Future planning options include:
The framework executes via an AgentPlatform
implementation.
An agent platform supports the following modes of execution:
GoalChoiceApprover
interface provides developers a way to limit goal choice further.Open mode is the most powerful, but least deterministic.
In open mode, the platform is capable of finding novel paths that were not envisioned by developers, and even combining functionality from multiple providers.
Even in open mode, the platform will only perform individual steps that have been specified. (Of course, steps may themselves be LLM transforms, in which case the prompts are controlled by user code but the results are still non-deterministic.)
Possible future modes:
Embabel agent systems will also support federation, both with other Embabel systems (allowing planning to incorporate remote actions and goals) and third party agent frameworks.
Get an agent running in under 5 minutes.
Create your own agent repo from our Java or Kotlin GitHub template by clicking the "Use this template" button.
You can also create your own Embabel agent project locally with our quick start tool:
uvx --from git+https://github.com/embabel/project-creator.git project-creator
Choose Java or Kotlin and specify your project name and package name and you'll have an agent running in under a minute, if you already have an OPENAI_API_KEY
and have Maven installed.
📚 For examples and tutorials, see the Embabel Agent Examples Repository
🚗 For a realistic example application, see the Tripper travel planner agent
AI-generated travel itinerary with detailed recommendations
Map link included in output
TL;DR Because the evolution of agent frameworks is early and there's a lot of room for improvement; because an agent framework on the JVM will deliver great business value.
In Java or Kotlin, agent implementation code is intuitive and easy to test.
Java@Agent(description = "Find news based on a person's star sign") public class StarNewsFinder { private final HoroscopeService horoscopeService; private final int storyCount; // Services are injected by Spring public StarNewsFinder( HoroscopeService horoscopeService, @Value("${star-news-finder.story.count:5}") int storyCount) { this.horoscopeService = horoscopeService; this.storyCount = storyCount; } @Action public StarPerson extractStarPerson(UserInput userInput, OperationContext context) { return context.ai() .withLlm(OpenAiModels.GPT_41) .createObjectIfPossible( """ Create a person from this user input, extracting their name and star sign: %s""".formatted(userInput.getContent()), StarPerson.class ); } @Action public Horoscope retrieveHoroscope(StarPerson starPerson) { return new Horoscope(horoscopeService.dailyHoroscope(starPerson.sign())); } // toolGroups specifies tools that are required for this action to run @Action(toolGroups = {CoreToolGroups.WEB}) public RelevantNewsStories findNewsStories( StarPerson person, Horoscope horoscope, OperationContext context) { var prompt = """ %s is an astrology believer with the sign %s. Their horoscope for today is: <horoscope>%s</horoscope> Given this, use web tools and generate search queries to find %d relevant news stories summarize them in a few sentences. Include the URL for each story. Do not look for another horoscope reading or return results directly about astrology; find stories relevant to the reading above. For example: - If the horoscope says that they may want to work on relationships, you could find news stories about novel gifts - If the horoscope says that they may want to work on their career, find news stories about training courses.""".formatted( person.name(), person.sign(), horoscope.summary(), storyCount); return context.ai() .withDefaultLlm() .createObject(prompt, RelevantNewsStories.class); } // The @AchievesGoal annotation indicates that completing this action // achieves the given goal, so the agent can be complete @AchievesGoal( description = "Write an amusing writeup for the target person based on their horoscope and current news stories", export = @Export( remote = true, name = "starNewsWriteupJava", startingInputTypes = {StarPerson.class, UserInput.class}) ) @Action public Writeup writeup( StarPerson person, RelevantNewsStories relevantNewsStories, Horoscope horoscope, OperationContext context) { var llm = LlmOptions .withModel(OpenAiModels.GPT_41_MINI) // High temperature for creativity .withTemperature(0.9); var newsItems = relevantNewsStories.getItems().stream() .map(item -> "- " + item.getUrl() + ": " + item.getSummary()) .collect(Collectors.joining("\n")); var prompt = """ Take the following news stories and write up something amusing for the target person. Begin by summarizing their horoscope in a concise, amusing way, then talk about the news. End with a surprising signoff. %s is an astrology believer with the sign %s. Their horoscope for today is: <horoscope>%s</horoscope> Relevant news stories are: %s Format it as Markdown with links.""".formatted( person.name(), person.sign(), horoscope.summary(), newsItems); return context.ai() .withLlm(llm) .createObject(prompt, Writeup.class); } }Kotlin
@Agent(description = "Find news based on a person's star sign") class StarNewsFinder( // Services such as Horoscope are injected by Spring private val horoscopeService: HoroscopeService, // Potentially externalized by Spring @param:Value("\${star-news-finder.story.count:5}") private val storyCount: Int = 5, ) { @Action fun extractPerson( userInput: UserInput, context: OperationContext ): StarPerson = // All prompts are typesafe context.ai().withDefaultLlm() .createObject("Create a person from this user input, extracting their name and star sign: $userInput") // This action doesn't use an LLM // Embabel makes it easy to mix LLM use with regular code @Action fun retrieveHoroscope(starPerson: StarPerson) = Horoscope(horoscopeService.dailyHoroscope(starPerson.sign)) // This action uses tools // "toolGroups" specifies tools that are required for this action to run @Action(toolGroups = [ToolGroup.WEB]) fun findNewsStories( person: StarPerson, horoscope: Horoscope, context: OperationContext ): RelevantNewsStories = context.ai().withDefaultLlm().createObject( """ ${person.name} is an astrology believer with the sign ${person.sign}. Their horoscope for today is: <horoscope>${horoscope.summary}</horoscope> Given this, use web tools and generate search queries to find $storyCount relevant news stories summarize them in a few sentences. Include the URL for each story. Do not look for another horoscope reading or return results directly about astrology; find stories relevant to the reading above. For example: - If the horoscope says that they may want to work on relationships, you could find news stories about novel gifts - If the horoscope says that they may want to work on their career, find news stories about training courses. """.trimIndent() ) // The @AchievesGoal annotation indicates that completing this action // achieves the given goal, so the agent run will be complete @AchievesGoal( description = "Write an amusing writeup for the target person based on their horoscope and current news stories", ) @Action fun writeup( person: StarPerson, relevantNewsStories: RelevantNewsStories, horoscope: Horoscope, context: OperationContext, ): Writeup = context.ai() .withLlm( LlmOptions .withModel(model) .withTemperature(0.9) ) .createObject( """ Take the following news stories and write up something amusing for the target person. Begin by summarizing their horoscope in a concise, amusing way, then talk about the news. End with a surprising signoff. ${person.name} is an astrology believer with the sign ${person.sign}. Their horoscope for today is: <horoscope>${horoscope.summary}</horoscope> Relevant news stories are: ${relevantNewsStories.items.joinToString("\n") { "- ${it.url}: ${it.summary}" }} Format it as Markdown with links. """.trimIndent() ) }
The following domain classes ensure type safety:
Java@JsonClassDescription("Person with astrology details") @JsonDeserialize(as = StarPerson.class) public record StarPerson( String name, @JsonPropertyDescription("Star sign") String sign ) implements Person { @JsonCreator public StarPerson( @JsonProperty("name") String name, @JsonProperty("sign") String sign ) { this.name = name; this.sign = sign; } @Override public String getName() { return name; } } public record Horoscope(String summary) { } @JsonClassDescription("Writeup relating to a person's horoscope and relevant news") public record Writeup(String text) implements HasContent { @JsonCreator public Writeup(@JsonProperty("text") String text) { this.text = text; } @Override public String getContent() { return text; } }Kotlin
data class RelevantNewsStories( val items: List<NewsStory> ) data class NewsStory( val url: String, val summary: String, ) data class Subject( val name: String, val sign: String, ) data class Horoscope( val summary: String, ) data class FunnyWriteup( override val text: String, ) : HasContent
It's easy to unit test your agents to ensure that they correctly execute logic and pass the correct prompts and hyperparameters to LLMs. For example:
public class StarNewsFinderTest { @Test void writeupPromptMustContainKeyData() { HoroscopeService horoscopeService = mock(HoroscopeService.class); StarNewsFinder starNewsFinder = new StarNewsFinder(horoscopeService, 5); var context = new FakeOperationContext(); context.expectResponse(new com.embabel.example.horoscope.Writeup("Gonna be a good day")); NewsStory cockatoos = new NewsStory( "https://fake.com.au", "Cockatoo behavior", "Cockatoos are eating cabbages" ); NewsStory emus = new NewsStory( "https://morefake.com.au", "Emu movements", "Emus are massing" ); StarPerson starPerson = new StarPerson("Lynda", "Scorpio"); RelevantNewsStories relevantNewsStories = new RelevantNewsStories(Arrays.asList(cockatoos, emus)); Horoscope horoscope = new Horoscope("This is a good day for you"); starNewsFinder.writeup(starPerson, relevantNewsStories, horoscope, context); var prompt = context.getLlmInvocations().getFirst().getPrompt(); var toolGroups = context.getLlmInvocations().getFirst().getInteraction().getToolGroups(); assertTrue(prompt.contains(starPerson.getName())); assertTrue(prompt.contains(starPerson.sign())); assertTrue(prompt.contains(cockatoos.getSummary())); assertTrue(prompt.contains(emus.getSummary())); assertTrue(toolGroups.isEmpty(), "The LLM should not have been given any tool groups"); } }
We believe that all aspects of software development can and should be greatly accelerated through the use of AI agents. The ultimate decision makers remain human, but they can and should be greatly augmented.
This project practices extreme dogfooding.
Our key principles:
Developers must carefully read all code they commit and improve generated code if possible.
Choose one of the following:
git clone https://github.com/embabel/embabel-agent
Environment variables are consistent with common usage, rather than Spring AI. For example, we prefer
OPENAI_API_KEY
toSPRING_AI_OPENAI_API_KEY
.
Required:
OPENAI_API_KEY
: For the OpenAI APIOptional:
ANTHROPIC_API_KEY
: For the Anthropic API. Necessary for the coding agent.We strongly recommend providing both an OpenAI and Anthropic key, as some examples require both. And it's important to try to find the best LLM for a given task, rather than automatically choose a familiar provider.
You will need a Docker Desktop version >4.43.2
. Be sure to activate the following MCP tools from the catalog:
You can also set up your own MCP tools using Spring AI conventions. See the
application-docker-desktop.yml
file for an example.
If you're running Ollama locally, set the ollama
profile and Embabel will automatically connect to your Ollama endpoint and make all models available.
Create your own agent project with
uvx --from git+https://github.com/embabel/project-creator.git project-creator
📚 For examples and tutorials, see the Embabel Agent Examples Repository
# Clone and run examples git clone https://github.com/embabel/embabel-agent-examples cd embabel-agent-examples/scripts/kotlin ./shell.sh
Spring Shell is an easy way to interact with the Embabel agent framework, especially during development.
Type help
to see available commands. Use execute
or x
to run an agent:
execute "Lynda is a Scorpio, find news for her" -p -r
This will look for an agent, choose the star finder agent and run the flow. -p
will log prompts -r
will log LLM responses. Omit these for less verbose logging.
Options:
-p
logs prompts-r
logs LLM responsesUse the chat
command to enter an interactive chat with the agent. It will attempt to run the most appropriate agent for each command.
Spring Shell supports history. Type
!!
to repeat the last command. This will survive restarts, so is handy when iterating on an agent.
Example commands within the shell:
# Perplexity style deep research
# Requires both OpenAI and Anthropic keys and Docker Desktop with the MCP extension (or your own web tools)
execute "research the recent australian federal election. what is the position of the greens party?"
# x is a shortcut for execute
x "fact check the following: holden cars are still made in australia; the koel is a bird native only to australia; fidel castro is justin trudeau's father"
Bringing in additional LLMs Local models with well-known providers
The Embabel Agent Framework supports local models from:
ollama
profile and your local Ollama endpoint will be queries. All local models will be available.docker
profile and your local Docker endpoint will be queried. All local models will be available.You can define an LLM for any provider for which a Spring AI ChatModel
is available.
Simply define Spring beans of type Llm
. See the OpenAiConfiguration
class as an example.
Remember:
This project is in its early stages, but we have big plans. The milestones and issues in this repository are a good reference. Our key goals:
There is a lot to do, and you are awesome. We look forward to your contribution!
Applications center around domain objects. These can be instantiated by LLMs or user code, and manipulated by user code.
Use Jackson annotations to help LLMs with descriptions as well as mark fields to ignore. For example:
@JsonClassDescription("Person with astrology details") data class StarPerson( override val name: String, @get:JsonPropertyDescription("Star sign") val sign: String, ) : Person
See Java Json Schema Generation - Module Jackson for documentation of the library used.
Domain objects can have behaviors that are automatically exposed to LLMs when they are in scope. Simply annotate methods with the Spring AI @Tool
annotation.
Using Embabel as an MCP serverWhen exposing
@Tool
methods on domain objects, be sure that the tool is safe to invoke. Even the best LLMs can get trigger-happy. For example, be careful about methods that can mutate or delete data. This is likely better modeled via an explicit call to a non-tool method on the same domain class, in a code action.
You can use the Embabel agent platform as an MCP server from a UI like Claude Desktop. The Embabel MCP server is available over SSE.
Configure Claude Desktop as follows in your claude_desktop_config.yml
:
{ "mcpServers": { "embabel": { "command": "npx", "args": [ "-y", "mcp-remote", "http://localhost:8080/sse" ] } } }
See MCP Quickstart for Claude Desktop Users for how to configure Claude Desktop.
The MCP Inspector is a helpful tool for interacting with your Embabel SSE server, manually invoking tools and checking the exposed prompts and resources.
Start the MCP Inspector with:
npx @modelcontextprotocol/inspector
The Embabel Agent Framework provides built-in support for consuming Model Context Protocol (MCP) servers, allowing you to extend your applications with powerful AI capabilities through standardized interfaces.
Model Context Protocol (MCP) is an open protocol that standardizes how applications provide context and extra functionality to large language models. Introduced by Anthropic, MCP has emerged as the de facto standard for connecting AI agents to tools, functioning as a client-server protocol where:
MCP simplifies integration between AI applications and external tools, transforming an "M×N problem" into an "M+N problem" through standardization - similar to what USB did for hardware peripherals.
Configuring MCP in Embabel AgentTo configure MCP servers in your Embabel Agent application, add the following to your application.yml
:
spring: ai: mcp: client: enabled: true name: embabel version: 1.0.0 request-timeout: 30s type: SYNC stdio: connections: docker-mcp: command: docker args: - run - -i - --rm - alpine/socat - STDIO - TCP:host.docker.internal:8811
This configuration sets up an MCP client that connects to a Docker-based MCP server. The connection uses STDIO transport through Docker's socat utility to connect to a TCP endpoint.
Docker Desktop MCP IntegrationDocker has embraced MCP with their Docker MCP Catalog and Toolkit, which provides:
The Docker MCP ecosystem includes over 100 verified tools from partners like Stripe, Elastic, Neo4j, and more, all accessible through Docker's infrastructure.
Embabel integrates with the A2A protocol, allowing you to connect to other A2A-enabled agents and services.
Enable the a2a
Spring profile to start the A2A server.
You'll need the following environment variable:
GOOGLE_STUDIO_API_KEY
: Your Google Studio API key, which is used for Gemini.Start the Google A2A web interface using the a2a
Docker profile:
docker compose --profile a2a up
Go to the web interface running within the container at http://localhost:12000/
.
Connect to your agent at host.docker.internal:8080/a2a
. Note that localhost:8080/a2a
won't work as the server cannot access it when running in a Docker container.
Run the tests via Maven.
This will run both unit and integration tests but will not require an internet connection or any external services.
Spring profiles are used to configure the application for different environments and behaviors.
Interaction profiles:
shell
: Runs agent in interactive shell. Does not start web process.Model profiles:
ollama
: Looks for Ollama models. You will need to have Ollama installed locally and the relevant models pulled.docker-desktop
: Looks for Docker-managed local models when running outside Docker but talking to Docker Desktop with the MCP extension. This is recommended for the best experience, with Docker-provided web tools.docker
: Looks for Docker-managed local models when running in a Docker container.Logging profiles:
severance
: Severance specific logging. Praise Kier!starwars
: Star Wars specific logging. Feel the forcecolossus
: Colossus specific logging. The Forbin Project.A key goal of this framework is ease of testing. Just as Spring eased testing of early enterprise Java applications, this framework facilitates testing of AI applications.
Types of testing:
All logging in this project is either debug logging in the relevant class itself, or results from the stream of events of type AgentEvent
.
Edit application.yml
if you want to see debug logging from the relevant classes and packages.
Available logging experiences:
severance
: Severance logging. Praise Kierstarwars
: Star Wars logging. Feel the force. The default as it's understood throughout the galaxy.colossus
: Colossus logging. The Forbin Project.montypython
: Monty Python logging. No one expects it.hh
: Hitchhiker's Guide to the Galaxy logging. The answer is 42.If none of these profiles is chosen, Embabel will use vanilla logging. This makes me sad.
Adding Embabel Agent Framework to Your ProjectThe easiest way is to add the Embabel Spring Boot starter dependency to your pom.xml
:
<dependency> <groupId>com.embabel.agent</groupId> <artifactId>embabel-agent-starter</artifactId> <version>${embabel-agent.version}</version> </dependency>
Add the required repositories to your build.gradle.kts
:
repositories { mavenCentral() maven { name = "embabel-releases" url = uri("https://repo.embabel.com/artifactory/libs-release") mavenContent { releasesOnly() } } maven { name = "embabel-snapshots" url = uri("https://repo.embabel.com/artifactory/libs-snapshot") mavenContent { snapshotsOnly() } } maven { name = "Spring Milestones" url = uri("https://repo.spring.io/milestone") } }
Add the Embabel Agent starter:
dependencies { implementation('com.embabel.agent:embabel-agent-starter:${embabel-agent.version}') }
Add the required repositories to your build.gradle
:
repositories { mavenCentral() maven { name = 'embabel-snapshots' url = 'https://repo.embabel.com/artifactory/libs-snapshot' mavenContent { snapshotsOnly() } } maven { name = 'Spring Milestones' url = 'https://repo.spring.io/milestone' } }
Add the Embabel Agent starter:
dependencies { implementation 'com.embabel.agent:embabel-agent-starter:0.1.0-SNAPSHOT' }
Note: The Spring Milestones repository is required because the Embabel BOM (
embabel-agent-dependencies
) has transitive dependencies on experimental Spring components, specifically themcp-bom
. This BOM is not available on Maven Central and is only published to the Spring milestone repository. Unlike Maven, Gradle does not inherit repository configurations declared in parent POMs or BOMs. Therefore, it is necessary to explicitly declare the Spring milestone repository in your repositories block to ensure proper resolution of all transitive dependencies.
Binary Packages are located in Embabel Maven Repository. You would need to add Embabel Snapshot Repository to your pom.xml or configure in settings.xml
<repositories> <repository> <id>embabel-snapshots</id> <url>https://repo.embabel.com/artifactory/libs-snapshot</url> <snapshots> <enabled>false</enabled> </snapshots> </repository> <repository> <id>embabel-snapshots</id> <url>https://repo.embabel.com/artifactory/libs-snapshot</url> <releases> <enabled>false</enabled> </releases> <snapshots> <enabled>true</enabled> </snapshots> </repository> </repositories>
We welcome contributions to the Embabel Agent Framework.
Look at the coding style guide for style guidelines. This file also informs coding agent behavior.
(c) Embabel Software Inc 2024-2025.
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4