all-MiniLM-L6-v2部署教程:Ollama + FastAPI封装REST接口,供Java/Go后端调用

你是不是也遇到过这样的问题?想在自己的Java或Go后端项目里用上强大的文本语义理解能力,比如做智能搜索、文档分类或者问答系统,但一看到动辄几个G的大模型就头疼?部署复杂、资源消耗大、调用不方便,让很多开发者望而却步。

今天,我就带你解决这个痛点。我们将使用一个轻量级但能力不俗的模型——all-MiniLM-L6-v2,通过Ollama来一键部署,再用FastAPI封装成标准的REST接口。这样一来,你的Java或Go后端服务,就能像调用普通HTTP API一样,轻松获得文本嵌入向量,实现各种AI功能。

整个过程非常简单,即使你之前没怎么接触过Python或AI模型部署,也能跟着一步步做下来。我们最终的目标是:让你在30分钟内,拥有一个高性能、可随时调用的文本嵌入服务。

1. 环境准备与工具介绍

在开始动手之前,我们先快速了解一下今天要用到的几个核心工具。别担心,它们都非常友好,安装和使用都很简单。

1.1 主角登场:all-MiniLM-L6-v2

首先是我们今天的主角,all-MiniLM-L6-v2。你可以把它理解为一个“文本理解专家”。

  • 它是什么:一个专门将文本(比如一句话、一段描述)转换成一串数字(我们叫它“向量”或“嵌入”)的模型。这串数字就像是文本的“数字指纹”,包含了文本的语义信息。
  • 它有多强:虽然它体积很小(只有大约23MB),但能力不容小觑。它基于著名的BERT架构,通过一种叫“知识蒸馏”的技术,从更大的老师模型那里学到了精髓,所以在很多任务上表现接近大模型,但速度要快得多。
  • 为什么选它:对于大多数后端应用场景,比如计算用户查询和商品描述的相似度、给新闻文章自动分类、或者在海量文档中快速找到相关内容,这个模型的精度完全够用。最关键的是,它速度快、资源占用少,非常适合集成到在线服务中。

简单来说,你给它一段文字,它就还你一个能代表这段文字含义的“数字密码”。后续的相似度计算、分类等操作,都是基于这个“数字密码”来进行的。

1.2 得力助手:Ollama

接下来是Ollama,它是我们部署模型的“瑞士军刀”。

  • 它做什么:Ollama专门用来在本地(或者你的服务器上)快速运行各种开源大语言模型。它把复杂的模型下载、环境配置、运行命令都打包好了,你只需要几条简单的命令就能让模型跑起来。
  • 为什么用它:没有Ollama之前,部署一个模型可能要折腾各种Python环境、依赖库、版本冲突。有了Ollama,就像是给模型提供了一个即开即用的“运行容器”,大大降低了门槛。它本身也提供了一个基础的API,但我们今天要把它包装得更易用。

1.3 桥梁工程师:FastAPI

最后是FastAPI,它是我们搭建API服务的“快速框架”。

  • 它做什么:FastAPI是一个现代的Python Web框架,用来构建API接口速度快如闪电。我们将用它来创建一个Web服务,这个服务接收来自Java或Go后端的HTTP请求,然后去调用Ollama运行的模型,最后把结果返回回去。
  • 为什么用它:它代码简洁、性能高,并且能自动生成交互式API文档。这样,你的后端同事一看文档就知道怎么调用了,联调效率非常高。

好了,工具介绍完毕。总结一下我们的技术方案:用Ollama拉取并运行all-MiniLM-L6-v2模型,然后用FastAPI写一个简单的Web应用作为中间层,对外提供RESTful API。你的Java/Go服务通过HTTP调用这个API即可。

下面,我们就开始一步步实现它。

2. 第一步:使用Ollama部署模型服务

这是最基础的一步,我们要先把模型跑起来。确保你的机器上已经安装了Docker,这是运行Ollama最简单的方式。

2.1 拉取并运行Ollama

打开你的终端(Linux/Mac)或命令提示符/PowerShell(Windows),执行下面的命令。这会在后台启动一个Ollama服务。

docker run -d -v ollama:/root/.ollama -p 11434:11434 --name ollama ollama/ollama

