embabel/embabel-agent
Agent framework for the JVM. Pronounced Em-BAY-bel /ɛmˈbeɪbəl/
About embabel/embabel-agent
embabel/embabel-agent is an open-source project on GitHub, mainly written in Kotlin. Agent framework for the JVM. Pronounced Em-BAY-bel /ɛmˈbeɪbəl/ It currently holds 4,480 stars and 438 forks with 0 open issues, and was last pushed on an unknown date (repository created unknown).
Project Overview
AI Homed tracks it on the Today's Trending board, currently at rank #82 with 4 new stars today.
GitHub Repository Details
README
Embabel Agent Framework
[//]: # ([](https://sonarcloud.io/summary/new_code?id=embabel_embabel-agent))
[//]: # ([](https://sonarcloud.io/summary/new_code?id=embabel_embabel-agent))
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.
Talk to the Docs
Have questions? Talk to the docs via the Embabel-powered hub — an Embabel agent that answers your questions about the framework in natural language.
Key Concepts
Models agentic flows in terms of:
- Actions: Steps an agent takes
- Goals: What an agent is trying to achieve
- Conditions: Conditions to assess before executing an action or determining that a goal has been achieved.
- Domain model: Objects underpinning the flow and informing Actions, Goals and Conditions.
- Plan: A sequence of actions to achieve a goal. Plans are dynamically formulated by the system, not the programmer.
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:
- Sophisticated planning. Goes beyond a finite state machine or sequential execution
- Superior extensibility and reuse: Because of dynamic planning, adding more domain objects, actions, goals and
- Strong typing and the benefits of object orientation: Actions, goals and conditions are informed by a domain
Other benefits:
- Platform abstraction: Clean separation between programming model and platform internals allows running locally
- Designed for LLM mixing: It is easy to build applications that mix LLMs, ensuring the most cost-effective yet
- Built on Spring and the JVM, making it easy to access existing enterprise functionality and capabilities.
- Spring can inject and manage agents, including using Spring AOP to decorate functions.
- Robust persistence and transaction management solutions are available.
- Designed for testability from the ground up. Both unit testing and agent end to end testing are easy.
- An annotation-based model similar to Spring MVC, with types annotated with the Spring stereotype
@Agent, using
@Goal, @Condition and
@Action methods.
- Idiomatic Kotlin DSL with
agent {andaction {blocks.
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. Embabel also supports Utility AI out of the box, which can run the same actions but chooses actions based on (potentially dynamic) utility scores rather than strict preconditions and postconditions. This is valuable for exploration and open-ended tasks, when we do not need to achieve a specific goal but want to maximize overall utility.
The framework executes via an AgentPlatform implementation.
An agent platform supports the following modes of execution:
- Focused, where user code requests particular functionality: User code calls a method to run a particular agent,
- Closed, where user intent (or another incoming event) is classified to choose an agent. The platform tries to
- Open, where the user's intent is assessed and the platform uses _all_ its resources to try to achieve it. The
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:
- Evolving mode: Where the platform can work with multiple goals in the same process and modify a running process to
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.
Quick Start
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'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 sophisticated, realistic example application, see the Tripper travel planner agent
AI-generated travel itinerary with detailed recommendations
Map link included in output
Why Is Embabel Needed?
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.
- _Why do we need an agent framework at all_? We can write code without higher level abstractions, directly invoking
- Breaking up LLM interactions, making them simpler and more focused. This maximizes reuse and minimizes cost and
- Facilitating both unit and integration testing, which remain as important with agentic systems as with any other
- Increasing composability where subflows and individual actions can be reused
- Making applications more manageable and robust, enabling a workflow manager to control their execution and retry
- Enhancing safety through the ability to apply guardrails in many places
- _Why do we need an agent framework for the JVM when solutions exist in Python?_: While agent frameworks initially
- _Why not use just Spring AI?_ Spring AI is great. We build on it, and embrace the Spring component model. However, we
- _Why not attempt to contribute this project to Spring?_ This project requires different governance
Show Me The Code
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, Ai ai) {
return 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,
Ai ai) {
var prompt = """
%s is an astrology believer with the sign %s.
Their horoscope for today is:
%s
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 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,
Ai ai) {
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:
%s
Relevant news stories are:
%s
Format it as Markdown with links.""".formatted(
person.name(), person.sign(), horoscope.summary(), newsItems);
return 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,
ai: Ai
): StarPerson =
// All prompts are typesafe
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,
ai: Ai,
): RelevantNewsStories =
ai.withDefaultLlm().createObject(
"""
${person.name} is an astrology believer with the sign ${person.sign}.
Their horoscope for today is:
${horoscope.summary}
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,
ai: Ai,
): Writeup =
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.summary}
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
)
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:
```java 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, relev
