LangChain4j + Milvus向量数据库实战

4,673 阅读4分钟

本篇文章仅介绍一下 LangChain4j 支持的向量数据库,并演示如何安装Milvus向量数据库,并使用LangChain4j 框架实现对接 Milvus向量数据库完成向量存储以及相似性搜索功能的演示。

LangChain4j支持的向量数据库

向量数据库支持元数据存储支持元数据过滤删除嵌入数据用途
In-memory一般用于测试
Elasticsearch
Milvus
PGVector
Azure AI Search
Weaviate
Astra DB
Cassandra
Chroma
Infinispan
MongoDB Atlas
OpenSearch
Qdrant
Redis
Vearch
Azure CosmosDB Mongo vCore
Azure CosmosDB NoSQL
Vespa
Neo4j
Pinecone

LangChain4j 框架支持15+向量数据库的集成,从表格看,仅对向量数据库是否支持元数据,是否支持元数据过滤以及是否支持删除能力三个方面对比。

安装 Milvus 数据库

Milvus 创建于 2019 年,其目标只有一个:存储、索引和管理由深度神经网络和其他机器学习 (ML) 模型生成的海量嵌入向量。

Docker 上安装

wget https://github.com/milvus-io/milvus/releases/download/v2.4.5/milvus-standalone-docker-compose.yml -O docker-compose.yml
sudo docker compose up -d

当出现如下,就说明安装Ok。 image.png

具体其它安装方式请参考官方网站。支持多种方式安装!!!

安装 attu 「 The GUI for Milvus 」

docker run -p 8000:3000 -e MILVUS_URL=127.0.0.1:19530 zilliz/attu:v2.4

访问:http://127.0.0.1:8000 但是当连接到数据库时,会报如下错误; image.png

原因在于:Milvus使用docker-compose安装,指定了网络,而且attu单独启动,导致网络不通。我们将attu安装加入到docker-compose.yml文件中,该文件可以在示例项目中找到。

安装客户端:github.com/zilliztech/…

代码实战

依赖包

<dependency>
    <groupId>dev.langchain4j</groupId>
    <artifactId>langchain4j-ollama-spring-boot-starter</artifactId>
    <version>${langchain4j.version}</version>
</dependency>
<dependency>
    <groupId>dev.langchain4j</groupId>
    <artifactId>langchain4j-milvus-spring-boot-starter</artifactId>
    <version>${langchain4j.version}</version>
</dependency>

yml 配置

spring:
  application:
    name: vector-store

langchain4j:
  ollama:
    embedding-model:
      base-url: http://localhost:11434
      model-name: qwen:7b
  milvus:
    host: 127.0.0.1
    port: 19530
    database-name: milvus_embedding

server:
  port: 8808

Service 实现

在Service中实现两种方法:1.仅存向量,2.向量 + 存储元数据和文本数据

推荐我们使用第二种方式,在相似性查询时需要,同时元数据也有一定的用处!!!

package org.ivy.vectorstore.store;

import dev.langchain4j.data.document.Metadata;
import dev.langchain4j.data.embedding.Embedding;
import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.model.embedding.EmbeddingModel;
import dev.langchain4j.model.output.Response;
import dev.langchain4j.store.embedding.milvus.MilvusEmbeddingStore;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;

@Component
@RequiredArgsConstructor
public class VectorStoreService {
    private final EmbeddingModel embeddingModel;
    private final MilvusEmbeddingStore milvusEmbeddingStore;

    /**
     * 仅存储了向量,没有原文本和元数据信息
     *
     * @param text 要嵌入的文本
     * @return 生成ID
     */
    public String embedding(String text) {
        Response<Embedding> embed = embeddingModel.embed(text);
        return milvusEmbeddingStore.add(embed.content());
    }

    /**
     * 带有元数据的方式
     *
     * @param text 要嵌入的文本
     * @return 生成ID
     */
    public String embeddingWithMeta(String text) {
        TextSegment textSegment = TextSegment.from(text, Metadata.from("userId", "1"));
        Response<Embedding> embed = embeddingModel.embed(textSegment);
        return milvusEmbeddingStore.add(embed.content(), textSegment);
    }
}

