Spring AI + bge-large + Milvus 构建私有化语义内容检索方案

一、Spring AI 构建私有化语义内容检索

Spring AISpring 官方社区项目,旨在简化 Java AI 应用程序开发,让 Java 开发者像使用 Spring 开发普通应用一样开发 AI 应用。

上篇文章我们基于 Spring AI MCP + DeepSeek R1 快速实现了数据库 ChatBI 能力。在AIGC领域,向量语义能力同样也有着十分重要的地位。因此本篇文章探索 Spring AI + bge-large-zh-v1.5 + Milvus 搭建一套私有化的内容语义检索能力方案。

其中 bge-large-zh-v1.5 本地私有化部署采用 vLLM 进行部署和优化。Spring AI 采用 1.0.0-SNAPSHOT 版本。

处理过程大致如下图所示:

在这里插入图片描述

实验前需要提前部署好 Milvus 向量库,如果不清楚可参考下面文章的介绍。

Milvus 向量数据库介绍及使用

在这里插入图片描述

二、vLLM 部署 bge-large-zh-v1.5 模型

如果不了解 vLLM ,可参考下面这篇文章中的介绍和使用过程:

利用 vLLM 优化部署私有化大模型,让推理速度飞起

首先使用 modelscope 下载模型:

modelscope download --model="BAAI/bge-large-zh-v1.5" --local_dir bge-large-zh-v1.5

加载模型,启动服务:

vllm serve bge-large-zh-v1.5 \
  --port 8070 \
  --dtype auto \
  --task embed \
  --trust-remote-code \
  --api-key token-abc123

在这里插入图片描述

使用 curl 测试服务接口是否正常:

curl http://localhost:8070/v1/embeddings \
    -H "Content-Type: application/json" \
    -H "Authorization: Bearer token-abc123" \
    -d '{
        "model": "bge-large-zh-v1.5",
        "input": [
            "你好,小毕超"
        ]
    }'

在这里插入图片描述

三、Spring AI 集成 OpenAI 格式向量模型

Spring AI 关于集成 OpenAI 格式向量模型的文档介绍如下:

https://docs.spring.io/spring-ai/reference/api/embeddings/openai-embeddings.html

