This is an automated email from the ASF dual-hosted git repository.
paulk pushed a commit to branch asf-site
in repository https://gitbox.apache.org/repos/asf/groovy-website.git
The following commit(s) were added to refs/heads/asf-site by this push:
new 251a7cb initial version of embabel agentic patterns
251a7cb is described below
commit 251a7cb9ae28a514a5af00faa214177e203fa314
Author: Paul King <[email protected]>
AuthorDate: Fri Nov 7 23:17:32 2025 +1000
initial version of embabel agentic patterns
---
site/src/site/blog/embabel-agentic-patterns.adoc | 376 +++++++++++++++++++++++
1 file changed, 376 insertions(+)
diff --git a/site/src/site/blog/embabel-agentic-patterns.adoc
b/site/src/site/blog/embabel-agentic-patterns.adoc
new file mode 100644
index 0000000..6cd0af6
--- /dev/null
+++ b/site/src/site/blog/embabel-agentic-patterns.adoc
@@ -0,0 +1,376 @@
+= Groovy™, Embabel, and Agentic Design Patterns
+Paul King
+:revdate: 2025-11-07T23:00:00+00:00
+:keywords: groovy, spring ai, embabel, agentic
+:description: This post highlights some agentic code patterns supported by
Embabel.
+
+A recent
https://medium.com/@springrod/build-better-agents-in-java-vs-python-embabel-vs-langgraph-f7951a0d855c[blog
post] by Rod Johnson, looked at using Embabel and Java to solve some common
agentic design patterns.
+Rod's post in turn reference's
+another blog post by Hamza Boulahia using LangGraph with Python. Rod's post
makes some
+excellent arguments as to the benefits of a JVM solution over LangGraph and
Python.
+We won't repeat those arguments here, but we highly recommend that you read
Rod's article.
+
+This blog post looks at those same examples using Groovy. Groovy is sometimes
referred to
+as the Python of the JVM world. It's syntax mostly resembles Java but some of
its features
+will be familiar to Python programmers. So, for Python folks who want to try
out some JVM
+AI frameworks, Groovy might be a nice entry point.
+Also, for Groovy folks, we want to show them how easy it is to use powerful
JVM frameworks like Embabel.
+As an added bonus, we'll get to show off a few neat features of Groovy on the
way.
+
+We used Java 25, Groovy 5.0.2, and Embabel 0.2.0-SNAPSHOT.
+Embabel itself is built on Spring boot. We used Spring boot 3.5.6.
+Embabel supports a range of LLMs. We used Mistral:7b with Ollama.
+
+Embabel can make use of Spring Shell functionality and our example is all set
up to use that.
+First, you'll need to have Ollama running locally, or set it up using docker
as follows:
+
+[source,shell]
+----
+docker run -d -v ollama:/root/.ollama -p 11434:11434 --name ollama
ollama/ollama
+docker exec -it ollama ollama run mistral:7b
+----
+
+You can of course use other LLMs and other models.
+Follow the https://docs.embabel.com/embabel-agent/guide/0.1.3/[Embabel
documentation]
+to configure such artifacts if needed.
+
+Assuming you have cloned the
https://github.com/paulk-asert/langgraph-patterns-groovy[example repo], you can
then run the example using:
+
+[source,shell]
+----
+./gradlew run
+----
+
+This invokes our Embabel (Spring Boot) application:
+
+[source,groovy]
+----
+@SpringBootApplication
+@EnableAgents(loggingTheme = LoggingThemes.COLOSSUS)
+void main() {
+ SpringApplication.run(PatternsApplication)
+}
+----
+
+You can then interact with the Embabel agents from the command line (or IDE
run terminal window).
+
+Now, we're ready to start exploring some of those design patterns.
+
+== Prompt Chaining
+
+Prompt chaining breaks tasks into manageable pieces.
+The blog title generator example picks topics from user input,
+and then generates several blog titles for each topic.
+The Groovy solution follows the Java solution very closely:
+
+[source,groovy]
+----
+@Agent(description = 'Blog Titler Agent')
+class BlogTitler {
+
+ private final Actor<?> techWriter = new Actor<>('''
+ You are an expert technical writer. Always give clear,
+ concise, and straight-to-the-point answers.
+ ''', LlmOptions.withAutoLlm())
+
+ record Topics(List<String> topics) {
+ }
+
+ record TopicTitles(String topic, List<String> titles) {
+ }
+
+ record BlogTitles(List<TopicTitles> topicTitles) {
+ }
+
+ @Action
+ Topics extractTopics(UserInput userInput, Ai ai) {
+ techWriter.promptRunner(ai)
+ .creating(Topics)
+ .fromPrompt("""
+ Extract 1-3 key topics from the following text:
+ $userInput.content
+ """)
+ }
+
+ @Action
+ @AchievesGoal(description='Generate Titles for Topics')
+ BlogTitles generateBlogTitles(Topics topics, OperationContext context) {
+ var titles = context.parallelMap(
+ topics.topics(),
+ 10,
+ topic -> techWriter.promptRunner(context)
+ .creating(TopicTitles)
+ .fromPrompt("""
+ Generate two catchy blog titles for this topic:
+ $topic
+ """))
+ new BlogTitles(titles)
+ }
+}
+----
+
+It has domain records like `Topics` and `BlogTitles` to provide a richer
context for our prompts
+compared to simple strings. The Groovy version has some minor improvements
compared to Java.
+The most important is possibly the interpolated string support, which is still
coming for Java.
+
+At the shell prompt, you can enter a command like:
+
+----
+Colossus> blogs topics "Why Groovy is a great option for your AI applications
especially when using Embabel"
+----
+
+And the output might be something like this:
+
+----
+{
+ "topicTitles" : [ {
+ "topic" : "Groovy",
+ "titles" : [ "Unleashing Potential: Mastering Groovy for Modern
Development", "Exploring the Future of Coding: Top Groovy Trends and Techniques
in 2025" ]
+ }, {
+ "topic" : "AI Applications",
+ "titles" : [ "Unleashing Potential: Top AI Applications Shaping Our
Future", "Revolutionizing Everyday Life: Exploring Cutting-Edge AI
Applications" ]
+ }, {
+ "topic" : "Embabel",
+ "titles" : [ "Unveiling the Future: A Deep Dive into Embabel Technology",
"Embabel Revolution: Transforming Tomorrow's Tech Landscape Today" ]
+ } ]
+}
+----
+
+== Reflection
+
+Reflection, also know by other names such as the evaluator-optimizer pattern,
+is where agents critique and improve outputs.
+The draft and refine example explores drafting text in response to a
user-specified task
+and critiquing it until it’s satisfactory.
+
+Here is the Groovy code:
+
+[source,groovy]
+----
+@Configuration
+class DraftAndRefine {
+
+ record Draft(String content) {
+ }
+
+ @Bean
+ Agent draftAndRefineAgent() {
+ RepeatUntilAcceptableBuilder
+ .returning(Draft)
+ .consuming(UserInput)
+ .withMaxIterations(7)
+ .withScoreThreshold(.99)
+ .repeating(tac -> {
+ tac.ai()
+ .withAutoLlm()
+ .withId('draft')
+ .createObject("""
+ You are an assistant helping to complete the following
task:
+
+ Task:
+ $tac.input.content
+
+ Current Draft:
+ ${tac.lastAttemptOr("no draft yet")}
+
+ Feedback:
+ ${tac.lastFeedbackOr("no feedback yet")}
+
+ Instructions:
+ - If there is no draft and no feedback, generate a
clear and complete response to the task.
+ - If there is a draft but no feedback, improve the
draft as needed for clarity and quality.
+ - If there is both a draft and feedback, revise the
draft by incorporating the feedback directly.
+ - Always produce a single, improved draft as your
output.
+ """, Draft)
+ })
+ .withEvaluator(tac -> {
+ tac.ai().withAutoLlm()
+ .withId('evaluate_draft')
+ .createObject("""
+ Evaluating the following draft, based on the given
task.
+ Score it from 0.0 to 1.0 (best) and provide
constructive feedback for improvement.
+
+ Task:
+ $tac.input.content
+
+ Draft:
+ $tac.resultToEvaluate
+ """, TextFeedback)
+ })
+ .buildAgent('draft_and_refine_agent', 'An agent that drafts and
refines content')
+ }
+}
+----
+
+Again, it follows the Java code very closely with some minor improvements.
+
+You might invoke it with something like:
+
+----
+Colossus> x "Draft and refine a paragraph about using Groovy and Embabel"
+----
+
+The output might be something like:
+
+----
+You asked: UserInput(content=Draft and refine a paragraph about using Groovy
and Embabel, timestamp=2025-11-07T10:37:55.707939Z)
+
+{
+ "content" : "Groovy, a dynamic language for the Java platform with an
elegant syntax and seamless integration with Java, is particularly useful when
combined with Embabel. In complex problem-solving scenarios such as test
automation and scripting tasks, Groovy's concise and readable code enables
developers to tackle these challenges effectively. Embabel simplifies the
process by abstracting away low-level programming details, further improving
performance and reducing development time [...]
+}
+
+LLMs used: [mistral:7b] across 15 calls
+Prompt tokens: 7,502,
+Completion tokens: 1,914
+----
+
+== Routing
+
+Another fundamental pattern is routing within a workflow.
+The sentiment classification example first performs sentiment analysis,
+and then chooses between alternative options for generating a response.
+
+The Java Embabel solution itself incorporates a coding design pattern:
+Algebraic Data Types (ADTs). This pattern is worth exploring a bit more.
+ADTs have _product_ types, like records,
+and _sum_ types, like what you get with a sealed base class and some subtype
variants.
+Other programming languages, like Haskell, represent sum types with different
approaches.
+A common scenario is capturing state as types (positive/negative in our
+example but more elaborate state machines can be represented this way).
+It is also convenient being able to convert between the states and a data type
+like an enum. As an added bonus, serializability to JSON also helps in many
scenarios
+including constructing AI payloads.
+
+The first half of the `ClassificationAgent` class
+has the relevant types for our ADT (copied from Rod's example):
+
+[source,java]
+----
+@Agent(description = "Perform sentiment analysis") // Java
+public class ClassificationAgent {
+
+ @JsonTypeInfo(
+ use = JsonTypeInfo.Id.SIMPLE_NAME,
+ include = JsonTypeInfo.As.PROPERTY,
+ property = "type"
+ )
+ public sealed interface Sentiment {
+ }
+
+ public static final class Positive implements Sentiment {
+ }
+
+ public static final class Negative implements Sentiment {
+ }
+
+ private enum SentimentType {
+ POSITIVE,
+ NEGATIVE;
+
+ public Sentiment toSentiment() {
+ return switch (this) {
+ case POSITIVE -> new Positive();
+ case NEGATIVE -> new Negative();
+ };
+ }
+ }
+
+ public record Response(String message) {
+ }
+----
+
+You could imagine similar scenarios where we'd want to capture similar ADTs.
+Whenever we have common boilerplate code, Groovy AST transforms can be a
useful tool.
+We'll create a `@SumType` AST transform (details in the source code repo).
Then our code becomes:
+
+[source,groovy]
+----
+@Agent(description = 'Perform sentiment analysis')
+class ClassificationAgent {
+
+ @SumType(variantHelper = 'toSentiment')
+ interface Sentiment {
+ Positive()
+ Negative()
+ }
+
+ record Response(String message) {
+ }
+----
+
+It generates the same types and methods in Rod's example.
+
+The second half of the classification agent follows the Java version closely:
+
+[source,groovy]
+----
+ @Action
+ Sentiment classify(UserInput userInput, Ai ai) {
+ ai.withAutoLlm()
+ .createObject("""
+ Determine if the sentiment of the following text is positive
or negative.
+ Text: "$userInput.content"
+ """, SentimentType)
+ .toSentiment()
+ }
+
+ @Action
+ Response encourage(UserInput userInput, Positive sentiment, Ai ai) {
+ ai.withAutoLlm()
+ .createObject("""
+ Generate an encouraging response to the following positive
text:
+ $userInput.content
+ """, Response)
+ }
+
+ @Action
+ Response help(UserInput userInput, Negative sentiment, Ai ai) {
+ ai.withAutoLlm()
+ .createObject("""
+ Generate a supportive response to the following negative text:
+ $userInput.content
+ """, Response)
+ }
+
+ @AchievesGoal(description = 'Generate a response based on discerning
sentiment in user input')
+ @Action
+ Response done(Response response) {
+ response
+ }
+
+}
+----
+
+We can ask a query that will trigger these agents:
+
+----
+x "Analyze sentiment and respond to 'Groovy makes you more productive when
writing your AI applications' "
+----
+
+The output might look like this:
+
+----
+You asked: UserInput(content=Analyze sentiment and respond to 'Groovy makes
you more productive when writing your AI applications' ,
timestamp=2025-11-07T10:40:14.120002Z)
+
+{
+ "message" : "Continuing to utilize Groovy for your AI applications will
undeniably foster increased productivity and efficiency in your development
process. Keep up the great work!"
+}
+
+LLMs used: [mistral:7b] across 2 calls
+Prompt tokens: 425,
+Completion tokens: 47
+----
+
+== Discussion
+
+Hopefully you can see that writing Embabel applications is easy using Groovy.
+Python users might find the Groovy syntax a friendly starting point to try out
JVM development.
+Certainly, Groovy has
+powerful DSL capabilities. We could provide an alternative DSL that offered
+an even more Python-like coding experience for the Embabel framework,
+but that's a topic for a different blog post.
+
+.Update history
+****
+*07/Nov/2025*: Initial version.
+****