image.png

Controller

package org.ivy.vectorstore.controller;

import lombok.RequiredArgsConstructor;
import org.ivy.vectorstore.store.VectorStoreService;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequiredArgsConstructor
public class EmbeddingController {

    private final VectorStoreService vectorStoreService;

    @PostMapping("/embed")
    public String embed(@RequestBody String text) {
        return vectorStoreService.embedding(text);
    }

    @PostMapping("/embed-meta")
    public String embedMeta(@RequestBody String text) {
        return vectorStoreService.embeddingWithMeta(text);
    }
}

相似性查询使用

查询方法

public interface EmbeddingStore<Embedded> {
    default EmbeddingSearchResult<Embedded> search(EmbeddingSearchRequest request) {
        List<EmbeddingMatch<Embedded>> matches =
                findRelevant(request.queryEmbedding(), request.maxResults(), request.minScore());
        return new EmbeddingSearchResult<>(matches);
    }
}

@Deprecated
default List<EmbeddingMatch<Embedded>> findRelevant(Embedding referenceEmbedding, int maxResults, double minScore) {
    EmbeddingSearchRequest embeddingSearchRequest = EmbeddingSearchRequest.builder()
            .queryEmbedding(referenceEmbedding)
            .maxResults(maxResults)
            .minScore(minScore)
            .build();
    EmbeddingSearchResult<Embedded> embeddingSearchResult = search(embeddingSearchRequest);
    return embeddingSearchResult.matches();
}

仅使用这一个方法即可,像 findRelevant 方法都废弃,直接忘掉吧!一般在 xxxEmbeddingStore 都会重写 search方法的。

元数据过滤

统一过滤接口,提供一个test方法。默认提供 and、or、not 的Filter。

public interface Filter {
    // 判断是否满足条件,true:满足/false:不满足
    boolean test(Object object);

    default Filter and(Filter filter) {
        return and(this, filter);
    }

    static Filter and(Filter left, Filter right) {
        return new And(left, right);
    }

    default Filter or(Filter filter) {
        return or(this, filter);
    }

    static Filter or(Filter left, Filter right) {
        return new Or(left, right);
    }

    static Filter not(Filter expression) {
        return new Not(expression);
    }
}

Filter实现接口

名称功能使用示例
And同时满足姓名为张三并且年龄大于20:
Filter.and(new IsEqualTo("username","张三"), new IsGreaterThan("age",20))
Or满足其中之一姓名为张三或者年龄大于20:
Filter.or(new IsEqualTo("username","张三"), new IsGreaterThan("age",20))
Not不满足姓名不为张三:Filter.not(new IsEqualTo("username","张三"))
IsEqualTo等于用户ID为1:new IsEqualTo("userId", "1")
IsNotEqualTo不等于用户ID不为1:new IsNotEqualTo("userId", "1")
IsGreaterThan大于年龄大于18:new IsGreaterThan("age", 18)
IsGreaterThanOrEqualTo大于等于年龄大于等于18:new IsGreaterThanOrEqualTo("age", 18)
IsLessThan小于年龄小于18:new IsLessThan("age", 18)
IsLessThanOrEqualTo小于等于年龄小于等于18:new IsLessThanOrEqualTo("age", 18)
IsIn在..内用户ID在1,2,3 之间的:new IsIn("userId", List.of("1", "2","3"))
IsNotIn不在...内用户ID不在1,2,3 之间的:new IsNotIn("userId", List.of("1","2","3"))

创建Filter方式:

  • 使用 new 的方式。如上表中的方式
  • 使用 MetadataFilterBuilder。MetadataFilterBuilder.metadataKey("userId").isEqualTo("1")

相似度评分设置

EmbeddingSearchRequest 请求类中设置 minScore的值,其取值范围在 0~1 之间。如果想更加精确,就将值设置的大一点。

示例代码与总结

vector-store

介绍了LangChain4j框架支持的向量数据库,并使用LangChain4j + Omalla:qwen:7b 作为嵌入模型 + Milvus向量数据库,完成向量的存储,相似性搜索功能,特别对相似性搜索的元数据过滤进行说明。