命令解释

  • docker run -d:在后台运行一个容器。
  • -v ollama:/root/.ollama:把容器里的模型数据目录挂载到本地一个叫ollama的卷上,这样即使容器删除,下载的模型还在。
  • -p 11434:11434:把容器内部的11434端口映射到本机的11434端口。Ollama的API就在这个端口上。
  • --name ollama:给这个容器起个名字,方便管理。
  • ollama/ollama:要运行的Ollama镜像。

运行成功后,你可以用 docker ps 命令看到这个名为ollama的容器正在运行。

2.2 拉取并运行all-MiniLM-L6-v2模型

Ollama服务跑起来后,它自己还不知道我们要用什么模型。我们需要告诉它去下载我们指定的模型。还是在终端里,执行:

docker exec -it ollama ollama pull nomic-embed-text

这里有个重要说明:在Ollama的官方模型库中,all-MiniLM-L6-v2这个模型被命名为 nomic-embed-text。所以我们是拉取这个名字的模型。这个过程会下载模型文件,因为模型很小,所以很快。

下载完成后,我们需要在Ollama容器内部启动这个模型的服务:

docker exec -d ollama ollama run nomic-embed-text

这个命令会在Ollama容器内部后台运行nomic-embed-text模型。现在,模型服务已经在Ollama内部准备就绪,并监听请求了。

到这一步,我们的“文本理解专家”已经上线了,它正待在Ollama这个“运行容器”里,等着我们通过端口11434去调用它。接下来,我们就来搭建一个更友好的“前台接待处”——FastAPI服务。

3. 第二步:用FastAPI封装REST接口

现在模型服务跑起来了,但它的接口可能不太符合你后端团队的习惯。我们用一个FastAPI应用来包装它,提供更标准、更清晰的REST API。

首先,创建一个新的项目目录,比如叫做 embedding_api,然后进入这个目录。

3.1 创建Python环境与依赖文件

在项目根目录下,创建一个名为 requirements.txt 的文件,里面写上我们需要的Python库:

fastapi==0.104.1
uvicorn[standard]==0.24.0
requests==2.31.0
pydantic==2.5.0

然后,建议你创建一个Python虚拟环境来安装这些依赖,避免污染全局环境。

# 创建虚拟环境(根据你的Python版本,这里以python3为例)
python3 -m venv venv

# 激活虚拟环境
# Linux/Mac:
source venv/bin/activate
# Windows:
# venv\Scripts\activate

# 安装依赖
pip install -r requirements.txt

3.2 编写核心的FastAPI应用代码

在项目根目录下,创建一个 main.py 文件,这就是我们服务的主文件。将下面的代码复制进去:

from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from typing import List
import requests
import logging

# 配置日志,方便查看运行情况
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

# 创建FastAPI应用实例
app = FastAPI(
    title="文本嵌入向量服务 API",
    description="基于Ollama和all-MiniLM-L6-v2模型,提供文本生成嵌入向量的REST接口。",
    version="1.0.0"
)

# 定义请求体的数据模型:接收一个字符串列表
class EmbeddingRequest(BaseModel):
    texts: List[str]

# 定义响应体的数据模型:返回向量列表和模型信息
class EmbeddingResponse(BaseModel):
    embeddings: List[List[float]]
    model: str
    total_tokens: int

# Ollama服务的地址和端点
OLLAMA_BASE_URL = "http://localhost:11434"
EMBEDDING_ENDPOINT = "/api/embed"

@app.get("/")
async def root():
    """健康检查端点,返回服务状态。"""
    return {"status": "ok", "message": "文本嵌入向量服务正在运行。"}

