跳到主要内容

Hibernate

LangChain4j 与 Hibernate 无缝集成,允许开发者在 Hibernate 支持的所有数据库中直接存储和查询向量嵌入。此集成非常适合语义搜索、RAG 等应用。

Maven 依赖


<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-hibernate</artifactId>
<version>1.18.1-beta28</version>
</dependency>

Gradle 依赖

implementation 'dev.langchain4j:langchain4j-hibernate:1.18.1-beta28'

API

  • HibernateEmbeddingStore

参数摘要

通用存储

当你只想使用 EmbeddingStore API,而不想关心 Hibernate 的具体细节(如实体类定义和 Hibernate 配置)时,推荐使用此类存储。

要配置它,请使用 HibernateEmbeddingStore.dynamicBuilder()HibernateEmbeddingStore.dynamicDatasourceBuilder()

普通 Java 属性说明默认值必需/可选
datasource用于数据库连接的 DataSource 对象。仅在 HibernateEmbeddingStore.dynamicDatasourceBuilder() 构建器变体中可用。如果未提供,则必须在 HibernateEmbeddingStore.dynamicBuilder() 构建器变体中分别提供 jdbcUrluserpassword如果未分别提供 jdbcUrluserpassword,则为必需。
jdbcUrl数据库服务器的 JDBC URL。如果未提供 DataSource 以及 hostportdatabase,则为必需。仅在 HibernateEmbeddingStore.dynamicBuilder() 构建器变体中可用。如果未提供 DataSourcehostportdatabase,则为必需
host数据库服务器的主机名。如果既未提供 DataSource 也未提供 jdbcUrl,则为必需。仅在 HibernateEmbeddingStore.dynamicBuilder() 构建器变体中可用。如果既未提供 DataSource 也未提供 jdbcUrl,则为必需
port数据库服务器的端口号。如果既未提供 DataSource 也未提供 jdbcUrl,则为必需。仅在 HibernateEmbeddingStore.dynamicBuilder() 构建器变体中可用。如果既未提供 DataSource 也未提供 jdbcUrl,则为必需
database要连接的数据库名称。如果既未提供 DataSource 也未提供 jdbcUrl,则为必需。仅在 HibernateEmbeddingStore.dynamicBuilder() 构建器变体中可用。如果既未提供 DataSource 也未提供 jdbcUrl,则为必需
databaseKind数据库类型。如果提供了 DataSource 或无法从 jdbcUrl 推断类型,则为必需。如果提供了 DataSource 或无法从 jdbcUrl 推断类型,则为必需
user数据库认证的用户名。如果未提供 DataSource,则为必需。仅在 HibernateEmbeddingStore.dynamicBuilder() 构建器变体中可用。如果未提供 DataSource,则为必需
password数据库认证的密码。如果未提供 DataSource,则为必需。仅在 HibernateEmbeddingStore.dynamicBuilder() 构建器变体中可用。如果未提供 DataSource,则为必需
table用于存储嵌入的数据库表名。必需
dimension嵌入向量的维度。应与所使用的嵌入模型匹配。使用 embeddingModel.dimension() 可动态设置。必需
createIndex指定是否自动为向量嵌入创建索引。false可选
indexType数据库特定的索引类型,例如 ivfflathnsw。IVFFlat 索引将向量划分为列表,然后搜索最接近查询向量的那些列表的子集。与 HNSW 相比,构建速度更快、内存占用更少,但查询性能较低(在速度-召回权衡方面)。应使用 IVFFlat 索引。可选。默认为首选索引类型,例如 PostgreSQL 上的 ivfflat
indexOptions为向量嵌入索引配置的选项。何时必需:如果 createIndextrue 且索引类型为 ivfflat,在 PostgreSQL 上必须提供 lists = 1 选项且必须大于零。否则,程序将在表初始化期间抛出异常。何时可选:如果 createIndexfalse,则忽略此属性,无需设置。
createTable指定是否自动创建嵌入表。false可选
dropTableFirst指定是否在重新创建表之前先删除表(对测试很有用)。false可选
distanceFunction向量搜索使用的距离函数。支持情况因数据库而异:
  • COSINE
  • EUCLIDEAN
  • EUCLIDEAN_SQUARED
  • MANHATTAN
  • INNER_PRODUCT
  • NEGATIVE_INNER_PRODUCT
  • HAMMING
  • JACCARD
