<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged spring-ai</title>
<link>https://coderlegion.com/tag/spring-ai</link>
<description>Powered by Question2Answer</description>
<item>
<title>Skill-Based Sub-Agents with LangGraph4j and Spring AI</title>
<link>https://coderlegion.com/16271/skill-based-sub-agents-with-langgraph4j-and-spring-ai</link>
<description>&lt;h3 id=&quot;combine-tools-amp-skill-to-create-agent&quot;&gt;Combine Tools &amp;amp; Skill to create Agent&lt;/h3&gt;
&lt;p&gt;Modern agentic systems usually expose two strong but separate abstractions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Tools&lt;/strong&gt;, which give an LLM executable capabilities&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Skills&lt;/strong&gt;, which package reusable instructions for a narrow task&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This article shows how to combine both ideas in [LangGraph4j] so that a skill is not just prompt text, but a fully operational &lt;strong&gt;sub-agent exposed as a tool&lt;/strong&gt;. The result is a practical pattern for building modular multi-agent systems in Java with &lt;strong&gt;[Spring AI]&lt;/strong&gt; and &lt;strong&gt;[LangGraph4j]&lt;/strong&gt;, while keeping each agent focused, reusable, and low in context cost.&lt;/p&gt;
&lt;p&gt;Starting from a standard ReACT agent, the approach implemented is quite simple.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Load a &lt;code&gt;SKILL.md&lt;/code&gt; file&lt;/li&gt;
&lt;li&gt;Read its front matter to define the tool contract ( name and argument description)&lt;/li&gt;
&lt;li&gt;Use the skill body as the sub-agent system prompt&lt;/li&gt;
&lt;li&gt;Restrict the sub-agent to only the tools declared by the skill&lt;/li&gt;
&lt;li&gt;Register the sub-agent in a parent ReAct agent as if it were a normal tool&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In other words, a skill becomes an &lt;em&gt;executable tool-backed agent&lt;/em&gt;.&lt;/p&gt;
&lt;h3 id=&quot;why-this-pattern-matters&quot;&gt;Why This Pattern Matters&lt;/h3&gt;
&lt;p&gt;The standard ReAct pattern already gives an LLM a strong execution model: reason, choose a tool, inspect the result, and continue until the task is complete. That model is useful because the LLM does not need to commit to a fixed plan up front. It can adapt after every tool call.&lt;/p&gt;
&lt;p&gt;The next step is to let a tool itself be implemented by another agent.&lt;/p&gt;
&lt;p&gt;Once that happens, the parent agent is no longer forced to know every operational detail. It only needs to know:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;which specialized capability exists&lt;/li&gt;
&lt;li&gt;when to invoke it&lt;/li&gt;
&lt;li&gt;what input to pass&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The specialized logic is delegated to a narrower agent with its own instructions and its own restricted toolset. This gives us a clean &lt;strong&gt;sub-agent architecture&lt;/strong&gt; without inventing a new orchestration primitive. We reuse the same tool-calling semantics that ReAct agents already understand well.&lt;/p&gt;
&amp;lt;!--
## Advantages

There are multiple advantages to use this approach:

### Reuse Tool execution plan (coordination)

One of the most important feature of the LLM, since from beginnig, was able to define a tool execution planning to resolve the problem. The ReAct agent was introdced to add an execution layer and an evaluation loop to execute tools and involve again LLM,  based on tool execution result in order to dynamically revaluate the execution planning itself.
If we consider that a tool can involve a ReACT agent in turn we gain the sub agent involvment that colud involve other sub agents in turn in a iterative way

### Minimize agent context (less token)

