LangChain4j 向量嵌入与向量数据库

概述
向量嵌入(Embedding)是将文本转换为数值向量的技术,是 RAG 和语义搜索的基础。本教程将介绍如何使用 LangChain4j 的 Embedding 功能和向量数据库。
什么是 Embedding?
文本到向量的转换
java
EmbeddingModel model = new BgeSmallEnV15QuantizedEmbeddingModel();
// 将文本转换为向量
Embedding embedding = model.embed("人工智能").content();
System.out.println("向量维度: " + embedding.dimension()); // 384
System.out.println("向量值: " + Arrays.toString(embedding.vector()));
// 输出: [0.123, -0.456, 0.789, ...]
语义相似度
相似的文本会产生相似的向量:
java
Embedding e1 = model.embed("猫").content();
Embedding e2 = model.embed("小猫").content();
Embedding e3 = model.embed("汽车").content();
// 计算余弦相似度
double similarity12 = CosineSimilarity.between(e1, e2); // 0.85(高相似度)
double similarity13 = CosineSimilarity.between(e1, e3); // 0.12(低相似度)
Embedding 模型
1. 本地模型(推荐用于开发)
BGE Small (英文)
java
import dev.langchain4j.model.embedding.onnx.bgesmallenv15q.BgeSmallEnV15QuantizedEmbeddingModel;
EmbeddingModel model = new BgeSmallEnV15QuantizedEmbeddingModel();
// 嵌入单个文本
Embedding embedding = model.embed("Hello World").content();
// 批量嵌入
List<TextSegment> segments = List.of(
TextSegment.from("第一段文本"),
TextSegment.from("第二段文本"),
TextSegment.from("第三段文本")
);
List<Embedding> embeddings = model.embedAll(segments).content();
特点:
- ✅ 完全本地运行,无需 API
- ✅ 免费,无使用限制
- ✅ 快速(量化版本)
- ❌ 主要适用于英文
- 维度:384
All-MiniLM-L6-v2 (多语言)
java
import dev.langchain4j.model.embedding.onnx.allminilml6v2q.AllMiniLmL6V2QuantizedEmbeddingModel;
EmbeddingModel model = new AllMiniLmL6V2QuantizedEmbeddingModel();
特点:
- 支持多语言
- 维度:384
- 速度快
2. OpenAI Embedding(质量最高)
java
import dev.langchain4j.model.openai.OpenAiEmbeddingModel;
EmbeddingModel model = OpenAiEmbeddingModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.modelName("text-embedding-3-small") // 或 text-embedding-3-large
.build();
Embedding embedding = model.embed("人工智能正在改变世界").content();
模型对比:
| 模型 | 维度 | 价格 | 适用场景 |
|---|---|---|---|
| text-embedding-3-small | 1536 | 低 | 通用场景 |
| text-embedding-3-large | 3072 | 中 | 高精度需求 |
| text-embedding-ada-002 | 1536 | 低 | 旧版(不推荐) |
3. 其他云端模型
Cohere
java
import dev.langchain4j.model.cohere.CohereEmbeddingModel;
EmbeddingModel model = CohereEmbeddingModel.builder()
.apiKey(System.getenv("COHERE_API_KEY"))
.modelName("embed-multilingual-v3.0") // 多语言模型
.build();
Azure OpenAI
java
import dev.langchain4j.model.azure.AzureOpenAiEmbeddingModel;
EmbeddingModel model = AzureOpenAiEmbeddingModel.builder()
.apiKey(System.getenv("AZURE_OPENAI_KEY"))
.endpoint(System.getenv("AZURE_OPENAI_ENDPOINT"))
.deploymentName("text-embedding-ada-002")
.build();
HuggingFace
java
import dev.langchain4j.model.huggingface.HuggingFaceEmbeddingModel;
EmbeddingModel model = HuggingFaceEmbeddingModel.builder()
.apiKey(System.getenv("HUGGINGFACE_API_KEY"))
.modelId("sentence-transformers/all-MiniLM-L6-v2")
.build();
向量数据库
1. InMemoryEmbeddingStore(开发测试)
java
package dev.langchain4j.example;
import dev.langchain4j.data.embedding.Embedding;
import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.model.embedding.EmbeddingModel;
import dev.langchain4j.model.embedding.onnx.bgesmallenv15q.BgeSmallEnV15QuantizedEmbeddingModel;
import dev.langchain4j.store.embedding.EmbeddingMatch;
import dev.langchain4j.store.embedding.EmbeddingStore;
import dev.langchain4j.store.embedding.inmemory.InMemoryEmbeddingStore;
import java.util.List;
public class InMemoryStoreExample {
public static void main(String[] args) {
// 1. 创建 Embedding 模型
EmbeddingModel embeddingModel = new BgeSmallEnV15QuantizedEmbeddingModel();
// 2. 创建内存存储
EmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();
// 3. 准备文档
List<TextSegment> segments = List.of(
TextSegment.from("Java 是一种面向对象的编程语言"),
TextSegment.from("Python 是一种简洁易学的编程语言"),
TextSegment.from("JavaScript 主要用于 Web 开发"),
TextSegment.from("机器学习是人工智能的一个分支")
);
// 4. 嵌入并存储
for (TextSegment segment : segments) {
Embedding embedding = embeddingModel.embed(segment).content();
embeddingStore.add(embedding, segment);
}
// 5. 搜索
String query = "什么编程语言适合 Web?";
Embedding queryEmbedding = embeddingModel.embed(query).content();
List<EmbeddingMatch<TextSegment>> matches = embeddingStore.findRelevant(
queryEmbedding,
3, // 返回前 3 个结果
0.5 // 最低相似度
);
// 6. 显示结果
System.out.println("查询: " + query);
System.out.println("\n最相关的文档:");
for (EmbeddingMatch<TextSegment> match : matches) {
System.out.printf("相似度: %.3f - %s\n",
match.score(),
match.embedded().text());
}
}
}
输出示例:
查询: 什么编程语言适合 Web?
最相关的文档:
相似度: 0.856 - JavaScript 主要用于 Web 开发
相似度: 0.678 - Python 是一种简洁易学的编程语言
相似度: 0.621 - Java 是一种面向对象的编程语言
2. Chroma(本地持久化)
java
import dev.langchain4j.store.embedding.chroma.ChromaEmbeddingStore;
// 启动 Chroma 服务: docker run -p 8000:8000 chromadb/chroma
EmbeddingStore<TextSegment> chromaStore = ChromaEmbeddingStore.builder()
.baseUrl("http://localhost:8000")
.collectionName("my-documents")
.build();
// 添加数据
chromaStore.add(embedding, segment);
// 搜索
List<EmbeddingMatch<TextSegment>> results = chromaStore.findRelevant(
queryEmbedding,
5
);
Docker 启动:
bash
docker run -d -p 8000:8000 chromadb/chroma
3. Pinecone(云端生产)
java
import dev.langchain4j.store.embedding.pinecone.PineconeEmbeddingStore;
EmbeddingStore<TextSegment> pineconeStore = PineconeEmbeddingStore.builder()
.apiKey(System.getenv("PINECONE_API_KEY"))
.environment("us-east-1-aws")
.projectId("my-project")
.index("my-index")
.nameSpace("production") // 可选
.build();
特点:
- ✅ 完全托管,无需维护
- ✅ 高性能,低延迟
- ✅ 自动扩展
- ❌ 付费服务
4. PostgreSQL (pgvector)
java
import dev.langchain4j.store.embedding.pgvector.PgVectorEmbeddingStore;
EmbeddingStore<TextSegment> pgStore = PgVectorEmbeddingStore.builder()
.host("localhost")
.port(5432)
.database("vectordb")
.user("postgres")
.password("password")
.table("embeddings")
.dimension(384) // 根据 embedding 模型调整
.createTable(true) // 自动创建表
.build();
PostgreSQL 设置:
sql
-- 安装 pgvector 扩展
CREATE EXTENSION vector;
-- 创建表(如果不使用 createTable=true)
CREATE TABLE embeddings (
id UUID PRIMARY KEY,
embedding VECTOR(384),
text TEXT,
metadata JSONB
);
-- 创建索引
CREATE INDEX ON embeddings USING ivfflat (embedding vector_cosine_ops);
5. Elasticsearch
java
import dev.langchain4j.store.embedding.elasticsearch.ElasticsearchEmbeddingStore;
EmbeddingStore<TextSegment> esStore = ElasticsearchEmbeddingStore.builder()
.serverUrl("http://localhost:9200")
.indexName("embeddings")
.dimension(384)
.build();
6. Redis
java
import dev.langchain4j.store.embedding.redis.RedisEmbeddingStore;
EmbeddingStore<TextSegment> redisStore = RedisEmbeddingStore.builder()
.host("localhost")
.port(6379)
.dimension(384)
.indexName("embeddings-idx")
.build();
7. Qdrant
java
import dev.langchain4j.store.embedding.qdrant.QdrantEmbeddingStore;
EmbeddingStore<TextSegment> qdrantStore = QdrantEmbeddingStore.builder()
.host("localhost")
.port(6334)
.collectionName("my-collection")
.build();
Docker 启动:
bash
docker run -p 6333:6333 -p 6334:6334 qdrant/qdrant
向量数据库选择指南
| 数据库 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| InMemory | 开发测试 | 简单快速 | 不持久化 |
| Chroma | 小型项目 | 本地部署,易用 | 性能有限 |
| pgvector | 已有 PostgreSQL | 无需额外服务 | 性能较专业向量DB差 |
| Pinecone | 生产环境 | 高性能,免维护 | 付费 |
| Qdrant | 中大型项目 | 开源,高性能 | 需自行部署 |
| Elasticsearch | 已有 ES 集群 | 综合搜索能力 | 向量搜索非专长 |
| Redis | 需要缓存 | 速度极快 | 内存占用大 |
元数据与过滤
添加元数据
java
import dev.langchain4j.data.document.Metadata;
// 创建带元数据的文本段
TextSegment segment = TextSegment.from(
"Java 是一种面向对象的编程语言",
Metadata.from(
"category", "编程语言",
"difficulty", "中等",
"year", 1995,
"author", "James Gosling"
)
);
// 嵌入并存储
Embedding embedding = embeddingModel.embed(segment).content();
embeddingStore.add(embedding, segment);
使用过滤器搜索
java
import dev.langchain4j.store.embedding.filter.Filter;
// 创建过滤器
Filter filter = Filter.metadataKey("category").isEqualTo("编程语言");
// 过滤搜索
List<EmbeddingMatch<TextSegment>> matches = embeddingStore.findRelevant(
queryEmbedding,
5,
0.7,
filter // 只返回编程语言类别的文档
);
复杂过滤条件
java
import static dev.langchain4j.store.embedding.filter.Filter.*;
// AND 条件
Filter andFilter = and(
metadataKey("category").isEqualTo("编程语言"),
metadataKey("year").isGreaterThan(2000)
);
// OR 条件
Filter orFilter = or(
metadataKey("difficulty").isEqualTo("简单"),
metadataKey("difficulty").isEqualTo("中等")
);
// 组合条件
Filter complexFilter = and(
metadataKey("category").isEqualTo("编程语言"),
or(
metadataKey("difficulty").isEqualTo("简单"),
metadataKey("year").isGreaterThan(2010)
)
);
实战示例
构建语义搜索引擎
java
package dev.langchain4j.example;
import dev.langchain4j.data.document.Document;
import dev.langchain4j.data.document.DocumentParser;
import dev.langchain4j.data.document.DocumentSplitter;
import dev.langchain4j.data.document.parser.TextDocumentParser;
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.bgesmallenv15q.BgeSmallEnV15QuantizedEmbeddingModel;
import dev.langchain4j.store.embedding.EmbeddingMatch;
import dev.langchain4j.store.embedding.EmbeddingStore;
import dev.langchain4j.store.embedding.inmemory.InMemoryEmbeddingStore;
import java.nio.file.Path;
import java.util.List;
import static dev.langchain4j.data.document.loader.FileSystemDocumentLoader.loadDocuments;
public class SemanticSearchEngine {
private final EmbeddingModel embeddingModel;
private final EmbeddingStore<TextSegment> embeddingStore;
public SemanticSearchEngine() {
this.embeddingModel = new BgeSmallEnV15QuantizedEmbeddingModel();
this.embeddingStore = new InMemoryEmbeddingStore<>();
}
// 索引文档
public void indexDocuments(String directoryPath) {
// 1. 加载文档
List<Document> documents = loadDocuments(Path.of(directoryPath), "*.txt");
System.out.println("加载了 " + documents.size() + " 个文档");
// 2. 分割文档
DocumentSplitter splitter = DocumentSplitters.recursive(300, 50);
for (Document document : documents) {
List<TextSegment> segments = splitter.split(document);
// 3. 嵌入并存储每个片段
for (TextSegment segment : segments) {
Embedding embedding = embeddingModel.embed(segment).content();
embeddingStore.add(embedding, segment);
}
}
System.out.println("索引完成");
}
// 搜索
public List<SearchResult> search(String query, int maxResults) {
// 1. 嵌入查询
Embedding queryEmbedding = embeddingModel.embed(query).content();
// 2. 搜索
List<EmbeddingMatch<TextSegment>> matches = embeddingStore.findRelevant(
queryEmbedding,
maxResults,
0.6
);
// 3. 转换结果
return matches.stream()
.map(match -> new SearchResult(
match.embedded().text(),
match.score(),
match.embedded().metadata()
))
.toList();
}
public static void main(String[] args) {
SemanticSearchEngine engine = new SemanticSearchEngine();
// 索引文档
engine.indexDocuments("documents/");
// 搜索
List<SearchResult> results = engine.search("如何学习 Java?", 3);
System.out.println("\n搜索结果:");
for (int i = 0; i < results.size(); i++) {
SearchResult result = results.get(i);
System.out.printf("%d. (相似度: %.3f)\n%s\n\n",
i + 1,
result.score(),
result.text());
}
}
record SearchResult(String text, double score, Metadata metadata) {}
}
性能优化
1. 批量嵌入
java
// ❌ 不好:逐个嵌入
for (TextSegment segment : segments) {
Embedding embedding = embeddingModel.embed(segment).content();
embeddingStore.add(embedding, segment);
}
// ✅ 好:批量嵌入
List<Embedding> embeddings = embeddingModel.embedAll(segments).content();
embeddingStore.addAll(embeddings, segments);
2. 使用量化模型
java
// 使用量化模型(更快,体积更小)
EmbeddingModel model = new BgeSmallEnV15QuantizedEmbeddingModel();
// 而不是完整精度模型
3. 调整向量维度(如果支持)
java
// OpenAI 支持降维
EmbeddingModel model = OpenAiEmbeddingModel.builder()
.apiKey(apiKey)
.modelName("text-embedding-3-large")
.dimensions(512) // 降至 512 维(默认 3072)
.build();
4. 创建索引(向量数据库)
sql
-- PostgreSQL pgvector
CREATE INDEX ON embeddings USING ivfflat (embedding vector_cosine_ops)
WITH (lists = 100);
-- 或使用 HNSW 索引(更快)
CREATE INDEX ON embeddings USING hnsw (embedding vector_cosine_ops);
最佳实践
1. 选择合适的模型
java
// 开发/测试:本地模型
EmbeddingModel devModel = new BgeSmallEnV15QuantizedEmbeddingModel();
// 生产/高质量:OpenAI
EmbeddingModel prodModel = OpenAiEmbeddingModel.builder()
.apiKey(apiKey)
.modelName("text-embedding-3-small")
.build();
2. 文本预处理
java
public String preprocessText(String text) {
return text
.toLowerCase() // 转小写
.replaceAll("\\s+", " ") // 规范化空白
.replaceAll("[^\\p{L}\\p{N}\\s]", "") // 移除标点
.trim();
}
3. 监控嵌入质量
java
// 检查嵌入分布
List<Embedding> embeddings = embeddingModel.embedAll(segments).content();
double avgNorm = embeddings.stream()
.mapToDouble(e -> {
double[] vector = e.vector();
return Math.sqrt(Arrays.stream(vector).map(v -> v * v).sum());
})
.average()
.orElse(0);
System.out.println("平均向量范数: " + avgNorm);
常见问题
Q1: 不同的 Embedding 模型可以混用吗?
A: 不可以。同一个向量存储中的所有向量必须使用相同的模型和维度。
Q2: 如何选择向量维度?
A:
- 小维度(384): 速度快,存储小,精度稍低
- 中维度(768-1536): 平衡选择(推荐)
- 大维度(3072): 精度高,但慢且占用大
Q3: 向量数据库的数据可以持久化吗?
A:
- InMemoryEmbeddingStore: 不持久化
- 其他数据库: 都支持持久化
下一步学习
- 07-RAG检索增强生成 - 应用 Embedding 实现 RAG
- 09-智能体工作流 - 构建智能体系统
- 10-框架集成 - 生产环境部署
参考资料
- 向量存储示例:
rag-examples/src/main/java/_2_naive/Naive_RAG_Example.java - 官方文档: https://docs.langchain4j.dev/tutorials/embedding-models
- 向量数据库对比: https://docs.langchain4j.dev/integrations/embedding-stores