COSINE可选。如果未设置,则使用带有 COSINE 的默认配置。

实体存储

要在 EmbeddingStore API 中利用现有的 Hibernate 实体模型,或应用数据模型自定义,推荐使用实体存储。

要配置它,请使用 HibernateEmbeddingStore.builder()

普通 Java 属性说明默认值必需/可选
sessionFactoryentityClass 所属的 SessionFactory 对象。必需
databaseKind数据库类型。如果无法从 Hibernate ORM 方言推断类型,则为必需。如果无法从 Hibernate ORM 方言推断类型,则为必需
entityClass指定 SessionFactory 中用于 EmbeddingStore 的实体类。必需
embeddingAttributeName指定表示向量嵌入的实体属性名称。可选。如果未设置,则扫描实体中带有 @EmbeddingVector 注解的属性
embeddedTextAttributeName指定表示向量嵌入源文本的实体属性名称。可选。如果未设置,则扫描实体中带有 @EmbeddedText 注解的属性
unmappedMetadataAttributeName指定表示存储未映射元数据的 JSON 列的实体属性名称。可选。如果未设置,则扫描实体中带有 @UnmappedMetadata 注解的属性
metadataAttributeNames指定显式映射到文本元数据的实体属性名称。可选。如果未设置,则扫描实体中带有 @MetadataAttribute 注解的属性
distanceFunction向量搜索使用的距离函数。支持情况因数据库而异:
  • COSINE
  • EUCLIDEAN
  • EUCLIDEAN_SQUARED
  • MANHATTAN
  • INNER_PRODUCT
  • NEGATIVE_INNER_PRODUCT
  • HAMMING
  • JACCARD
COSINE可选。如果未设置,则扫描实体中带有 @EmbeddingVector 注解的属性并使用其 distance 值;如果缺少该值,则使用带有 COSINE 的默认配置。

示例

为了演示这些功能,例如可以使用 Docker 化的 PostgreSQL 设置。它利用 Testcontainers 运行带有 PGVector 的 PostgreSQL。

使用 Docker 快速开始

要快速设置带有 PGVector 扩展的 PostgreSQL 实例,可以使用以下 Docker 命令:

docker run --rm --name langchain4j-postgres-test-container -p 5432:5432 -e POSTGRES_USER=my_user -e POSTGRES_PASSWORD=my_password pgvector/pgvector

命令说明:

  • docker run: 运行一个新容器。
  • --rm: 容器停止后自动删除,确保没有残留数据。
  • --name langchain4j-postgres-test-container: 将容器命名为 langchain4j-postgres-test-container,便于识别。
  • -p 5432:5432: 将本地机器的端口 5432 映射到容器中的端口 5432。
  • -e POSTGRES_USER=my_user: 将 PostgreSQL 用户名设置为 my_user。
  • -e POSTGRES_PASSWORD=my_password: 将 PostgreSQL 密码设置为 my_password。
  • pgvector/pgvector: 指定要使用的 Docker 镜像,已预配置 PGVector 扩展。

以下是两个创建 HibernateEmbeddingStore 的代码示例。第一个仅使用必需参数,第二个配置了所有可用参数。

  1. 仅必需参数
HibernateEmbeddingStore embeddingStore = HibernateEmbeddingStore.dynamicBuilder()
.databaseKind(DatabaseKind.POSTGRESQL) // Required: The database kind
.host("localhost") // Required: Host of the database server
.port(5432) // Required: Port of the database server
.database("postgres") // Required: Database name
.user("my_user") // Required: Database user
.password("my_password") // Required: Database password
.table("my_embeddings") // Required: Table name to store embeddings
.dimension(embeddingModel.dimension()) // Required: Dimension of embeddings
.build();
  1. 设置所有参数

