LangChain4j 向量化

4 阅读5分钟

向量

简单定义:向量就是一个有序的数字列表

可以把向量想象成“事物的特征指纹”

任何事物——无论是文字、图片、声音还是视频——都可以通过一系列的数字来描述其特征,这一串数字就构成了该事物的向量。

例如:如何用一个三维向量来描述水果?

假设我们只用三个特征来描述水果:甜度酸度脆度

  • 一个红苹果:甜度很高(9分),酸度适中(3分),非常脆(8分)。它的向量就是 [9, 3, 8]
  • 一个柠檬:甜度很低(1分),酸度极高(9分),基本不脆(1分)。它的向量就是 [1, 9, 1]
  • 一根香蕉:甜度很高(8分),酸度很低(2分),不脆(0分)。它的向量就是 [8, 2, 0]

你看,我们只用三个数字,就为每种水果创建了一个独一无二的“数字ID”或“指纹”。在实际的AI模型中,向量的维度(即数字的个数)会大得多,通常是几百甚至几千维。这些维度捕捉的是更复杂、更抽象的特征,比如词语的“词性”、“情感色彩”、“在句子中的角色”,或者图片的“纹理”、“形状”、“颜色分布”等。

向量的几个用途:

  • 度量相似相
  • 语义搜索
  • 等...

为此 LangChain4j 提供了将事物向量化的功能。主要的几个概念有:

  • Embedding
  • Embedding Model
  • Embedding Store
  • EmbeddingSearchRequest

这些概念和 API 比较长,我放在了最后,可以简单的看一下各个的“简介”,然后就去阅读“示例”中的代码即可。

如果你知道什么是向量,那么直接阅读“示例”即可。我这里也只是将官方文档简单翻译(机翻)并整理了一下。

Tips:文中的 Embedding(嵌入),也可以说成是 Vector(向量),这样可能更加好理解一些。

Qdrant

Qdrant 向量数据库的安装方式和运行方式这里不再赘述,自行百度。

$ docker run -p 6333:6333 -p 6334:6334 225e903e65c3
  • 6336 用户 HTTP API,WEB 管理后台
  • 6334 用于 gRPC API

引入 qdrant 客户端依赖

<dependency>
    <groupId>dev.langchain4j</groupId>
    <artifactId>langchain4j-qdrant</artifactId>
</dependency>

示例

模型配置

@Configuration
public class LLMConfig {
    @Bean
    public EmbeddingModel embeddingModel() {
        return OpenAiEmbeddingModel.builder()
                .apiKey(System.getenv("ALI_QWEN_API_KEY"))
                .modelName("text-embedding-v4")
                .baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")
                .build();
    }

    @Bean
    public EmbeddingStore<TextSegment> embeddingStore() {
        return QdrantEmbeddingStore.builder()
                .host("10.24.26.46")
                .port(6334)
                .collectionName("test-qdrant")
                .build();
    }
}

qdrant 客户端配置

@Configuration
public class QDrantConfig {
    /**
     * 创建 Qdrant 客户端
     */
    @Bean
    public QdrantClient qdrantClient() {
        QdrantGrpcClient.Builder builder
                = QdrantGrpcClient.newBuilder("10.24.26.46", 6334, false);
        return new QdrantClient(builder.build());
    }
}

创建向量数据库

新增向量数据库实例和创建索引:test-qdrant

@Test
public void createCollection() {
    Collections.VectorParams vectorParams = Collections.VectorParams.newBuilder()
        .setDistance(Collections.Distance.Cosine)
        .setSize(1024)
        .build();
    qdrantClient.createCollectionAsync("test-qdrant", vectorParams);
}

插入数据

往向量数据库中新增文本记录,将 prompt 向量化后插入数据库。

@Test
public void add() {
    String prompt = "超级无敌大帅哥说:我超级无敌帅!";
    TextSegment segment = TextSegment.from(prompt);
    segment.metadata().put("author", "wwj");
    Embedding embedding = embeddingModel.embed(segment).content(); // 向量化
    String res = embeddingStore.add(embedding, segment); // 插入数据库
    System.out.println(res);
}

查询数据

@Test
public void query1() {
    Embedding queryEmbedding = embeddingModel.embed("超级无敌大帅哥说的是什么?").content();
    EmbeddingSearchRequest req = EmbeddingSearchRequest.builder()
        .queryEmbedding(queryEmbedding) // 查询向量
        .maxResults(1) // 最大返回 1 条
        .build();
    EmbeddingSearchResult<TextSegment> result = embeddingStore.search(req);
    System.out.println(result.matches().get(0).embedded().text());
}

filter 查询

查询 metadata 元数据中 author=wwj 的向量数据。

