【Langchain4j-Java AI开发】08-向量嵌入与向量数据库

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: 不持久化
  • 其他数据库: 都支持持久化

下一步学习

参考资料

相关推荐
Coder_Boy_2 小时前
基于SpringAI的智能平台基座开发-(三)
人工智能·springboot·aiops·langchain4j
qq_377112372 小时前
从零开始深入理解并发、线程与等待通知机制
java·开发语言
小徐Chao努力2 小时前
【Langchain4j-Java AI开发】07-RAG 检索增强生成
java·人工智能·python
TG:@yunlaoda360 云老大2 小时前
华为云国际站代理商GSL主要有什么作用呢?
网络·数据库·华为云
TG:@yunlaoda360 云老大2 小时前
华为云国际站代理商GSL的流量用量与资费合规是如何实现的?
网络·数据库·华为云
360智汇云2 小时前
存储压缩:不是“挤水分”,而是让数据“轻装上阵
大数据·人工智能
冰冰菜的扣jio2 小时前
MySQL三大重要日志详解
数据库·mysql
JoannaJuanCV2 小时前
自动驾驶—CARLA仿真(30)交通管理器(Traffic Manager)
java·redis·自动驾驶
l1t2 小时前
postgresql递归查询指定搜索顺序的方法
数据库·postgresql·dfs·递归·cte