@app.post("/v1/embeddings", response_model=EmbeddingResponse)
async def create_embeddings(request: EmbeddingRequest):
    """
    生成文本的嵌入向量。
    
    - **texts**: 需要生成嵌入向量的文本列表,例如 ["今天天气真好", "我喜欢编程"]。
    """
    if not request.texts:
        raise HTTPException(status_code=400, detail="文本列表不能为空")
    
    logger.info(f"收到嵌入请求,文本数量:{len(request.texts)}")
    
    embeddings = []
    total_tokens = 0
    
    # 循环处理每个文本,调用Ollama接口
    for text in request.texts:
        payload = {
            "model": "nomic-embed-text",
            "prompt": text
        }
        
        try:
            # 向Ollama服务发送请求
            response = requests.post(
                f"{OLLAMA_BASE_URL}{EMBEDDING_ENDPOINT}",
                json=payload,
                timeout=30  # 设置超时时间
            )
            response.raise_for_status()  # 如果响应状态码不是200,抛出异常
            result = response.json()
            
            # 从Ollama响应中提取嵌入向量
            if "embedding" in result:
                embeddings.append(result["embedding"])
                total_tokens += result.get("total_tokens", 0)
            else:
                logger.error(f"Ollama响应中未找到'embedding'字段: {result}")
                raise HTTPException(status_code=500, detail="模型服务返回格式错误")
                
        except requests.exceptions.RequestException as e:
            logger.error(f"调用Ollama接口失败: {e}")
            raise HTTPException(status_code=503, detail=f"模型服务暂时不可用: {str(e)}")
        except Exception as e:
            logger.error(f"处理文本时发生未知错误: {e}")
            raise HTTPException(status_code=500, detail="内部服务器错误")
    
    logger.info(f"嵌入向量生成成功,共处理{len(embeddings)}个文本。")
    
    # 构造并返回标准响应
    return EmbeddingResponse(
        embeddings=embeddings,
        model="all-MiniLM-L6-v2 (via Ollama)",
        total_tokens=total_tokens
    )

if __name__ == "__main__":
    import uvicorn
    # 启动服务,监听在本机的8000端口
    uvicorn.run(app, host="0.0.0.0", port=8000)

代码关键点解读

  1. 定义标准接口:我们创建了一个 POST /v1/embeddings 的接口。这模仿了OpenAI等主流AI服务的API格式,让你的后端调用起来感觉更熟悉。
  2. 数据验证:使用Pydantic的BaseModel来定义请求和响应的格式。这能自动验证传入的数据是否合法,比如texts字段是不是一个字符串列表。
  3. 错误处理:代码里包含了详细的错误处理。如果Ollama服务没响应,或者返回的数据不对,我们的API会返回明确的错误信息(如503服务不可用、500内部错误),而不是直接崩溃。
  4. 日志记录:加了日志功能,服务运行起来后,你可以在控制台看到谁调用了、处理了多少文本,方便排查问题。
  5. 超时设置:请求Ollama时设置了30秒超时,防止某个请求卡住整个服务。

3.3 启动FastAPI服务

代码写好了,现在让我们启动它。在项目根目录下(确保虚拟环境已激活),运行:

python main.py

你会看到类似下面的输出,说明服务启动成功了:

INFO:     Started server process [12345]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)

太好了!现在你的FastAPI服务已经在http://localhost:8000上运行了。打开浏览器,访问 http://localhost:8000/docs,你会看到一个自动生成的、非常漂亮的交互式API文档页面。你可以在这里直接测试接口,而不需要写任何客户端代码。

模型服务和API网关都准备好了,接下来,我们看看如何从你的Java或Go后端来调用这个服务。

4. 第三步:Java与Go后端调用示例

我们的FastAPI服务提供了一个标准的HTTP接口,这意味着任何能发送HTTP请求的语言都可以调用它。这里我分别给出Java(使用Spring Boot框架)和Go语言的简单调用示例。

4.1 Java (Spring Boot) 调用示例

假设你有一个Spring Boot项目,你可以使用RestTemplate或者更现代的WebClient来调用我们的嵌入服务。

首先,你需要在pom.xml中添加Spring Boot Web的依赖(如果还没有的话)。

然后,创建一个服务类EmbeddingService.java

import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.util.List;

@Service
public class EmbeddingService {
    
    // 你的FastAPI服务地址
    private static final String API_URL = "http://localhost:8000/v1/embeddings";
    private final RestTemplate restTemplate;
    
    public EmbeddingService(RestTemplateBuilder restTemplateBuilder) {
        this.restTemplate = restTemplateBuilder.build();
    }
    
    /**
     * 调用远程嵌入服务,获取文本的向量表示
     * @param texts 文本列表
     * @return 嵌入向量响应
     */
    public EmbeddingResponse getEmbeddings(List<String> texts) {
        // 1. 构造请求体
        EmbeddingRequest request = new EmbeddingRequest();
        request.setTexts(texts);
        
        // 2. 设置请求头
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        
        HttpEntity<EmbeddingRequest> entity = new HttpEntity<>(request, headers);
        
        // 3. 发送POST请求
        ResponseEntity<EmbeddingResponse> response = restTemplate.exchange(
                API_URL,
                HttpMethod.POST,
                entity,
                EmbeddingResponse.class
        );
        
        // 4. 返回响应体
        if (response.getStatusCode() == HttpStatus.OK && response.getBody() != null) {
            return response.getBody();
        } else {
            throw new RuntimeException("调用嵌入服务失败: " + response.getStatusCode());
        }
    }
    
