<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged vector</title>
<link>https://coderlegion.com/tag/vector</link>
<description>Powered by Question2Answer</description>
<item>
<title>Beyond Elasticsearch: The Rise of Unified Vector and Keyword Search</title>
<link>https://coderlegion.com/887/beyond-elasticsearch-the-rise-of-unified-vector-and-keyword-search</link>
<description>&lt;p&gt;This post was written by &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.linkedin.com/in/xiaofan-james-luan-37012872/&quot;&gt;James Luan&lt;/a&gt;, VP of Engineering at &lt;a rel=&quot;nofollow&quot; href=&quot;https://zilliz.com/&quot;&gt;Zilliz&lt;/a&gt; and key maintainer of the Milvus open source project.&lt;/p&gt;
&lt;h2 id=&quot;the-evolution-of-modern-search-architecture&quot;&gt;The Evolution of Modern Search Architecture&lt;/h2&gt;
&lt;p&gt;You're debugging a critical production issue, and you know the solution exists in your documentation. Your vector search returns semantically relevant results about error handling and system recovery but somehow misses that one crucial Stack Overflow answer you saved last month with the exact error code. Sound familiar?&lt;/p&gt;
&lt;p&gt;This common frustration highlights a fundamental challenge in modern search technology: the need to balance semantic understanding with precise term matching. The rise of AI and large language models has brought powerful semantic search capabilities through vector embeddings while revealing the continued importance of traditional keyword matching. When supporting customers, for instance, vector search brilliantly handles natural language queries like &quot;my payment isn't going through&quot; by understanding user intent. Still, keyword search remains essential for matching specific product codes and error messages.&lt;/p&gt;
&lt;p&gt;Recent experiences with &lt;a rel=&quot;nofollow&quot; href=&quot;https://zilliz.com/learn/Retrieval-Augmented-Generation&quot;&gt;RAG&lt;/a&gt; implementations across the industry have revealed interesting insights about search technology. While dense vector embeddings excel at capturing semantic relationships and contextual understanding, traditional &lt;a rel=&quot;nofollow&quot; href=&quot;https://zilliz.com/learn/mastering-bm25-a-deep-dive-into-the-algorithm-and-application-in-milvus&quot;&gt;BM25&lt;/a&gt; keyword matching still proves superior in certain scenarios, particularly with domain-specific queries and rare terms. This complementary strength of different search approaches has pushed the community toward exploring unified solutions.&lt;/p&gt;
&lt;p&gt;However, implementing hybrid search at scale presents its own set of challenges. While frameworks like LangChain or LlamaIndex have made proof-of-concept implementations straightforward, scaling these solutions to production reveals significant architectural complexities. The conventional approach requires maintaining separate vector databases and search engines, resulting in the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Data redundancy across multiple storage systems&lt;/li&gt;
&lt;li&gt;Complex consistency management between separate indices&lt;/li&gt;
&lt;li&gt;Fragmented security controls and access patterns&lt;/li&gt;
&lt;li&gt;Increased latency due to cross-system synchronization&lt;/li&gt;
&lt;li&gt;Higher operational complexity in high-throughput environments&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=15354306068231238477&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;An Example of Complex Architectures of Hybrid Search&lt;/em&gt;&lt;/p&gt;
&lt;h2 id=&quot;elasticsearch-s-technical-limitations&quot;&gt;Elasticsearch's Technical Limitations&lt;/h2&gt;
&lt;p&gt;As the long-standing leader in enterprise search, Elasticsearch has powered everything from e-commerce platforms to logging systems for over a decade. Their dominance in keyword-based search and their extensive enterprise feature set, made them an obvious candidate for bridging the gap between traditional and AI-powered search. Recognizing this opportunity, Elasticsearch made a significant move by introducing vector search capabilities in version 8.0, aiming to unify traditional keyword search with modern vector operations under a single platform.&lt;/p&gt;
&lt;p&gt;However, despite its market leadership and ambitious steps toward vector search capabilities, Elasticsearch faces several fundamental constraints that impact its effectiveness in modern search architectures:&lt;/p&gt;
&lt;h4 id=&quot;performance-and-resource-management&quot;&gt;Performance and Resource Management&lt;/h4&gt;
&lt;p&gt;The tight coupling between write operations, index construction, and query processing creates substantial overhead during data ingestion and indexing. This architectural decision leads to CPU and I/O contention, particularly during bulk updates. The &quot;near real-time&quot; search design introduces latency that can impair AI applications requiring rapid interactions or dynamic decision-making.&lt;/p&gt;
&lt;h4 id=&quot;architectural-constraints&quot;&gt;Architectural Constraints&lt;/h4&gt;
&lt;p&gt;The JVM-based architecture introduces additional complexity through memory management overhead and garbage collection pauses. Organizations often find themselves over-provisioning resources to maintain stable performance as data volumes grow. The non-cloud-native design, with tightly coupled storage and compute, requires simultaneous scaling of both resources, reducing deployment flexibility.&lt;/p&gt;
&lt;h4 id=&quot;scaling-limitations&quot;&gt;Scaling Limitations&lt;/h4&gt;
&lt;p&gt;The shard management system presents a fundamental dilemma: too many shards in small datasets lead to performance degradation. However, too few shards in large datasets limit scalability and cause data distribution imbalances. In multi-replica scenarios, the requirement for independent index construction per shard multiplies compute costs and reduces resource efficiency.&lt;/p&gt;
&lt;h4 id=&quot;vector-search-performance&quot;&gt;Vector Search Performance&lt;/h4&gt;
&lt;p&gt;The vector search implementation, built on Lucene's core, shows notable performance gaps compared to specialized engines, particularly in high-dimensional spaces. Internal benchmarks indicate performance degradation becomes acute beyond 128 dimensions, with query latency increasing exponentially in high-concurrency scenarios.&lt;/p&gt;
&lt;h2 id=&quot;technical-innovation-a-new-approach-to-unified-search&quot;&gt;Technical Innovation: A New Approach to Unified Search&lt;/h2&gt;
&lt;p&gt;Recent advances in search technology have emerged from extensive experience with real-world search challenges across thousands of production deployments. A consistent pattern has emerged through deep engagement with the open-source community. While vector search excels at semantic matching, organizations often need to maintain separate systems for traditional keyword search, leading to architectural complexities and operational overhead.&lt;/p&gt;
&lt;p&gt;This insight led to a fundamental reimagining of how unified search capabilities could work in the open source vector database &lt;a rel=&quot;nofollow&quot; href=&quot;https://zilliz.com/what-is-milvus&quot;&gt;Milvus&lt;/a&gt;. The result is Sparse-BM25, introduced by Milvus 2.5, an enhanced approach that leverages vector operations while preserving the precision of traditional keyword matching. This technical innovation builds on Milvus's expertise in vector operations while addressing the broader community's need for unified search capabilities.&lt;/p&gt;
&lt;h4 id=&quot;core-technical-architecture&quot;&gt;Core Technical Architecture&lt;/h4&gt;
&lt;p&gt;The implementation introduces several key technical innovations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Advanced text processing pipeline powered by Tantivy, delivering&lt;br&gt;
sophisticated stemming, lemmatization, and stop word filtering&lt;br&gt;
capabilities with support for over 30 languages&lt;/li&gt;
&lt;li&gt;Distributed term frequency management system that efficiently handles&lt;br&gt;
corpus-wide statistics at scale, with automatic rebalancing and&lt;br&gt;
optimization&lt;/li&gt;
&lt;li&gt;Novel sparse vector generation utilizing Corpus TF for documents and&lt;br&gt;
Query TF with global IDF for queries, optimized for high-cardinality&lt;br&gt;
vocabularies&lt;/li&gt;
&lt;li&gt;Custom BM25 distance function optimized for high-dimensional sparse&lt;br&gt;
vector spaces, with configurable saturation parameters&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The system implements a sophisticated indexing hierarchy that adapts to query patterns. The WAND-based inverted index provides efficient retrieval for standard queries, while the Block-Max WAND algorithm development promises further optimization for long-tail queries. Through careful pruning of long-tail terms and vector quantization, the system achieves more than 5x performance improvement while keeping recall degradation under 1% and reducing memory usage by over 50%.&lt;/p&gt;
&lt;p&gt;The graph index support extends beyond basic HNSW implementation, incorporating dynamic graph pruning and rebuilding based on query patterns. This approach has shown particular effectiveness in handling high-dimensional sparse vectors, where traditional inverted indices struggle with the curse of dimensionality. The system automatically maintains optimal graph connectivity while minimizing index maintenance overhead.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=2326955108801626135&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;technical-advances-in-modern-search-systems&quot;&gt;Technical Advances in Modern Search Systems&lt;/h3&gt;
&lt;h5 id=&quot;algorithm-flexibility&quot;&gt;Algorithm Flexibility&lt;/h5&gt;
&lt;p&gt;Modern search systems transform similarity calculations into vector distance computations, enabling more complex queries and corpus distance analysis. Recent research like &quot;End-to-End Query Term Weighting&quot; has introduced innovations such as the Term Weighting BERT (TW-BERT) algorithm, which uses BERT to infer n-gram term weights from queries and construct query representations. When combined with BM25 for calculating document relevance, these approaches demonstrate significant improvements over traditional token-based methods in both in-domain and out-of-domain scenarios.&lt;/p&gt;
&lt;h5 id=&quot;efficiency-considerations&quot;&gt;Efficiency Considerations&lt;/h5&gt;
&lt;p&gt;Advanced search architectures implement lexical search through sparse vectors, combining traditional inverted index compression techniques with lossy compression for dense embeddings. Through careful pruning of long-tail terms and vector quantization, these systems can achieve substantial performance improvements while maintaining high recall rates and reducing resource usage. Ongoing research continues to optimize data compression to further reduce storage costs and query I/O overhead.&lt;/p&gt;
&lt;h5 id=&quot;handling-complex-queries&quot;&gt;Handling Complex Queries&lt;/h5&gt;
&lt;p&gt;While traditional search engines commonly use WAND (Weak AND) technology to optimize inverted index queries, its effectiveness can diminish with long queries due to excessive inverted list intersections and reduced pruning efficiency. Modern approaches address this by combining sparse embeddings with graph indices like HNSW, showing particular promise for sparse vectors with high dimensionality.&lt;/p&gt;
&lt;p&gt;These technical advances in unified vector and keyword search are particularly significant for RAG applications, where queries often combine natural language questions with specific technical terms or metadata filters. Efficiently processing these mixed queries while maintaining high performance is crucial for production deployments.&lt;/p&gt;
&lt;h2 id=&quot;looking-forward-the-future-of-search&quot;&gt;Looking Forward: The Future of Search&lt;/h2&gt;
&lt;p&gt;Remember that frustrated developer from our opening scenario? Today's reality is that teams shouldn't have to choose between semantic understanding and precise matching, performance and flexibility, or simplicity and scale. As our industry continues to push the boundaries of AI and search technology, we're moving toward a future where search infrastructure becomes truly intelligent - understanding not just what users are searching for but why they're searching for it.&lt;/p&gt;
&lt;p&gt;The next frontier isn't just about combining vector and keyword search - it's about creating systems that can dynamically adapt to different types of queries, learn from user interactions, and seamlessly integrate with the growing ecosystem of AI tools and frameworks. As we continue to build and experiment with these technologies in Milvus, we're not just solving technical challenges - we're reshaping how humans interact with and make sense of the vast amounts of information at our disposal.&lt;/p&gt;
&lt;p&gt;This is an incredibly exciting time for the developers and engineers working on these challenges every day. The tools and capabilities we're building today will form the foundation of how future generations will access and understand information. And that's worth getting right.&lt;/p&gt;
</description>
<category>Database</category>
<guid isPermaLink="true">https://coderlegion.com/887/beyond-elasticsearch-the-rise-of-unified-vector-and-keyword-search</guid>
<pubDate>Wed, 22 Jan 2025 11:21:23 +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>