利用RAG将Spring AI和OpenAI GPT应用到您自己的文件中使之有用
利用 RAG,将 Spring AI 和 OpenAI GPT 应用到您自己的文件中,使之变得有用
该AIDocumentLibraryChat项目使用Spring AI项目与OpenAI一起在文档库中搜索问题的答案。为此,使用了检索增强生成(Retrieval Augmented Generation)。
检索增强生成
该过程如下:
该过程如下:
- 上传文档
- 将文档存储在Postgresql数据库中。
- 将文档拆分以创建嵌入。
- 通过调用OpenAI嵌入模型创建嵌入。
- 将文档嵌入存储在Postgresql向量数据库中。
搜索文档:
- 创建搜索提示
- 通过调用OpenAI嵌入模型创建搜索提示的嵌入。
- 查询Postgresql向量数据库以获取最近嵌入距离的文档。
- 查询Postgresql数据库以获取文档。
- 使用搜索提示和文档文本块创建提示。
- 向GPT模型请求答案,并根据搜索提示和文档文本块显示答案。
文档上传
上传的文档存储在数据库中,以便获取答案的源文档。必须将文档文本拆分为块,以创建每个块的嵌入。嵌入由OpenAI的嵌入模型创建,并且是表示文本块的超过1500个维度的向量。嵌入存储在带有块文本和源文档ID的AI文档中,该ID在向量数据库中。
文档搜索
文档搜索使用搜索提示并使用OpenAI嵌入模型将其转换为嵌入。该嵌入用于在向量数据库中搜索最近邻居向量。这意味着搜索提示和具有最大相似性的文本块的嵌入。在AIDocument中的ID用于读取关系数据库中的文档。使用搜索提示和AIDocument的文本块,创建文档提示。然后,调用OpenAI GPT模型以使用提示基于搜索提示和文档上下文创建答案。这会导致模型根据提供的文档创建更接近的答案,并提高准确性。 GPT模型的答案被返回并显示,并附带文档的链接以提供答案的来源。
架构
该项目的架构基于Spring Boot与Spring AI。Angular UI提供用户界面以显示文档列表、上传文档并提供带答案和源文档的搜索提示。它通过REST接口与Spring Boot后端通信。Spring Boot后端为前端提供REST控制器,并使用Spring AI与OpenAI模型和Postgresql Vector数据库通信。文档使用Jpa存储在Postgresql关系数据库中。选择使用Postgresql数据库是因为它将关系数据库和向量数据库结合在一个Docker镜像中。
实施
前端
前端基于使用Angular构建的惰性加载的独立组件。惰性加载的独立组件在app.config.ts中进行配置:
该配置设置路由并启用http客户端和动画。
惰性加载的路由在app.routes.ts中定义:
在’loadChildren’中,’import(“…”).then((mod) => mod.XXX)’惰性地从提供的路径加载路由,并设置在’mod.XXX’常量中定义的导出路由。
惰性加载路由’docsearch’具有index.ts导出常量:
通过此导出,doc-search.routes.ts:
它定义了到 ‘DocSearchComponent’ 的路由。
文件上传可以在DocImportComponent中找到,其模板为doc-import.component.html:
文件上传是通过 ‘<input type=”file” (change)=”onFileInputChange($event)”>’ 标签完成的。它提供了上传功能,并在每次上传后调用 ‘onFileInputChange(…)’ 方法。
‘上传’按钮在点击时调用 ‘upload()’ 方法将文件发送到服务器。
doc-import.component.ts 中有模板的方法:
这是一个独立的组件,它导入了自己的模块并注入了 ‘DestroyRef’。
‘onFileInputChange(…)’ 方法接受事件参数,并将其 ‘files’ 属性存储在 ‘files’ 常量中。然后它检查第一个文件并将其存储在 ‘file’ 组件属性中。
‘upload()’ 方法检查 ‘file’ 属性,并创建用于文件上传的 ‘FormData()’。’formData’ 常量包含数据类型(’file’)、内容(’this.file’)和文件名(’this.file.name’)。然后使用 ‘documentService’ 将 ‘FormData()’ 对象发送到服务器。’takeUntilDestroyed(this.destroyRef)’ 函数在组件销毁后取消订阅 Rxjs 流。这使得在 Angular 中取消订阅流非常方便。
后端
后端是一个使用 Spring AI 框架的 Spring Boot 应用程序。Spring AI 管理对 OpenAI 模型和向量数据库请求的请求。
Liquibase 数据库设置
数据库设置使用 Liquibase 进行,脚本可以在db.changelog-1.xml中找到:
在 changeset 4 中,使用主键 ‘id’ 创建了 Jpa 文档实体的表。由于内容类型/大小未知,因此设置为 ‘blob’。在 changeset 5 中,创建了 Jpa 实体的序列,该序列具有 Hibernate 6 默认属性,Spring Boot 3.x 使用这些属性。
在 changeset 6 中,创建了名为 ‘vector_store’ 的表,它具有由 ‘uuid-ossp’ 扩展创建的 ‘id’ 主键。’content’ 列的类型为 ‘text’(在其他数据库中为 ‘clob’),可具有灵活的大小。’metadata’ 列以 ‘json’ 类型存储 AIDocuments 中的元数据。’embedding’ 列存储具有 OpenAI 维度数的嵌入向量。
在 changeset 7 中,设置了用于快速搜索 ’embeddings’ 列的索引。由于 Liquibase ‘<createIndex …>’ 的参数有限,因此直接使用 ‘<sql>’ 创建它。
Spring Boot / Spring AI 实现
前端的DocumentController如下所示:
‘handleDocumentUpload(…)’ 使用 ‘documentService’ 处理位于 ‘/rest/document/upload’ 路径上的上传文件。
‘getDocumentList()’ 处理获取文档列表的请求,并删除文档内容以减小响应大小。
‘getDocumentContent(…)’ 处理获取文档内容的请求。它使用 ‘documentService’ 加载文档,并将 ‘DocumentType’ 映射到 ‘MediaType’。然后返回内容和内容类型,浏览器根据内容类型打开内容。
‘postDocumentSearch(…)’ 方法将请求内容放入 ‘SearchDto’ 对象中,并返回 ‘documentService.queryDocuments(…)’ 调用的 AI 生成结果。
DocumentService 的 ‘storeDocument(…)’ 方法如下:
‘storeDocument(…)’ 方法将文档保存到关系数据库中。然后,该文档被转换为 ‘ByteArrayResource’ 并使用 Spring AI 的 ‘TikaDocumentReader’ 读取,将其转换为 AIDocument 列表。然后,AIDocument 列表被映射为使用 ‘splitToTokenLimit(…)’ 方法拆分为具有储存文档的 Metadata 映射中的 ‘id’ 的新 AIDocuments 的文档块。 Metadata 中的 ‘id’ 可以用于加载与 AIDocuments 匹配的文档实体。然后隐式调用 ‘documentVsRepository.add(…)’ 方法来创建 AIDocuments 的嵌入,该方法调用 OpenAI Embedding 模型并将 AIDocuments 与嵌入一起存储在矢量数据库中。然后返回结果。
‘queryDocument(…)’ 方法如下:
该方法首先从矢量数据库中加载与 ‘searchDto.getSearchString()’ 最匹配的文档。为此,调用 OpenAI Embedding 模型将搜索字符串转换为嵌入,并使用该嵌入查询矢量数据库以获取具有最低距离(搜索嵌入和数据库嵌入之间的距离)的 AIDocuments。然后将具有最低距离的 AIDocument 存储在 ‘mostSimilar’ 变量中。然后通过匹配它们 Metadata ‘id’ 的文档实体 id 来收集文档块的所有 AIDocuments。使用 ‘documentChunks’ 或 ‘mostSimilar’ 内容创建 ‘systemMessage’。使用 ‘getSystemMessage(…)’ 方法将其作为 OpenAI GPT 模型可以处理的大小剪切 contentChunks,并返回 ‘Message’。然后将 ‘systemMessage’ 和 ‘userMessage’ 转换为 ‘prompt’,并使用 ‘aiClient.generate(prompt)’ 发送给 OpenAi GPT 模型。之后 AI 的回答就可用了,并且使用 ‘mostSimilar’ AIDocument 的元数据的 id 加载文档实体。使用搜索字符串、GPT 回答、文档实体创建 ‘AiResult’ 并返回。
带有 Spring AI ‘VectorStore’ 的矢量数据库存储库 DocumentVsRepositoryBean 如下所示:
存储库具有用于访问矢量数据库的 ‘vectorStore’ 属性。它在构造函数中通过注入参数使用 ‘new PgVectorStore(…)’ 调用来创建。PgVectorStore 类被提供为Postgresql 矢量数据库扩展程序。它具有用于使用 OpenAI Embedding 模型的 ’embeddingClient’ 和用于访问数据库的 ‘jdbcTemplate’。
‘add(…)’ 方法调用 OpenAI Embedding 模型并将 AIDocuments 添加到矢量数据库中。
‘retrieve(…)’ 方法查询具有最低距离的嵌入的矢量数据库。
结论
Angular 让前端的创建变得容易。使用惰性加载的独立组件使初始加载变得很小。Angular Material 组件对实现有很大帮助,并且易于使用。
Spring Boot与Spring AI使得使用大型语言模型变得容易。Spring AI 提供了一个框架来隐藏嵌入的创建,并提供了一个易于使用的接口来将 AIDocuments 存储在矢量数据库中(支持多个)。为了加载最近的 AIDocuments,搜索提示的嵌入创建也是自动完成的,矢量数据库接口很简单。Spring AI 的提示类还使得为 OpenAI GPT 模型创建提示很简单。使用注入的 ‘aiClient’ 调用模型,并返回结果。
Spring AI 是Spring团队的一个非常好的框架。在实验版本中没有出现任何问题。
使用 Spring AI,我们可以轻松地在自己的文档上使用大型语言模型。