    // 内部类:定义请求体结构(对应Python端的EmbeddingRequest)
    @Data
    public static class EmbeddingRequest {
        @JsonProperty("texts")
        private List<String> texts;
    }
    
    // 内部类:定义响应体结构(对应Python端的EmbeddingResponse)
    @Data
    public static class EmbeddingResponse {
        @JsonProperty("embeddings")
        private List<List<Double>> embeddings;
        @JsonProperty("model")
        private String model;
        @JsonProperty("total_tokens")
        private Integer totalTokens;
    }
}

使用方式:在你的Controller或业务逻辑里,注入这个EmbeddingService,然后调用getEmbeddings方法,传入一个字符串列表(比如["查询文本", "文档内容"]),就能得到一个向量列表。每个向量就是一个List<Double>

4.2 Go语言调用示例

在Go语言中,我们可以使用标准库的net/http或者第三方库如github.com/go-resty/resty。这里展示标准库的方式。

创建一个文件,比如 embedding_client.go

package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
)

// 定义请求和响应的结构体
type EmbeddingRequest struct {
	Texts []string `json:"texts"`
}

type EmbeddingResponse struct {
	Embeddings [][]float64 `json:"embeddings"`
	Model      string      `json:"model"`
	TotalTokens int         `json:"total_tokens"`
}

// GetEmbeddings 调用嵌入服务
func GetEmbeddings(texts []string) (*EmbeddingResponse, error) {
	// 1. 构造请求体
	request := EmbeddingRequest{Texts: texts}
	jsonData, err := json.Marshal(request)
	if err != nil {
		return nil, fmt.Errorf("序列化请求失败: %v", err)
	}

	// 2. 创建HTTP请求
	req, err := http.NewRequest("POST", "http://localhost:8000/v1/embeddings", bytes.NewBuffer(jsonData))
	if err != nil {
		return nil, fmt.Errorf("创建请求失败: %v", err)
	}
	req.Header.Set("Content-Type", "application/json")

	// 3. 发送请求
	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		return nil, fmt.Errorf("发送请求失败: %v", err)
	}
	defer resp.Body.Close()

	// 4. 检查响应状态
	if resp.StatusCode != http.StatusOK {
		body, _ := io.ReadAll(resp.Body)
		return nil, fmt.Errorf("服务返回错误: %s, 详情: %s", resp.Status, string(body))
	}

	// 5. 解析响应体
	var embeddingResp EmbeddingResponse
	if err := json.NewDecoder(resp.Body).Decode(&embeddingResp); err != nil {
		return nil, fmt.Errorf("解析响应失败: %v", err)
	}

	return &embeddingResp, nil
}

func main() {
	// 示例:调用函数
	texts := []string{"Go语言性能很高", "Python以简洁易用著称"}
	result, err := GetEmbeddings(texts)
	if err != nil {
		fmt.Printf("调用失败: %v\n", err)
		return
	}

	fmt.Printf("使用的模型: %s\n", result.Model)
	fmt.Printf("消耗的总token数: %d\n", result.TotalTokens)
	fmt.Printf("第一个文本的向量维度: %d\n", len(result.Embeddings[0]))
	// 你可以在这里使用result.Embeddings进行后续操作,比如计算相似度
}

使用方式:将GetEmbeddings函数集成到你的Go项目中。调用时传入字符串切片,函数会返回一个包含向量切片的EmbeddingResponse结构体指针和一个错误。你可以用这些向量来做余弦相似度计算等操作。

看到这里,你的后端服务已经能够通过简单的HTTP调用,获得强大的文本语义向量了。无论是用Java还是Go,集成起来都非常方便。

5. 进阶使用与优化建议

基础服务搭建好了,但在实际生产环境中,我们还需要考虑更多。这里分享几个进阶思路和优化建议,让你的嵌入服务更健壮、更高效。

5.1 如何计算文本相似度?

拿到文本的嵌入向量后,最常用的操作就是计算相似度。这里给你一个Python示例,展示如何计算两个向量之间的余弦相似度(最常用的方法):

