在上一篇文章中Spring AI :Java 生态原生 AI 框架入门指南,我们初步认识了 Spring AI 的核心概念与快速上手方法,体验了与 Deepseek 模型的对话交互。而在 AI 的高级场景中,如语义搜索、聚类分析、检索增强生成(RAG),Embedding 技术是不可或缺的基础。本文将深入拆解 Embedding 的核心原理与应用场景,以智普 AI 为例,带大家实操文本向量化、相似文本查找,并铺垫 RAG 技术的核心逻辑,帮助大家掌握 Spring AI 中 Embedding 模块的使用精髓。

一、什么是 Embedding?核心原理与价值

(一)Embedding 定义
Embedding 直译是 “嵌入”,在 AI 领域特指将非结构化数据(如文本、图像、视频)转换为低维稠密数字向量的技术。这些向量并非随机生成,而是通过模型学习数据的语义信息,最终在向量空间中形成有意义的分布 ——向量之间的距离越近,代表原始数据的语义相似度越高。

例如,“咖啡” 和 “美式咖啡” 的向量距离会非常近,而 “咖啡” 和 “登山鞋” 的向量距离则会很远。这种特性让计算机能够 “理解” 文本的语义,而非仅仅识别字符本身,为后续的语义相关操作提供了可能。

(二)Spring AI 中的 Embedding 优势
Spring AI 通过 EmbeddingModel 接口对 Embedding 技术进行了统一封装,带来两大核心优势:

多模型兼容:支持 OpenAI、Titan、Azure、Ollama、智普 AI 等主流 Embedding 模型,切换模型仅需修改配置,无需改动业务代码。
调用简单:开发者无需关注模型底层实现,通过简洁的 API 即可完成文本向量化操作,同时支持单文本、多文本批量向量化。

(三)Embedding 核心应用场景
Embedding 的应用场景覆盖了 AI 开发的多个核心领域,具体包括:

相似度计算 / 语义搜索:将查询文本与文档库都转换为向量,通过向量检索快速找到语义相似的文档。
聚类与分类:将文本向量输入传统机器学习算法(如 K-Means),实现文本自动分组或分类。
检索增强生成(RAG):向量化私有知识库,让大模型结合外部知识生成更准确的回答(后续重点讲解)。
推荐系统:基于用户兴趣文本的向量,推荐语义相似的内容、商品或问答。
异常检测:识别语义偏离正常范围的异常内容(如垃圾评论、违规文本)。

二、基于 Embedding 向量的文本相似度核心算法

实现相似文本检索的核心是通过算法计算两个 Embedding 向量的相似度。Embedding 向量的核心价值,正是依托相似度计算得以落地;常见的文本相似度算法有:余弦相似度、欧氏距离、曼哈顿距离 等算法方案,这些算法适配不同业务场景,下文将对各类主流算法展开详细解析:

1. 余弦相似度(Cosine Similarity)
核心逻辑:衡量两个向量在空间中的夹角余弦值,取值范围 [-1, 1],越接近 1 表示语义相似度越高。
公式:
在这里插入图片描述

核心优势:仅关注向量 “方向”(语义核心),忽略 “长度” 差异,适配 Embedding 向量的语义特性。
适用场景:绝大多数通用场景(语义搜索、RAG 检索、短 / 长文本匹配),是主流 Embedding 模型的默认算法。

2. 欧氏距离(Euclidean Distance)
核心逻辑:计算两个向量在空间中的直线距离,距离越小,语义相似度越高,通常归一化到 [0,1] 区间使用。
公式:
在这里插入图片描述

核心优势:计算逻辑直观,能反映向量空间中的绝对距离关系。
适用场景:向量维度较低(<100 维)、数据分布均匀的场景,如简单文本聚类、短文本快速筛选。

3. 曼哈顿距离(Manhattan Distance)
核心逻辑:又称 “城市街区距离”,计算向量各维度差值的绝对值之和,值越小相似度越高,归一化后适配比较场景。
公式:
在这里插入图片描述

核心优势:对异常值不敏感,计算速度快,适合海量数据快速匹配。
适用场景:含噪声的文本数据(如口语化查询、不规范输入)、批量文本初步筛选。

主流 Embedding 大模型的算法选择
当前 OpenAI text-embedding-3、智普 embedding-2、通义千问 Embedding 等主流模型,默认均以余弦相似度作为核心匹配算法,部分会做工程优化:

选型建议:实际开发中优先使用余弦相似度,仅在特殊场景(如含噪声、高维稀疏数据)切换其他算法。

二、实操准备:智普 AI 环境配置

