Building an AI Agent in Java with Embabel and Ollama (DeepSeek-R1:8B)
AI agents are no longer just experimental chatbots. In modern applications, they’re intent extractors, decision makers, and task…
Building an AI Agent in Java with Embabel and Ollama (DeepSeek-R1:8B)

AI agents are no longer just experimental chatbots. In modern applications, they’re intent extractors, decision makers, and task delegators. Imagine a system where you tell your assistant:
“Get me the sales summary and purchase summary, then send it to John Doe.”
The assistant should understand your request, break it into atomic tasks, and delegate those tasks to the right sub-agents — all without you writing tons of boilerplate orchestration logic.
That’s exactly what I set out to build. In this article, I’ll show you how I combined **Embabel — a Java agent framework — with Ollama running DeepSeek-R1:8B to build an Intent Extraction Agent** that makes this possible.
Why Embabel?
Normally, wiring an AI agent in Java means manually coding:
- Controllers and service layers.
- Prompt construction for the LLM.
- Parsing unstructured model output.
- Orchestrating workflows across multiple modules.
Embabel simplifies all of this.
- You define goals (
@Goal) and actions (@Action). - The framework handles orchestration and execution.
- Structured responses are automatically mapped into Java objects.
- You focus on business-specific logic, not AI plumbing.
This makes Embabel especially powerful for Java/Spring developers who want to plug AI agents into their existing ecosystems.
Why Ollama + DeepSeek-R1:8B?
Running large language models locally is now practical thanks to Ollama.
- Ollama lets you run models like Mistral, Llama3, Phi3, and DeepSeek entirely on your machine.
- DeepSeek-R1:8B is reasoning-focused and relatively lightweight, making it great for local agent tasks like intent extraction.
- No cloud dependency, no data leakage — everything runs in your environment.
In application.properties, we configure Embabel to use Ollama + DeepSeek as the default brain for our agents:
embabel.models.defaultLlm=deepseek-r1:8b
embabel.agent.platform.ranking.llm=deepseek-r1:8b
A Problem I Hit Along the Way
Initially, I tried using Llama3.2:1b as the model behind Embabel’s ranking LLM — the component responsible for choosing which agent should handle a user’s input.
The issue?
- Llama3.2:1b wasn’t accurately following the strict instructions.
- Instead of keeping agent names exactly as defined, it would return slightly altered versions or even generate free text.
- This broke the delegation flow, since Embabel relies on exact matches between intents and agents.
Switching to DeepSeek-R1:8B solved this problem. It was far more deterministic, followed the rules, and consistently returned exactly the mapped agent names I asked for.
This was a key lesson: 👉 Choosing the right model for each agent’s responsibility matters more than just using the “biggest” one.
Architecture
Here’s the high-level flow:
User Input
↓
Intent Extraction Agent (Embabel + DeepSeek)
↓
Tasks (Sales report, Purchase report, Summarization, Send)
↓
Delegated to Placeholder Agents:
- Sales Reporting Agent
- Purchase Reporting Agent
- Report Summarization Agent
- Email Sending Agent
The Intent Extraction Agent is the entry point. It breaks down user input into atomic tasks and maps them to the right downstream agents.
Implementation
Let’s walk through the code.
1. Intent-to-Agent Mapping
We need a way to map intents (like fetch_sales_report) to agents (like Sales Reporting Agent).
public class IntentAndAgent {
private String intent;
private String agent;
// getters and setters...
}
And the mapping class:
public class IntentAndAgentMap implements PromptContributor {
private List<IntentAndAgent> intentAndAgentList = new ArrayList<>();
public void addIntentAndAgent(String intent, String agent) {
IntentAndAgent intentAndAgent = new IntentAndAgent();
intentAndAgent.setIntent(intent);
intentAndAgent.setAgent(agent);
intentAndAgentList.add(intentAndAgent);
}
@Override
public String contribution() {
return intentAndAgentList.stream()
.map(a -> a.getIntent() + ": " + a.getAgent())
.collect(Collectors.joining("\n"));
}
}
This is like our lookup table for routing.
2. Response Schema
We want structured output, not free text. So we define a schema:
public class Task {
private String intent;
private String target;
private String[] dependsOn;
// getters and setters...
}
public class IntentExtractionResponse {
private List<Task> tasks;
// getters and setters...
}
This ensures the LLM returns usable JSON-like output that maps directly to Java objects.
3. The Intent Extraction Agent
Here’s the star of the show:
@Agent(description = "Extracts the intent from a user's message and breaks them into atomic tasks")
public class IntentExtractionAgent {
private IntentAndAgentMap intentAndAgentMap;
@Autowired
private AgentPlatform agentPlatform;
IntentExtractionAgent() {
intentAndAgentMap = new IntentAndAgentMap();
intentAndAgentMap.addIntentAndAgent("fetch_sales_report", "Sales Reporting Agent");
intentAndAgentMap.addIntentAndAgent("fetch_purchase_report", "Purchase Reporting Agent");
intentAndAgentMap.addIntentAndAgent("summarize_reports", "Report Summarization Agent");
intentAndAgentMap.addIntentAndAgent("send_to_user", "Email Sending Agent");
}
@Action
@AchievesGoal(description = "Extracts the intent from a user's message and breaks them into atomic tasks")
void IntentExtractionResponse(final UserInput userInput, OperationContext context) {
final IntentExtractionResponse response = context.ai()
.withLlm("deepseek-r1:8b")
.withPromptElements(
Persona.create("Task Planner",
"You are a task planner. Break down a user's message into atomic tasks.",
"Polite",
"You are a polite and helpful assistant."),
intentAndAgentMap
)
.createObject(String.format("""
Critically analyze the user's message and break it down into atomic tasks.
Keep the intent and agent name as specified, do not change them.
# User Input
%s
""", userInput.getContent()), IntentExtractionResponse.class);
System.out.println(response);
}
}
What’s happening here?
- We define the agent with
@Agent. - Embabel wires everything up automatically.
- The LLM (DeepSeek via Ollama) receives the user input and mapping.
- The output is parsed into an
IntentExtractionResponse.
Multi-Model Flexibility
One of the most underrated features of Embabel is that you’re not limited to one model for everything.
You can assign different models to different agents:
// Use DeepSeek for deterministic intent extraction
context.ai().withLlm("deepseek-r1:8b");
// Use Llama 3.1:70B for richer summarization
context.ai().withLlm("llama3.1:70b");
// Use Mistral for lightweight classification
context.ai().withLlm("mistral:7b");
This means you can optimize for:
- Accuracy (instruction-following vs reasoning).
- Latency (small vs large models).
- Cost (local vs cloud).
In my setup:
- DeepSeek-R1:8B handled intent extraction and ranking reliably.
- A larger model like Llama 3.1:70B could be plugged into the report summarization agent where creativity and context length matter more.
👉 Embabel makes hybrid, multi-model workflows a first-class citizen.
Running the Agent
Suppose the user input is:
“Get me the sales summary and purchase summary and send it to John Doe.”
The LLM produces structured tasks:
{
"tasks": [
{"intent": "fetch_sales_report", "target": "Sales Reporting Agent"},
{"intent": "fetch_purchase_report", "target": "Purchase Reporting Agent"},
{"intent": "summarize_reports", "target": "Report Summarization Agent"},
{"intent": "send_to_user", "target": "Email Sending Agent", "dependsOn": ["summarize_reports"]}
]
}
From here, Embabel can delegate these tasks to the respective agents. For now, the downstream agents are placeholders, but this is exactly where you’d plug in reporting services, summarizers, or email modules.
Why This Matters
Without Embabel, you’d be manually:
- Building prompts.
- Parsing JSON responses from the LLM.
- Handling orchestration logic across services.
With Embabel:
- Declarative agent building with annotations.
- Structured AI responses mapped to Java objects.
- Local-first execution with Ollama + DeepSeek.
- Multi-model orchestration — use the right LLM for the right job.
Conclusion
In this example, we saw how to build an Intent Extraction Agent in Java using Embabel + Ollama (DeepSeek-R1:8B).
- The agent breaks down a complex user request into atomic tasks.
- Maps each task to a specific downstream agent.
- Delegates execution through Embabel’s orchestration layer.
- Supports multi-model strategies to balance determinism, creativity, and performance.
This is just the beginning — you can expand this with real sales/purchase reporting agents, email integration, or even multi-agent workflows.
If you’re a Java developer looking to build AI-native applications, Embabel + Ollama is a powerful, local-first starting point.
메타데이터
- post_id
- d9d982fd2c4e
- slug
- building-an-ai-agent-in-java-with-embabel-and-ollama-deepseek-r1-8b-d9d982fd2c4e
- url
- https://medium.com/@amanjn53/building-an-ai-agent-in-java-with-embabel-and-ollama-deepseek-r1-8b-d9d982fd2c4e
- canonical_url
- https://medium.com/@amanjn53/building-an-ai-agent-in-java-with-embabel-and-ollama-deepseek-r1-8b-d9d982fd2c4e
- author_url
- https://medium.com/@amanjn53
- status
- ok
- fetched_at
- 2026-07-17 18:16:58