import numpy as np

def cosine_similarity(vec_a, vec_b):
    """
    计算两个向量的余弦相似度。
    值越接近1,表示越相似;越接近0,表示越不相关。
    """
    a = np.array(vec_a)
    b = np.array(vec_b)
    # 点积除以模长的乘积
    dot_product = np.dot(a, b)
    norm_a = np.linalg.norm(a)
    norm_b = np.linalg.norm(b)
    return dot_product / (norm_a * norm_b)

# 示例:假设从API拿到了两个文本的向量
vector1 = [0.1, 0.5, -0.2, ...] # 文本"A"的向量
vector2 = [0.12, 0.48, -0.19, ...] # 文本"B"的向量

similarity = cosine_similarity(vector1, vector2)
print(f"文本A和文本B的余弦相似度为: {similarity:.4f}")

你可以把这个计算逻辑放在FastAPI服务里,提供一个 /v1/similarity 的接口,直接返回相似度分数;也可以放在你的Java/Go后端里,拿到向量后自己算。

5.2 性能优化与生产部署建议

  1. 批处理支持:我们的示例代码是循环调用Ollama接口,一次处理一个文本。你可以修改FastAPI代码,尝试构造一个批量的prompt(如果模型支持)发送给Ollama,或者使用异步请求(asyncio + aiohttp)来并发调用,这样可以大幅提升处理一批文本的速度。
  2. 服务高可用
    • 多实例负载均衡:使用Nginx或云负载均衡器,在后面部署多个FastAPI服务实例和Ollama实例。
    • 健康检查:为FastAPI服务添加/health端点,让负载均衡器能判断实例是否健康。
    • 容器化部署:将Ollama和你的FastAPI服务都打包成Docker镜像,使用Docker Compose或Kubernetes来编排,管理起来更方便。
  3. 配置管理:不要把Ollama服务的地址(localhost:11434)硬编码在代码里。使用环境变量或配置文件来管理,这样在不同环境(开发、测试、生产)部署时更容易切换。
  4. 限流与鉴权:生产环境的API一定要考虑安全。使用FastAPI的中间件,可以很容易地添加速率限制(防止被刷)、API密钥认证等机制。

5.3 常见问题排查(FAQ)

  • Q: 调用FastAPI接口超时或失败?
    • A: 首先检查Ollama容器是否在运行 (docker ps)。然后检查FastAPI服务日志,看错误信息。最常见的问题是Ollama服务没启动,或者网络端口不通。
  • Q: 返回的向量维度是多少?
    • A: all-MiniLM-L6-v2模型生成的向量维度是384。你拿到手的每个embedding列表长度就是384。
  • Q: 支持多长的文本?
    • A: 这个模型最大支持256个token(约等于180-200个汉字)。超过长度的文本会被截断。对于长文档,常见的做法是分段处理,然后取各段向量的平均值或使用其他池化策略。
  • Q: 如何更新模型?
    • A: 如果需要更新Ollama中的模型,可以执行 docker exec ollama ollama pull nomic-embed-text 拉取最新版,然后重启Ollama容器中的模型进程。

6. 总结

回顾一下我们今天完成的事情:我们成功地将一个轻量级但强大的文本嵌入模型all-MiniLM-L6-v2,通过Ollama和FastAPI,封装成了一个标准的、易于调用的REST API服务。

整个流程的核心价值在于

  1. 简化部署:利用Ollama,我们避免了复杂的Python环境和模型依赖管理,一条命令就让模型跑了起来。
  2. 统一接口:通过FastAPI,我们为模型套上了一层标准化的“外衣”,提供了清晰的API文档和稳定的HTTP接口。
  3. 跨语言调用:无论你的主力后端是Java(Spring Boot)、Go,还是Python、Node.js,现在都可以通过简单的HTTP客户端,以相同的方式获取文本的语义向量。

这个方案特别适合那些不想在业务代码中直接耦合复杂Python AI栈的团队。AI模型服务被独立部署和运维,后端业务代码只需关注HTTP调用和业务逻辑,架构清晰,职责分离。

你现在拥有的,不再只是一个模型,而是一个随时待命的企业级文本理解微服务。你可以用它来增强搜索、优化推荐、分类内容、检测相似问题……想象空间很大。希望这篇教程能帮你顺利跨出AI能力落地的第一步。


获取更多AI镜像

想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