Spring AI 与 Qdrant 和 Ollama 相遇:变革性文档分析的三重奏
到目前为止,Python 一直是实现检索增强生成 (RAG) 应用程序的首选语言,基本上成为开发大型语言模型 (LLM) 应用程序的默认选择。然而,对于 Java 的爱好者和拥护者来说,这种趋势并不意味着终结。恰恰相反,这是一个创新的机会。在我即将发表的文章中,我们将探讨如何创建一个可扩展的本地化 RAG 应用程序,专门用于处理复杂的文档。这将通过将 # springboot的稳健性与 # qdrant的效率和 # ollama的智能相结合来实现。请继续关注这个令人兴奋的指南!
介绍:
图中所示的架构代表了一种处理和分析复杂文档(如调查报告、财务报告等)的复杂方法。在这个系统的开始阶段,我们有用户交互,这是用户与系统交互的门户。用户首先通过称为的 API 上传文档/load
,然后使用另一个称为的 API 向系统提出查询/ask
。这表明这是一个交互式系统,其初始操作是文档上传,然后是查询过程,允许用户从上传的文档中提取有意义的信息。
该架构的核心是“Spring AI”,它可以被视为弥合原始数据与可操作见解之间差距的智能处理器。当用户上传文档时,Spring AI 接管并解析和分析文本。它将复杂文档的内容转换为适合高级数据处理技术的结构化形式。Spring AI 的本质在于它能够在细粒度级别上消化和理解这些文档的内容,为流程的下一阶段准备数据。
经过 Spring AI 的初步处理后,我们进入数据处理和存储领域。在这里,处理后的数据被转换成嵌入,嵌入本质上是捕捉文档语义本质的数值表示。这种转换至关重要,因为它使系统能够执行复杂的推理,这项任务由一个昵称的组件管理Ollama
。然后,人工智能驱动的Llama3
模型使用这些嵌入来理解和解释内容,从而可以智能地响应用户查询。最后,这些嵌入存储在Qdrant
专为文档设计的向量存储中。通过将信息存储为向量,系统可确保其能够高效地检索和分析数据,从而快速智能地响应用户查询。这种存储解决方案巩固了系统处理重复交互的能力,每个查询都利用存储的嵌入来提供精确且与上下文相关的见解。
通过这种三部分架构,该系统提供了从文档上传到信息检索的无缝体验,并由Llama3
处理和理解复杂调查数据的复杂人工智能提供支持。
执行
项目脚手架如下:
首先让我们从观察依赖库开始。
pom.xml:
<?xml version= "1.0" encoding= "UTF-8" ?>
< project xmlns = "http://maven.apache.org/POM/4.0.0" xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation = "http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" >
< modelVersion > 4.0.0 </ modelVersion >
< parent >
< groupId > org.springframework.boot </ groupId >
< artifactId > spring-boot-starter-parent </ artifactId >
< version > 3.2.5 </ version >
< relationPath /> <!-- 从存储库查找父级 -->
</ parent >
< groupId > com.thevaslabs </ groupId >
< artifactId > springboot - rag </artifactId> <version> 0.0.1 - SNAPSHOT </version> <name> springboot - rag </name> <description> springboot - rag </description> <properties> < java.version > 21 < /java.version > < spring - ai.version > 0.8.1 < /spring-ai.version > </properties> <dependencies>
<dependency> <groupId> org.springframework.boot </groupId> <artifactId> spring - boot - starter - web </artifactId> </dependency> <dependency> <groupId> org.springframework .人工智能</ groupId > < artifactId > spring-ai-ollama-spring-boot-starter </ artifactId > </ dependency > <
依赖项>
< groupId > org.springframework.ai </ groupId >
< artifactId > spring-ai-qdrant-store-spring-boot-starter </ artifactId >
</ dependency >
< dependency >
< groupId > org.springframework.ai </ groupId >
< artifactId > spring-ai-pdf-document-reader </ artifactId >
</ dependency >
< dependency >
< groupId > org.springframework.boot </ groupId >
< artifactId > spring-boot-devtools </ artifactId >
< scope >运行时</ scope >
< optional > true </ optional >
</ dependency >
< dependency >
< groupId > org.projectlombok </ groupId >
< artifactId > lombok </ artifactId >
< optional > true </ optional >
</ dependency >
< dependency >
< groupId > org.springframework.boot </ groupId >
< artifactId > spring-boot-starter-test </ artifactId >
<scope>测试</scope> </dependency> </dependencies>
<dependencyManagement> <dependencies> <dependency>
<groupId> org.springframework .
ai </ groupId >
< artifactId > spring- ai -bom </ artifactId >
<version> $ {spring-ai.version} < / version >
<type> pom</type> <scope> import
</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins>
<plugin> <groupId> org.springframework.boot </groupId> <artifactId> spring - boot - maven - plugin </artifactId> <configuration> <excludes> <exclude> <groupId> org.projectlombok </groupId> <artifactId> lombok </artifactId> </exclude> </excludes> </configuration> </plugin> </plugins> </build> <repositories> <repositories> <id> spring - milestones </id> <name> Spring Milestones </name> <url> https://repo.spring.io/milestone </url> <snapshots> <enabled> false </enabled> </snapshots> </repository> <repository> <id> spring - snapshots </id> <name> Spring Snapshots < /名称> <网址> https://repo.spring.io/snapshot </ url > < releases > < enabled > false </ enabled > < / releases > < / repositories > < / project >
一旦我们理解了依赖关系,就该创建配置文件了。application.yaml:
这里要注意的最重要的事情是,我们已经配置了从 LLM 到本地矢量存储的所有内容,这为我们提供了最高的隐私和安全性,但这始终可能无法满足我们的要求。因此,您只需更改此文件中的托管 URL,其余代码即可正常工作。
spring:
应用程序:
名称: springboot-rag
线程:
虚拟:
已启用: true
ai:
ollama:
base-url: “http://localhost:11434”
嵌入:
模型: “mxbai-embed-large:latest”
聊天:
模型: “llama3:latest”
选项:
温度: 0.3
top-k: 2
top-p: 0.2
num-gpu: 1 #在MAC上启用Metal gpuvectorstore
:
qdrant:
主机: localhost
端口: 6334
集合名称: infoq-report
我们将从ravendb下载一份关于 2023-2024 年 NoSql 趋势的综合报告。
现在让我们将系统提示文件和数据分别保存在我们项目的特定文件夹prompts/system.pmt
中data/InfoQ-NoSql-Trend-Report.pdf
。
您正在处理与 InfoQ 提供的技术趋势报告相关的询问。正在处理与 InfoQ 提供的技术趋势报告相关的询问。该报告汇集了2023 年
最受欢迎的 InfoQ 趋势报告,涵盖了技术进步、软件开发趋势和组织最佳实践等一系列主题。它深入探讨了各种主题,例如混合工作环境的动态、包括单体、微服务和模块化在内的架构模式的比较分析、平台工程的作用、大型语言模型(LLM) 的演变以及Java 领域的最新发展。利用 DOCUMENTS 部分的见解来指导您的回答,利用这些信息就像它是您自己的知识库一样。如果答案不清楚,最好承认信息方面的差距。文档:{documents}
让我们看一下两个主要接口及其实现。
DataIndexer.java
包 com.thevaslabs.springbootrag.service;
公共 接口 DataIndexer {
void loadData () ;
long count () ;
}
DataindexerServiceImpl.java:
包com.thevaslabs.springbootrag.service.impl;
导入com.thevaslabs.springbootrag.service.DataIndexer;
导入org.slf4j.Logger;
导入org.slf4j.LoggerFactory;
导入org.springframework.ai.document.DocumentReader;
导入org.springframework.ai.reader.ExtractedTextFormatter ;导入org.springframework.ai.reader.JsonReader;导入 org.springframework.ai.reader.TextReader;导入
org.springframework.ai.reader.pdf.PagePdfDocumentReader;
导入org.springframework.ai.reader.pdf.config.PdfDocumentReaderConfig ;导入org.springframework.ai.transformer.splitter.TokenTextSplitter
;导入
org.springframework.ai.vectorstore.VectorStore;导入org.springframework.beans.factory。注释.Value;导入org.springframework.core.io.Resource;导入org.springframework.stereotype.Service; @Service public class DataIndexerServiceImpl实现DataIndexer { @Value( "classpath:/data/InfoQ-NoSql-trend-Report.pdf" ) private Resource documentResource; private final VectorStore vectorStore; Logger logger = LoggerFactory.getLogger(DataIndexerServiceImpl. class ); public DataIndexerServiceImpl(VectorStore vectorStore) { this .vectorStore = vectorStore; } @Override public void loadData() { DocumentReader documentReader = null ; if ( this .documentResource.getFilename() != null && this .documentResource.getFilename().endsWith( ".pdf" )){ this .logger.info( "正在加载PDF文档" ); documentReader = 新的 PagePdfDocumentReader(此.documentResource, PdfDocumentReaderConfig.builder()。 withPageExtractedTextFormatter(ExtractedTextFormatter.builder()。 withNumberOfBottomTextLinesToDelete(3) 。withNumberOfTopPagesToSkipBeforeDelete(3) 。build())。 withPagesPerDocument(3)
.build());
} else if ( this .documentResource.getFilename() != null && this .documentResource.getFilename().endsWith( ".txt" )) {
documentReader = new TextReader( this .documentResource);
} else if ( this .documentResource.getFilename() != null && this .documentResource.getFilename().endsWith( ".json" )) {
documentReader = new JsonReader( this .documentResource);
}
if (documentReader != null ){
var textSplitter = new TokenTextSplitter();
this .logger.info( "将文本文档加载到 qdrant 矢量数据库" );
this .vectorStore.accept(textSplitter.apply(documentReader. get ()));
this .logger.info( "已将文本文档加载到 qdrant 矢量数据库" );
}
}
@Override
public long count() {
返回 此.vectorStore.similaritySearch( "*" ).size();
}
}
RAGService.java:
包com.thevaslabs.springbootrag.service;
公共 接口 RAGService {
字符串findAnswer (字符串查询) ;
}
RAGServiceImpl.java:
包com.thevaslabs.springbootrag.service.impl;
导入com.thevaslabs.springbootrag.service.RAGService;
导入org.slf4j.Logger;
导入org.slf4j.LoggerFactory;导入 org.springframework.ai.chat.ChatClient;
导入org.springframework.ai.chat.ChatResponse;
导入org.springframework.ai.chat.messages.Message;导入 org.springframework.ai.chat.messages.UserMessage;
导入org.springframework.ai.chat.prompt.Prompt ;导入
org.springframework.ai.chat.prompt.SystemPromptTemplate;导入org.springframework.ai.document.Document;
导入org.springframework.ai.vectorstore.VectorStore;导入org.springframework.beans.factory.annotation.Value;导入org.springframework.core.io.Resource;导入org.springframework.stereotype.Service;导入java.util.List;导入java.util.Map;导入java.util.stream.Collectors;@Service public class RAGServiceImpl实现RAGService { private final Logger logger = LoggerFactory.getLogger( this .getClass()); @Value("classpath:/prompts/system.pmt") private Resource systemPromptResource; private final VectorStore vectorStore; private final ChatClient aiClient; public RAGServiceImpl (VectorStore vectorStore,ChatClient aiClient) { this .vectorStore = vectorStore; this .aiClient = aiClient; } @Override public String findAnswer (String query) { // 将系统消息检索和 AI 模型调用合并为一个操作ChatResponse aiResponse = aiClient.call( new Prompt (List.of( getRelevantDocs(query), new UserMessage (query)))); // 仅记录必要的信息,并使用高效的字符串格式 logger.info( "询问 AI 模型并收到响应。" ); return aiResponse.getResult().getOutput().getContent(); } private Message getRelevantDocs (String query) {
List<Document> similarDocuments = vectorStore.similaritySearch(query);
// 高效记录文档数量
if (logger.isInfoEnabled()) {
logger.info( "Found {} related documents." , similarDocuments.size());
}
// 简化文档内容检索
String documents = similarDocuments.stream()
.map(Document::getContent)
.collect(Collectors.joining( "\n" ));
SystemPromptTemplate systemPromptTemplate = new SystemPromptTemplate ( this .systemPromptResource);
return systemPromptTemplate.createMessage(Map.of( "documents" , documents));
}
}
为了公开服务,我们应该有控制器,它们处理特定的操作,从将报告加载到矢量存储到回答用户的问题。
DataIndexController.java:
包com.thevaslabs.springbootrag.controller;
导入com.thevaslabs.springbootrag.service.DataIndexer;
导入 o rg.springframework.http.HttpStatus;
导入 o rg.springframework.http.ResponseEntity ;
导入org.springframework.web.bind.注释.GetMapping;
导入org.springframework.web.bind.注释.PostMapping;
导入org.springframework.web.bind.注释.RequestMapping;
导入org.springframework.web.bind.注释.RestController;
@RestController
@RequestMapping(“/ api / v1”)
公共 类 DataIndexController {
private final DataIndexer dataIndexer;
公共DataIndexController(DataIndexer dataIndexer){
this .dataIndexer = dataIndexer;
}
@PostMapping(“/ data / load”)
公共ResponseEntity <String> load(){
尝试{
this .dataIndexer.loadData();
return ResponseEntity.ok( "数据索引成功!" );
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body( "索引数据时发生错误: " + e.getMessage());
}
}
@GetMapping( "/data/count" )
public long count() {
return dataIndexer.count();
}
}
RAGController.java:
包com.thevaslabs.springbootrag.controller;
导入com.thevaslabs.springbootrag.service.RAGService;
导入org.springframework.web.bind.注释.GetMapping;
导入org.springframework.web.bind.注释.RequestMapping;
导入org.springframework.web.bind .注释.RequestParam;
导入org.springframework.web.bind.注释.RestController;
导入java.util.LinkedHashMap;
导入java.util.Map;
@RestController
@RequestMapping(“/api/v1”)
公共 类 RAGController {
私有 最终RAGService ragService;
公共RAGController(RAGService ragService){
this .ragService = ragService;
}
@GetMapping( "/ask" )
public Map findAnswer( @RequestParam(value = "question" , defaultValue = "给我趋势报告的总体概述" ) String question) {
String answer = this .ragService.findAnswer(question);
Map map = new LinkedHashMap();
map.put( "question" , question);
map.put( "answer" , answer);
return map;
}
}
就这样,现在是我们运行代码库的时候了。一旦一切正常,我们应该看到服务器输出以及服务启动的端口,如下所示。
服务器运行成功。
加载 api:数据加载成功,状态代码为 200。
回答用户提出的问题。
结论:
总之,Spring AI 深入复杂文档分析的核心,并将其与 Ollama 和 Qdrant 集成,为我们指明了未来,在这个未来中,对密集文本的理解深度和检索精度不仅仅是理想化的目标,更是切实可行的现实。Spring AI 是我们的检索器-答案生成器 (RAG) 所依赖的智能基石,它提供了一种细致入微的方法来消化和解释复杂的调查报告。它与 Ollama 的无缝协同作用使系统不仅能够检索答案,还能通过上下文感知响应生成理解,从本质上使 RAG 成为对话伙伴,而不是简单的查询工具。Qdrant 通过提供优化的向量存储进一步提升了这一过程,该存储支持 RAG 快速准确地获取最相关的文档部分。这三种技术共同代表了我们处理复杂文档的方式的一次飞跃,从单纯的文本分析过渡到真正的智能文档交互。
存中…(img-6vJFBGeu-1722244262625)]
回答用户提出的问题。
结论:
总之,Spring AI 深入复杂文档分析的核心,并将其与 Ollama 和 Qdrant 集成,为我们指明了未来,在这个未来中,对密集文本的理解深度和检索精度不仅仅是理想化的目标,更是切实可行的现实。Spring AI 是我们的检索器-答案生成器 (RAG) 所依赖的智能基石,它提供了一种细致入微的方法来消化和解释复杂的调查报告。它与 Ollama 的无缝协同作用使系统不仅能够检索答案,还能通过上下文感知响应生成理解,从本质上使 RAG 成为对话伙伴,而不是简单的查询工具。Qdrant 通过提供优化的向量存储进一步提升了这一过程,该存储支持 RAG 快速准确地获取最相关的文档部分。这三种技术共同代表了我们处理复杂文档的方式的一次飞跃,从单纯的文本分析过渡到真正的智能文档交互。
“我们来 RAG 吧”。
博客原文:专业人工智能技术社区