Spring AI(11) :ChatPDF-向量数据库、PDF处理、向量写入和向量搜索

本章代码已分享至Gitee:https://gitee.com/lengcz/ai-study.git

文章目录

向量数据库

向量数据库的核心作用是‌实现语义层面的相似性检索‌,区别于传统数据库的字面精确匹配,能快速找到"含义最贴近"的数据,是AI时代的关键基础设施。

核心基础能力‌

它可将文本、图片、音视频等非结构化数据转化为高维向量,通过余弦相似度、欧氏距离计算向量间的接近程度,完成高效的相似性匹配检索,支持亿级规模向量的毫秒级查询。

主流落地场景‌

支撑RAG检索增强生成,为大模型对接外部知识库,大幅缓解大模型"幻觉"问题,提升回答的准确性和时效性。

应用于推荐系统,基于用户兴趣向量实现精准的内容、商品匹配推送。

落地以图搜图、语音/视频内容检索、异常检测等场景,完成多模态数据的快速相似查找。

Spring AI 支持的向量数据库有哪些

官网文档地址: https://docs.spring.io/spring-ai/reference/1.0/api/vectordbs.html

VectorStore 的实现,Spring AI支持以下向量数据库

数据库名称 官方描述 数据库类型 适用场景 / 特点
Azure Vector Search The Azure vector store. 云服务(全托管) 深度集成Azure生态,适合微软云用户
Apache Cassandra The Apache Cassandra vector store. NoSQL(宽列) 高可用、大规模分布式写入,适合海量数据
Chroma The Chroma vector store. 专用向量数据库 轻量级、开源,适合本地开发和原型验证
Elasticsearch The Elasticsearch vector store. 搜索引擎 已有ES集群,需同时支持全文检索+向量检索
GemFire The GemFire vector store. 企业级内存数据库 VMware生态,高性能缓存与实时计算场景
MariaDB The MariaDB vector store. 关系型 (SQL) 基于MySQL分支,适合小型到中型数据量
Milvus The Milvus vector store. 专用向量数据库 高性能、分布式,适合大规模生产环境
MongoDB Atlas The MongoDB Atlas vector store. NoSQL(文档) JSON文档存储与向量检索结合,灵活易用
Neo4j The Neo4j vector store. NoSQL(图数据库) 需同时处理知识图谱/关系网络与向量检索
OpenSearch The OpenSearch vector store. 搜索引擎 AWS开源版ES,适合云上与全文检索融合
Oracle The Oracle Database vector store. 关系型 (SQL) 企业级重型数据库,适合已有Oracle栈的客户
PgVector The PostgreSQL/PGVector vector store. 关系型 (SQL) Postgres扩展,最适合已用PG的技术栈
Pinecone The PineCone vector store. 专用向量数据库(云原生) 全托管、低延迟,适合SaaS和Serverless应用
Qdrant The Qdrant vector store. 专用向量数据库 高性能、Rust编写,支持过滤和Payload丰富
Redis The Redis vector store. NoSQL(键值/缓存) 实时性要求极高,需结合缓存与向量检索
SAP Hana The SAP HANA vector store. 企业级内存列式数据库 已有SAP生态,需进行实时分析和向量融合
Typesense The Typesense vector store. 搜索引擎 轻量、易用,追求开箱即用的搜索体验
Weaviate The Weaviate vector store. 专用向量数据库 内置GraphQL,支持混合搜索和自动向量化
SimpleVectorStore A simple implementation... good only for testing. 内存级测试实现 仅限开发/单元测试,不适用于生产

Spring AI 中如何使用向量数据呢(以redis stack为例)

注意:这里的redis 不是指redis缓存服务器,是指redis stack。

官网文档地址: https://docs.spring.io/spring-ai/reference/1.0/api/vectordbs/redis.html

安装redis stack

这里为了方便,提供docker命令安装

bash 复制代码
docker run -d --name redis-stack -p 6379:6379 -p 8001:8001 redis/redis-stack:latest

引入依赖

xml 复制代码
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-vector-store-redis</artifactId>
</dependency>

配置文件

yaml 复制代码
spring:
  data:
    redis:
      url: 127.0.0.1:6379
  ai:
    vectorstore:
      redis:
        initialize-schema: true #向量库索引名
        index-name: my_doc_ai_index #是否初始化向量向量索引结构
        prefix: "doc:" #向量库key浅醉

如何使用

Spring AI已经为所有向量数据库统一了一套接口,都实现了VectorStore,各向量数据库除了依赖和配置有差异外,使用方式都相同。