在 Spring AI 中使用 Embedding,需要选择支持该功能的 AI 模型(Deepseek 目前暂不提供 Embedding 模型)。本文选择智普 AI 的 embedding-2 模型进行演示,需先完成以下准备工作:

1. 智普 AI 账号准备
官网地址:https://open.bigmodel.cn/
API Key 获取:登录后进入「用户中心 → 项目管理 → API Keys」创建密钥。
官方文档:https://open.bigmodel.cn/dev/howuse/introduction(了解更多模型参数细节)。

2. 创建 Spring Boot 项目
项目名称:Weiz-SpringAI-Embedding
核心配置:JDK 17、Spring Boot 3.5.0、Maven
依赖选择:Spring Web(后续通过 pom.xml 补充 Embedding 相关依赖)

项目结构如下:

Weiz-SpringAI-Embedding/
├── src/
│   ├── main/
│   │   ├── java/
│   │   │   └── com/
│   │   │       └── example/
│   │   │           └── weizspringai/
│   │   │               ├── WeizSpringAiEmbeddingApplication.java
│   │   │               ├── controller/
│   │   │               └── service/
│   │   └── resources/
│   │       ├── application.properties
│   └── test/
└── pom.xml

3. 配置 pom.xml 依赖
导入 Spring AI BOM 与智普 AI 相关依赖,同时引入文档处理工具包:

<?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">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.example</groupId>
        <artifactId>Weiz-SpringAI</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
    <artifactId>Weiz-SpringAI-Embedding</artifactId>
    <name>Weiz-SpringAI-Embedding</name>
    <description>Weiz-SpringAI-Embedding</description>

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

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

        <!-- spring-ai-client-chat 中包括 TokenTextSplitterTextReaderDocument 等工具 -->
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-client-chat</artifactId>
            <version>${spring-ai.version}</version>
        </dependency>

    </dependencies>
</project>

4. 配置 application.properties
在 src/main/resources/application.properties 中配置智普 AI 密钥、模型信息:

# 应用名称
sspring.application.name=WeizSpringAIEmbedding

server.port=8080

# 智谱 AI 地址 
spring.ai.zhipuai.api-key=你的智普 AI API Key
spring.ai.zhipuai.base-url=https://open.bigmodel.cn/api/paas
spring.ai.zhipuai.embedding.options.model=embedding-2

spring.ai.zhipuai.chat.options.model=GLM-4-Flash

注意,需要替换 你的智普 AI API Key 为实际获取的密钥,配置完成后即可开始 Embedding 相关开发。

三、实操案例 1:文本向量化基础实现

1. 核心思路
通过 Spring AI 提供的 EmbeddingModel 接口,调用智普 AI 的 embedding-2 模型,将用户输入的文本转换为 1024 维的向量(embedding-2 模型默认输出维度),并返回给前端。

2. 编写 EmbeddingController
创建 com.example.weizspringai.controller.EmbeddingController 类,注入 EmbeddingModel 并提供接口:

import org.springframework.ai.embedding.EmbeddingModel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;

@RestController
@RequestMapping("/ai")
public class EmbeddingController {
    // 自动注入 EmbeddingModel(智普 AI 实现)
    @Autowired
    private EmbeddingModel embeddingModel;

    /**
     * 文本向量化接口
     * @param message 待向量化的文本(默认值:推荐一款入门级露营装备)
     * @return 包含原始文本与向量的响应
     */
    @GetMapping("/embedding")
    public Map<String, Object> embedding(
            @RequestParam(value = "message", defaultValue = "推荐一款入门级露营装备") String message) {
        // 调用 embed 方法完成文本向量化(默认返回 1024 维向量)
        float[] vector = embeddingModel.embed(message);
        // 返回结果(原始文本 + 向量)
        return Map.of(
                "message", message,
                "vectorDimension", vector.length, // 向量维度
                "vector", vector
        );
    }
}

3. 测试文本向量化接口
启动 SpringAIEmbedding 项目。
浏览器访问:http://localhost:8080/ai/embedding?message=城市周边短途游攻略。
响应结果如下(向量仅展示部分):

{
  "message": "城市周边短途游攻略",
  "vectorDimension": 1024,
  "vector": [
    0.06892345,
    -0.01234567,
    0.04567891,
    -0.02890123,
    // 省略后续 1020 个维度...
  ]
}

可以看到,文本 “城市周边短途游攻略” 被成功转换为 1024 维的向量。如果需要对多条文本批量向量化,可使用 embeddingModel.embed(List) 方法,例如:

List<String> texts = List.of("北京周边短途游攻略", "欧洲七天游攻略", "赛里木湖攻略");
List<float[]> vectors = embeddingModel.embed(texts);

四、实操案例 2:相似文本查找(多相似度算法)