在此变体中,我们包含了所有常用的可选参数,如 createIndex、indexOptions、createTable、dropTableFirst 和 distanceFunction。根据需要调整这些值:

HibernateEmbeddingStore embeddingStore = HibernateEmbeddingStore.dynamicBuilder()
// Required parameters
.databaseKind(DatabaseKind.POSTGRESQL)
.host("localhost")
.port(5432)
.database("postgres")
.user("my_user")
.password("my_password")
.table("my_embeddings")
.dimension(embeddingModel.dimension())

// Optional parameters
.createIndex(true) // Enable vector index creation
.indexType("ivfflat") // Index type IVFFlat
.indexOptions("lists = 100") // Number of lists for IVFFlat index
.createTable(true) // Automatically create the table if it doesn’t exist
.dropTableFirst(false) // Don’t drop the table first (set to true if you want a fresh start)
.distanceFunction(DistanceFunction.MANHATTEN) // Use MANHATTAN distance function for vector search

.build();

如果你只想用最小配置快速开始,请使用第一个示例。 第二个示例展示了如何利用所有可用的构建器参数以获得更多控制和自定义。

当你不再需要 HibernateEmbeddingStore 时,不要忘记关闭它,以关闭底层的 Hibernate 资源。

自定义 Hibernate 实体

当你想自定义数据模型,或想复用现有实体作为 EmbeddingStore 的数据源时,可以使用注解 @EmbeddingVector@EmbeddedText@UnmappedMetadata@MetadataAttribute 来标记 Hibernate EmbeddingStore 实现要使用的实体属性。

@Entity
public class MyEmbeddingEntity {
@Id
UUID id;
@EmbeddingVector
@Array(length = 384) // The dimension of the embedding vector based on the embedding model
float[] embedding;
@EmbeddedText
String text;
@UnmappedMetadata
Map<String, Object> metadata; // Can be either a Map<String, Object> or a String

@MetadataAttribute
String mimeType; // Explicitly mapped. Synchronizes TextSegment#metadata with this attribute
@MetadataAttribute
String fileName; // Explicitly mapped. Synchronizes TextSegment#metadata with this attribute
}

构建器随后会查找这些注解并推导出属性名称。

HibernateEmbeddingStore embeddingStore = HibernateEmbeddingStore.builder()
.sessionFactory(sessionFactory) // Required: The SessionFactory containing your entity class
.entityClass(MyEmbeddingEntity.class) // Required: The embedding entity class
.build();

或者,如果不希望对实体模型进行注解,也可以显式提供属性名称。

HibernateEmbeddingStore embeddingStore = HibernateEmbeddingStore.builder()
.sessionFactory(sessionFactory)
.entityClass(MyEmbeddingEntity.class)
.embeddingAttributeName("embedding")
.embeddedTextAttributeName("text")
.unmappedMetadataAttributeName("metadata")
.metadataAttributeNames("mimeType", "fileName")
.build();

元数据也可以嵌套在同样带有 @MetadataAttribute 注解的 @OneToOne@ManyToOne@Embedded 属性中,或通过使用 .(点)分隔符指定显式属性路径。

@Entity
public class Book {
@Id
private Long id;
private String title;
private String content;
@MetadataAttribute
@Embedded
private BookDetails details = new BookDetails();
@MetadataAttribute
@ManyToOne(fetch = FetchType.LAZY)
private Author author;

@EmbeddingVector
@Array(length = 384)
private float[] embedding;
@UnmappedMetadata
private Map<String, Object> metadata;
}
@Entity
public class Author {
@Id
@MetadataAttribute
@GeneratedValue
private Long id;
private String firstname;
private String lastname;
}
@Embeddable
public class BookDetails {
@MetadataAttribute
private String language;
private String abstractText;
}

等效的属性路径是 details.languageauthor.id,然后可通过将这些路径指定为元数据键用于过滤,例如:

MetadataFilterBuilder.metadataKey("details.language").isEqualTo("English")

MetadataFilterBuilder.metadataKey("author.id").isEqualTo(2L)

或者,HibernateEmbeddingStore API 还提供 search 方法,允许你使用类型安全的 Hibernate ORM Restriction API。

