LangChain4j + Milvus向量数据库实战

6,966 阅读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向量数据库,完成向量的存储,相似性搜索功能,特别对相似性搜索的元数据过滤进行说明。