以下是 VectorStore接口的定义

java 复制代码
/**
 * 向量存储接口,用于管理文档的向量化表示及其持久化。
 * 提供添加、删除以及基于向量相似度的检索能力。
 * 继承自 {@link DocumentWriter},支持文档写入操作。
 */
public interface VectorStore extends DocumentWriter {

    /**
     * 获取当前向量存储实例的名称。
     * 默认实现返回类的简单名称(不含包路径)。
     *
     * @return 存储实例的名称,默认使用类名
     */
    default String getName() {
        return this.getClass().getSimpleName();
    }

    /**
     * 批量添加文档到向量存储中。
     * 实现类需将文档内容转换为向量表示并存储,同时保存元数据以便后续检索。
     *
     * @param documents 要添加的文档列表,不能为空
     */
    void add(List<Document> documents);

    /**
     * 根据文档 ID 列表批量删除文档。
     *
     * @param idList 要删除的文档 ID 列表
     */
    void delete(List<String> idList);

    /**
     * 根据过滤表达式删除符合条件的文档。
     * 使用结构化过滤表达式(如字段比较、逻辑组合)进行条件删除。
     *
     * @param filterExpression 过滤表达式,定义删除条件
     */
    void delete(Filter.Expression filterExpression);

    /**
     * 根据字符串形式的过滤表达式删除文档(默认实现)。
     * 提供一种便捷的字符串表达式解析方式,具体语法由子类实现。
     * 默认实现抛出 {@link UnsupportedOperationException},建议子类覆写。
     *
     * @param filterExpression 字符串形式的过滤表达式
     */
    default void delete(String filterExpression) {
        // 默认实现为空或抛出异常,这里以省略号表示原有逻辑
        throw new UnsupportedOperationException("String filter expression not supported by default.");
    }

    /**
     * 执行基于向量相似度的搜索,使用默认的查询向量(由 query 字符串生成)。
     * 返回与查询最相似的文档列表,通常按相似度降序排列。
     *
     * @param query 查询文本,将自动转换为向量
     * @return 与查询相似的文档列表
     */
    List<Document> similaritySearch(String query);

    /**
     * 执行基于向量相似度的搜索,支持丰富的搜索参数。
     * 可以通过 {@link SearchRequest} 指定查询向量、返回数量、过滤条件、相似度阈值等。
     *
     * @param request 搜索请求对象,封装所有检索参数
     * @return 符合搜索条件的文档列表
     */
    List<Document> similaritySearch(SearchRequest request);

    /**
     * 获取底层原生客户端的可选实例。
     * 当向量存储基于某个第三方库(如 Elasticsearch、Pinecone 等)实现时,
     * 可通过此方法暴露原始客户端,以便进行高级操作。
     * 默认返回 {@link Optional#empty()},表示不支持或无需暴露。
     *
     * @param <T> 原生客户端类型
     * @return 包含原生客户端的 Optional,若不存在则为空
     */
    default <T> Optional<T> getNativeClient() {
        return Optional.empty();
    }
}

配置SimpleVectorStore(简单的内存向量模型)

SimpleVectorStore 仅用于学习,请勿用于生产和测试

引入依赖

xml 复制代码
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-vector-store</artifactId>
            <version>${spring-ai.version}</version> <!-- 请使用实际的 Spring AI 版本 -->
        </dependency>

配置SimpleVectorStore

java 复制代码
    @Bean
    public VectorStore vectorStore(OpenAiEmbeddingModel model){
        return SimpleVectorStore.builder(model).build();
    }

文档处理和向量写入

读取完成文档拆分

这里文档需要经过读取,转换,写入 三个操作,实现文档写入到向量数据库。

这个Document有什么组成?

Document的组成

  • id: 文档的id
  • content: 内容
  • metadata: 元数据,文件名,文件大小,创建时间,修改时间等
  • Media: 媒体,就是音频,视频等文件

而针对Document Reader 文档读取过程,spring-ai已经提供了很多相关的Reader支持不同文件类型的读取解析。

  • JSON
  • Text
  • Markdown
  • PDF Page
  • PDF Paragraph
  • Tika(DOCX,PPT,HTML...)

PDF Reader(示例)

其他Document Reader 请参考官网文档:https://docs.spring.io/spring-ai/reference/1.0/api/etl-pipeline.html#page-title

spring ai 提供了针对不同文件类型的Document Reader,根据不同文档类型,引入不同的依赖

PDF 这里引入的依赖

xml 复制代码
  <!--Document Reader 针对Pdf 的reader-->
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-pdf-document-reader</artifactId>
        </dependency>