Declaring a sub agent as a tool, when its parent agent plan to call it, it provides the information requested by sub agent extracting them from the current context automatically reducing them for the specific call. This guarantee that the sub agent receives only the required information that become the user input for it.
Moreover for each sub agents we can declare the `allowed-tools` that further leaning the context making the agent more efficient and effective
--&amp;gt;
&lt;h3 id=&quot;why-reusing-tool-semantics-is-powerful&quot;&gt;Why Reusing Tool Semantics Is Powerful&lt;/h3&gt;
&lt;p&gt;One of the most valuable properties of LLM-based agents is their ability to build and revise a tool execution plan while reasoning over intermediate results. ReAct made this practical by combining thought, action, and observation in a loop.&lt;/p&gt;
&lt;p&gt;When a tool call can transparently invoke another agent, we preserve that planning model instead of replacing it.&lt;/p&gt;
&lt;p&gt;The parent agent can:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;decide that a specialized capability is needed&lt;/li&gt;
&lt;li&gt;provide only the task-relevant context&lt;/li&gt;
&lt;li&gt;wait for a result exactly as it would for a standard tool&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The child agent can:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;run its own reasoning loop&lt;/li&gt;
&lt;li&gt;invoke its own allowed tools&lt;/li&gt;
&lt;li&gt;return a compact outcome to the parent&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This gives hierarchical coordination without exposing the full inner workflow to the parent prompt.&lt;/p&gt;
&lt;h3 id=&quot;minimizing-context-and-token-usage&quot;&gt;Minimizing Context and Token Usage&lt;/h3&gt;
&lt;p&gt;This pattern also improves context discipline.&lt;/p&gt;
&lt;p&gt;Without sub-agents, a single generalist agent often needs:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;every tool definition&lt;/li&gt;
&lt;li&gt;all operating instructions for every domain&lt;/li&gt;
&lt;li&gt;every procedural rule in one large system prompt&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That quickly becomes expensive and unreliable.&lt;/p&gt;
&lt;p&gt;With skill-based sub-agents:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the parent only sees high-level capabilities&lt;/li&gt;
&lt;li&gt;each child sees only the instructions relevant to its task&lt;/li&gt;
&lt;li&gt;each child receives only the tools it is allowed to use&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This narrower context has practical benefits:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;fewer tokens sent to the model&lt;/li&gt;
&lt;li&gt;lower prompt interference between unrelated domains&lt;/li&gt;
&lt;li&gt;clearer tool choice&lt;/li&gt;
&lt;li&gt;better modularity as the system grows&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;&lt;p&gt;The &lt;code&gt;allowed-tools&lt;/code&gt; skill property plays an important role here anabling tool filtering. It turns tool access into an explicit design decision instead of an accidental side effect of what happened to be registered globally.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;reference-implementation&quot;&gt;Reference implementation&lt;/h3&gt;
&lt;p&gt;I've started implementation in the new [LangGraph4j] release stream &lt;code&gt;1.9&lt;/code&gt; currently in active development. The implementation is centered on [`SkilledReactSubAgent`], which turns a markdown skill into both:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a compiled LangGraph4j sub-graph&lt;/li&gt;
&lt;li&gt;a Spring AI tool (i.e. &lt;code&gt;ToolCallback&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That means the same component can participate in the graph as an agent and present itself to the parent LLM as a callable tool.&lt;/p&gt;
&lt;p&gt;In particular the flow is:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Parse the &lt;code&gt;SKILL.md&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Extract &lt;code&gt;name&lt;/code&gt; and &lt;code&gt;description&lt;/code&gt; from front matter&lt;/li&gt;
&lt;li&gt;Optionally filter tools using &lt;code&gt;allowed-tools&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Use the markdown body as the sub-agent instructions&lt;/li&gt;
&lt;li&gt;Compile the agent&lt;/li&gt;
&lt;li&gt;Wrap it into a function tool whose input is a single &lt;code&gt;context&lt;/code&gt; field&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;&lt;p&gt;The important design choice is that the &lt;strong&gt;tool definition is derived from the skill metadata&lt;/strong&gt;, while the &lt;strong&gt;agent behavior is derived from the skill content&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id=&quot;skill-format&quot;&gt;Skill Format&lt;/h4&gt;
&lt;p&gt;The supported skill format is &lt;/p&gt;
&lt;h3 id=&quot;section&quot;&gt;&lt;/h3&gt;
&lt;p&gt;name: &lt;br&gt;
description: |&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    &amp;lt;multi line description&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;allowed-tools-&quot;&gt;allowed-tools: &lt;/h3&gt;




### From Skill to Tool-Backed Agent

The conversion happens in `SkilledReactSubAgent.Builder#build(...)`. This is the crucial bridge where:

* the skill metadata defines the external API
* the skill body defines the internal operating behavior
* the allowed tools define the sub-agent execution boundary

The resulting agent is then wrapped as a Spring AI function tool.

&amp;gt; From the parent agent point of view, this is just another tool. Internally, it is a complete ReAct-capable agent workflow.


## Examples

### Purchase Assistant Agent

This is an agent that allow to select a produdct from a reference marketplace and purchase it. Below the simplified component diagram.

![](https://bsorrentino.github.io/bsorrentino/assets/langgraph-java/sub-agents/PurchaseAssistantAgent1.png)

### Marketplace Skill

&lt;pre&gt;
---
name: agent-marketplace
description: |
    marketplace agent, ask for information about products. This is agent that provides the information on the product marketplace.
    required data are:
    * product name
allowed-tools: searchByProduct
---

## Retrieve product information
We need all information about about the product of interest in particular is mandatory to have
* product name

with such information we have to invoke tool `searchByProduct` and return the result
&lt;/pre&gt;

### Payment Skill

&lt;pre&gt;
---
name: agent-payment
description: |
    payment agent, this is the agent that provides payment service.
    required data are:
    * Product name
    * Product price
    * Product currency
    * the International Bank Account Number (IBAN) - optional

allowed-tools:
    - submit-payment
    - retrieve-iban
---

## submit a payment for purchasing a specific product
We need all information about purchasing to allow the payment

* Product name
* Product price
* Product price currency
* International Bank Account Number (IBAN)

With such information you must invoke tool `submit-payment` and return the result of the payment transaction plus the IBAN.
If IBAN is not provided invoke tool `retrieve-iban` to retrieve the required IBAN.

&lt;/pre&gt;

Observing the skills above they define three different concerns:

* `name`: the tool name exposed to the parent agent
* `description`: the tool description shown to the model
* `allowed-tools`: the only tools available inside the sub-agent

Everything after the front matter becomes the sub-agent system prompt.

&amp;gt; Again, this is a strong separation of concerns. The parent agent sees a concise tool contract, while the child agent receives richer operating instructions.

## Finally the code

Below a representative code snippet that show how wires two skill-based sub-agents into a parent agent:

```java
var subAgentMarketplace = SkilledReactSubAgent.builder()
        .chatModel(chatModel)
        .tools(Marketplace.tools())
        .build(compileConfig,
                SkillSource.of(Paths.get(&quot;skills/agent-marketplace/&quot;)));

var subAgentPayment = SkilledReactSubAgent.builder()
        .chatModel(chatModel)
        .tools(Payment.tools())
        .build(compileConfig,
                SkillSource.of(Paths.get(&quot;skills/agent-payment/&quot;)));

var purchaseAssistantAgent = AgentExecutorEx.builder()
        .chatModel(chatModel)
        .tool(subAgentMarketplace)
        .tool(subAgentPayment)
        .build(compileConfig);

var input = &quot;&quot;&quot;
        search for product 'X'.
        If found proceed to payment with IBAN US82WEST1234567890123456 
        to purchase it
        &quot;&quot;&quot;;

var result = purchaseAssistantAgent.invoke( 
        GraphInput.args( Map.of(&quot;messages&quot;, new UserMessage(input) )), runnableConfig); 

```

This composition is interesting because the purchase agent (the parent) does not need detailed knowledge of:

* how product lookup works
* how payment submission works
* when IBAN retrieval is required

Those rules are delegated to the sub agents themselves.

That is a simple but good example of behavioral encapsulation. The parent agent only decides to involve marketplace agent to retrieve product and after that to involve payment agent to purchase.

This is a very basic but representative example, it is useful because it shows the intended layering:

* the user gives a single business request
* the parent agent decomposes the work
* specialized sub-agents execute their own logic
* the final answer comes back through the normal tool-response path

&amp;gt; Interesting note that each sub agent, potentially, can relies on different LLM chat model so we can further optimize overall agent execution process

Below a more detailed componet diagram generated by [LangGraph4j] itself.

![](https://bsorrentino.github.io/bsorrentino/assets/langgraph-java/sub-agents/PurchaseAssistantAgent.png)

From this diagram it is quite clear that the sub agents are nothing but sub-graphs

## Design Advantages

This approach has several practical advantages.

1. **Reuse Existing Agent Patterns**
   
   There is no need to invent a separate orchestration protocol for sub-agents. A sub-agent is exposed as a tool, so the parent agent can use standard ReAct behavior without extra prompting complexity.

2. **Better Modularity**

   Each skill owns:
   * its tool-facing identity
   * its internal instructions
   * its allowed tool boundary

   That makes the system easier to evolve than a single monolithic agent prompt.

3. **Smaller and Cleaner Context**

   The child agent receives only what it needs. This reduces token usage and lowers the risk that unrelated instructions distort tool choice or task execution.

4. **Safer Capability Scoping**

   By filtering tools from `allowed-tools`, the framework prevents a child agent from accidentally seeing tools outside its intended domain.

5. **Better Reusability**

   A skill can be packaged, versioned, and reused across different parent agents. The same marketplace or payment skill can participate in many workflows as long as the tool contract remains stable.

## Tradeoffs and Implementation Considerations

This pattern is strong, but it is not free of tradeoffs.

1. **The Parent Must Summarize Well**

   Because the child receives a single `context` field, the parent agent needs to pass the relevant facts clearly. If critical information is omitted, the child may need extra recovery steps or may fail to act.

2. **Tool Descriptions Matter More**

   Since the parent sees the child as a tool, the `name` and `description` fields in the skill front matter become part of the routing surface for the LLM. Weak metadata will reduce invocation quality.

3. **Prompt Boundaries Need Discipline**

   The skill body is effectively the child system prompt. If it is vague, contradictory, or overloaded, the benefits of modularity disappear. Skills need to stay focused.

4. **Observability Is Important**

   Nested agents can make execution harder to inspect if tracing is weak. LangGraph4j's graph structure and step streaming help here, but production systems should still invest in logging and traceability.

## Why LangGraph4j Fits This Well

[LangGraph4j] is a good fit for this pattern because it already models agent workflows as explicit graphs and integrates naturally with Spring AI tool abstractions.

That matters for two reasons:

* the sub-agent is not just prompt composition; it is a compiled graph with state and execution hooks
* the parent integration remains ergonomic because Spring AI already treats capabilities as tools
* [LangGraph4j] support interruptions in subgraph enabling **Human In The Loop** also when involving sub agents

So the implementation is not forcing two incompatible ideas together. It is aligning:

* LangGraph4j graph execution
* Spring AI tool calling
* markdown-defined skills

into a single reusable agent-building pattern.

## Conclusion

Turning a skill into a sub-agent is a small change with a large impact.

It lets us:

* preserve the strengths of the ReAct loop
* delegate specialized behavior cleanly
* reduce context size
* constrain tool access per domain
* build multi-agent systems out of reusable skill modules

The idea is implemented in a compact and pragmatic way: a `SKILL.md` file becomes the source of both the sub-agent prompt and the tool contract exposed to the parent agent.

For Java teams building agentic applications with Spring AI and LangGraph4j, this is a practical pattern for moving from a single large agent toward a modular multi-agent design without abandoning the tool semantics that current LLMs already handle well.

The implementation is in progress on release `1.9-SNAPSHOT` in develop branch of [LangGraph4j] repository but the first result are very promising.

Hope this could help and encourage usage of [LangGraph4j] for your next Agentic Workflow. Checkout project, try it and let me know your feedback and... happy AI coding! 


[LangGraph4j]: https://github.com/langgraph4j/langgraph4j
[Spring AI]: https://spring.io/projects/spring-ai

</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/16271/skill-based-sub-agents-with-langgraph4j-and-spring-ai</guid>
<pubDate>Thu, 30 Apr 2026 21:42:33 +0000</pubDate>
</item>
<item>
<title>Designing APIs for the AI Era with Spring AI and MCP</title>
<link>https://coderlegion.com/5481/designing-apis-for-the-ai-era-with-spring-ai-and-mcp</link>
<description>&lt;p&gt;As backend developers, we've been building robust REST APIs for years. We design them to be consumed by our UIs, mobile apps, or other microservices. It's a paradigm we've mastered. But what if I told you that with minimal effort, that same API could have a second interface—one that allows AI agents to interact with your business logic using natural language?&lt;/p&gt;
&lt;p&gt;Today, thinking about artificial intelligence isn't an afterthought; it's a design strategy. It's not just about creating a service that responds to GET or POST requests, but about asking ourselves: &lt;strong&gt;how can an AI model consume my service to provide extra value?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;For those of us developing with Java, thanks to &lt;strong&gt;Spring AI&lt;/strong&gt;, this idea has shifted from a complex task to a simple extension of what we already do. In this post, we'll explore how incredibly easy it is to add an &lt;strong&gt;MCP (Model Context Protocol) Server&lt;/strong&gt; to an existing Spring Boot application, using a real-world API for managing a product catalog as our example.&lt;/p&gt;
&lt;h3 id=&quot;the-dual-api-paradigm-rest-for-machines-mcp-for-ai-&quot;&gt;The Dual API Paradigm: REST for Machines, MCP for AI ????&lt;/h3&gt;
&lt;p&gt;A REST API is great for structured machine-to-machine communication. But an AI agent doesn't think in terms of JSON or endpoints.&lt;/p&gt;
&lt;p&gt;This is where the MCP Server comes in. It acts as a translator, exposing your application's logic in a way that an LLM (Large Language Model) can understand and use. This allows you to have the best of both worlds:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Your traditional REST API&lt;/strong&gt;, stable and reliable for your applications.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;An MCP Server&lt;/strong&gt;, offering a native interface for AI integration.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This approach forces you to design from the outset with the functions (&lt;code&gt;Tools&lt;/code&gt;), data (&lt;code&gt;Resources&lt;/code&gt;), and guides (&lt;code&gt;Prompts&lt;/code&gt;) that an intelligent agent would need to operate autonomously within your domain.&lt;/p&gt;
&lt;h3 id=&quot;what-is-the-model-context-protocol-&quot;&gt;What is the Model Context Protocol?&lt;/h3&gt;
&lt;p&gt;The &lt;strong&gt;Model Context Protocol&lt;/strong&gt; (MCP) is the standard that defines how an AI model can discover and use your application's capabilities in a secure and structured way. It provides the AI with the necessary &lt;strong&gt;context&lt;/strong&gt; about what actions it can perform and what information it can query.&lt;/p&gt;
&lt;p&gt;This protocol is built on three pillars: &lt;strong&gt;Tools, Resources, and Prompts&lt;/strong&gt;. If you want to dive deeper into how it works and what each of these concepts means, you can read my more detailed post: &lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://es.thedavestack.com/model-context-protocol-exposed/&quot;&gt;Model Context Protocol Exposed: The Power of Resources and Prompts Beyond Tools&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;For this article, the key takeaway is seeing how easy it is to implement these three pillars with Spring AI.&lt;/p&gt;
&lt;h3 id=&quot;from-restcontroller-to-mcp-server&quot;&gt;From @RestController to MCP Server&lt;/h3&gt;
&lt;p&gt;To demonstrate this, I'll use my &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/The-Dave-Stack/product-catalog&quot;&gt;Product Catalog&lt;/a&gt; project.&lt;/p&gt;
&lt;h4 id=&quot;step-0-build-your-traditional-spring-boot-service&quot;&gt;Step 0: Build Your Traditional Spring Boot Service&lt;/h4&gt;
&lt;p&gt;I won't spend much time on this point because creating the foundation for a Spring Boot project is almost trivial using &lt;a rel=&quot;nofollow&quot; href=&quot;https://start.spring.io/&quot;&gt;https://start.spring.io/&lt;/a&gt;. I'll also leave a link to their documentation if you want more details: &lt;a rel=&quot;nofollow&quot; href=&quot;https://spring.io/guides/gs/spring-boot&quot;&gt;Getting Started | Building an Application with Spring Boot&lt;/a&gt;.&lt;/p&gt;
&lt;h4 id=&quot;step-1-configuration-is-a-breeze&quot;&gt;Step 1: Configuration is a Breeze&lt;/h4&gt;
&lt;p&gt;Once you have your Spring Boot service, you just need to add the Spring AI dependency and enable the server. To manage versions centrally, we add the Spring AI &quot;Bill of Materials&quot; (BOM) to the &lt;code&gt;&amp;lt;dependencyManagement&amp;gt;&lt;/code&gt; section of your &lt;code&gt;pom.xml&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;&amp;lt;dependencyManagement&amp;gt;  
    &amp;lt;dependencies&amp;gt;  
        &amp;lt;dependency&amp;gt;  
            &amp;lt;groupId&amp;gt;org.springframework.ai&amp;lt;/groupId&amp;gt;  
            &amp;lt;artifactId&amp;gt;spring-ai-bom&amp;lt;/artifactId&amp;gt;  
            &amp;lt;version&amp;gt;1.0.0&amp;lt;/version&amp;gt;  
            &amp;lt;type&amp;gt;pom&amp;lt;/type&amp;gt;  
            &amp;lt;scope&amp;gt;import&amp;lt;/scope&amp;gt;  
        &amp;lt;/dependency&amp;gt;  
    &amp;lt;/dependencies&amp;gt;  
&amp;lt;/dependencyManagement&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, in your &lt;code&gt;&amp;lt;dependencies&amp;gt;&lt;/code&gt; section, you only need to add the MCP server &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.spring.io/spring-ai/reference/api/mcp/mcp-server-boot-starter-docs.html#_starters&quot;&gt;starter&lt;/a&gt;, which in this case is &lt;code&gt;spring-ai-starter-mcp-server-webmvc&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;&amp;lt;dependencies&amp;gt;  
    &amp;lt;dependency&amp;gt;  
        &amp;lt;groupId&amp;gt;org.springframework.ai&amp;lt;/groupId&amp;gt;  
        &amp;lt;artifactId&amp;gt;spring-ai-starter-mcp-server-webmvc&amp;lt;/artifactId&amp;gt;  
    &amp;lt;/dependency&amp;gt;  
&amp;lt;/dependencies&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Finally, in your &lt;code&gt;application.properties&lt;/code&gt;, you enable and configure the server. It's that simple:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-properties&quot;&gt;# application.properties

# Enable the MCP server  
spring.ai.mcp.server.enabled=true  
spring.ai.mcp.server.name=product-catalog-mcp-server  
spring.ai.mcp.server.version=1.0.0

# Define general instructions for the AI  
spring.ai.mcp.server.instructions=A server for managing a product catalog with AI-powered tools, resources, and guides.

# Enable the capabilities you want to expose  
spring.ai.mcp.server.capabilities.tool=true  
spring.ai.mcp.server.capabilities.resource=true  
spring.ai.mcp.server.capabilities.prompt=true
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With just these few lines, your application now has a functional MCP server.&lt;/p&gt;
&lt;h4 id=&quot;step-2-implementing-the-mcp-features&quot;&gt;Step 2: Implementing the MCP Features&lt;/h4&gt;
&lt;p&gt;Now that we have the Spring AI dependencies and the MCP starter, it's time to dive in.&lt;/p&gt;
&lt;h5 id=&quot;2-1-expose-your-services-as-tools&quot;&gt;&lt;strong&gt;2.1: Expose Your Services as Tools&lt;/strong&gt;&lt;/h5&gt;
&lt;p&gt;You already have the business logic in your &lt;code&gt;@Service&lt;/code&gt; classes. To expose a method as a tool for the AI, you just need one annotation: &lt;code&gt;@Tool&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Tool(
    description =
        &quot;Creates a new product in the catalog with comprehensive validation and audit logging. Required fields: name, price, category, stockQuantity, minStockLevel. Optional fields: description, SKU (auto-generated if not provided), weight, dimensions, imageUrl. SKU must be globally unique if provided. Automatically creates audit log entry. Throws DuplicateSkuException for duplicate SKUs. Use this for adding new products to the inventory system.&quot;)
@Transactional
public Product createProduct(
    @ToolParam(
        description =
            &quot;Product object with required fields: name, price, category, stockQuantity, minStockLevel. Optional: description, sku, weight, dimensions, imageUrl&quot;)
    Product product) {
...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And that's it! Your &lt;code&gt;createProduct&lt;/code&gt; method is now a tool that any AI agent can invoke.&lt;/p&gt;
&lt;h5 id=&quot;2-2-expose-live-data-with-resources&quot;&gt;&lt;strong&gt;2.2: Expose Live Data with Resources&lt;/strong&gt;&lt;/h5&gt;
&lt;p&gt;A &lt;code&gt;Resource&lt;/code&gt; acts as a live data feed for the AI. Instead of having static data in a prompt, you can expose information that changes within your application, like a list of product categories.&lt;/p&gt;
&lt;p&gt;To do this, we define a &lt;code&gt;URI&lt;/code&gt; (e.g., &lt;code&gt;categories://overview/all&lt;/code&gt;) and associate it with a function that returns the data.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public List&amp;lt;McpServerFeatures.SyncResourceSpecification&amp;gt; getResourceSpecifications() {
    return List.of(
            // All categories overview resource
            new McpServerFeatures.SyncResourceSpecification(
                    new McpSchema.Resource(
                            &quot;categories://overview/all&quot;,
                            &quot;All Categories Overview&quot;,
                            &quot;Complete overview of all product categories with descriptions and guidelines&quot;,
                            &quot;application/json&quot;,
                            null),
                    this::getAllCategoriesResource),
            ...
            );
}

private McpSchema.ReadResourceResult getAllCategoriesResource(
        McpSyncServerExchange exchange, McpSchema.ReadResourceRequest request) {
    try {
        List&amp;lt;Map&amp;lt;String, Object&amp;gt;&amp;gt; categoriesInfo =
                Arrays.stream(Category.values())
                        .map(this::createCategoryOverview)
                        .collect(Collectors.toList());

        String jsonContent =
                objectMapper.writeValueAsString(
                        Map.of(
                                &quot;totalCategories&quot;,
                                Category.values().length,
                                &quot;categories&quot;,
                                categoriesInfo,
                                &quot;description&quot;,
                                &quot;Complete catalog of product categories with their descriptions and key characteristics&quot;,
                                &quot;lastUpdated&quot;,
                                Instant.now().toString()));

        return new McpSchema.ReadResourceResult(
                List.of(
                        new McpSchema.TextResourceContents(
                                request.uri(), &quot;application/json&quot;, jsonContent)));
    } catch (Exception e) {
        log.error(&quot;Failed to get all categories resource&quot;, e);
        throw new RuntimeException(&quot;Failed to retrieve categories overview&quot;, e);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h5 id=&quot;2-3-expose-your-workflows-with-prompts&quot;&gt;&lt;strong&gt;2.3: Expose Your Workflows with Prompts&lt;/strong&gt;&lt;/h5&gt;
&lt;p&gt;&lt;code&gt;Prompts&lt;/code&gt; are structured guides that teach the AI how to perform complex, multi-step tasks. They are like pre-designed prompt templates that you can invoke, combining instructions, data from &lt;code&gt;Resources&lt;/code&gt;, and &lt;code&gt;Tools&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Imagine you want a guide for analyzing inventory. You could create a &lt;code&gt;Prompt&lt;/code&gt; named &lt;code&gt;inventory-analysis&lt;/code&gt; that instructs the AI to first fetch low-stock products (using a &lt;code&gt;Tool&lt;/code&gt;) and then generate a report.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public List&amp;lt;McpServerFeatures.SyncPromptSpecification&amp;gt; getPromptSpecifications() {
    return List.of(
            ...

            // Inventory analysis prompt
            new McpServerFeatures.SyncPromptSpecification(
                    new McpSchema.Prompt(
                            &quot;inventory-analysis&quot;,
                            &quot;Inventory Analysis and Recommendations&quot;,
                            List.of(
                                    new McpSchema.PromptArgument(
                                            &quot;analysisType&quot;,
                                            &quot;Type of analysis (stock-health/reorder/turnover)&quot;,
                                            true),
                                    new McpSchema.PromptArgument(
                                            &quot;category&quot;, &quot;Focus on specific category&quot;, false))),
                    this::createInventoryAnalysisPrompt),
            
            ...
            );
}

private McpSchema.GetPromptResult createInventoryAnalysisPrompt(
        McpSyncServerExchange exchange, McpSchema.GetPromptRequest request) {
    try {
        String analysisType = (String) request.arguments().get(&quot;analysisType&quot;);
        String category = (String) request.arguments().getOrDefault(&quot;category&quot;, &quot;ALL&quot;);

        String promptText = createInventoryAnalysisPromptText(analysisType, category);

        McpSchema.PromptMessage promptMessage =
                new McpSchema.PromptMessage(
                        McpSchema.Role.ASSISTANT, new McpSchema.TextContent(promptText));

        return new McpSchema.GetPromptResult(
                &quot;Inventory analysis guidance for &quot; + analysisType + &quot; analysis&quot;,
                List.of(promptMessage));
    } catch (Exception e) {
        log.error(&quot;Failed to create inventory analysis prompt&quot;, e);
        throw new RuntimeException(&quot;Failed to generate inventory analysis prompt&quot;, e);
    }
}

private String createInventoryAnalysisPromptText(String analysisType, String category) {
    return switch (analysisType) {
        case &quot;stock-health&quot; -&amp;gt; &quot;&quot;&quot;
            You are an inventory management specialist conducting a stock health analysis%s.

            ## Stock Health Analysis Framework

            ### Key Metrics to Evaluate:
            1. **Out of Stock Items** - Immediate attention required
            2. **Low Stock Warnings** - Items below minimum levels
            3. **Overstock Situations** - Items with excessive inventory
            4. **Stock Turnover Rates** - How quickly inventory moves
            5. **Dead Stock** - Items with no recent sales

            ### Analysis Process:
            1. Retrieve current inventory levels using available tools
            2. Compare against minimum stock thresholds
            3. Identify critical alerts requiring immediate action
            4. Calculate stock health scores by category
            5. Generate actionable recommendations

            ### Recommended Actions:
            - **Critical**: Immediate reorders for out-of-stock items
            - **Warning**: Schedule reorders for low-stock items
            - **Optimization**: Review slow-moving inventory for discounts
            - **Planning**: Adjust minimum stock levels based on trends

            Please conduct a comprehensive stock health analysis and provide prioritized recommendations.
            &quot;&quot;&quot;
                .formatted(category.equals(&quot;ALL&quot;) ? &quot;&quot; : &quot; for &quot; + category + &quot; category&quot;);

        case &quot;reorder&quot; -&amp;gt; &quot;&quot;&quot;
            You are a procurement specialist developing reorder recommendations%s.

            ## Reorder Analysis Framework

            ### Reorder Triggers:
            1. Current stock ≤ minimum stock level
            2. Projected stockouts based on demand trends
            3. Lead time considerations
            4. Economic order quantities
            5. Seasonal demand adjustments

            ### Analysis Steps:
            1. Identify products below reorder points
            2. Calculate optimal order quantities
            3. Consider supplier minimums and volume discounts
            4. Factor in lead times and safety stock
            5. Prioritize based on sales velocity and margin

            ### Reorder Recommendations:
            - High priority: Fast-moving items near stockout
            - Medium priority: Steady sellers below minimum
            - Low priority: Slow movers with adequate runway

            Please generate a prioritized reorder plan with specific quantities and timing.
            &quot;&quot;&quot;
                .formatted(category.equals(&quot;ALL&quot;) ? &quot;&quot; : &quot; for &quot; + category + &quot; category&quot;);

        case &quot;turnover&quot; -&amp;gt; &quot;&quot;&quot;
            You are a financial analyst evaluating inventory turnover performance%s.

            ## Inventory Turnover Analysis

            ### Key Calculations:
            1. **Turnover Ratio** = Cost of Goods Sold / Average Inventory Value
            2. **Days Sales Inventory** = 365 / Turnover Ratio
            3. **Stock Velocity** = Units Sold / Average Units in Stock
            4. **Carrying Cost Impact** = Average Inventory × Carrying Cost Rate

            ### Performance Benchmarks:
            - Electronics: 8-12 turns/year (Fast-moving technology)
            - Books: 3-5 turns/year (Diverse demand patterns)
            - Clothing: 4-6 turns/year (Seasonal collections)
            - General: 6-8 turns/year (Standard retail)

            ### Analysis Focus:
            1. Compare actual vs. target turnover rates
            2. Identify slow-moving inventory
            3. Calculate carrying cost implications
            4. Recommend optimization strategies

            Please analyze turnover performance and suggest improvements for capital efficiency.
            &quot;&quot;&quot;
                .formatted(category.equals(&quot;ALL&quot;) ? &quot;&quot; : &quot; for &quot; + category + &quot; category&quot;);

        default -&amp;gt; &quot;Please specify analysis type: stock-health, reorder, or turnover.&quot;;
    };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;step-3-configure-the-beans-to-expose-everything&quot;&gt;Step 3: Configure the Beans to Expose Everything&lt;/h4&gt;
&lt;p&gt;Once you have all the pieces, exposing &lt;code&gt;Tools&lt;/code&gt;, &lt;code&gt;Resources&lt;/code&gt;, and &lt;code&gt;Prompts&lt;/code&gt; is as simple as creating Spring Beans for them.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@SpringBootApplication
public class ProductCatalogSpringApplication {

    public static void main(String[] args) {
        SpringApplication.run(ProductCatalogSpringApplication.class, args);
    }

    @Bean
    public ToolCallbackProvider productCatalogTools(ProductService productService) {
        return MethodToolCallbackProvider.builder().toolObjects(productService).build();
    }

    @Bean
    public List&amp;lt;McpServerFeatures.SyncResourceSpecification&amp;gt; productCatalogResources() {
        List&amp;lt;McpServerFeatures.SyncResourceSpecification&amp;gt; productCatalogResources =
                new ArrayList&amp;lt;&amp;gt;();
        ...
        return productCatalogResources;
    }

    @Bean
    public List&amp;lt;McpServerFeatures.SyncPromptSpecification&amp;gt; productCatalogPrompts(
            ProductManagementPromptProvider productManagementPromptProvider) {
        return productManagementPromptProvider.getPromptSpecifications();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With these simple steps, you've built a direct and secure bridge between your business logic and the world of AI.&lt;/p&gt;
&lt;h3 id=&quot;conclusion-the-next-natural-step-for-your-apis&quot;&gt;Conclusion: The Next Natural Step for Your APIs&lt;/h3&gt;
&lt;p&gt;As we've seen, adding an MCP Server to your Spring Boot application isn't a Herculean task. It's the next logical step in the evolution of our services. Spring AI abstracts away all the complexity, allowing us to reuse the code we already have to open our applications to a universe of new possibilities with artificial intelligence.&lt;/p&gt;
&lt;p&gt;Designing with AI in mind from the start is no longer an option—it's a competitive advantage.&lt;/p&gt;
&lt;h3 id=&quot;what-s-next-your-turn-to-act-&quot;&gt;What's Next? Your Turn to Act ????&lt;/h3&gt;
&lt;p&gt;I hope this post has inspired you to look at your APIs from a new perspective. Here are a few ideas to get you started:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Explore the source code&lt;/strong&gt;: All the code from this example is available in my &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/The-Dave-Stack/product-catalog&quot;&gt;&lt;strong&gt;Product Catalog API&lt;/strong&gt;&lt;/a&gt; GitHub repository. Clone it, run it with Docker, and experiment for yourself. The best way to learn is by doing!&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Start small&lt;/strong&gt;: You don't need a complex architecture. Pick a key method from one of your current Spring Boot applications and try exposing it as a &lt;code&gt;@Tool&lt;/code&gt;. You'll see how simple it is.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Join the conversation&lt;/strong&gt;: What do you think of this approach to AI-native APIs? What other use cases can you think of for an MCP Server in your projects?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dive into the documentation&lt;/strong&gt;: If you want to learn all the details, the &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.spring.io/spring-ai/reference/api/mcp/mcp-overview.html&quot;&gt;&lt;strong&gt;official Spring AI documentation&lt;/strong&gt;&lt;/a&gt; is the best place to keep learning.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h3 id=&quot;further-reading&quot;&gt;Further Reading&lt;/h3&gt;
&lt;p&gt;The internet is full of articles that can also help you learn more about the world of Spring AI and MCP. Here are a couple I've found very interesting:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.baeldung.com/spring-ai-model-context-protocol-mcp&quot;&gt;Exploring Model Context Protocol (MCP) With Spring AI | Baeldung&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://spring.io/blog/2025/05/04/spring-ai-dynamic-tool-updates-with-mcp&quot;&gt;Dynamic Tool Updates in Spring AI's Model Context Protocol&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Backend Dev</category>
<guid isPermaLink="true">https://coderlegion.com/5481/designing-apis-for-the-ai-era-with-spring-ai-and-mcp</guid>
<pubDate>Mon, 22 Sep 2025 13:49:16 +0000</pubDate>
</item>
<item>
<title>Power Up Java with AI: Integrate LLaMA 3 Using Spring Boot</title>
<link>https://coderlegion.com/4333/power-up-java-with-ai-integrate-llama-3-using-spring-boot</link>
<description>&lt;p&gt;In today’s world of artificial intelligence, creating a personal AI assistant has become surprisingly accessible thanks to various LLM models. With Spring Boot and open-source AI models, we can build a conversational AI within Java applications. Let’s explore how this works.&lt;/p&gt;
&lt;p&gt;In this example, we’ll install and use Ollama locally, and create an endpoint that accepts a user’s prompt and returns a response using AI.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 1: Install Ollama&lt;/strong&gt;&lt;br&gt;
To get started, install Ollama using this link.&lt;br&gt;
Once the installation is complete, download one of the available models. For this example, we’ll use the LLaMA 3 model:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ollama run llama3 // or any other model
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After the model is downloaded, you can also interact with it directly through the terminal:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; hello
Hello! It's nice to meet you. Is there something I can help you with, or would you like to chat?
&amp;gt;&amp;gt;&amp;gt; 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Step 2: Connect to Ollama from a Spring Boot Application&lt;/strong&gt;&lt;br&gt;
Now it’s time to connect Ollama with a Spring Boot application.&lt;br&gt;
Let’s quickly generate a Spring Boot project using start.spring.io.&lt;br&gt;
Here’s an example of the build.gradle file with the necessary dependencies:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;plugins {
 id 'java'
 id 'org.springframework.boot' version '3.5.0'
 id 'io.spring.dependency-management' version '1.1.7'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'

java {
 toolchain {
  languageVersion = JavaLanguageVersion.of(21)
 }
}

repositories {
 mavenCentral()
}

ext {
 set('springAiVersion', &quot;1.0.0&quot;)
}

dependencies {
 implementation 'org.springframework.ai:spring-ai-starter-model-ollama'
}

dependencyManagement {
 imports {
  mavenBom &quot;org.springframework.ai:spring-ai-bom:${springAiVersion}&quot;
 }
}

tasks.named('test') {
 useJUnitPlatform()
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Step 3: Create a REST Endpoint&lt;/strong&gt;&lt;br&gt;
After setting up the dependencies, all we need is a simple REST endpoint that receives a user prompt, sends it to the LLaMA 3 model, and returns the response.&lt;br&gt;
For this, we can use the ChatModel class.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.ollama.api.OllamaModel;
import org.springframework.ai.ollama.api.OllamaOptions;

@RestController
@RequiredArgsConstructor
public class ChatController {
    private final ChatModel chatModel;

    @GetMapping(&quot;/message&quot;)
    public String handleMessage(@RequestParam String message) {
        var response = chatModel.call(
                new Prompt(
                        message,
                        OllamaOptions.builder()
                                .model(OllamaModel.LLAMA3)
                                .temperature(0.4)
                                .build()
                ));
        return response.getResult().getOutput().getText();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Key Components:&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;ChatModel&lt;/strong&gt; — Interface that calls the AI model to generate responses.&lt;br&gt;
&lt;strong&gt;Prompt&lt;/strong&gt; — Wraps the user’s message and AI configuration into a single request.&lt;br&gt;
&lt;strong&gt;OllamaOptions&lt;/strong&gt; — Configures model settings such as temperature and model selection.&lt;br&gt;
&lt;strong&gt;OllamaModel.LLAMA3&lt;/strong&gt; — Specifies the LLaMA 3 model for processing the input.&lt;br&gt;
&lt;strong&gt;temperature&lt;/strong&gt; — Controls the creativity of the response (0 = strict, 1 = more random).&lt;/p&gt;
&lt;p&gt;Its time to test:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=9451787985168007589&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;br&gt;
Using Ollama locally with Spring AI makes it easy to build AI-powered APIs without relying on external cloud services. Without spending anything for the LLM model, an AI-powered chat endpoint is ready. We can do a little bit of prompt engineering to make it more specialized.&lt;/p&gt;
</description>
<category>Java</category>
<guid isPermaLink="true">https://coderlegion.com/4333/power-up-java-with-ai-integrate-llama-3-using-spring-boot</guid>
<pubDate>Wed, 30 Jul 2025 07:25:28 +0000</pubDate>
</item>
<item>
<title>Learn how to write GenAI applications with Java using the Spring AI framework and utilize RAG for improving answers.</title>
<link>https://coderlegion.com/393/learn-write-genai-applications-with-java-using-spring-framework-utilize-improving-answers</link>
<description>&lt;p&gt;Generative AI (GenAI) is currently a hot topic in the tech world. Retrieval-Augmented Generation (RAG) is a technique that enhances the accuracy and reliability of generative AI models by grounding them in external knowledge sources.&lt;/p&gt;
&lt;p&gt;In this blog post, we'll look at how to write GenAI applications with Java using the Spring AI framework and utilize RAG for improving answers.&lt;/p&gt;
&lt;h3 id=&quot;what-is-spring-ai-&quot;&gt;What is Spring AI?&lt;/h3&gt;
&lt;p&gt;Spring AI is a framework for building generative AI applications in Java. It provides a set of tools and utilities for working with generative AI models and architectures, such as large language models (LLMs) and retrieval augmented generation (RAG). Spring AI is built on top of the Spring Framework, which is a popular Java framework for building enterprise applications, allowing those already familiar with or involved in the Spring ecosystem the ability to incorporate GenAI strategies to their already existing applications and workflow.&lt;/p&gt;
&lt;p&gt;There are also other options for GenAI in Java, such as Langchain4j, but we'll focus on Spring AI for this post.&lt;/p&gt;
&lt;h3 id=&quot;creating-a-project&quot;&gt;Creating a project&lt;/h3&gt;
&lt;p&gt;To get started with Spring AI, you'll need to either create a new project or add the appropriate dependencies to an existing project. You can create a new project using the Spring Initializr at &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/JMHReif/springai-goodreads/blob/main/src/main/java/com/jmhreif/springaigoodreads/SpringaiGoodreadsApplication.java#L19-L32&quot;&gt;https://start.spring.io/&lt;/a&gt;, which is a web-based tool for generating Spring Boot projects.&lt;/p&gt;
&lt;p&gt;When creating a new project, you'll need to add the following dependencies:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Spring Web&lt;/li&gt;
&lt;li&gt;OpenAI (or other LLM model, such as Mistral, Ollama, etc.)&lt;/li&gt;
&lt;li&gt;Neo4j Vector Database (other vector database options also available)&lt;/li&gt;
&lt;li&gt;Spring Data Neo4j&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you're adding these dependencies manually to an existing project, you can see the dependency details in &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/JMHReif/springai-goodreads/blob/main/src/main/java/com/jmhreif/springaigoodreads/SpringaiGoodreadsApplication.java#L19-L32&quot;&gt;today's related Github repository&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The Spring Web dependency allows us to create a REST API for our GenAI application. We need the OpenAI dependency to access the OpenAI model, which is a popular LLM. The Neo4j Vector Database dependency allows us to store and query vectors, which are used for similarity searches. Finally, adding the Spring Data Neo4j dependency provides support for working with Neo4j databases in Spring applications, allowing us to run Cypher queries in Neo4j and map entities to Java objects.&lt;/p&gt;
&lt;p&gt;Go ahead and generate the project, and then open it in your favorite IDE. Looking at the &lt;code&gt;pom.xml&lt;/code&gt; file, you should see that the milestone repository is included. Since Spring AI is not a general-availability release yet, we need to include the milestone repository to get the pre-release version of the dependencies.&lt;/p&gt;
&lt;h3 id=&quot;a-bit-of-boilerplate&quot;&gt;A bit of boilerplate&lt;/h3&gt;
&lt;p&gt;First thing that we need is a Neo4j database. I like to use the &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/JMHReif/springai-goodreads&quot;&gt;Neo4j Aura free tier&lt;/a&gt; because the instance is managed for me, but there are also Docker images and other methods.&lt;/p&gt;
&lt;p&gt;Depending on the LLM model you chose, you will also need an API key. For OpenAI, you can get one by signing up at &lt;a rel=&quot;nofollow&quot; href=&quot;https://platform.openai.com/signup&quot;&gt;OpenAI&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Once you have a Neo4j database and an API key, you can set up the config in the &lt;code&gt;application.properties&lt;/code&gt; file. Here's an example of what that might look like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;spring.ai.openai.api-key=&amp;lt;YOUR API KEY HERE&amp;gt;
spring.neo4j.uri=&amp;lt;NEO4J URI HERE&amp;gt;
spring.neo4j.authentication.username=&amp;lt;NEO4J USERNAME HERE&amp;gt;
spring.neo4j.authentication.password=&amp;lt;NEO4J PASSWORD HERE&amp;gt;
spring.data.neo4j.database=&amp;lt;NEO4J DATABASE NAME HERE&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note: It's a good idea to keep sensitive information like API keys and passwords in environment variables or other location external to the application. To create environment variables, you can use the &lt;code&gt;export&lt;/code&gt; command in the terminal or set them in your IDE.&lt;/p&gt;
&lt;p&gt;We can set up &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.baeldung.com/spring-bean&quot;&gt;Spring Beans&lt;/a&gt; for the OpenAI client and the Neo4j vector store that will allow us to access necessary components wherever we need them in our application.&lt;/p&gt;
&lt;p&gt;We can put these in our &lt;code&gt;SpringAiApplication&lt;/code&gt; class by adding the &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/JMHReif/springai-goodreads/blob/main/src/main/java/com/jmhreif/springaigoodreads/SpringaiGoodreadsApplication.java#L19-L32&quot;&gt;related code to the class&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;EmbeddingClient&lt;/code&gt; bean creates a client for the OpenAI API and passes in our API key environment variable. Lastly, the &lt;code&gt;Neo4jVectorStore&lt;/code&gt; bean configures Neo4j as the store for embeddings (vectors). We customize the configuration by specifying the label for the nodes that will store the embeddings, as Spring's default looks for &lt;code&gt;Document&lt;/code&gt; entities. We also specify our index name for the embeddings (default is &lt;code&gt;spring-ai-document-index&lt;/code&gt;).&lt;/p&gt;
&lt;h3 id=&quot;data-set&quot;&gt;Data set&lt;/h3&gt;
&lt;p&gt;For this example, we'll use a dataset of books and reviews from Goodreads. You can pull a curated version of the dataset from &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/JMHReif/graph-demo-datasets/blob/main/goodreadsUCSD/ai-embeddings/ai-load-data.cypher&quot;&gt;here&lt;/a&gt;. The dataset contains information about books, as well as related reviews.&lt;/p&gt;
&lt;p&gt;I have already generated embeddings using OpenAI's API, so if you want to generate your own, you will need to comment out the &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/JMHReif/graph-demo-datasets/blob/main/goodreadsUCSD/ai-embeddings/ai-load-data.cypher#L92&quot;&gt;final Cypher statement in the script&lt;/a&gt; and instead run the &lt;code&gt;generate-embeddings.py&lt;/code&gt; script (or your custom version) to generate and load the review embeddings to Neo4j.&lt;/p&gt;
&lt;h3 id=&quot;application-model&quot;&gt;Application model&lt;/h3&gt;
&lt;p&gt;Next, we need to create a domain model in our application to map to our database model. In this example, we'll create a &lt;code&gt;Book&lt;/code&gt; entity that represents a book node. We'll also create a &lt;code&gt;Review&lt;/code&gt; entity that represents a review of a book. The &lt;code&gt;Review&lt;/code&gt; entity will have an embedding (vector) associated with it, which we'll use for similarity searches.&lt;/p&gt;
&lt;p&gt;These entities are standard Spring Data Neo4j code, so I won't show the code here. However, full code for each class is available in the Github repository - &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/JMHReif/springai-goodreads/blob/main/src/main/java/com/jmhreif/springaigoodreads/Book.java&quot;&gt;Book class&lt;/a&gt;, &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/JMHReif/springai-goodreads/blob/main/src/main/java/com/jmhreif/springaigoodreads/Review.java&quot;&gt;Review class&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We also need a repository interface defined so that we can interact with the database. While we will need to define a custom query, we'll come back and add that in a bit later.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public interface BookRepository extends Neo4jRepository&amp;lt;Book, String&amp;gt; {
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, the core of this application where all the magic happens is the controller class. This class will contain the logic for taking a search phrase provided by the user and calling the &lt;code&gt;Neo4jVectorStore&lt;/code&gt; to calculate and return the most similar ones. We can then pass those similar reviews into a Neo4j query to retrieve connected entities, providing additional context in the prompt for the LLM. It will use all the information provided to respond with some similar book recommendations for the original searched phrase.&lt;/p&gt;
&lt;h3 id=&quot;controller&quot;&gt;Controller&lt;/h3&gt;
&lt;p&gt;Our controller class contains a couple of common annotations, to start. We'll also inject the &lt;code&gt;Neo4jVectorStore&lt;/code&gt; and &lt;code&gt;BookRepository&lt;/code&gt; beans that we defined earlier, as well as the &lt;code&gt;OpenAiChatClient&lt;/code&gt; for our embedding client.&lt;/p&gt;
&lt;p&gt;The next thing is to define a string for our prompt. This is the text that we will pass to the LLM to generate the response. We'll use the search phrase provided by the user and the similar reviews we find in the database to populate our prompt parameters in a few minutes. Next, we define the constructor for the controller class, which will inject the necessary beans.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@RestController
@RequestMapping(&quot;/&quot;)
public class BookController {
    private final ChatClient client;
    private final Neo4jVectorStore vectorStore;
    private final BookRepository repo;

    String prompt = &quot;&quot;&quot;
            You are a book expert providing recommendations from high-quality book information in the CONTEXT section.
            Please summarize the books provided in the context section.
                        
            CONTEXT:
            {context}
                        
            PHRASE:
            {searchPhrase}
            &quot;&quot;&quot;;

    public BookController(ChatClient.Builder builder, Neo4jVectorStore vectorStore, BookRepository repo) {
        this.client = builder.build();
        this.vectorStore = vectorStore;
        this.repo = repo;
    }

    //Retrieval Augmented Generation with Neo4j - vector search + retrieval query for related context
    @GetMapping(&quot;/rag&quot;)
    public String generateResponseWithContext(@RequestParam String searchPhrase) {
        List&amp;lt;Document&amp;gt; results = vectorStore.similaritySearch(SearchRequest.query(searchPhrase).withTopK(10));

        //more code shortly!
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Finally, we define a method that will be called when a user makes a GET request to the &lt;code&gt;/rag&lt;/code&gt; endpoint. This method will first take a search phrase as a query parameter and pass that to the vector store's &lt;code&gt;similaritySearch()&lt;/code&gt; method to find similar reviews. I have also added a couple of customization filters to the query.&lt;/p&gt;
&lt;p&gt;Then, we map the similar &lt;code&gt;Review&lt;/code&gt; nodes back to &lt;code&gt;Document&lt;/code&gt; entities because Spring AI expects a general document type. The &lt;code&gt;Neo4jVectorStore&lt;/code&gt; class contains methods to convert &lt;code&gt;Document&lt;/code&gt; to a custom record, as well as the reverse for record to &lt;code&gt;Document&lt;/code&gt; conversion.&lt;/p&gt;
&lt;p&gt;Back in our controller method for book recommendations, we now have similar reviews for the user's searched phrase. But reviews (and their accompanying text) aren't really helpful in giving us book recommendations. So now we need to run a query in Neo4j to retrieve the related books for those reviews. This is the retrieval augmented generation (RAG) piece of the application.&lt;/p&gt;
&lt;p&gt;Let's write the query in the &lt;code&gt;BookRepository&lt;/code&gt; interface to find the books associated with those reviews.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public interface BookRepository extends Neo4jRepository&amp;lt;Book, String&amp;gt; {
    @Query(&quot;MATCH (b:Book)&amp;lt;-[rel:WRITTEN_FOR]-(r:Review) &quot; +
            &quot;WHERE r.id IN $reviewIds &quot; +
            &quot;AND r.text &amp;lt;&amp;gt; 'RTC' &quot; +
            &quot;RETURN b, collect(rel), collect(r);&quot;)
    List&amp;lt;Book&amp;gt; findBooks(List&amp;lt;String&amp;gt; reviewIds);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the query, we pass in the ids of the reviews from the similarity search (&lt;code&gt;$reviewIds&lt;/code&gt;) and pull the &lt;code&gt;Review -&amp;gt; Book&lt;/code&gt; pattern for those reviews. We also filter out any reviews that have the text 'RTC' (which is a placeholder for reviews that don't have text). We then return the &lt;code&gt;Book&lt;/code&gt; nodes, the relationships, and the &lt;code&gt;Review&lt;/code&gt; nodes.&lt;/p&gt;
&lt;p&gt;Now we need to call that method in our controller and pass the results to a prompt template. We will pass that to the LLM to generate a response with a book recommendation list based on the user's search phrase (we hope!). :)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;//Retrieval Augmented Generation with Neo4j - vector search + retrieval query for related context
@GetMapping(&quot;/rag&quot;)
public String generateResponseWithContext(@RequestParam String searchPhrase) {
    List&amp;lt;Document&amp;gt; results = vectorStore.similaritySearch(SearchRequest.query(searchPhrase).withTopK(10));

    List&amp;lt;Book&amp;gt; bookList = repo.findBooks(results.stream().map(Document::getId).collect(Collectors.toList()));
    System.out.println(&quot;--- Book list ---&quot;);
    System.out.println(bookList);

    var template = new PromptTemplate(prompt, Map.of(&quot;context&quot;, bookList.stream().map(b -&amp;gt; b.toString()).collect(Collectors.joining(&quot;\n&quot;)), &quot;searchPhrase&quot;, searchPhrase));
    System.out.println(&quot;----- PROMPT -----&quot;);
    System.out.println(template.render());

    return client.prompt(template.create()).call().content();

}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Starting right after the similarity search, we call our new &lt;code&gt;findBooks()&lt;/code&gt; method and pass in the list of review ids from the similarity search. The retrieval query returns to a list of books called &lt;code&gt;bookList&lt;/code&gt;. Next, we create a prompt template with the prompt string, the context data from the graph, and the user's search phrase, mapping the &lt;code&gt;context&lt;/code&gt; and &lt;code&gt;searchPhrase&lt;/code&gt; prompt parameters to the graph data (list with each item on new line) and the user's search phrase, respectively. I have also added a &lt;code&gt;System.out.println()&lt;/code&gt; to print the prompt to the console so that we can see what is getting passed to the LLM.&lt;/p&gt;
&lt;p&gt;Finally, we call the template's &lt;code&gt;create()&lt;/code&gt; method to generate the response from the LLM. The returning JSON object has a &lt;code&gt;contents&lt;/code&gt; key that contains the response string with the list of book recommendations based on the user's search phrase.&lt;/p&gt;
&lt;p&gt;Let's test it out!&lt;/p&gt;
&lt;h3 id=&quot;running-the-application&quot;&gt;Running the application&lt;/h3&gt;
&lt;p&gt;To run our Goodreads AI application, you can use the &lt;code&gt;./mvnw spring-boot:run&lt;/code&gt; command in the terminal. Once the application is running, you can make a GET request to the &lt;code&gt;/rag&lt;/code&gt; endpoint with a search phrase as a query parameter. Some examples are included next.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;http &quot;:8080/rag?searchPhrase=happy%20ending&quot;
http &quot;:8080/rag?searchPhrase=encouragement&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;sample-call-and-output-full-prompt&quot;&gt;Sample call and output + full prompt&lt;/h3&gt;
&lt;p&gt;Call and returned book recommendations:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;jenniferreif@elf-lord springai-goodreads % http &quot;:8080/rag?searchPhrase=encouragement&quot;
    
The books recommended in the context section are all inspirational and encouraging reads. They include &quot;An Autobiography,&quot; &quot;The Greatest Gift,&quot; &quot;The Cross and the Switchblade,&quot; &quot;David and Goliath,&quot; &quot;The Art of Recklessness,&quot; &quot;90 Minutes in Heaven,&quot; &quot;Just Kids,&quot; and &quot;The Five People You Meet in Heaven.&quot; These books offer great insight, hope, and encouragement to readers looking for uplifting stories and messages.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Application log output:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;----- PROMPT -----
You are a book expert providing recommendations from high-quality book information in the CONTEXT section.
Please summarize the books provided in the context section.

CONTEXT:
Book[book_id=125441, title=An Autobiography, isbn=0717806677, isbn13=9780717806676, reviewList=[Review[id=d664f3888ecdfd78ec50606fe5f9b58c, text=Inspiring, rating=5]]]
Book[book_id=1488663, title=The Greatest Gift: The Original Story That Inspired the Christmas Classic It's a Wonderful Life, isbn=0670862045, isbn13=9780670862047, reviewList=[Review[id=b74851666f2ec1841ca5876d977da872, text=Inspiring, rating=4]]]
Book[book_id=772852, title=The Cross and the Switchblade, isbn=0515090255, isbn13=9780515090253, reviewList=[Review[id=f70c68721a0654462bcc6cd68e3259bd, text=encouraging, rating=4]]]
Book[book_id=15751404, title=David and Goliath: Underdogs, Misfits, and the Art of Battling Giants, isbn=0316204366, isbn13=9780316204361, reviewList=[Review[id=8c8bf791101b405c2cd134da193d2701, text=Inspiring, rating=4]]]
Book[book_id=7517330, title=The Art of Recklessness: Poetry as Assertive Force and Contradiction, isbn=1555975623, isbn13=9781555975623, reviewList=[Review[id=2df3600d488e182a3ef06bff7fc82eb8, text=Great insight, great encouragement, and great company., rating=4]]]
Book[book_id=89375, title=90 Minutes in Heaven: A True Story of Death and Life, isbn=0800759494, isbn13=9780800759490, reviewList=[Review[id=85ef80e09c64ebd013aeebdb7292eda9, text=inspiring &amp;amp; hope filled, rating=5]]]
Book[book_id=341879, title=Just Kids, isbn=006621131X, isbn13=9780066211312, reviewList=[Review[id=3eab2a67ac0bde179dd017014cb212bf, text=Inspiring, rating=5], Review[id=d731490fcfefe6547e459bdff204df4a, text=inspiring., rating=5]]]
Book[book_id=3431, title=The Five People You Meet in Heaven, isbn=1401308589, isbn13=9781401308582, reviewList=[Review[id=3ad8018734f177fd1fe47e6ada1c5df8, text=inspirational, rating=4], Review[id=2b94facb74fe09afd1fd54f0db2251c8, text=Inspiring, rating=5]]]

PHRASE:
encouragement
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can see that the LLM generated a response with a list of book recommendations based on the books found in the database (CONTEXT section of prompt). The results of the similarity search + graph retrieval query for the user's search phrase are in the prompt, and the LLM's answer uses that data for a response.&lt;/p&gt;
&lt;h3 id=&quot;wrapping-up-&quot;&gt;Wrapping Up!&lt;/h3&gt;
&lt;p&gt;I hope this post helps to get you started with Spring AI and beyond. Happy coding!&lt;/p&gt;
&lt;h3 id=&quot;resources&quot;&gt;Resources&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Code (Github repository): &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/JMHReif/springai-goodreads&quot;&gt;Spring AI Goodreads&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Webpage: &lt;a rel=&quot;nofollow&quot; href=&quot;https://spring.io/projects/spring-ai&quot;&gt;Spring AI project&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;API: &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.spring.io/spring-ai/docs/0.8.1/api/org/springframework/ai/vectorstore/Neo4jVectorStore.html#similaritySearch(org.springframework.ai.vectorstore.SearchRequest)&quot;&gt;Spring AI - Neo4jVectorStore&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Java</category>
<guid isPermaLink="true">https://coderlegion.com/393/learn-write-genai-applications-with-java-using-spring-framework-utilize-improving-answers</guid>
<pubDate>Sun, 22 Sep 2024 09:21:49 +0000</pubDate>
</item>
</channel>
</rss>