1. 案例实现思路
准备本地知识库文本(3 条示例文本)。
项目启动时,将知识库文本批量向量化并缓存。
接收用户查询文本,将其向量化。
计算查询向量与知识库向量的余弦相似度,返回最相似的文本。

2. 编写核心工具类:相似度算法统一实现
创建 com.example.weizspringai.service.SimilarityCalculator 类,封装相似文本查找逻辑:

import java.util.HashSet;
import java.util.Set;

/**
 * 基于 Embedding 向量的文本相似度计算工具类
 * 整合多种核心相似度算法
 */
public class SimilarityCalculator {

    // ====================== 1. 余弦相似度(默认推荐) ======================
    public static double cosineSimilarity(float[] a, float[] b) {
        checkVectorLength(a, b);
        double dotProduct = 0.0;
        double normA = 0.0;
        double normB = 0.0;

        for (int i = 0; i < a.length; i++) {
            dotProduct += a[i] * b[i];
            normA += Math.pow(a[i], 2);
            normB += Math.pow(b[i], 2);
        }

        if (normA == 0 || normB == 0) {
            return 0.0;
        }
        return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
    }

    // ====================== 2. 欧氏距离(归一化后) ======================
    public static double euclideanSimilarity(float[] a, float[] b) {
        checkVectorLength(a, b);
        double sum = 0.0;

        for (int i = 0; i < a.length; i++) {
            sum += Math.pow(a[i] - b[i], 2);
        }

        double distance = Math.sqrt(sum);
        // 归一化:1/(1+距离),将距离转换为相似度(值越大相似度越高)
        return 1.0 / (1.0 + distance);
    }

    // ====================== 3. 曼哈顿距离(归一化后) ======================
    public static double manhattanSimilarity(float[] a, float[] b) {
        checkVectorLength(a, b);
        double sum = 0.0;

        for (int i = 0; i < a.length; i++) {
            sum += Math.abs(a[i] - b[i]);
        }

        // 归一化:1/(1+距离)
        return 1.0 / (1.0 + sum);
    }

    // ====================== 辅助方法 ======================
    /** 校验两个向量长度一致 */
    private static void checkVectorLength(float[] a, float[] b) {
        if (a == null || b == null || a.length != b.length) {
            throw new IllegalArgumentException("向量长度不一致,无法计算相似度");
        }
    }

    /** 计算向量均值 */
    private static double getVectorMean(float[] vector) {
        double sum = 0.0;
        for (float v : vector) {
            sum += v;
        }
        return sum / vector.length;
    }

    /** 简化:假设协方差矩阵为单位矩阵,返回单位矩阵(实际场景需用矩阵工具计算逆矩阵) */
    private static double[][] invertMatrix(double[][] matrix) {
        int n = matrix.length;
        double[][] inv = new double[n][n];
        for (int i = 0; i < n; i++) {
            inv[i][i] = 1.0;
        }
        return inv;
    }
}

3. 编写 EmbeddingService:支持算法切换
创建com.example.weizspringai.service.EmbeddingService类,集成相似度工具类,支持灵活选择算法:

import org.springframework.ai.embedding.EmbeddingModel;
import org.springframework.stereotype.Service;
import java.util.List;

@Service
public class EmbeddingService {
    private final EmbeddingModel embeddingModel;
    // 本地知识库文本(旅行相关场景)
    private final List<String> docs = List.of(
            "海边露营需准备防水帐篷、防潮垫、速干衣和便携炊具。",
            "山地徒步要携带登山杖、防滑鞋、双肩包和应急医疗包。",
            "城市漫游推荐骑行共享单车,打卡老街区和小众咖啡馆。"
    );
    // 知识库文本对应的向量(项目启动时初始化)
    private final List<float[]> docVectors;

    // 算法类型枚举(方便调用)
    public enum SimilarityAlgorithm {
        COSINE,        // 余弦相似度(默认)
        EUCLIDEAN,     // 欧氏距离
        MANHATTAN     // 曼哈顿距离
    }

    // 构造方法注入 EmbeddingModel,初始化知识库向量
    public EmbeddingService(EmbeddingModel embeddingModel) {
        this.embeddingModel = embeddingModel;
        this.docVectors = this.embeddingModel.embed(docs);
    }

    /**
     * 查找与查询文本最相似的知识库文本(指定算法)
     * @param query 用户查询文本
     * @param algorithm 相似度算法
     * @return 最相似的文本
     */
    public String queryBestMatch(String query, SimilarityAlgorithm algorithm) {
        float[] queryVec = embeddingModel.embed(query);
        int bestIdx = -1;
        double bestSim = -1;

        for (int i = 0; i < docVectors.size(); i++) {
            double sim = calculateSimilarity(queryVec, docVectors.get(i), algorithm);
            if (sim > bestSim) {
                bestSim = sim;
                bestIdx = i;
            }
        }
        return docs.get(bestIdx);
    }