新建 SpringBoot 项目,在 pom 中修改如下依赖:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <groupId>com.example</groupId>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>embedding</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>embedding</name>
    <description>embedding</description>
    <properties>
        <java.version>17</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <spring-boot.version>3.3.0</spring-boot.version>
        <spring-ai.version>1.0.0-SNAPSHOT</spring-ai.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-starter-model-openai</artifactId>
        </dependency>
        
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.ai</groupId>
                <artifactId>spring-ai-bom</artifactId>
                <version>${spring-ai.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>


    <repositories>
        <repository>
            <name>Central Portal Snapshots</name>
            <id>central-portal-snapshots</id>
            <url>https://central.sonatype.com/repository/maven-snapshots/</url>
            <releases>
                <enabled>false</enabled>
            </releases>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
        </repository>
        <repository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
        <repository>
            <id>spring-snapshots</id>
            <name>Spring Snapshots</name>
            <url>https://repo.spring.io/snapshot</url>
            <releases>
                <enabled>false</enabled>
            </releases>
        </repository>
    </repositories>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>17</source>
                    <target>17</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>${spring-boot.version}</version>
                <configuration>
                    <mainClass>com.example.embedding.EmbeddingApplication</mainClass>
                    <skip>true</skip>
                </configuration>
                <executions>
                    <execution>
                        <id>repackage</id>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

修改 application.yml 添加 vLLM 服务的链接信息:

spring:
  ai:
    openai:
      base-url: http://127.0.0.1:8070
      api-key: token-abc123
      embedding:
        options:
          model: bge-large-zh-v1.5

使用向量能力:

@SpringBootTest
class EmbeddingApplicationTests {

    @Resource
    private EmbeddingModel embeddingModel;

    @Test
    void embed() {
        EmbeddingResponse embeddingResponse = embeddingModel.call(
                new EmbeddingRequest(List.of("你好,小毕超", "你叫什么名字"),
                        OpenAiEmbeddingOptions.builder().build()));
        embeddingResponse.getResults().forEach(e -> {
            System.out.println("向量维度:" + e.getOutput().length);
            for (float f : e.getOutput()) System.out.print(f);
            System.out.println();
        });
    }
}

运行后可以看到向量模型的维度是 1024 维,已经向量化后的信息。

在这里插入图片描述

四、Spring AI 集成 Milvus 向量数据库实现持久化和语义检索能力

Spring AI 关于 Milvus 的文档如下:

https://docs.spring.io/spring-ai/reference/api/vectordbs/milvus.html#milvus-properties

pom 中增加 Milvus 依赖:

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-vector-store-milvus</artifactId>
</dependency>

application.yml 中添加 Milvus 的链接信息:

spring:
  ai:
    openai:
      base-url: http://127.0.0.1:8070
      api-key: token-abc123
      embedding:
        options:
          model: bge-large-zh-v1.5
    vectorstore:
      milvus:
        client:
          host: 127.0.0.1
          port: 19530
          username: "root"
          password: "milvus"
        databaseName: "default"
        collectionName: "vector_test"
        embeddingDimension: 1024
        indexType: IVF_FLAT
        metricType: COSINE
        initialize-schema: true

3.1 内容持久化至向量库中

@SpringBootTest
class MilvusTests {

    @Resource
    VectorStore vectorStore;

    @Test
    void toMinvus() {
        List<Document> documents = List.of(
                new Document("我的爱好是打篮球", Map.of("name", "张三", "age", 18)),
                new Document("我的爱好的是学习!", Map.of("name", "李四", "age", 30)),
                new Document("今天的天气是多云", Map.of("name", "王五", "age", 50)),
                new Document("我的心情非常愉悦", Map.of("name", "赵六", "age", 25)),
                new Document("我叫小毕超", Map.of("name", "小毕超", "age", 28))
        );
        vectorStore.add(documents);
    }
}

运行后,可在 Milvus 中看到自动创建的 collection

在这里插入图片描述

3.2 语义内容检索

@SpringBootTest
class MilvusTests {

    @Resource
    VectorStore vectorStore;

    @Test
    void similarity() {
        List<Document> results = vectorStore.similaritySearch(
                SearchRequest.builder()
                        .query("你叫什么名字")
                        .topK(3)
                        .similarityThreshold(0.2)
                        .build()
        );
        Optional.ofNullable(results).ifPresent(res->{
            res.forEach(d -> System.out.println(d.getText()+"   "+d.getMetadata()));
        });
    }
}

在这里插入图片描述

根据 meta 进行过滤检索,过滤表达可以使用文本表达式方式:

@Test
void similarity2() {
    List<Document> results = vectorStore.similaritySearch(
            SearchRequest.builder()
                    .query("你叫什么名字")
                    .topK(5)
                    .similarityThreshold(0.2)
                    .filterExpression("age > 15 && age < 30")
                    .build()
    );
    Optional.ofNullable(results).ifPresent(res->{
        res.forEach(d -> System.out.println(d.getText()+"   "+d.getMetadata()));
    });
}

在这里插入图片描述

其中表达式也可使用 api 的方式:

@Test
void similarity3() {
    FilterExpressionBuilder filter = new FilterExpressionBuilder();
    List<Document> results = vectorStore.similaritySearch(
            SearchRequest.builder()
                    .query("你叫什么名字")
                    .topK(5)
                    .similarityThreshold(0.2)
                    .filterExpression(
                            filter.and(filter.gt("age", 15), filter.lt("age", 30)).build()
                    ).build()
    );
    Optional.ofNullable(results).ifPresent(res->{
        res.forEach(d -> System.out.println(d.getText()+"   "+d.getMetadata()));
    });
}

可以得到同样的结果:

在这里插入图片描述

<think>我们正在构建一个RAG系统,使用LangChain作为框架,Milvus作为向量数据库,bge-m3作为嵌入模型。以下是实现步骤:1.**环境准备**:-安装必要的库:langchain,pymilvus,transformers等。-下载bge-m3模型(可以从ModelScope或HuggingFace下载)。2.**文档加载与处理**:-使用LangChain的文档加载器(如`TextLoader`、`DirectoryLoader`)加载文档。-对文档进行拆分(使用`RecursiveCharacterTextSplitter`等),注意设置合适的块大小和重叠。3.**嵌入模型**:-初始化bge-m3模型,该模型能够生成稠密向量(denseembeddings)和稀疏向量(sparseembeddings)。-由于Milvus支持混合搜索(hybridsearch),我们将同时使用这两种向量。4.**向量数据库(Milvus)**:-Milvus中创建两个集合(collections)或一个支持多向量的集合,分别存储稠密向量和稀疏向量(或者使用一个集合包含两个向量字段)。-注意:Milvus2.4及以上版本支持多向量字段,因此可以创建一个集合,包含两个向量字段:一个稠密向量字段和一个稀疏向量字段(注意Milvus对稀疏向量的支持情况,目前Milvus支持稀疏向量SPARSE_FLOAT_VECTOR)。5.**文档向量化与存储**:-将拆分后的文档通过bge-m3模型生成稠密向量和稀疏向量。-将文档块内容、元数据以及对应的两种向量存储到Milvus中。6.**检索**:-用户输入查询问题,同样使用bge-m3生成查询的稠密向量和稀疏向量。-使用Milvus的混合搜索(hybridsearch)功能,同时利用两种向量进行检索,并融合结果(例如使用RRF(ReciprocalRankFusion)或其他方法)。-返回最相关的文档块。7.**生成**:-检索到的文档块作为上下文,与用户问题一起输入到大语言模型(LLM)中,生成回答。下面我们详细说明每一步,并给出代码示例。###步骤1:环境准备首先,安装必要的库:```bashpipinstalllangchainpymilvustransformers```然后,下载bge-m3模型。由于模型较大,建议使用ModelScope(国内更快)或HuggingFace。这里使用ModelScope:```bashgitlfsinstallgitclonehttps://www.modelscope.cn/Xorbits/bge-m3.git```###步骤2:文档加载与处理假设我们有一个文档目录,里面存放着多个文本文件。```pythonfromlangchain.document_loadersimportDirectoryLoaderfromlangchain.text_splitterimportRecursiveCharacterTextSplitter#加载文档loader=DirectoryLoader('./docs',glob="**/*.txt")documents=loader.load()#拆分文档text_splitter=RecursiveCharacterTextSplitter(chunk_size=500,chunk_overlap=50)texts=text_splitter.split_documents(documents)```###步骤3:初始化嵌入模型我们使用`transformers`库加载bge-m3模型。注意,bge-m3可以同时生成稠密向量和稀疏向量(稀疏向量是SPLADE表示)。```pythonfromtransformersimportAutoModel,AutoTokenizermodel_path="path/to/bge-m3"#替换为实际路径tokenizer=AutoTokenizer.from_pretrained(model_path)model=AutoModel.from_pretrained(model_path)```###步骤4:设置Milvus向量数据库确保Milvus服务已经启动。然后,我们使用`pymilvus`连接Milvus,并创建集合。```pythonfrompymilvusimportconnections,FieldSchema,CollectionSchema,DataType,Collection,utility#连接Milvusconnections.connect("default",host="localhost",port="19530")#定义集合的字段fields=[FieldSchema(name="id",dtype=DataType.INT64,is_primary=True,auto_id=True),FieldSchema(name="text",dtype=DataType.VARCHAR,max_length=1000),FieldSchema(name="dense_vector",dtype=DataType.FLOAT_VECTOR,dim=1024),#bge-m3稠密向量维度为1024FieldSchema(name="sparse_vector",dtype=DataType.SPARSE_FLOAT_VECTOR)#稀疏向量]#创建集合schema=CollectionSchema(fields,"RAGusingbge-m3andMilvus")collection_name="bge_m3_docs"ifutility.has_collection(collection_name):utility.drop_collection(collection_name)collection=Collection(collection_name,schema)#创建索引(稠密向量使用IVF_FLAT,稀疏向量使用SPARSE_INVERTED_INDEX)index_params_dense={"index_type":"IVF_FLAT","metric_type":"L2","params":{"nlist":1024}}index_params_sparse={"index_type":"SPARSE_INVERTED_INDEX","metric_type":"IP"#内积}collection.create_index("dense_vector",index_params_dense)collection.create_index("sparse_vector",index_params_sparse)```###步骤5:文档向量化与存储对每个文本块,使用bge-m3生成稠密向量和稀疏向量,然后插入Milvus。```pythonimporttorchimportnumpyasnpfrompymilvusimportMilvusClient#初始化Milvus客户端client=MilvusClient(uri="http://localhost:19530",db_name="default")#准备批量插入的数据data=[]fortextintexts:#使用bge-m3生成向量inputs=tokenizer(text.page_content,return_tensors="pt")withtorch.no_grad():outputs=model(**inputs)#获取稠密向量dense_embedding=outputs.last_hidden_state.mean(dim=1).squeeze().numpy()#获取稀疏向量(SPLADE)sparse_embedding=outputs.logits.max(dim=1)[0].squeeze().numpy()#将稀疏向量转换为字典格式(索引:值)sparse_dict={idx:valueforidx,valueinenumerate(sparse_embedding)ifvalue!=0}data.append({"text":text.page_content,"dense_vector":dense_embedding.tolist(),"sparse_vector":sparse_dict})#插入数据client.insert(collection_name,data)```###步骤6:检索用户查询时,同样生成两种向量,然后进行混合搜索。```pythondefhybrid_search(query,top_k=5):#生成查询向量inputs=tokenizer(query,return_tensors="pt")withtorch.no_grad():outputs=model(**inputs)dense_embedding=outputs.last_hidden_state.mean(dim=1).squeeze().numpy()sparse_embedding=outputs.logits.max(dim=1)[0].squeeze().numpy()sparse_dict={idx:valueforidx,valueinenumerate(sparse_embedding)ifvalue!=0}#分别进行稠密搜索和稀疏搜索#稠密搜索dense_results=client.search(collection_name=collection_name,data=[dense_embedding.tolist()],anns_field="dense_vector",param={"metric_type":"L2","params":{"nprobe":10}},limit=top_k,output_fields=["text"])#稀疏搜索(注意:Milvus的稀疏向量搜索使用内积,且返回的分数是内积值)sparse_results=client.search(collection_name=collection_name,data=[sparse_dict],anns_field="sparse_vector",param={"metric_type":"IP"},limit=top_k,output_fields=["text"])#融合结果(简单示例:使用RRF)#为每个文档分配一个融合后的分数scores={}#处理稠密结果foridx,hitinenumerate(dense_results[0]):doc_id=hit.id#RRF:1/(rank+k)这里k设为60(常用值)rrf_score=1.0/(idx+60)scores[doc_id]=scores.get(doc_id,0)+rrf_score#处理稀疏结果foridx,hitinenumerate(sparse_results[0]):doc_id=hit.idrrf_score=1.0/(idx+60)scores[doc_id]=scores.get(doc_id,0)+rrf_score#按融合分数排序sorted_scores=sorted(scores.items(),key=lambdax:x[1],reverse=True)top_doc_ids=[doc_idfordoc_id,scoreinsorted_scores[:top_k]]#获取文档内容results=client.get(collection_name,ids=top_doc_ids,output_fields=["text"])return[res['text']forresinresults]```###步骤7:生成使用LangChain的LCEL(LangChainExpressionLanguage)或简单的链式调用,将检索到的文档作为上下文,输入到LLM中。```pythonfromlangchain.llmsimportOpenAIfromlangchain.promptsimportPromptTemplatefromlangchain.schema.runnableimportRunnablePassthrough#初始化LLMllm=OpenAI(api_key="your-openai-key")#定义提示模板template="""基于以下上下文,回答用户问题:{context}问题:{question}"""prompt=PromptTemplate.from_template(template)#定义RAG链defretrieve(question):returnhybrid_search(question)chain=({"context":retrieve,"question":RunnablePassthrough()}|prompt|llm)#调用链response=chain.invoke("用户的问题")print(response)```###注意事项1.**性能考虑**:bge-m3模型较大,推理速度较慢,可以考虑使用GPU加速。2.**混合搜索的融合策略**:这里使用了简单的RRF,也可以尝试其他融合方法,如加权分数等。3.**Milvus的稀疏向量支持**:确保Milvus版本支持稀疏向量(2.4及以上)。4.**文档分块大小**:根据实际文档调整分块大小和重叠。以上是一个完整的RAG系统实现方案,结合了LangChain、Milvusbge-m3的优势。###相关问题1.如何优化bge-m3模型的推理速度?2.Milvus中,如何调整索引参数以提高搜索性能?3.除了RRF,还有哪些融合检索结果的方法?4.如何处理大文档的嵌入,避免内存溢出?</think>### 使用 LangChain + Milvus + BGE-M3 实现 RAG 方案 以下是完整的实现方案,分为 6 个关键步骤: #### 1. **环境准备** ```python # 安装核心库 pip install langchain pymilvus sentence-transformers git clone https://www.modelscope.cn/Xorbits/bge-m3.git # 下载模型[^1] ``` #### 2. **初始化组件** ```python from langchain.document_loaders import TextLoader from langchain.text_splitter import RecursiveCharacterTextSplitter from pymilvus import connections, Collection from transformers import AutoModel, AutoTokenizer # 连接 Milvus connections.connect(host="localhost", port="19530") # 加载 BGE-M3 模型[^3] model = AutoModel.from_pretrained("./bge-m3") tokenizer = AutoTokenizer.from_pretrained("./bge-m3") ``` #### 3. **文档处理与向量化** ```python # 加载文档 loader = TextLoader("data.txt") documents = loader.load() # 文档分块(关键步骤)[^2] text_splitter = RecursiveCharacterTextSplitter( chunk_size=512, chunk_overlap=50 ) chunks = text_splitter.split_documents(documents) # 生成混合向量 def get_embeddings(text): inputs = tokenizer(text, return_tensors="pt") outputs = model(**inputs) dense_embed = outputs.last_hidden_state.mean(dim=1).squeeze().tolist() sparse_embed = outputs.logits.max(dim=1)[0].squeeze().numpy() return dense_embed, sparse_embed ``` #### 4. **Milvus 向量库配置** ```python # 创建集合(支持混合向量)[^4] collection = Collection.create( "rag_collection", fields=[ {"name": "id", "type": "INT64", "is_primary": True}, {"name": "text", "type": "VARCHAR", "max_length": 1000}, {"name": "dense_vector", "type": "FLOAT_VECTOR", "dim": 1024}, {"name": "sparse_vector", "type": "SPARSE_FLOAT_VECTOR"} ] ) # 创建双索引 collection.create_index( field_name="dense_vector", index_params={"index_type": "IVF_FLAT", "metric_type": "L2"} ) collection.create_index( field_name="sparse_vector", index_params={"index_type": "SPARSE_INVERTED_INDEX"} ) ``` #### 5. **检索增强流程** ```python from langchain.chains import RetrievalQA from langchain.llms import OpenAI # 混合检索函数 def hybrid_search(query, top_k=5): dense_emb, sparse_emb = get_embeddings(query) # 并行执行双向量检索 dense_results = collection.search( data=[dense_emb], anns_field="dense_vector", limit=top_k ) sparse_results = collection.search( data=[sparse_emb], anns_field="sparse_vector", limit=top_k ) # 结果融合 (RRF 算法) return reciprocal_rank_fusion(dense_results, sparse_results) # 构建 RAG 链 qa_chain = RetrievalQA.from_chain_type( llm=OpenAI(temperature=0), chain_type="stuff", retriever=hybrid_search, return_source_documents=True ) ``` #### 6. **运行与优化** ```python # 插入文档向量 for chunk in chunks: dense_vec, sparse_vec = get_embeddings(chunk.page_content) collection.insert([ {"text": chunk.page_content}, {"dense_vector": dense_vec}, {"sparse_vector": sparse_vec} ]) # 执行查询 response = qa_chain("什么是量子纠缠?") print(f"答案: {response['result']}") print(f"来源: {response['source_documents'][0].page_content}") ``` ### 关键优势说明 1. **多粒度检索**:BGE-M3 支持词级/句级/段落级嵌入[^3],提升语义捕获能力 2. **混合搜索**:同时利用稠密向量的语义信息和稀疏向量的关键词信息[^4] 3. **性能优化**: - 分块大小建议 256-512 字符[^2] - 使用 `IVF_FLAT` + `SPARSE_INVERTED_INDEX` 组合索引 - 批处理文档插入(每次 500-1000 条) ### 常见问题解决 - **OOM 错误**:减小分块尺寸或启用 `model.half()` 半精度 - **低召回率**:调整分块重叠(建议 10-15%)或尝试不同分块策略 - **延迟过高**:在 Milvus 中启用 GPU 加速或增加 `nprobe` 参数 ### 相关问题 1. 如何评估 RAG 系统中检索结果的质量? 2. BGE-M3 与其他嵌入模型(如 OpenAI Embeddings)相比有何优势? 3. 在资源受限环境中如何优化 Milvus 的性能? 4. 如何处理 RAG 中的长文档上下文溢出问题? 5. 如何实现基于 RAG 的多轮对话系统? [^1]: 从 huggingface 或国内的 modelscope 下载 BGE-M3 模型 [^2]: 文档拆分的注意点,embedding model, chat model区别 [^3]: BGE-M3:Multi-Functionality Multi-Linguisticity Multi-Granularity [^4]: 混合搜索同时使用稠密向量和稀疏向量增强准确性
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小毕超

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值