使用LangChain4J的DefaultRetrievalAugmentor实现一个简单的RAG检索。
@Autowired
OpenAiChatModel chatModel;
@RequestMapping("/retrieve")
public String retrieve(HttpServletResponse response, String query, String filePath) {
response.setCharacterEncoding("UTF-8");
//1.加载文档
Document document = loadDocument(filePath, new ApacheTikaDocumentParser());
DocumentByParagraphSplitter splitter = new DocumentByParagraphSplitter(
300,
50
);
//2.分割文档
List<TextSegment> textSegments = splitter.split(document);
OpenAiEmbeddingModel embeddingModel = OpenAiEmbeddingModel.builder()
.modelName("text-embedding-v4") // 阿里云 DashScope 的 embedding 模型名称
.dimensions(768) // text-embedding-v4 支持 768 维度
.baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")
.apiKey("改成你自己的key").build();
//3.生成embedding
List<Embedding> allEmbeddings = new ArrayList<>();
for (int i = 0; i < textSegments.size(); i = i + 9) {
List<TextSegment> segmentList = textSegments.subList(i, Math.min(i + 9, textSegments.size()));
List<Embedding> embeddings = embeddingModel.embedAll(segmentList).content();
allEmbeddings.addAll(embeddings);
}
//4.向量存储
InMemoryEmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();
embeddingStore.addAll(allEmbeddings, textSegments);
//5.构建上下文融合器
DefaultContentInjector contentInjector = new DefaultContentInjector(new PromptTemplate("""
## 角色定位
你是一位专业的RAG问答助手。请根据提供的上下文信息,详细、准确地回答用户的问题。如果参考文档没有内容,请务必不要胡编乱造,请直接说明"没有找到相关信息"。
## 任务要求:
1. 请基于以下提供的参考文档内容,回答用户的问题。
2. 如果参考文档中没有相关信息,请直接说明"没有找到相关信息",不要编造内容。
3. 如果有了参考文档内容,请务必尽量回答问题。有可能用户的输入比较随意,你可以先尝试回答用户的问题,猜测他的实际需求,先给出回复,你需要尽量去贴合用户的问题需求。
## 格式要求:
1. 你的所有回答必须使用Markdown格式进行排版。
2. 上下文信息中包含了图片描述标签,格式为:`<image src="URL" description="多模态描述"></image>`。
3. 如果图片与用户提问高度相关,请将此标签转换为标准的Markdown图片格式 `
> 🖼️ 原文引用的图片资源未包含在导出文件中:`图片`
`。
4. 仅在必要时包含图片,请注意千万不要输出重复的内容和图片,图片确保最终生成的URL不要重复。
## 参考文档:
{{contents}}
## 用户问题:
{{userMessage}}
注意:如果参考文档下面的内容为空,请直接回答“没有找到相关信息”。
"""));
//6.构建检索增强器
DefaultRetrievalAugmentor retrievalAugmentor = DefaultRetrievalAugmentor.builder()
.contentRetriever(EmbeddingStoreContentRetriever.builder()
.embeddingStore(embeddingStore)
.embeddingModel(embeddingModel)
.maxResults(5)
.minScore(0.7)
.build())
.contentInjector(contentInjector)
.build();
//7.构建最终的AI服务
LangChainAiService langChainAiService = AiServices.builder(LangChainAiService.class)
.chatModel(chatModel)
.retrievalAugmentor(retrievalAugmentor)
.chatMemory(MessageWindowChatMemory.withMaxMessages(10))
.build();
//8.调用AI服务
return langChainAiService.chat(query);
}
需要注意的点: 1、embeddingStore.addAll(allEmbeddings, textSegments); 需要同时传入embeddings和segments,一定要注意textSegments记得传入,否则会报错。 java.lang.IllegalArgumentException: textSegment cannot be null 2、embeddingModel.embedAll(segmentList).content(); 需要先分批,否则会报错 或者也可以通过设置embeddingModel的maxSegmentsPerBatch来实现。
OpenAiEmbeddingModel embeddingModel = OpenAiEmbeddingModel.builder()
.modelName("text-embedding-v4") // 阿里云 DashScope 的 embedding 模型名称
.dimensions(768) // text-embedding-v3 支持 768 维度
.baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")
.maxSegmentsPerBatch(9)
.apiKey("sk-0227f9a97bef4f2c8fc899d82831aa25").build();
3、如果是word文档,需要使用ApacheTikaDocumentParser,否则无法解析,这个包来自:
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-document-parser-apache-tika</artifactId>
<version>1.8.0-beta15</version>
<scope>compile</scope>
</dependency>
其他的DocumentParse介绍: - TextDocumentParser : langchain4j中默认的的 TextDocumentParser,它可以解析纯文本格式(e.9.TXT、HTML、MD 等)的文件。 - ApachePdfBoxDocumentParser:需要依赖langchain4j-document-parser-apache-pdfbox,可以解析 PDF 文件 - ApachePoiDocumentParser:需要依赖langchain4j-document-parser-apache-poi,可以解析 MSOffice 文件格式(e.9.DOC、DOCX、PPT、PPTX、XLS、XLSX等) - ApacheTikaDocumentParser 需要依赖 langchain4j-document-parser-apache-tika ,可以自动检测和解析几乎所有现有的文件格式 比如使用TextDocumentParser来处理word文档的话,会报错java.lang.RuntimeException: The text " ..." (1 characters long) doesn't fit into the maximum segment size (300 characters), and there is no subSplitter defined to split it further. 5、提示词模板中的占位符的名字不能随便改,需要和默认模板中保持一致,即{{contents}}表示检索到的文档内容,{{userMessage}}表示用户的问题。 6、文档的分片+向量化+入库,可以简化,使用EmbeddingStoreIngestor来实现:
EmbeddingStoreIngestor ingestor = EmbeddingStoreIngestor.builder()
.documentSplitter(DocumentSplitters.recursive(300, 50))
.embeddingModel(embeddingModel)
.embeddingStore(embeddingStore)
.build();
ingestor.ingest(document);
简化后的完整代码如下:
@RequestMapping("/retrieve1")
public String retrieve1(HttpServletResponse response, String query, String filePath) {
response.setCharacterEncoding("UTF-8");
// 1. 配置 Embedding 模型
OpenAiEmbeddingModel embeddingModel = OpenAiEmbeddingModel.builder()
.modelName("text-embedding-v3")
.dimensions(768)
.baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")
.maxSegmentsPerBatch(9)
.apiKey("sk-0227f9a97bef4f2c8fc899d82831aa25")
.build();
// 2. 加载文档并生成 Embeddings
InMemoryEmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();
EmbeddingStoreIngestor.builder()
.documentSplitter(DocumentSplitters.recursive(300, 50))
.embeddingModel(embeddingModel)
.embeddingStore(embeddingStore)
.build()
.ingest(loadDocument(filePath, new ApacheTikaDocumentParser()));
// 3. 构建 RAG 增强器(使用链式调用)
DefaultRetrievalAugmentor retrievalAugmentor = DefaultRetrievalAugmentor.builder()
.contentRetriever(EmbeddingStoreContentRetriever.builder()
.embeddingStore(embeddingStore)
.embeddingModel(embeddingModel)
.maxResults(5)
.minScore(0.7)
.build())
.contentInjector(new DefaultContentInjector(new PromptTemplate("""
## 角色定位
你是一位专业的RAG问答助手。请根据提供的上下文信息,详细、准确地回答用户的问题。如果参考文档没有内容,请务必不要胡编乱造,请直接说明"没有找到相关信息"。
## 任务要求:
1. 请基于以下提供的参考文档内容,回答用户的问题。
2. 如果参考文档中没有相关信息,请直接说明"没有找到相关信息",不要编造内容。
3. 如果有了参考文档内容,请务必尽量回答问题。有可能用户的输入比较随意,你可以先尝试回答用户的问题,猜测他的实际需求,先给出回复,你需要尽量去贴合用户的问题需求。
## 格式要求:
1. 你的所有回答必须使用Markdown格式进行排版。
2. 上下文信息中包含了图片描述标签,格式为:`<image src="URL" description="多模态描述"></image>`。
3. 如果图片与用户提问高度相关,请将此标签转换为标准的Markdown图片格式 `
> 🖼️ 原文引用的图片资源未包含在导出文件中:`图片`
`。
4. 仅在必要时包含图片,请注意千万不要输出重复的内容和图片,图片确保最终生成的URL不要重复。
## 参考文档:
{{contents}}
## 用户问题:
{{userMessage}}
注意:如果参考文档下面的内容为空,请直接回答“没有找到相关信息”。
""")))
.build();
// 4. 构建 AI 服务并返回结果
return AiServices.builder(LangChainAiService.class)
.chatModel(chatModel)
.retrievalAugmentor(retrievalAugmentor)
.chatMemory(MessageWindowChatMemory.withMaxMessages(10))
.build()
.chat(query);
}