HibernateEmbeddingStore<Book> embeddingStore = embeddingStore();
embeddingStore.search(
embedding,
Path.from(Book.class)
.to(Book_.details)
.to(BookDetails_.language)
.equalTo("English"));

HibernateEmbeddingStore<Book> embeddingStore = embeddingStore();
embeddingStore.search(
embedding,
Path.from(Book.class)
.to(Book_.author)
.to(Author_.id)
.equalTo(2L));

使用 Hibernate 的完整 RAG 示例

本节演示如何使用 Hibernate 集成与带有 PGVector 扩展的 PostgreSQL 构建完整的检索增强生成(RAG)系统,用于语义搜索。

概述

RAG 系统由两个主要阶段组成:

  1. 索引阶段(离线):加载文档、拆分为块、生成嵌入并存储到 pgvector
  2. 检索阶段(在线):嵌入用户查询、搜索相似块、将上下文注入 LLM 提示

前提条件

确保你有一个运行中的带有 PGVector 的 PostgreSQL 实例(参见上面的 Docker 设置)。

1. 文档摄取(索引阶段)

此示例展示如何加载文档、将其拆分为块,并将嵌入存储到 pgvector:

import dev.langchain4j.data.document.Document;
import dev.langchain4j.data.document.DocumentParser;
import dev.langchain4j.data.document.DocumentSplitter;
import dev.langchain4j.data.document.parser.apache.pdfbox.ApachePdfBoxDocumentParser;
import dev.langchain4j.data.document.splitter.DocumentSplitters;
import dev.langchain4j.data.embedding.Embedding;
import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.model.embedding.EmbeddingModel;
import dev.langchain4j.model.embedding.onnx.allminilml6v2.AllMiniLmL6V2EmbeddingModel;
import dev.langchain4j.store.embedding.EmbeddingStore;
import dev.langchain4j.store.embedding.EmbeddingStoreIngestor;

import static dev.langchain4j.data.document.loader.FileSystemDocumentLoader.loadDocument;

// Load document (PDF, TXT, etc.)
Document document = loadDocument("/path/to/document.pdf", new ApachePdfBoxDocumentParser());

// Split document into smaller chunks
// 300 tokens per chunk, 50 tokens overlap for context continuity
DocumentSplitter splitter = DocumentSplitters.recursive(300, 50);

// Create embedding model (384 dimensions for AllMiniLmL6V2)
EmbeddingModel embeddingModel = new AllMiniLmL6V2EmbeddingModel();

// Create pgvector embedding store
HibernateEmbeddingStore embeddingStore = HibernateEmbeddingStore.dynamicBuilder()
.databaseKind(DatabaseKind.POSTGRESQL)
.host("localhost")
.port(5432)
.database("postgres")
.user("my_user")
.password("my_password")
.table("document_embeddings")
.dimension(embeddingModel.dimension()) // 384 for AllMiniLmL6V2
.build();

// Ingest: split document, generate embeddings, and store in pgvector
EmbeddingStoreIngestor.builder()
.documentSplitter(splitter)
.embeddingModel(embeddingModel)
.embeddingStore(embeddingStore)
.build()
.ingest(document);

System.out.println("Document ingested successfully!");

2. 查询(检索阶段)

此示例展示如何使用用户问题查询 RAG 系统:

import dev.langchain4j.data.embedding.Embedding;
import dev.langchain4j.data.message.AiMessage;
import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.openai.OpenAiChatModel;
import dev.langchain4j.store.embedding.EmbeddingMatch;

import java.util.List;
import java.util.stream.Collectors;

// User's question
String question = "What is the refund policy?";

// Generate embedding for the question
Embedding questionEmbedding = embeddingModel.embed(question).content();

// Search for the most similar text segments (top 3 results)
EmbeddingSearchResult<TextSegment> result = embeddingStore.search(
EmbeddingSearchRequest.builder()
.queryEmbedding(questionEmbedding)
.maxResults(3) // Retrieve top 3 most similar chunks
.build()
);

