embabel / embabel-agent
- четверг, 13 августа 2026 г. в 00:00:13
Agent framework for the JVM. Pronounced Em-BAY-bel /ɛmˈbeɪbəl/
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.
Have questions? Talk to the docs via the Embabel-powered hub — an Embabel agent that answers your questions about the framework in natural language.
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. 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:
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'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
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.
@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:
<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 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:
<horoscope>%s</horoscope>
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);
}
}@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>${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,
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>${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:
@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;
}
}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,
) : HasContentIt'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 and business 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.
Coding agents are a special case. While the
embabel-agent-codesubmodule offers support for project modification that is useful for project bootstrapping, coding agents are the most mature of commercial agents, and their vendors are heavily subsidising their users, making it economically irrational to insist on our own platform.
Choose one of the following:
git clone https://github.com/embabel/embabel-agentEnvironment variables are consistent with common usage, rather than Spring AI. For example, we prefer
OPENAI_API_KEYtoSPRING_AI_OPENAI_API_KEY.
Required:
OPENAI_API_KEY: For the OpenAI APIOptional:
ANTHROPIC_API_KEY: For the Anthropic API. Necessary for the coding agent.MINIMAX_API_KEY: For the MiniMax API. Supports MiniMax-M3, MiniMax-M2.7 and MiniMax-M2.7-highspeed models.ZAI_API_KEY: For the Z.ai (Zhipu AI) API. Supports GLM-5.2, GLM-4.7, GLM-4.6, GLM-4.5-Air and GLM-4.7-Flash models.embabel-agent-starter-oci-genai and set
embabel.agent.platform.models.ocigenai.compartment-id; OCI config file, instance principal, resource principal,
workload identity, session token and simple key authentication are supported.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.ymlfile for an example.
If you're running Ollama locally, include the embabel ollama starter and Embabel will automatically connect to your
Ollama
endpoint and make all models available.
<dependency>
<groupId>com.embabel.agent</groupId>
<artifactId>embabel-agent-starter-ollama</artifactId>
</dependency>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.shSpring 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"
The Embabel Agent Framework supports local models from:
embabel-agent-starter-ollama starter to your pom.xml and your local Ollama endpoint will be
queries. All local models will be
available.embabel-agent-starter-dockermodels starter to your pom.xml and your local Docker endpoint will be
queried. All local models will be available.Add embabel-agent-starter-oci-genai to use OCI Generative AI chat and embedding models.
<dependency>
<groupId>com.embabel.agent</groupId>
<artifactId>embabel-agent-starter-oci-genai</artifactId>
</dependency>Configure embabel.agent.platform.models.ocigenai.compartment-id and, if needed, set
embabel.agent.platform.models.ocigenai.authentication-type to FILE, INSTANCE_PRINCIPAL, RESOURCE_PRINCIPAL,
WORKLOAD_IDENTITY, SESSION_TOKEN or SIMPLE. When the standard OpenAI provider is not on the classpath, the OCI
starter supplies OCI defaults for Embabel's default LLM and embedding model:
embabel.models.default-llm=cohere.command-a-03-2025
embabel.models.default-embedding-model=cohere.embed-v4.0Override those values in application configuration if you want another OCI model. Use OCI model ids such as
cohere.command-a-03-2025 or meta.llama-3.3-70b-instruct for Embabel model selection.
The Spring bean names registered by the starter are Java-friendly aliases such as cohere_command_a and
llama_33_70b.
If your application exposes Spring Boot Actuator env or configprops values, keep those endpoints secured and ensure
OCI credential fields such as pass-phrase, session-token and private-key are sanitized.
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,
) : PersonSee 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.
When exposing
@Toolmethods 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/inspectorThe 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.
To 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:8811This 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 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 upGo 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 unit tests via Maven. This will not require an internet connection or any external services.
mvn testIntegration tests (*IT) hit real provider APIs and are excluded from the default mvn test run.
To run them, ensure the following environment variables are set:
OPENAI_API_KEYANTHROPIC_API_KEYDEEPSEEK_API_KEYMISTRAL_API_KEYThen run:
mvn -Dtest='*IT,!LLMOllama*IT' -Dsurefire.failIfNoSpecifiedTests=false testThis runs all integration tests except Ollama (which requires a local Ollama server).
To run a specific module's integration tests, add -pl:
mvn -Dtest='*IT,!LLMOllama*IT' -Dsurefire.failIfNoSpecifiedTests=false test -pl embabel-agent-openaiSpring profiles are used to configure the application for different environments and behaviors.
Model profiles:
docker-desktop: Talking to Docker Desktop with the MCP extension. This is recommended for the best experience,
with Docker-provided web tools.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.
Since version 0.2.0, Embabel Agent Framework is available directly on Maven Central, simplifying dependency management. You no longer need to configure custom repositories for stable releases.
Simply 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>No additional repository configuration is needed! Maven Central is configured by default.
You need to add the Embabel repositories to your pom.xml:
<repositories>
<repository>
<id>embabel-releases</id>
<url>https://repo.embabel.com/artifactory/libs-release</url>
<releases>
<enabled>true</enabled>
</releases>
<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>Then add the dependency:
<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 = "Spring Milestones"
url = uri("https://repo.spring.io/milestone")
}
}Add the Embabel Agent starter:
dependencies {
implementation("com.embabel.agent:embabel-agent-starter:${embabelAgentVersion}")
}Add all 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:${embabelAgentVersion}")
}Add the required repositories to your build.gradle:
repositories {
mavenCentral()
maven {
name = 'Spring Milestones'
url = 'https://repo.spring.io/milestone'
}
}Add the Embabel Agent starter:
dependencies {
implementation "com.embabel.agent:embabel-agent-starter:${embabelAgentVersion}"
}Add all required repositories to your build.gradle:
repositories {
mavenCentral()
maven {
name = 'embabel-releases'
url = 'https://repo.embabel.com/artifactory/libs-release'
mavenContent {
releasesOnly()
}
}
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:${embabelAgentVersion}'
}The Spring Milestones repository is required because the Embabel BOM (embabel-agent-dependencies) has transitive
dependencies on experimental Spring components, specifically the mcp-bom. This BOM is not available on Maven Central
and is only published to the Spring milestone repository.
Note for Gradle users: 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.
0.3.0-SNAPSHOT)<dependency>
<groupId>com.embabel.agent</groupId>
<artifactId>embabel-agent-starter</artifactId>
<version>0.3.0</version>
</dependency>implementation("com.embabel.agent:embabel-agent-starter:0.3.0")implementation 'com.embabel.agent:embabel-agent-starter:0.3.0'Add full tracing and metrics to your Embabel agents with zero code changes.
<dependency>
<groupId>com.embabel.agent</groupId>
<artifactId>embabel-agent-starter-observability</artifactId>
<version>${embabel-agent.version}</version>
</dependency>Pick one (or combine multiple):
Zipkin (simplest — no account needed):
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-exporter-zipkin</artifactId>
</dependency>Langfuse (LLM-focused observability):
<dependency>
<groupId>com.quantpulsar</groupId>
<artifactId>opentelemetry-exporter-langfuse</artifactId>
<version>0.4.0</version>
</dependency># Enable observability
embabel:
agent:
platform:
observability:
enabled: true
service-name: my-agent-app
# Enable Spring Boot tracing
management:
tracing:
export:
enabled: true
sampling:
probability: 1.0
# Zipkin exporter
zipkin:
tracing:
endpoint: http://localhost:9411/api/v2/spansTo use Langfuse instead of (or alongside) Zipkin:
management:
langfuse:
enabled: true
endpoint: https://cloud.langfuse.com/api/public/otel # or your self-hosted URL
public-key: pk-lf-...
secret-key: sk-lf-...docker run -d -p 9411:9411 openzipkin/zipkin
./mvnw spring-boot:runOpen http://localhost:9411 — run an agent and you will see traces like:
Agent: CustomerServiceAgent
├── Action: AnalyzeRequest
│ └── ChatModel: gpt-4 (Spring AI)
│ └── tool:searchKnowledgeBase
├── Action: GenerateResponse
│ └── ChatModel: gpt-4 (Spring AI)
└── status: completed [duration=2340ms]
With Langfuse, you get a rich LLM-focused view of your agent traces:
Use the @Tracked annotation to add observability spans to your own methods — inputs, outputs, duration, and errors are captured automatically:
@Tracked("enrichCustomer")
public Customer enrich(Customer input) {
// Your logic here
}You can specify a type and description for richer traces:
@Tracked(value = "callPaymentApi", type = TrackType.EXTERNAL_CALL, description = "Payment gateway call")
public PaymentResult processPayment(Order order) {
// ...
}When called within an agent execution, @Tracked spans are automatically nested under the current action:
Agent: CustomerServiceAgent
├── Action: ProcessOrder
│ ├── @Tracked: enrichCustomer (PROCESSING)
│ ├── ChatModel: gpt-4
│ └── @Tracked: callPaymentApi (EXTERNAL_CALL)
└── status: completed
For the full configuration reference, MDC log correlation, and advanced options, see the Observability Module Documentation.
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-2026.