@Test
public void query2() {
    Embedding embedding = embeddingModel.embed("超级无敌").content();
    EmbeddingSearchRequest req = EmbeddingSearchRequest.builder()
        .queryEmbedding(embedding)
        .filter(MetadataFilterBuilder.metadataKey("author").isEqualTo("wwj")) // here
        .maxResults(1)
        .build();
    EmbeddingSearchResult<TextSegment> result = embeddingStore.search(req);
    List<EmbeddingMatch<TextSegment>> list = result.matches();
    if(list.isEmpty()) {
        System.out.println("啥都没有");
    } else {
        System.out.println(list.get(0).embedded().text());
    }
}

API 查询

Embedding

Embedding 类封装了一个数值向量,表示嵌入内容的"语义意义"(通常是文本,如 TextSegment )。

了解更多关于向量嵌入的内容:

通常的用法:

  • Embedding.dimension() 返回嵌入向量的维度(即其长度)
  • CosineSimilarity.between(Embedding, Embedding) 计算 2 个 Embedding 之间的余弦相似度
  • Embedding.normalize() 将嵌入向量进行归一化(原地操作)

Embedding Model

EmbeddingModel 接口代表一种特殊的模型,可以将文本转换为 Embedding 。(将给定文本向量化)

通常的用法:

  • EmbeddingModel.embed(String) 将给定文本向量化
  • EmbeddingModel.embed(TextSegment) 将给定的 TextSegment 向量化
  • EmbeddingModel.embedAll(List<TextSegment>) 向量化所有给定的 TextSegment
  • EmbeddingModel.dimension() 返回此模型生成的 Embedding 的维度

Embedding Store

EmbeddingStore 接口表示一个用于 Embedding 的存储,也称为向量数据库。它允许存储和高效搜索相似的(在向量空间中接近的) Embedding

EmbeddingStore 可以单独存储 Embedding 或与相应的 TextSegment 一起存储:

  • 它可以通过 ID 存储 Embedding 。原始向量数据可以存储在其他地方,并使用 ID 进行关联。
  • 它可以存储 Embedding 和向量化后的原始数据(通常是 TextSegment )。

添加一个:

  • EmbeddingStore.add(Embedding) 将给定的 Embedding 添加到存储中并返回一个随机 ID
  • EmbeddingStore.add(String id, Embedding) 将指定的 Embedding 添加到存储中,并分配一个 ID
  • EmbeddingStore.add(Embedding, TextSegment) 将指定的 Embedding 添加到存储中,并关联一个 TextSegment ,然后返回一个随机 ID

添加多个:

  • EmbeddingStore.addAll(List<Embedding>) 将一系列给定的 Embedding 添加到存储中,并返回一系列随机 ID
  • EmbeddingStore.addAll(List<Embedding>, List<TextSegment>) 将一系列给定的 Embedding 与关联的 TextSegment 添加到存储中,并返回一系列随机 ID
  • EmbeddingStore.addAll(List<String> ids, List<Embedding>, List<TextSegment>) 添加具有相关 ID 和 TextSegmentEmbedding 列表到存储中

搜索:

  • EmbeddingStore.search(EmbeddingSearchRequest) 搜索最相似的 Embedding

删除:

  • EmbeddingStore.remove(String id) 通过 ID 从存储中删除单个 Embedding
  • EmbeddingStore.removeAll(Collection<String> ids) 从存储中删除所有 ID 存在于给定集合中的 Embedding
  • EmbeddingStore.removeAll(Filter) 删除存储中所有与指定的 Filter 匹配的 Embedding
  • EmbeddingStore.removeAll() 从存储中删除所有 Embedding

EmbeddingSearchRequest

EmbeddingSearchRequest 表示在 EmbeddingStore 中进行搜索的请求。它具有以下属性:

  • Embedding queryEmbedding : 用作参考的嵌入。
  • int maxResults : 返回的最大结果数量。这是一个可选参数。默认值:3。
  • double minScore : 最小分数,范围从 0 到 1(包含两端)。只有分数 >= minScore 的嵌入结果才会被返回。这是一个可选参数。默认值:0。
  • Filter filter : 在搜索过程中应用于 Metadata 的过滤器。只有 TextSegmentMetadataFilter 匹配的结果才会被返回。

Filter

Filter 允许在执行向量搜索时通过 Metadata 条目进行过滤。

目前,支持以下 Filter 类型/操作:

  • IsEqualTo
  • IsNotEqualTo
  • IsGreaterThan
  • IsGreaterThanOrEqualTo
  • IsLessThan
  • IsLessThanOrEqualTo
  • IsIn
  • IsNotIn
  • ContainsString
  • And
  • Not
  • Or

并非所有嵌入存储都支持通过 Metadata 进行过滤,请参考模型比较表的“按元数据过滤(Filtering by Metadata)”列。 【模型比较表】docs.langchain4j.dev/integration…

支持通过 Metadata 进行过滤的某些存储并不支持所有可能的 Filter 类型/操作。例如 ContainsString 操作目前仅由 Milvus、PgVector 和 Qdrant 支持。 【其他资料】github.com/langchain4j…