// Build context from retrieved segments
String context = result.matches().stream()
.map(match -> match.embedded().text())
.collect(Collectors.joining("\n\n"));

// Create prompt with retrieved context
String promptWithContext = String.format("""
Answer the question based on the following context.
If the context doesn't contain relevant information, say "I don't have enough information to answer."

Context:
%s

Question: %s

Answer:
""", context, question);

// Send to LLM with context
ChatModel chatModel = OpenAiChatModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.modelName("gpt-4")
.build();

String answer = chatModel.generate(promptWithContext);
System.out.println("Answer: " + answer);

生产环境注意事项

基于实际使用经验,以下是生产部署的重要注意事项:

1. 连接池

对于生产环境,使用带有连接池的 DataSource,而不是单独的连接参数:

import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;

HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:postgresql://localhost:5432/postgres");
config.setUsername("my_user");
config.setPassword("my_password");
config.setMaximumPoolSize(10);

HikariDataSource dataSource = new HikariDataSource(config);

EmbeddingStore<TextSegment> embeddingStore = HibernateEmbeddingStore.dynamicDatasourceBuilder()
.databaseKind(DatabaseKind.POSTGRESQL)
.datasource(dataSource)
.table("document_embeddings")
.dimension(384)
.build();

2. 索引优化

对于大型数据集(>10 万个嵌入),在 PostgreSQL 上启用 IVFFlat 索引以提高查询性能:

HibernateEmbeddingStore embeddingStore = HibernateEmbeddingStore.dynamicBuilder()
// ... other config ...
.createIndex(true)
.indexOptions("lists = 100") // Adjust based on dataset size
.build();

注意:在大型数据集上创建索引可能需要时间。请在查询速度与索引构建时间之间取得平衡。 注意:索引维护可能会减慢数据摄取速度,因此在摄取大量数据时,可考虑删除并重新创建索引。

3. 块大小调优

根据用例尝试不同的块大小:

  • 较小的块(200-300 token):精度更高,答案更具体
  • 较大的块(500-800 token):更多上下文,但可能降低相关性

4. 错误处理

始终优雅地处理数据库连接失败:

try {
embeddingStore.add(embedding, textSegment);
} catch (Exception e) {
logger.error("Failed to store embedding", e);
// Implement retry logic or fallback behavior
}

5. 自定义 Hibernate 实体 DDL

使用自定义 Hibernate 实体时,你负责管理 DDL。 可考虑创建 import.sql 文件来创建索引,例如对于 PostgreSQL:

create index if not exists my_entity_ivfflat_index 
on my_entity using ivfflat(embedding vector_cosine_ops) with (lists = 1);

有关 SessionFactory 配置的详细信息,请参阅 Hibernate ORM 文档

其他数据库的向量索引具有不同的语法和选项。有关详细信息,请参阅相应数据库提供商的文档。

DB2

有关详细信息,请参阅向量索引文章

create vector index my_entity_vector_index 
on my_entity(embedding) with distance cosine;
MariaDB

有关详细信息,请参阅 create index 语句文档

create vector index if not exists my_entity_vector_index 
on my_entity(embedding) distance=cosine;
MySQL

MySQL HeatWave 自动创建索引,不需要手动创建索引。

PostgreSQL

有关详细信息,请参阅 pgvector 文档

create index if not exists my_entity_ivfflat_index
on my_entity using ivfflat(embedding vector_cosine_ops) with (lists = 1);
CockroachDB

有关详细信息,请参阅 CockroachDB 文档

create vector index if not exists my_entity_ivfflat_index
on my_entity (embedding vector_cosine_ops);
Oracle

有关详细信息,请参阅 create index 语句文档

create vector index my_entity_vector_index 
on my_entity(embedding) organization neighbor partitions with distance cosine;
SQL Server

有关详细信息,请参阅 create vector index 语句文档

create vector index my_entity_vector_index 
on my_entity(embedding) with (metric='cosine');
SAP HANA

有关详细信息,请参阅 create vector index 语句文档

create hnsw vector index my_entity_vector_index 
on my_entity(embedding) with similarity function cosine_similarity;