Skip to content

Commit 4d58a77

Browse files
committed
quarkus readme
1 parent 168933d commit 4d58a77

File tree

1 file changed

+302
-2
lines changed

1 file changed

+302
-2
lines changed

quarkus/README.md

Lines changed: 302 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,302 @@
1-
# quarkus-agentic-dapr
2-
Quarkus Agentic Dapr Workflows integration
1+
# Quarkus Agentic Dapr Examples
2+
3+
This module demonstrates how to use `quarkus-agentic-dapr` to orchestrate LangChain4j agents with [Dapr Workflows](https://docs.dapr.io/developing-applications/building-blocks/workflow/workflow-overview/) as the underlying execution engine.
4+
5+
## What is Quarkus Agentic Dapr?
6+
7+
`quarkus-agentic-dapr` is a Quarkus extension that bridges LangChain4j's agentic framework with Dapr Durable Workflows. It is a **drop-in replacement** for the `quarkus-langchain4j-agentic` module — your agent definitions, annotations, and application code stay exactly the same. The only thing that changes is the dependency in your `pom.xml`.
8+
9+
### In-Memory vs Dapr Orchestration
10+
11+
LangChain4j ships with `quarkus-langchain4j-agentic`, which provides in-memory orchestration of agents using JVM thread pools. This works well for development and simple use cases, but has limitations in production:
12+
13+
- **No durability** — if the process crashes, all in-flight agent work is lost.
14+
- **No observability** — agent execution state lives only in memory; there is no history of what happened.
15+
- **No fault tolerance** — a failed tool call or LLM call requires restarting the entire flow from scratch.
16+
- **Single JVM only** — orchestration cannot span multiple service instances.
17+
18+
`quarkus-agentic-dapr` solves these problems by routing every orchestration decision, tool call, and LLM call through Dapr Workflow activities. This gives you:
19+
20+
| Capability | `quarkus-langchain4j-agentic` | `quarkus-agentic-dapr` |
21+
|---|---|---|
22+
| Durability | None — lost on crash | Full workflow history persisted by Dapr |
23+
| Observability | Application logs only | Dapr dashboard + per-activity status tracking |
24+
| Fault tolerance | Manual retry | Dapr auto-retries failed activities |
25+
| Scalability | Single JVM thread pool | Distributed across Dapr sidecars |
26+
| Tool call audit trail | Log-based | Every tool call recorded in workflow history with inputs/outputs |
27+
| Code changes required | None | **None** — just swap the dependency |
28+
29+
### How Dapr Workflows Work Behind the Scenes
30+
31+
When you use `quarkus-agentic-dapr`, your agent orchestration is backed by a hierarchy of Dapr Workflows:
32+
33+
1. **Orchestration Workflow** (top level) — one of `SequentialOrchestrationWorkflow`, `ParallelOrchestrationWorkflow`, `LoopOrchestrationWorkflow`, or `ConditionalOrchestrationWorkflow`. This coordinates the order in which agents execute.
34+
35+
2. **AgentRunWorkflow** (per agent) — each agent gets its own child workflow that manages its full ReAct loop lifecycle. It listens for events like `"tool-call"`, `"llm-call"`, and `"done"`, scheduling the appropriate Dapr activities for each.
36+
37+
3. **Activities** (individual operations) — `ToolCallActivity` executes tool methods, `LlmCallActivity` executes LLM calls. Each activity is a durable unit of work that Dapr can retry on failure and that appears in the workflow history.
38+
39+
This architecture means that every decision the LLM makes, every tool it calls, and every result it receives is recorded as a durable workflow event. If the process crashes mid-execution, Dapr replays the workflow from the last checkpoint — no work is lost.
40+
41+
The routing is completely transparent. At build time, the Quarkus extension automatically applies interceptors to all `@Tool`-annotated methods and generates CDI decorators for all `@Agent` interfaces. No changes to your agent code are required.
42+
43+
### Java SPI Discovery
44+
45+
The integration uses Java SPI (Service Provider Interface) to register itself. When LangChain4j encounters `@SequenceAgent`, `@ParallelAgent`, `@LoopAgent`, or `@ConditionalAgent`, it discovers `DaprWorkflowAgentsBuilder` via SPI and uses Dapr-based planners instead of in-memory ones. This is why the swap is transparent — just adding the dependency is enough.
46+
47+
## Prerequisites
48+
49+
- Java 17+
50+
- Maven 3.9+
51+
- An OpenAI API key (or compatible LLM provider)
52+
53+
No separate Dapr installation is needed for development — the extension uses **Quarkus Dapr Dev Services** to automatically start a Dapr sidecar, placement service, scheduler, and state store when you run in dev mode.
54+
55+
## Project Setup
56+
57+
### Dependency
58+
59+
Replace `quarkus-langchain4j-agentic` with `quarkus-agentic-dapr` in your `pom.xml`:
60+
61+
```xml
62+
<!-- Remove this (in-memory orchestration) -->
63+
<dependency>
64+
<groupId>io.quarkiverse.langchain4j</groupId>
65+
<artifactId>quarkus-langchain4j-agentic</artifactId>
66+
</dependency>
67+
68+
<!-- Add this (Dapr workflow orchestration) -->
69+
<dependency>
70+
<groupId>io.dapr.quarkus</groupId>
71+
<artifactId>quarkus-agentic-dapr</artifactId>
72+
<version>1.18.0-SNAPSHOT</version>
73+
</dependency>
74+
```
75+
76+
You also need an LLM provider and a REST endpoint:
77+
78+
```xml
79+
<dependency>
80+
<groupId>io.quarkiverse.langchain4j</groupId>
81+
<artifactId>quarkus-langchain4j-openai</artifactId>
82+
</dependency>
83+
<dependency>
84+
<groupId>io.quarkus</groupId>
85+
<artifactId>quarkus-rest</artifactId>
86+
</dependency>
87+
```
88+
89+
Optionally, add the agent registry to auto-register agent metadata in a Dapr state store:
90+
91+
```xml
92+
<dependency>
93+
<groupId>io.dapr.quarkus</groupId>
94+
<artifactId>quarkus-agentic-dapr-agents-registry</artifactId>
95+
<version>1.18.0-SNAPSHOT</version>
96+
</dependency>
97+
```
98+
99+
### Configuration
100+
101+
Add to `application.properties`:
102+
103+
```properties
104+
# Enable Dapr Dev Services (auto-starts sidecar, placement, scheduler, state store)
105+
quarkus.dapr.devservices.enabled=true
106+
quarkus.dapr.workflow.enabled=true
107+
108+
# LLM provider configuration
109+
quarkus.langchain4j.openai.api-key=${OPENAI_API_KEY}
110+
quarkus.langchain4j.openai.chat-model.model-name=gpt-4o-mini
111+
```
112+
113+
## Examples
114+
115+
### 1. Sequential Agent — Story Creator
116+
117+
A composite agent that runs two sub-agents in sequence: first a `CreativeWriter` generates a story draft, then a `StyleEditor` refines it.
118+
119+
**Agent definitions:**
120+
121+
```java
122+
public interface CreativeWriter {
123+
124+
@UserMessage("""
125+
You are a creative writer.
126+
Generate a draft of a story no more than 3 sentences around the given topic.
127+
Return only the story and nothing else.
128+
The topic is {{topic}}.
129+
""")
130+
@Agent(name = "creative-writer-agent",
131+
description = "Generate a story based on the given topic", outputKey = "story")
132+
String generateStory(@V("topic") String topic);
133+
}
134+
135+
public interface StyleEditor {
136+
137+
@UserMessage("""
138+
You are a style editor.
139+
Review the following story and improve its style to match the requested style: {{style}}.
140+
Return only the improved story and nothing else.
141+
Story: {{story}}
142+
""")
143+
@Agent(name = "style-editor-agent",
144+
description = "Edit a story to improve its writing style", outputKey = "story")
145+
String editStory(@V("story") String story, @V("style") String style);
146+
}
147+
```
148+
149+
**Orchestration:**
150+
151+
```java
152+
public interface StoryCreator {
153+
154+
@SequenceAgent(name = "story-creator-agent",
155+
outputKey = "story",
156+
subAgents = { CreativeWriter.class, StyleEditor.class })
157+
String write(@V("topic") String topic, @V("style") String style);
158+
}
159+
```
160+
161+
**REST endpoint:**
162+
163+
```java
164+
@Path("/story")
165+
public class StoryResource {
166+
167+
@Inject
168+
StoryCreator storyCreator;
169+
170+
@GET
171+
@Produces(MediaType.TEXT_PLAIN)
172+
public String createStory(
173+
@QueryParam("topic") @DefaultValue("dragons and wizards") String topic,
174+
@QueryParam("style") @DefaultValue("fantasy") String style) {
175+
return storyCreator.write(topic, style);
176+
}
177+
}
178+
```
179+
180+
Behind the scenes, this starts a `SequentialOrchestrationWorkflow` in Dapr that runs `CreativeWriter` first, passes the `story` output to `StyleEditor`, and returns the final result.
181+
182+
**Try it:**
183+
184+
```bash
185+
curl "http://localhost:8080/story?topic=dragons&style=comedy"
186+
```
187+
188+
### 2. Parallel Agent — Story + Research
189+
190+
A composite agent that runs a `StoryCreator` (itself a sequential agent) and a `ResearchWriter` in parallel. This demonstrates nested composite agents.
191+
192+
```java
193+
public interface ParallelCreator {
194+
195+
@ParallelAgent(name = "parallel-creator-agent",
196+
outputKey = "storyAndCountryResearch",
197+
subAgents = { StoryCreator.class, ResearchWriter.class })
198+
ParallelStatus create(@V("topic") String topic, @V("country") String country, @V("style") String style);
199+
200+
@Output
201+
static ParallelStatus output(String story, String summary) {
202+
if (story == null || summary == null) {
203+
return new ParallelStatus("ERROR", story, summary);
204+
}
205+
return new ParallelStatus("OK", story, summary);
206+
}
207+
}
208+
```
209+
210+
Behind the scenes, a `ParallelOrchestrationWorkflow` spawns both sub-agents concurrently using Dapr's `allOf()` task composition and waits for all to complete.
211+
212+
**Try it:**
213+
214+
```bash
215+
curl "http://localhost:8080/parallel?topic=dragons&country=France&style=comedy"
216+
```
217+
218+
### 3. Standalone Agent with Tool Calls — Research Writer
219+
220+
An agent that uses `@Tool`-annotated methods to fetch data. Tool calls are automatically routed through `ToolCallActivity` Dapr activities.
221+
222+
```java
223+
public interface ResearchWriter {
224+
225+
@ToolBox(ResearchTools.class)
226+
@UserMessage("""
227+
You are a research assistant.
228+
Write a concise 2-sentence summary about the country {{country}}
229+
using the available tools to fetch accurate data.
230+
Return only the summary.
231+
""")
232+
@Agent(name = "research-location-agent",
233+
description = "Researches and summarises facts about a country", outputKey = "summary")
234+
String research(@V("country") String country);
235+
}
236+
```
237+
238+
The tools are plain CDI beans — no Dapr-specific code needed:
239+
240+
```java
241+
@ApplicationScoped
242+
public class ResearchTools {
243+
244+
@Tool("Looks up real-time population data for a given country")
245+
public String getPopulation(String country) {
246+
return switch (country.toLowerCase()) {
247+
case "france" -> "France has approximately 68 million inhabitants (2024).";
248+
case "germany" -> "Germany has approximately 84 million inhabitants (2024).";
249+
default -> country + " population data is not available in this demo.";
250+
};
251+
}
252+
253+
@Tool("Returns the official capital city of a given country")
254+
public String getCapital(String country) {
255+
return switch (country.toLowerCase()) {
256+
case "france" -> "The capital of France is Paris.";
257+
case "germany" -> "The capital of Germany is Berlin.";
258+
default -> "Capital city for " + country + " is not available in this demo.";
259+
};
260+
}
261+
}
262+
```
263+
264+
When the LLM decides to call `getPopulation` or `getCapital`, the Dapr extension intercepts the call and executes it as a `ToolCallActivity`. This means every tool invocation is recorded in the Dapr workflow history and can be retried automatically on failure.
265+
266+
**Try it:**
267+
268+
```bash
269+
curl "http://localhost:8080/research?country=France"
270+
```
271+
272+
## Running the Examples
273+
274+
1. Set your OpenAI API key:
275+
276+
```bash
277+
export OPENAI_API_KEY=sk-...
278+
```
279+
280+
2. Start in Quarkus dev mode (Dapr Dev Services will start automatically):
281+
282+
```bash
283+
cd quarkus/examples
284+
mvn quarkus:dev
285+
```
286+
287+
3. Call the endpoints:
288+
289+
```bash
290+
# Sequential story creation
291+
curl "http://localhost:8080/story?topic=space+exploration&style=noir"
292+
293+
# Parallel story + research
294+
curl "http://localhost:8080/parallel?topic=robots&country=Japan&style=sci-fi"
295+
296+
# Standalone research with tool calls
297+
curl "http://localhost:8080/research?country=Germany"
298+
```
299+
300+
## Summary
301+
302+
`quarkus-agentic-dapr` lets you keep writing standard LangChain4j agent code while gaining the production-grade durability, observability, and fault tolerance of Dapr Workflows. Swap the dependency, add two lines of configuration, and your agents are now backed by durable workflows — no code changes required.

0 commit comments

Comments
 (0)