PGvector :在 Spring AI 中实现向量数据库存储与相似性搜索

PGvector :在 Spring AI 中实现向量数据库存储与相似性搜索

一、PGvector 概述与核心价值

1.1 什么是 PGvector

PGvector 是 PostgreSQL 的开源扩展,专为向量相似性搜索而设计。它允许开发者在 PostgreSQL 数据库中存储和搜索机器学习生成的嵌入(embeddings),支持精确和近似最近邻搜索。

💡 为什么选择 PGvector?

  • 无缝集成:作为 PostgreSQL 扩展,与现有数据库生态系统无缝协作
  • ACID 合规:保持 PostgreSQL 的事务完整性
  • 功能丰富:支持多种距离度量和索引类型
  • 高性能:针对大规模向量搜索进行优化
  • 易用性:提供标准 SQL 接口,无需学习新查询语言

1.2 PGvector 的关键特性

特性 描述 优势
向量存储 支持多种向量类型(vector, halfvec, bit, sparsevec 适应不同精度和内存需求
距离度量 L2、内积、余弦距离、L1、汉明距离、杰卡德距离 适用于不同场景的相似性度量
索引类型 HNSW、IVFFlat 平衡查询速度和召回率
元数据过滤 基于 JSON 的元数据过滤 精确控制检索结果
混合搜索 结合向量搜索和文本搜索 提高检索相关性

1.3 PGvector 与 Spring AI 的集成

Spring AI 通过 spring-ai-starter-vector-store-pgvector 提供了 PGvector 的开箱即用支持,使开发者能够轻松地将向量数据库集成到 RAG(检索增强生成)应用中。

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

二、PGvector 安装与配置

2.1 环境准备

2.1.1 前提条件

在使用 PGvector 之前,需要确保 PostgreSQL 实例已启用以下扩展:

sql 复制代码
CREATE EXTENSION IF NOT EXISTS vector;
CREATE EXTENSION IF NOT EXISTS hstore;
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";

如果没有参考 ✅ 宝塔 PostgreSQL 安装 contrib 扩展完整指南

2.2 手动创建向量存储表

在数据库中创建向量存储表(推荐在应用启动时由 Spring AI 自动创建):

sql 复制代码
CREATE TABLE IF NOT EXISTS vector_store (
    id uuid DEFAULT uuid_generate_v4() PRIMARY KEY,
    content text,
    metadata json,
    embedding vector(1536)  -- 1536 是默认的嵌入维度
);

CREATE INDEX IF NOT EXISTS vector_index 
ON vector_store USING hnsw (embedding vector_cosine_ops);

💡 重要提示 :如果使用不同的嵌入维度,请将 1536 替换为实际的嵌入维度。PGvector 对 HNSW 索引最多支持 2000 个维度。


三、Spring AI 集成 PGvector

3.1 依赖配置

在 Maven 项目中添加 PGvector 向量存储依赖:

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

3.2 自动配置

Spring AI 提供了自动配置功能,通过 application.yml 配置 PGvector:

yaml 复制代码
spring:
  datasource:
    url: jdbc:postgresql://localhost:5432/postgres
    username: postgres
    password: postgres
  ai:
   vectorstore:
      pgvector:
        index-type: HNSW
        distance-type: COSINE_DISTANCE
        dimensions: 1536
        max-document-batch-size: 10000 

💡 关键配置说明

  • initialize-schema: true:在应用启动时自动创建所需的数据库表
  • dimensions:嵌入维度(如果未指定,将从 EmbeddingModel 获取)
  • index-type:索引类型(HNSW、IVFFlat、NONE)
  • distance-type:距离类型(COSINE_DISTANCE、EUCLIDEAN_DISTANCE、NEGATIVE_INNER_PRODUCT)

3.3 手动配置(高级用法)

不使用 Spring Boot 自动配置时,可以手动创建 PgVectorStore

java 复制代码
@Configuration
public class PgVectorConfig {
    
    @Bean
    public VectorStore vectorStore(JdbcTemplate jdbcTemplate, EmbeddingModel embeddingModel) {
        return PgVectorStore.builder(jdbcTemplate, embeddingModel)
            .dimensions(1536)  // 默认为模型维度
            .distanceType(COSINE_DISTANCE)
            .indexType(HNSW)
            .initializeSchema(true)  // 启用自动表创建
            .schemaName("public")  
            .vectorTableName("vector_store")  // 哪一个数据库
            .maxDocumentBatchSize(10000)
            .build();
    }
}

3.4 依赖项要求

手动配置时,需要添加以下依赖:

xml 复制代码
<!-- Spring Boot JDBC 支持 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>

<!-- PostgreSQL JDBC 驱动 -->
<dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
    <scope>runtime</scope>
</dependency>

<!-- Spring AI PGvector Store -->
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-pgvector-store</artifactId>
</dependency>

四、向量存储操作实践

4.1 存储文档

java 复制代码
 @Resource
    @Qualifier("pgVectorStore")
    private VectorStore vectorStore;

    /**
     * 文档嵌入与存储
     */
    public void storeDocuments() {
        // 4.1.1 创建文档对象
        List<Document> documents = List.of(
                new Document(
                        "Spring AI rocks!! Spring AI rocks!! Spring AI rocks!! Spring AI rocks!! Spring AI rocks!!",
                       Map.of("author", "john", "article_type", "blog", "meta1", "meta1")
                ),
                new Document(
                        "The World is Big and Salvation Lurks Around the Corner",
                        Map.of("author", "jill", "article_type", "news", "category", "philosophy")
                ),
                new Document(
                        "You walk forward facing the past and you turn back toward the future.",
                        Map.of("author", "john", "article_type", "blog", "meta2", "meta2", "category", "philosophy")
                ),
                new Document(
                        "Spring Framework is a powerful Java framework for building enterprise applications.",
                        Map.of("author", "alice", "article_type", "tutorial", "category", "programming")
                ),
                new Document(
                        "Artificial Intelligence is transforming the tech industry.",
                        Map.of("author", "bob", "article_type", "article", "category", "ai")
                )
        );

        // 4.1.2 存储文档到向量存储
        System.out.println("开始存储文档到向量存储...");
        vectorStore.add(documents);
        System.out.println("文档存储完成!");

        // 验证存储
        List<Document> results = vectorStore.similaritySearch(
                SearchRequest.builder()
                        .query("Spring")
                        .topK(5)
                        .build()
        );

        System.out.println("\n存储验证 - 检索结果:");
        results.forEach(doc -> {
            System.out.println("内容: " + Objects.requireNonNull(doc.getText()).substring(0, Math.min(50, doc.getText().length())) + "...");
            System.out.println("元数据: " + doc.getMetadata());
            System.out.println("---");
        });
    }


4.2 基本相似性搜索

java 复制代码
/**
     *  基本相似性搜索
     */
    public List<Document> basicSimilaritySearch() {
        System.out.println("\n执行基本相似性搜索...");
        SearchRequest searchRequest = SearchRequest.builder()
                .query("Spring")
                .topK(3)
                .similarityThreshold(0.5) // 相似度阈值(0-1之间)
                .build();

        List<Document> results = vectorStore.similaritySearch(searchRequest);

        System.out.println("基本搜索 - 查询 'Spring' 的结果:");
        results.forEach(doc -> {
            System.out.println("内容: " + doc.getText());
            System.out.println("元数据: " + doc.getMetadata());
            System.out.println("相似度: " + doc.getMetadata().get("distance"));
            System.out.println("---");
        });

        return results;
    }

4.3 使用元数据过滤 - 文本表达式

java 复制代码
 /**
     * 使用元数据过滤 - 文本表达式
     */
    public List<Document> searchWithMetadataFilter() {
        System.out.println("\n使用元数据过滤搜索...");


        SearchRequest searchRequest = SearchRequest.builder()
                .query("technology")
                .topK(5)
                .similarityThreshold(0.3)
                // 注意:具体过滤表达式语法取决于向量存储实现
                // 这里使用 Spring AI 的标准过滤语法
                .filterExpression("author in ['john', 'jill'] && article_type == 'blog'")
                .build();
        // 方法1: 使用文本表达式语言进行过滤
        List<Document> results = vectorStore.similaritySearch(searchRequest);

        System.out.println("元数据过滤搜索 - 文本表达式结果:");
        results.forEach(doc -> {
            System.out.println("内容: " + doc.getText());
            System.out.println("元数据: " + doc.getMetadata());
            System.out.println("---");
        });

        return results;
    }

4.4 使用元数据过滤 - Filter.Expression DSL

java 复制代码
public List<Document> searchWithProgrammaticFilter() {
        System.out.println("\n使用编程式元数据过滤搜索...");

        // 方法2: 使用 Filter.Expression DSL 进行编程式过滤
        FilterExpressionBuilder b = new FilterExpressionBuilder();

        List<Document> results = vectorStore.similaritySearch(
                SearchRequest.builder()
                        .query("technology")
                        .topK(5)
                        .similarityThreshold(0.3)
                        .filterExpression(b.and(
                                b.in("author", "john", "jill"),
                                b.eq("article_type", "blog")
                        ).build())
                        .build()
        );

        System.out.println("编程式过滤搜索结果:");
        results.forEach(doc -> {
            System.out.println("内容: " + doc.getText());
            System.out.println("元数据: " + doc.getMetadata());
            System.out.println("---");
        });

        return results;
    }

4.5 高级搜索示例:组合查询和过滤

java 复制代码
  public List<Document> advancedSearchExample() {
        System.out.println("\n高级搜索示例...");

        // 创建复杂过滤条件
        FilterExpressionBuilder builder = new FilterExpressionBuilder();

        // (author == 'john' || author == 'jill') && category == 'philosophy'
        Filter.Expression filter = builder.and(
                builder.or(
                        builder.eq("author", "john"),
                        builder.eq("author", "jill")
                ),
                builder.eq("category", "philosophy")
        ).build();

        List<Document> results = vectorStore.similaritySearch(
                SearchRequest.builder()
                        .query("life and future")
                        .topK(10)
                        .similarityThreshold(0.0)
                        .filterExpression(filter)
                        .build()
        );

        System.out.println("高级搜索结果:");
        results.forEach(doc -> {
            System.out.println("内容: " + doc.getText());
            System.out.println("元数据: " + doc.getMetadata());
            System.out.println("---");
        });

        return results;
    }

4.6 删除特定文档(根据元数据)

java 复制代码
 public void deleteDocumentsByMetadata() {
        System.out.println("\n删除 author 为 'john' 的文档...");

        // 注意:删除功能可能因向量存储实现而异
        // PGvector 通常支持根据元数据过滤删除

        // 先搜索要删除的文档
        FilterExpressionBuilder builder = new FilterExpressionBuilder();
        Filter.Expression filter = builder.eq("author", "john").build();

        List<Document> docsToDelete = vectorStore.similaritySearch(
                SearchRequest.builder()
                        .query("")
                        .topK(100)
                        .filterExpression(filter)
                        .build()
        );

        if (!docsToDelete.isEmpty()) {
            // 提取文档ID(PGvector中id在metadata中)
            List<String> ids = docsToDelete.stream()
                    .map(Document::getId)
                    .filter(Objects::nonNull)
                    .toList();

            System.out.println("找到 " + ids.size() + " 个要删除的文档");

            // 删除文档(具体方法取决于VectorStore实现)
             vectorStore.delete(ids);
        }
    }

五、PGvector 配置参数详解

5.1 关键配置属性

属性 描述 默认值 适用场景
index-type 最近邻搜索索引类型 HNSW 需要高性能检索时
distance-type 搜索距离类型 COSINE_DISTANCE 向量已归一化时
dimensions 嵌入维度 从 EmbeddingModel 获取 与嵌入模型一致
remove-existing-vector-store-table 启动时删除现有表 false 重置数据库时
initialize-schema 是否初始化 schema false 首次使用时
schema-name 向量存储 schema 名称 public 多 schema 环境
table-name 向量存储表名称 vector_store 自定义表名
schema-validation 启用 schema 和表名验证 false 安全敏感环境
max-document-batch-size 单批处理的最大文档数 10000 大批量数据导入

5.2 索引类型对比

索引类型 构建时间 查询性能 内存使用 适用场景
HNSW 较慢 优秀 较高 高性能要求,数据量大
IVFFlat 一般 较低 数据量小,内存有限
NONE 一般 测试环境,小数据量

💡 HNSW 与 IVFFlat 选择建议

  • 对于 100K+ 向量,选择 HNSW
  • 对于 10K-100K 向量,选择 IVFFlat
  • 对于 <10K 向量,使用 NONE

5.3 距离类型选择

距离类型 适用场景 性能 说明
COSINE_DISTANCE 向量已归一化 优秀 适用于大多数嵌入模型
EUCLIDEAN_DISTANCE 向量未归一化 一般 需要精确距离
NEGATIVE_INNER_PRODUCT 向量已归一化 优秀 与 COSINE_DISTANCE 等效

💡 重要提示:如果向量已经归一化到长度 1(如 OpenAI 嵌入),使用 COSINE_DISTANCE 或 NEGATIVE_INNER_PRODUCT 能获得最佳性能。

六、高级用法与最佳实践

6.1 性能优化技巧

6.1.1 索引优化
java 复制代码
// HNSW 索引优化参数
CREATE INDEX ON vector_store USING hnsw (embedding vector_cosine_ops) 
WITH (m = 16, ef_construction = 64);
  • m:每层的最大连接数(默认 16)
  • ef_construction:构建图时的动态候选列表大小(默认 64)

💡 优化建议 :对于高召回率要求,提高 ef_construction;对于快速构建,降低它。

6.1.2 查询优化
java 复制代码
// 设置查询时的动态候选列表大小
SET hnsw.ef_search = 100;
  • ef_search:查询时的动态候选列表大小(默认 40)
  • 值越大,召回率越高,但速度越慢

💡 优化建议 :对于关键查询,设置 ef_search=100;对于普通查询,保持默认。

6.1.3 批量处理优化
java 复制代码
// 批量添加文档(使用最大批次大小)
vectorStore.add(documents, 10000); // 10000 是 maxDocumentBatchSize

💡 优化建议 :使用 maxDocumentBatchSize 配置值作为批量大小,避免内存溢出。

6.2 安全最佳实践

6.2.1 数据库权限管理
sql 复制代码
-- 为应用用户创建最小权限
CREATE ROLE app_user;
GRANT CONNECT ON DATABASE postgres TO app_user;
GRANT USAGE ON SCHEMA public TO app_user;
GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE vector_store TO app_user;
6.2.2 元数据过滤安全
java 复制代码
// 在应用层验证过滤表达式
if (isValidFilterExpression(filterExpression)) {
    // 执行查询
}

💡 安全建议:不要直接使用用户输入的过滤表达式,进行验证和清理。

6.2.3 启用 schema 验证
yaml 复制代码
spring:
  ai:
    vectorstore:
      pgvector:
        schema-validation: true

💡 安全建议 :在生产环境中始终启用 schema-validation,防止 SQL 注入。

6.3 与 RAG 流程集成

6.3.1 使用 QuestionAnswerAdvisor
java 复制代码
@Bean
public Advisor questionAnswerAdvisor(VectorStore vectorStore) {
    return QuestionAnswerAdvisor.builder(vectorStore)
        .searchRequest(SearchRequest.builder()
            .similarityThreshold(0.7)
            .topK(5)
            .build())
        .build();
}
6.3.2 使用 RetrievalAugmentationAdvisor
java 复制代码
@Bean
public Advisor retrievalAugmentationAdvisor(VectorStore vectorStore) {
    return RetrievalAugmentationAdvisor.builder()
        .documentRetriever(VectorStoreDocumentRetriever.builder()
            .vectorStore(vectorStore)
            .similarityThreshold(0.7)
            .topK(5)
            .build())
        .queryAugmenter(ContextualQueryAugmenter.builder()
            .allowEmptyContext(true)
            .build())
        .build();
}
6.3.3 RAG 流程工作流程
  1. 用户输入:用户提出问题
  2. 查询处理:使用 QueryTransformer 优化查询
  3. 文档检索:使用 VectorStoreDocumentRetriever 检索相关文档
  4. 上下文增强:使用 ContextualQueryAugmenter 增强查询
  5. 生成响应:使用 ChatModel 生成最终响应

七、常见问题与解决方案

7.1 问题:查询性能差

可能原因

  • 没有使用合适的索引
  • 索引参数未优化

解决方案

  1. 确保已创建 HNSW 或 IVFFlat 索引

  2. 优化索引参数:

    sql 复制代码
    CREATE INDEX ON vector_store USING hnsw (embedding vector_cosine_ops) 
    WITH (m = 16, ef_construction = 64);
  3. 设置查询参数:

    sql 复制代码
    SET hnsw.ef_search = 100;

7.2 问题:向量维度不匹配

可能原因

  • 数据库表中的嵌入维度与 EmbeddingModel 不一致

解决方案

  1. 确认 EmbeddingModel 的维度

  2. 在数据库表中设置正确的维度:

    sql 复制代码
    CREATE TABLE vector_store (
        id uuid DEFAULT uuid_generate_v4() PRIMARY KEY,
        content text,
        metadata json,
        embedding vector(1536)  -- 确保与模型一致
    );
  3. 或在 Spring AI 配置中指定维度:

    yaml 复制代码
    spring:
      ai:
        vectorstore:
          pgvector:
            dimensions: 1536

7.3 问题:元数据过滤无效

可能原因

  • 过滤表达式格式不正确
  • 元数据存储格式不一致

解决方案

  1. 确认元数据格式:

    java 复制代码
    new Document("内容", Map.of("key1", "value1", "key2", "value2"));
  2. 使用正确的过滤表达式:

    java 复制代码
    .filterExpression("key1 == 'value1' && key2 == 'value2'")
  3. 检查 PostgreSQL 日志,查看实际执行的 SQL 语句

7.4 问题:内存不足

可能原因

  • HNSW 索引构建时内存不足
  • 查询时内存不足

八、总结

关键要点回顾

  • PGvector :是 PostgreSQL 的开源向量扩展,支持高效向量相似性搜索
  • Spring AI :通过 spring-ai-starter-vector-store-pgvector 提供了开箱即用的集成
  • 向量存储 :需要正确配置维度、距离类型和索引类型
  • 元数据过滤 :提供了强大的结果筛选能力
  • 性能优化 :通过调整索引和查询参数实现
相关推荐
墨_浅-14 小时前
分阶段训练金融大模型02-百度千帆实际步骤
人工智能·金融·百度云
明天好,会的14 小时前
分形生成实验(三):Rust强类型驱动的后端分步实现与编译时契约
开发语言·人工智能·后端·rust
甄心爱学习14 小时前
计算机视觉-特征提取,特征点提取与描述,图像分割
人工智能·计算机视觉
雷焰财经14 小时前
科技普惠,织就乡村智慧网:中和农信赋能农业现代化新实践
人工智能·科技
a1879272183114 小时前
MySQL 硬件优化和操作系统优化
数据库·mysql·优化·raid·numa·sysbench·系统参数
只想早点退休的90后14 小时前
sql面试题分享
数据库·sql
枫叶丹414 小时前
【Qt开发】Qt系统(三)->事件过滤器
java·c语言·开发语言·数据库·c++·qt
草莓熊Lotso14 小时前
Python 库使用全攻略:从标准库到第三方库(附实战案例)
运维·服务器·汇编·人工智能·经验分享·git·python