
本示例将引导您一步步构建一个 Spring Boot 应用,演示如何利用 Spring AI Alibaba 的 Module RAG 功能,实现模块化的检索增强生成(RAG)系统。
1. 简介
Spring AI Module RAG: https://docs.spring.io/spring-ai/reference/api/retrieval-augmented-generation.html#modules
Spring AI 实现了一个模块化的 RAG 架构,其灵感来源于论文 "Modular RAG: Transforming RAG Systems into LEGO-like Reconfigurable Frameworks" 中详细阐述的模块化概念。
2. 组成部分
2.1 Pre-Retrieval(检索前处理)
增强和转换用户输入,使其更有效地执行检索任务,解决格式不正确的查询、query 语义不清晰、或不受支持的语言等。
- QueryAugmenter 查询增强:使用附加的上下文数据信息增强用户 query,提供大模型回答问题时的必要上下文信息;
- QueryTransformer 查询改写:因为用户的输入通常是片面的,关键信息较少,不便于大模型理解和回答问题。因此需要使用 prompt 调优手段或者大模型改写用户 query;
- QueryExpander 查询扩展:将用户 query 扩展为多个语义不同的变体以获得不同视角,有助于检索额外的上下文信息并增加找到相关结果的机会。
2.2 Retrieval(检索)
负责查询向量存储等数据系统并检索和用户 query 相关性最高的 Document。
- DocumentRetriever 检索器:根据 QueryExpander 使用不同的数据源进行检索,例如 搜索引擎、向量存储、数据库或知识图等;
- DocumentJoiner:将从多个 query 和从多个数据源检索到的 Document 合并为一个 Document 集合;
2.3 Post-Retrieval(检索后处理)
负责处理检索到的 Document 以获得最佳的输出结果,解决模型中的*中间丢失*和上下文长度限制等。
- DocumentRanker:根据 Document 和用户 query 的相关性对 Document 进行排序和排名;
- DocumentSelector:用于从检索到的 Document 列表中删除不相关或冗余文档;
- DocumentCompressor:用于压缩每个 Document,减少检索到的信息中的噪音和冗余。
2.4 生成
生成用户 Query 对应的大模型输出。
3. 技术栈与核心依赖
- Spring Boot 3.x
- Spring AI Alibaba (用于对接阿里云 DashScope 通义大模型)
- Elasticsearch (用于向量存储)
- Maven (项目构建工具)
在 pom.xml 中,你需要引入以下核心依赖:
<dependencies>
<!-- Spring AI Alibaba 核心启动器,集成 DashScope -->
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
<version>${spring-ai-alibaba.version}</version>
</dependency>
<!-- Spring Web 用于构建 RESTful API -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Elasticsearch 向量存储 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-vector-store-elasticsearch</artifactId>
<exclusions>
<exclusion>
<groupId>co.elastic.clients</groupId>
<artifactId>elasticsearch-java</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>co.elastic.clients</groupId>
<artifactId>elasticsearch-java</artifactId>
<version>8.13.4</version>
<exclusions>
<exclusion>
<artifactId>elasticsearch-rest-client</artifactId>
<groupId>org.elasticsearch.client</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<artifactId>elasticsearch-rest-client</artifactId>
<groupId>org.elasticsearch.client</groupId>
<version>8.13.4</version>
</dependency>
<!-- Spring AI RAG 相关组件 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-advisors-vector-store</artifactId>
</dependency>
<!-- Markdown 文档读取器 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-markdown-document-reader</artifactId>
</dependency>
</dependencies>
4. 项目配置
在 src/main/resources/application.yml 文件中,配置你的 DashScope API Key 和 Elasticsearch 连接信息。
server:
port: 10014
spring:
application:
name: spring-ai-alibaba-module-rag-example
datasource:
url: jdbc:postgresql://localhost:5432/vector_store
username: root
password: password
ai:
dashscope:
api-key: ${AI_DASHSCOPE_API_KEY}
vectorstore:
elasticsearch:
index-name: spring-ai-alibaba-index
similarity: cosine
dimensions: 1536
elasticsearch:
uris: http://127.0.0.1:9200
重要提示 :请将 AI_DASHSCOPE_API_KEY 环境变量设置为你从阿里云获取的有效 API Key。你也可以直接将其写在配置文件中,但这不推荐用于生产环境。
5. 准备文档数据
在 src/main/resources 目录下创建以下文件结构:
src/main/resources/
└── documents/
├── story-1.md
└── story-2.md
5.1 documents/story-1.md
这是我们将用作检索的文档内容之一。
# The Adventures of Iorek and Pingu
Iorek was a little polar bear who lived in the Arctic circle. He loved to explore the snowy landscape and
dreamt of one day going on an adventure around the North Pole. One day, he met a penguin named Pingu who
was on a similar quest. They quickly became friends and decided to embark on their journey together.
Iorek and Pingu set off early in the morning, eager to cover as much ground as possible before nightfall.
The air was crisp and cold, and the snow crunched under their paws as they walked. They chatted excitedly
about their dreams and aspirations, and Iorek told Pingu about his desire to see the Northern Lights.
As they journeyed onward, they encountered a group of playful seals who were sliding and jumping in the
snow. Iorek and Pingu watched in delight as the seals frolicked and splashed in the water. They even tried
to join in, but their paws kept slipping and they ended up sliding on their stomachs instead.
After a few hours of walking, Iorek and Pingu came across a cave hidden behind a wall of snow. They
cautiously entered the darkness, their eyes adjusting to the dim light inside. The cave was filled with
glittering ice formations that sparkled like diamonds in the flickering torchlight.
As they continued their journey, Iorek and Pingu encountered a group of walruses who were lounging on the
ice. They watched in amazement as the walruses lazily rolled over and exposed their tusks for a good
scratch. Pingu even tried to imitate them, but ended up looking more like a clumsy seal than a walrus.
As the sun began to set, Iorek and Pingu found themselves at the edge of a vast, frozen lake. They gazed
out across the glassy surface, mesmerized by the way the ice glinted in the fading light. They could see
the faint outline of a creature moving beneath the surface, and their hearts raced with excitement.
Suddenly, a massive narwhal burst through the ice and into the air, its ivory tusk glistening in the
sunset. Iorek and Pingu watched in awe as it soared overhead, its cries echoing across the lake. They felt
as though they were witnessing a magical moment, one that would stay with them forever.
As the night drew in, Iorek and Pingu settled down to rest in their makeshift camp. They huddled together
for warmth, gazing up at the starry sky above. They chatted about all they had seen and experienced during
their adventure, and Iorek couldn't help but feel grateful for the new friend he had made.
The next morning, Iorek and Pingu set off once again, determined to explore every inch of the North Pole.
They stumbled upon a hidden cave filled with glittering crystals that sparkled like diamonds in the
sunlight. They marveled at their beauty before continuing on their way.
As they journeyed onward, Iorek and Pingu encountered many more wonders and adventures. They met a group
of playful reindeer who showed them how to pull sledges across the snow, and even caught a glimpse of the
mythical Loch Ness Monster lurking beneath the icy waters. In the end, their adventure around the North
Pole had been an unforgettable experience, one that they would treasure forever.
5.2 documents/story-2.md
这是我们将用作检索的另一个文档内容。
# Lucio and Balosso explore the Alps
## Chapter 1: An Unlikely Friendship
Lucio was a little wolf who lived in the Italian Alps. He loved to explore the rugged landscape and dreamt
of one day going on an adventure around the mountains. One day, he met a brown bear named Balosso who was
on a similar quest. They quickly became friends and decided to embark on their journey together.
## Chapter 2: The Journey Begins
Lucio and Balosso set off early in the morning, eager to cover as much ground as possible before
nightfall. The air was crisp and cool, and the sun shone brightly overhead. They chatted excitedly about
their dreams and aspirations, and Lucio told Balosso about his desire to climb to the top of the highest
peak in the Alps.
## Chapter 3: Playful Encounters
As they journeyed onward, they encountered a group of playful marmots who were scampering across the rocky
terrain. Lucio and Balosso watched in delight as the marmots frolicked and chased each other, their paws
pattering on the stone. They even tried to join in, but their paws kept slipping and they ended up
tumbling onto their backsides.
## Chapter 4: The Hidden Glacier
After a few hours of walking, Lucio and Balosso came across a hidden glacier nestled between two towering
peaks. They cautiously approached the icy surface, their breath misting in the cold air. The glacier was
covered in intricate patterns and colors, shimmering like a shimmering jewel in the sunlight.
## Chapter 5: Soaring Eagles
As they continued their journey, Lucio and Balosso encountered a group of majestic eagles soaring
overhead. They watched in awe as the eagles swooped and dived, their wings spread wide against the blue
sky. Lucio even tried to imitate them, but ended up flapping his ears instead of wings.
## Chapter 6: The Highest Peak
As the sun began to set, Lucio and Balosso found themselves at the foot of the highest peak in the Alps.
They gazed upwards in awe, their hearts pounding with excitement. They could see the faint outline of a
summit visible through the misty veil that surrounded the mountain.
## Chapter 7: The Climb
Lucio and Balosso carefully climbed the steep slope, their claws digging into the rocky surface. The air
grew colder and thinner as they ascended, but they pressed onward, determined to reach the top. Finally,
they reached the summit, where they found a stunning view of the Italian Alps stretching out before them.
## Chapter 8: Summit Success
As they gazed out across the landscape, Lucio and Balosso felt an overwhelming sense of pride and
accomplishment. They had faced many challenges along the way, but their friendship had carried them
through. They even spotted a group of rare alpine ibex grazing on the distant slopes, adding to the
adventure's magic.
## Chapter 9: The Journey Home
As night began to fall, Lucio and Balosso made their way back down the mountain, their paws sore but their
spirits high. They couldn't wait to tell their friends and family about their amazing adventure around the
Italian Alps. Even as they drifted off to sleep, visions of the stunning landscape danced in their minds.
## Chapter 10: New Discoveries
The next morning, Lucio and Balosso set off once again, eager to explore every inch of the mountain range.
They stumbled upon a hidden valley filled with sparkling streams and towering trees, and even caught a
glimpse of a rare, elusive snow leopard lurking in the shadows.
## Epilogue: Unforgettable Memories
In the end, their adventure around the Italian Alps had been an unforgettable experience,
one that they would treasure forever.
6. 编写 Java 代码
6.1 ModuleRAGApplication.java
应用主类,配置 ChatMemory Bean。
package com.alibaba.cloud.ai.example.rag;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.memory.MessageWindowChatMemory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
public class ModuleRAGApplication {
public static void main(String[] args) {
SpringApplication.run(ModuleRAGApplication.class, args);
}
@Bean
public ChatMemory chatMemory() {
return MessageWindowChatMemory.builder().build();
}
}
6.2 VectorDBInit.java
向量数据库初始化类,用于创建 Elasticsearch 索引并加载文档数据。
package com.alibaba.cloud.ai.example.rag.init;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.mapping.DenseVectorProperty;
import co.elastic.clients.elasticsearch._types.mapping.KeywordProperty;
import co.elastic.clients.elasticsearch._types.mapping.ObjectProperty;
import co.elastic.clients.elasticsearch._types.mapping.Property;
import co.elastic.clients.elasticsearch._types.mapping.TextProperty;
import co.elastic.clients.elasticsearch._types.mapping.TypeMapping;
import co.elastic.clients.elasticsearch.indices.CreateIndexResponse;
import co.elastic.clients.elasticsearch.indices.IndexSettings;
import jakarta.annotation.PostConstruct;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ai.document.Document;
import org.springframework.ai.reader.markdown.MarkdownDocumentReader;
import org.springframework.ai.reader.markdown.config.MarkdownDocumentReaderConfig;
import org.springframework.ai.transformer.splitter.TokenTextSplitter;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.ai.vectorstore.elasticsearch.autoconfigure.ElasticsearchVectorStoreProperties;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
@Component
public class VectorDBInit {
private static final Logger logger = LoggerFactory.getLogger(VectorDBInit.class);
private final VectorStore vectorStore;
@Value("classpath:documents/story-1.md")
Resource file1;
@Value("classpath:documents/story-2.md")
Resource file2;
private final ElasticsearchClient elasticsearchClient;
private final ElasticsearchVectorStoreProperties options;
private static final String textField = "content";
private static final String vectorField = "embedding";
public VectorDBInit(
VectorStore vectorStore,
ElasticsearchClient elasticsearchClient,
ElasticsearchVectorStoreProperties options) {
this.vectorStore = vectorStore;
this.elasticsearchClient = elasticsearchClient;
this.options = options;
}
@PostConstruct
void run() {
logger.info("Loading *.md files as Documents");
var markdownReader1 = new MarkdownDocumentReader(file1, MarkdownDocumentReaderConfig.builder()
.withAdditionalMetadata("location", "North Pole")
.build());
List<Document> documents = new ArrayList<>(markdownReader1.get());
var markdownReader2 = new MarkdownDocumentReader(file2, MarkdownDocumentReaderConfig.builder()
.withAdditionalMetadata("location", "Italy")
.build());
documents.addAll(markdownReader2.get());
logger.info("Creating and storing Embeddings from Documents");
createIndexIfNotExists();
vectorStore.add(new TokenTextSplitter().split(documents));
}
private void createIndexIfNotExists() {
try {
String indexName = options.getIndexName();
Integer dimsLength = options.getDimensions();
if (!StringUtils.hasLength(indexName)) {
throw new IllegalArgumentException("Elastic search index name must be provided");
}
boolean exists = elasticsearchClient.indices().exists(idx -> idx.index(indexName)).value();
if (exists) {
logger.debug("Index {} already exists. Skipping creation.", indexName);
return;
}
String similarityAlgo = options.getSimilarity().name();
IndexSettings indexSettings = IndexSettings
.of(settings -> settings.numberOfShards(String.valueOf(1)).numberOfReplicas(String.valueOf(1)));
// Maybe using json directly?
Map<String, Property> properties = new HashMap<>();
properties.put(vectorField, Property.of(property -> property.denseVector(
DenseVectorProperty.of(dense -> dense.index(true).dims(dimsLength).similarity(similarityAlgo)))));
properties.put(textField, Property.of(property -> property.text(TextProperty.of(t -> t))));
Map<String, Property> metadata = new HashMap<>();
metadata.put("ref_doc_id", Property.of(property -> property.keyword(KeywordProperty.of(k -> k))));
properties.put("metadata",
Property.of(property -> property.object(ObjectProperty.of(op -> op.properties(metadata)))));
CreateIndexResponse indexResponse = elasticsearchClient.indices()
.create(createIndexBuilder -> createIndexBuilder.index(indexName)
.settings(indexSettings)
.mappings(TypeMapping.of(mappings -> mappings.properties(properties))));
if (!indexResponse.acknowledged()) {
throw new RuntimeException("failed to create index");
}
logger.info("create elasticsearch index {} successfully", indexName);
}
catch (IOException e) {
logger.error("failed to create index", e);
throw new RuntimeException(e);
}
}
}
6.3 ModuleRAGBasicController.java
基础 RAG 控制器,实现基本的检索增强生成功能。
package com.alibaba.cloud.ai.example.rag.controller;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.rag.advisor.RetrievalAugmentationAdvisor;
import org.springframework.ai.rag.retrieval.search.VectorStoreDocumentRetriever;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/module-rag")
public class ModuleRAGBasicController {
private final ChatClient chatClient;
private final RetrievalAugmentationAdvisor retrievalAugmentationAdvisor;
public ModuleRAGBasicController(ChatClient.Builder chatClientBuilder, VectorStore vectorStore) {
this.chatClient = chatClientBuilder.build();
this.retrievalAugmentationAdvisor = RetrievalAugmentationAdvisor.builder()
.documentRetriever(
VectorStoreDocumentRetriever.builder().similarityThreshold(0.50).vectorStore(vectorStore).build())
.build();
}
@GetMapping("/rag/basic")
public String chatWithDocument(@RequestParam("prompt") String prompt) {
return chatClient.prompt().advisors(retrievalAugmentationAdvisor).user(prompt).call().content();
}
}
6.4 ModuleRAGCompressionController.java
压缩 RAG 控制器,实现查询压缩功能。
package com.alibaba.cloud.ai.example.rag.controller;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
import org.springframework.ai.rag.advisor.RetrievalAugmentationAdvisor;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.rag.preretrieval.query.transformation.CompressionQueryTransformer;
import org.springframework.ai.rag.retrieval.search.VectorStoreDocumentRetriever;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import static org.springframework.ai.chat.memory.ChatMemory.CONVERSATION_ID;
/**
* CompressionQueryTransformer 使用示例
* https://docs.spring.io/spring-ai/reference/api/retrieval-augmented-generation.html#_compressionquerytransformer
*/
@RestController
@RequestMapping("/module-rag")
public class ModuleRAGCompressionController {
private final ChatClient chatClient;
private final MessageChatMemoryAdvisor chatMemoryAdvisor;
private final RetrievalAugmentationAdvisor retrievalAugmentationAdvisor;
public ModuleRAGCompressionController(ChatClient.Builder chatClientBuilder, ChatMemory chatMemory,
VectorStore vectorStore) {
this.chatClient = chatClientBuilder.build();
this.chatMemoryAdvisor = MessageChatMemoryAdvisor.builder(chatMemory).build();
var documentRetriever = VectorStoreDocumentRetriever.builder()
.vectorStore(vectorStore)
.similarityThreshold(0.50)
.build();
var queryTransformer = CompressionQueryTransformer.builder()
.chatClientBuilder(chatClientBuilder.build().mutate())
.build();
this.retrievalAugmentationAdvisor = RetrievalAugmentationAdvisor.builder()
.documentRetriever(documentRetriever)
.queryTransformers(queryTransformer)
.build();
}
@PostMapping("/rag/compression/{chatId}")
public String rag(@RequestBody String prompt, @PathVariable("chatId") String conversationId) {
return chatClient.prompt()
.advisors(chatMemoryAdvisor, retrievalAugmentationAdvisor)
.advisors(advisors -> advisors.param(CONVERSATION_ID,
conversationId))
.user(prompt)
.call()
.content();
}
}
6.5 ModuleRAGMemoryController.java
记忆 RAG 控制器,实现带记忆功能的检索增强生成。
package com.alibaba.cloud.ai.example.rag.controller;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
import org.springframework.ai.rag.advisor.RetrievalAugmentationAdvisor;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.rag.retrieval.search.VectorStoreDocumentRetriever;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import static org.springframework.ai.chat.memory.ChatMemory.CONVERSATION_ID;
/**
* RAG memory 使用示例
*/
@RestController
@RequestMapping("/module-rag")
public class ModuleRAGMemoryController {
private final ChatClient chatClient;
private final MessageChatMemoryAdvisor chatMemoryAdvisor;
private final RetrievalAugmentationAdvisor retrievalAugmentationAdvisor;
public ModuleRAGMemoryController(ChatClient.Builder chatClientBuilder, ChatMemory chatMemory,
VectorStore vectorStore) {
this.chatClient = chatClientBuilder.build();
this.chatMemoryAdvisor = MessageChatMemoryAdvisor.builder(chatMemory).build();
this.retrievalAugmentationAdvisor = RetrievalAugmentationAdvisor.builder()
.documentRetriever(
VectorStoreDocumentRetriever.builder().similarityThreshold(0.50).vectorStore(vectorStore).build())
.build();
}
@PostMapping("/rag/memory/{chatId}")
public String chatWithDocument(@RequestBody String prompt, @PathVariable("chatId") String conversationId) {
return chatClient.prompt()
.advisors(chatMemoryAdvisor, retrievalAugmentationAdvisor)
.advisors(advisors -> advisors.param(CONVERSATION_ID,
conversationId))
.user(prompt)
.call()
.content();
}
}
6.6 ModuleRAGRewriteController.java
重写 RAG 控制器,实现查询重写功能。
package com.alibaba.cloud.ai.example.rag.controller;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.rag.advisor.RetrievalAugmentationAdvisor;
import org.springframework.ai.rag.preretrieval.query.transformation.RewriteQueryTransformer;
import org.springframework.ai.rag.retrieval.search.VectorStoreDocumentRetriever;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* RewriteQueryTransformer 使用示例
* https://docs.spring.io/spring-ai/reference/api/retrieval-augmented-generation.html#_rewritequerytransformer
*/
@RestController
@RequestMapping("/module-rag")
public class ModuleRAGRewriteController {
private final ChatClient chatClient;
private final RetrievalAugmentationAdvisor retrievalAugmentationAdvisor;
public ModuleRAGRewriteController(ChatClient.Builder chatClientBuilder, VectorStore vectorStore) {
this.chatClient = chatClientBuilder.build();
var documentRetriever = VectorStoreDocumentRetriever.builder()
.vectorStore(vectorStore)
.similarityThreshold(0.50)
.build();
var queryTransformer = RewriteQueryTransformer.builder()
.chatClientBuilder(chatClientBuilder.build().mutate())
.targetSearchSystem("vector store")
.build();
this.retrievalAugmentationAdvisor = RetrievalAugmentationAdvisor.builder()
.documentRetriever(documentRetriever)
.queryTransformers(queryTransformer)
.build();
}
@PostMapping("/rag/rewrite")
public String rag(@RequestBody String prompt) {
return chatClient.prompt().advisors(retrievalAugmentationAdvisor).user(prompt).call().content();
}
}
6.7 ModuleRAGTranslationController.java
翻译 RAG 控制器,实现查询翻译功能。
package com.alibaba.cloud.ai.example.rag.controller;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.rag.advisor.RetrievalAugmentationAdvisor;
import org.springframework.ai.rag.preretrieval.query.transformation.TranslationQueryTransformer;
import org.springframework.ai.rag.retrieval.search.VectorStoreDocumentRetriever;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* TranslationQueryTransformer 使用示例
* https://docs.spring.io/spring-ai/reference/api/retrieval-augmented-generation.html#_translationquerytransformer
*/
@RestController
@RequestMapping("/module-rag")
public class ModuleRAGTranslationController {
private final ChatClient chatClient;
private final RetrievalAugmentationAdvisor retrievalAugmentationAdvisor;
public ModuleRAGTranslationController(ChatClient.Builder chatClientBuilder, VectorStore vectorStore) {
this.chatClient = chatClientBuilder.build();
var documentRetriever = VectorStoreDocumentRetriever.builder()
.vectorStore(vectorStore)
.similarityThreshold(0.50)
.build();
var queryTransformer = TranslationQueryTransformer.builder()
.chatClientBuilder(chatClientBuilder.build().mutate())
.targetLanguage("english")
.build();
this.retrievalAugmentationAdvisor = RetrievalAugmentationAdvisor.builder()
.documentRetriever(documentRetriever)
.queryTransformers(queryTransformer)
.build();
}
@PostMapping("/rag/translation")
public String rag(@RequestBody String prompt) {
return chatClient.prompt().advisors(retrievalAugmentationAdvisor).user(prompt).call().content();
}
}
7. 运行与测试
在启动示例之前,您本地应该有一个可以正常使用的 Elasticsearch。
7.1 Basic 基础 RAG 测试
启动应用后,可以使用以下命令测试基础 RAG 功能:
curl -X GET "http://localhost:10014/module-rag/rag/basic?prompt=Who are the characters going on an adventure in the North Pole?"
预期响应:
根据文档内容,Iorek(一只小北极熊)和 Pingu(一只企鹅)是前往北极冒险的角色。
7.2 Compression 压缩 RAG 测试
无 Compression 时:
curl -X POST http://127.0.0.1:10014/module-rag/rag/memory/123 \
-d '{"prompt": "Who are the characters going on an adventure in the North Pole?"}'
output:
I'm sorry, but I don't have the information needed to answer your question. Could you please provide more details or clarify your query? If it's outside my current knowledge base, I may not be able to assist accurately. Let me know how else I can help!
curl -X POST http://127.0.0.1:10014/module-rag/rag/memory/123 \
-d '{"prompt": "What places do they visit?"}'
output:
I understand. Here's how I would respond:
---
I'm sorry, but I don't have specific information about the characters going on an adventure in the North Pole. Could you provide more context or
details? If it's from a particular story, book, or game, letting me know might help me assist you better. Otherwise, I might not be able to provide an accurate answer. Let me know if there's anything else I can help with!
---
Is this response appropriate for your needs?
有 Compression 时:
curl -X POST http://127.0.0.1:10014/module-rag/rag/compression/123 \
-d '{"prompt": "Who are the characters going on an adventure in the North Pole?"}'
output:
Understood. Here's a polite and clear response for the user:
---
I'm sorry, but I don't have the specific information about the characters going on an adventure in the North Pole. If this is from a particular
story, book, or other source, providing more details might help me assist you better. Otherwise, I may not be able to provide an accurate answer. Let me know if there's anything else I can help with!
---
Is this appropriate for your needs?
curl -X POST http://127.0.0.1:10014/module-rag/rag/compression/123 \
-d '{"prompt": "What places do they visit?"}'
output:
Certainly! Here's a polite response to inform the user that the query is outside my knowledge base:
---
I'm sorry, but I don't have the specific information about the characters going on an adventure in the North Pole. If this is from a particular
story, book, or other source, providing more details might help me assist you better. Otherwise, I may not be able to provide an accurate answer. Let me know if there's anything else I can help with!
---
Or, more directly:
---
I'm sorry, but I don't have the information needed to answer your question about the characters going on an adventure in the North Pole. If you
can provide more context or specify the source, I'd be happy to try assisting further. Otherwise, I may not be able to provide an accurate answer. Let me know if there's anything else I can help with!
---
Is this suitable for your needs?
7.3 Memory 记忆 RAG 测试
curl -X POST http://127.0.0.1:10014/module-rag/rag/memory/123 \
-d '{"prompt": "Who are the characters going on an adventure in the North Pole?"}'
预期响应:
根据文档内容,Iorek(一只小北极熊)和 Pingu(一只企鹅)是前往北极冒险的角色。
curl -X POST http://127.0.0.1:10014/module-rag/rag/memory/123 \
-d '{"prompt": "What places do they visit?"}'
预期响应(基于之前的对话记忆):
根据文档内容,Iorek 和 Pingu 在他们的冒险中访问了多个地方,包括冰洞、冰冻湖泊、隐藏的洞穴和充满闪闪发光晶体的洞穴。
7.4 Rewrite 重写 RAG 测试
curl -X POST http://127.0.0.1:10014/module-rag/rag/rewrite \
-H "Content-Type: application/json" \
-d '{"prompt": "Tell me about the polar bear and penguin adventure"}'
预期响应:
根据文档内容,Iorek(小北极熊)和 Pingu(企鹅)一起在北极进行冒险。他们探索了冰雪覆盖的景观,遇到了海豹、海象,甚至看到了独角鲸从冰中跃出。
7.5 Translation 翻译 RAG 测试
curl -X POST http://127.0.0.1:10014/module-rag/rag/translation \
-H "Content-Type: application/json" \
-d '{"prompt": "北极熊和企鹅的冒险故事"}'
预期响应:
根据文档内容,Iorek(小北极熊)和 Pingu(企鹅)一起在北极进行冒险。他们探索了冰雪覆盖的景观,遇到了海豹、海象,甚至看到了独角鲸从冰中跃出。
8. 实现思路与扩展建议
实现思路
本示例的核心思想是**"模块化 RAG 架构"**。我们将 RAG 系统分解为四个主要模块:Pre-Retrieval(检索前处理)、Retrieval(检索)、Post-Retrieval(检索后处理)和生成。这种模块化设计使得:
- 灵活性高:可以根据具体需求选择和组合不同的模块组件。
- 可维护性强:每个模块可以独立开发和优化,降低了系统复杂度。
- 可扩展性好:可以轻松添加新的功能模块,如新的查询转换器或文档处理器。
扩展建议
- 多数据源集成:可以扩展 DocumentRetriever 以支持更多数据源,如关系数据库、图数据库或外部 API。
- 高级查询转换:实现更复杂的查询转换策略,如基于意图的查询转换或多轮对话的上下文感知查询转换。
- 智能文档处理:添加更高级的文档处理功能,如实体识别、关系抽取或自动摘要。
- 性能优化:实现缓存机制、并行处理或异步处理以提高系统性能。
- 评估与监控:添加 RAG 系统的性能评估和监控功能,以便持续优化系统效果。