    /**
     * 重载:默认使用余弦相似度
     */
    public String queryBestMatch(String query) {
        return queryBestMatch(query, SimilarityAlgorithm.COSINE);
    }

    /**
     * 统一相似度计算入口
     */
    private double calculateSimilarity(float[] a, float[] b, SimilarityAlgorithm algorithm) {
        return switch (algorithm) {
            case COSINE -> SimilarityCalculator.cosineSimilarity(a, b);
            case EUCLIDEAN -> SimilarityCalculator.euclideanSimilarity(a, b);
            case MANHATTAN -> SimilarityCalculator.manhattanSimilarity(a, b);
            case MAHALANOBIS -> {
                // 简化示例:使用单位矩阵作为协方差矩阵(实际需根据数据计算)
                double[][] covMatrix = new double[a.length][a.length];
                for (int i = 0; i < a.length; i++) {
                    covMatrix[i][i] = 1.0;
                }
                yield SimilarityCalculator.mahalanobisSimilarity(a, b, covMatrix);
            }
        };
    }
}

4. 扩展 EmbeddingController
在 EmbeddingController 增加算法参数,支持前端灵活选择相似度算法,同时,注入 EmbeddingService,添加相似文本查找接口:

import com.example.springaiembedding.service.EmbeddingService;
import com.example.springaiembedding.service.EmbeddingService.SimilarityAlgorithm;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.ai.embedding.EmbeddingModel;
import java.util.Map;

@RestController
@RequestMapping("/ai")
public class EmbeddingController {
    @Autowired
    private EmbeddingModel embeddingModel;

    @Autowired
    private EmbeddingService embeddingService;

    // 文本向量化接口(保持不变)
    @GetMapping("/embedding")
    public Map<String, Object> embedding(
            @RequestParam(value = "message", defaultValue = "推荐一款入门级露营装备") String message) {
        float[] vector = embeddingModel.embed(message);
        return Map.of(
                "message", message,
                "vectorDimension", vector.length,
                "vector", vector
        );
    }

    /**
     * 相似文本查找接口(支持指定算法)
     * @param query 查询文本
     * @param algorithm 相似度算法(默认 COSINE)
     */
    @GetMapping("/similarity")
    public Map<String, Object> findSimilarText(
            @RequestParam("query") String query,
            @RequestParam(value = "algorithm", defaultValue = "COSINE") String algorithm) {
        // 校验算法参数合法性
        SimilarityAlgorithm simAlgo;
        try {
            simAlgo = SimilarityAlgorithm.valueOf(algorithm.toUpperCase());
        } catch (IllegalArgumentException e) {
            return Map.of(
                    "error", "算法参数无效,支持的算法:COSINE/EUCLIDEAN/MANHATTAN/PEARSON/JACCARD/ADJUSTED_COSINE/MAHALANOBIS",
                    "code", 400
            );
        }

        // 执行相似文本查找
        String similarText = embeddingService.queryBestMatch(query, simAlgo);
        return Map.of(
                "query", query,
                "algorithm", simAlgo.name(),
                "answer", similarText
        );
    }
}

5. 测试相似文本查找
启动项目后,可通过以下 URL 测试不同算法的效果:

测试1:在浏览器中访问 http://localhost:8080/ai/similarity?query=露营准备&algorithm=COSINE,响应:

{
  "query":"露营准备",
  "algorithm":"COSINE",
  "answer":"海边露营需准备防水帐篷、防潮垫、速干衣和便携炊具。"
}

测试2:访问 http://localhost:8080/ai/similarity?query=徒步装备&algorithm=EUCLIDEAN,响应:

{
  "query":"徒步装备",
  "algorithm":"MAHALANOBIS",
  "answer":"山地徒步要携带登山杖、防滑鞋、双肩包和应急医疗包。"
}

测试结果分析:不同算法均能准确匹配语义相似的文本,其中余弦相似度在通用性、计算效率上表现最优,适合大多数场景;其他算法可根据数据特性(如含噪声、高维稀疏)灵活切换。

总结

本文聚焦 Embedding 技术的核心原理与实操落地,详细解析了 3 种主流文本相似度算法的逻辑、优势与适用场景,并通过智普 AI 完成了文本向量化与多算法适配的相似文本查找案例。Embedding 作为语义理解的核心技术,为 AI 高级场景提供了 “可计算的语义向量”,而 Spring AI 的统一接口封装让开发者无需关注底层细节,即可快速集成多模型、多算法的 Embedding 能力。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