读取并写入到向量数据库

java 复制代码
@Test
    void writeFileToVectorStore(){
        // 1. 准备 PDF 资源(从 classpath 加载)
        Resource pdfResource = new ClassPathResource("40、狂神说Docker.pdf");

        // 2. 配置 PDF 读取器(可选)
        PdfDocumentReaderConfig config = PdfDocumentReaderConfig.builder()
                .withPageExtractedTextFormatter(ExtractedTextFormatter.defaults())   // 每页最大词数
                .withPagesPerDocument(1) //每1页作为一个Document
                .build();

        // 3. 创建 PagePdfDocumentReader 实例
        PagePdfDocumentReader pdfReader = new PagePdfDocumentReader(pdfResource, config);

        // 4. 读取 PDF 文档,返回 Document 列表
        List<org.springframework.ai.document.Document> documents = pdfReader.get();
        // 或使用 pdfReader.read()

        // 5. 验证读取结果
        System.out.println("读取到 " + documents.size() + " 页文档");

        // 7. 写入向量数据库
        vectorStore.add(documents);

        // 8. 验证写入结果
        System.out.println("成功写入 " + documents.size() + " 个文档块到向量数据库");

        List<Document> result = vectorStore.similaritySearch("谁创办了Docker");
        if(null==result){
            System.out.println("没有找到结果");
            return;
        }
        System.out.println("搜索结果");
        result.forEach(c->{
            System.out.println(c.getId());
            System.out.println(c.getScore());
            System.out.println(c.getText());
        });
    }

测试用例运行结果

PDF 除了按页读取的PagePdfDocumentReader ,还有ParagraphPdfDocumentReader 按大纲读取,但是很多PDF文件不规范,没有大纲标题,会导致读取失败。

向量搜索

按照文本的向量搜索

java 复制代码
List<Document> result = vectorStore.similaritySearch("谁创办了Docker");
        if(null==result){
            System.out.println("没有找到结果");
            return;
        }
        System.out.println("搜索结果");
        result.forEach(c->{
            System.out.println(c.getId());
            System.out.println(c.getScore());
            System.out.println(c.getText());
        });

这里搜出了4页,但是实际上一些结果并不相关,我们并不想要关联度不是很高的结果。

增加配置的向量搜索

> 使用vectorStore,我们已经配置了向量模型,故在写向量和搜索向量时,并不需要我们显式的进行向量化操作,而是由其内部完成向量化转换。

java 复制代码
 // 8. 验证写入结果
        System.out.println("成功写入 " + documents.size() + " 个文档块到向量数据库");
        SearchRequest request = SearchRequest.builder()
                .query("谁创办了Docker") //匹配关键词
                .topK(2) //相似度最高的前几名
                .similarityThreshold(0.7) //相似度阈值
                .filterExpression("file_name =='40、狂神说Docker.pdf'") //过滤项目,表示搜索元数据里面文件名是40、狂神说Docker.pdf的文档
                .build();

//        List<Document> result = vectorStore.similaritySearch("谁创办了Docker");
        List<Document> result = vectorStore.similaritySearch(request);
        if(null==result){
            System.out.println("没有找到结果");
            return;
        }
        System.out.println("搜索结果");
        result.forEach(c->{
            System.out.println(c.getId());
            System.out.println(c.getScore());
            System.out.println(c.getText());
        });

经过这些过滤条件,我们可以看到只搜索到一条记录。

相关推荐
Wang's Blog1 小时前
AI Agent白手起家44: LangChain 文档切分实战 — 长度、文本架构与语义切片
人工智能·langchain
暗暗别做白日梦1 小时前
CompletableFuture 并发统计
网络·数据库·oracle
only-qi1 小时前
大模型微调流程深度解析:从面试题到工程实践
人工智能·机器学习·ai·llm
ClouGence1 小时前
告别 Navicat!CloudDM:开源免费一站式数据库开发管理工具
数据库·sql·开源·数据库开发
热心网友俣先生1 小时前
2026年华数杯C 题 超详细解题思路
c语言·开发语言·人工智能
灵析表格1 小时前
灵析表格功能函数深度分析报告
前端·数据库·microsoft
RSTJ_16251 小时前
PYTHON+AI LLM DAY ONE HUNDRED AND THIRTY
人工智能
jaboo121 小时前
postgresql从入门到精通
数据库·postgresql·langchain
晴天¥1 小时前
记一次Oracle集群归档日志爆满之后如何处理(涉及ASM磁盘的增添。有坑必踩)
数据库·oracle