一文讲透向量 Embedding:从数学概念到 LangChain4j + Qdrant 实战

📅 2026/7/16 0:21:16 👁️ 阅读次数 📝 编程学习
一文讲透向量 Embedding:从数学概念到 LangChain4j + Qdrant 实战

大模型的世界,本质上是一场「万物皆可向量」的游戏。 这篇文章带你从零开始,把「向量」这个看似抽象的概念,一路走到能跑起来的工程代码。


一、什么是向量?🧭

在深度学习的世界里,向量是最基础、也是最重要的概念之一。

它不仅用来表示数据,更贯穿了模型的输入、输出以及内部计算的每一个环节。下面我们从几个角度,把它彻底讲明白。

1. 基本定义

向量,本质上是一组有序排列的数字。这些数字被称为向量的「分量」或「元素」。

  • 在二维空间里,一个点可以用「横坐标 + 纵坐标」两个数来表示;
  • 在三维空间里,则需要「长、宽、高」三个数;
  • 更一般地,在 n 维空间中,一个向量就是n 个实数组成的序列

2. 向量在机器学习中的作用

向量几乎撑起了机器学习的半边天:

  • 特征表示:原始数据要先转换成数值形式,机器才能处理。这些特征值按顺序排好,就成了「特征向量」。
  • 模型参数:很多算法内部都在学习「权重向量」。比如线性回归,就是在找一个最优的权重向量 w,让预测值和真实值的误差最小。
  • 嵌入层(Embedding):在 NLP 领域,词向量、句向量被用来捕捉文本语义。训练得到的词嵌入,能真实反映词语之间的语义关系。

3. 向量的运算规则

向量支持加法、减法、标量乘法等基础运算,还有内积(点积)、外积(叉积)等特殊运算。

正是这些运算,构建起了复杂神经网络的底层骨架。

4. 向量化编程

为了提升效率,工程师往往会用「向量化」技术——一次性对整个数组做运算,而不是逐个元素循环处理

这种方式极大加速了计算,也让代码更简洁优雅。

5. 向量空间模型

这是信息检索中的经典方法:把文档表示成由词汇项组成的向量,每个维度对应一个术语,值则反映该术语在文档中的重要程度。

于是,「文档相似度」就变成了「向量相似度」的计算问题

一句话总结:向量是连接理论与实践的桥梁。理解了向量,你就握住了理解现代大模型的钥匙。🔑


二、向量的数学本质 📐

向量是既有大小、又有方向的量。在数学中,我们通常用「有向线段」来表示它——线段的长度代表大小,箭头的指向代表方向。

关于向量,有几条关键性质值得记住:

  1. 向量只有方向和大小。等长、平行、同向的向量,因为大小和方向都一致,就是同一个向量,也叫相等向量
  2. 两个向量相等可以用「=」连接。但向量之间没有大于或小于关系,不能用「>」「<」连接。
  3. 方向相同或相反的向量,叫作共线向量,也叫平行向量
  4. 向量之间可以用「∥」或「⊥」连接。其中「ab」表示 a 与 b 共线,「ab」表示 a 与 b 所在直线互相垂直。
  5. 向量a的大小,又称长度、模长,简称,记作 |a|。
  6. 模长是数值而非向量,所以可以用「>」「<」比较大小。
  7. 长度为 0 的向量叫作零向量
  8. 零向量与任意向量平行。
  9. 长度为 1 个单位的向量叫作单位向量

三、什么是向量化?🔄

一句话:向量化,就是把文档、字符等内容,转换成一组浮点数组。

下图来源于 Qdrant 官方文档(向量检索概述章节)。


四、实战示例:LangChain4j + Qdrant 💻

理论讲够了,接下来上真代码。我们参考 LangChain4j 官方示例(Qdrant Embedding Store Example),来跑通整条链路。

官方案例:环境搭建

新建模块,配置pom.xml

<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 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>com.whc</groupId> <artifactId>langChain4j-whc</artifactId> <version>1.0-SNAPSHOT</version> </parent> <artifactId>langchain4j-whc-embedding</artifactId> <packaging>jar</packaging> <name>langchain4j-whc-embedding</name> <properties> <maven.compiler.source>17</maven.compiler.source> <maven.compiler.target>17</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--langChain4j --> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j-open-ai</artifactId> </dependency> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j</artifactId> </dependency> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j-reactor</artifactId> </dependency> <!-- 阿里百炼平台 --> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j-community-dashscope-spring-boot-starter</artifactId> </dependency> <!--lombok--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <!--hutool--> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.8.22</version> </dependency> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j-spring-boot-starter</artifactId> </dependency> <!--qdrant相关--> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j-qdrant</artifactId> <version>1.5.0-beta11</version> </dependency> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j-embeddings-all-minilm-l6-v2</artifactId> <version>1.5.0-beta11</version> </dependency> <dependency> <groupId>org.testcontainers</groupId> <artifactId>qdrant</artifactId> <version>1.19.6</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build></project>

核心代码(含详细注释)

在官方类的基础上稍作修改,并补充了完整注释,方便理解每一步在做什么:

public class QdrantEmbeddingStoreExample { /** * gRPC服务端口配置 * 用于连接向量数据库的gRPC通信端口 */ private static int grpcPort = 6334; /** * 集合名称配置 * 指定向量数据库中使用的集合名称 */ private static String collectionName = "langchain4j-whc"; /** * 距离计算方式配置 * 设置向量相似度计算的距离度量方式为余弦距离 */ private static Collections.Distance distance = Collections.Distance.Cosine; /** * 向量维度配置 * 指定向量的维度大小为384维 */ private static int dimension = 384; /** * 数据库主机地址配置 * 向量数据库服务器的IP地址 */ private static final String HOST = "192.168.100.130"; /** * 数据库端口配置 * 向量数据库服务的端口号,与gRPC端口相同 */ private static final int PORT = 6334; /** * 主函数,演示了使用Qdrant向量数据库进行文本嵌入存储和相似性搜索的完整流程 * * @param args 命令行参数数组 * @throws ExecutionException 当异步执行出现异常时抛出 * @throws InterruptedException 当线程被中断时抛出 */ public static void main(String[] args) throws ExecutionException, InterruptedException { // 创建Qdrant嵌入存储实例,用于存储和检索文本嵌入向量 EmbeddingStore<TextSegment> embeddingStore = QdrantEmbeddingStore.builder() .host(HOST) .port(PORT) .collectionName(collectionName) .build(); // 创建Qdrant客户端,用于与Qdrant服务器进行通信 QdrantClient client = new QdrantClient( QdrantGrpcClient.newBuilder(HOST, PORT, false) .build()); // 异步创建集合,定义向量的维度和距离计算方式 client .createCollectionAsync( collectionName, Collections.VectorParams.newBuilder().setDistance(distance).setSize(dimension).build()) .get(); // 初始化嵌入模型,用于将文本转换为向量表示 EmbeddingModel embeddingModel = new AllMiniLmL6V2EmbeddingModel(); // 创建并存储第一个文本段落的嵌入向量 TextSegment segment1 = TextSegment.from("I've been to France twice."); Embedding embedding1 = embeddingModel.embed(segment1).content(); embeddingStore.add(embedding1, segment1); // 创建并存储第二个文本段落的嵌入向量 TextSegment segment2 = TextSegment.from("New Delhi is the capital of India."); Embedding embedding2 = embeddingModel.embed(segment2).content(); embeddingStore.add(embedding2, segment2); // 执行相似性搜索,查找与查询文本最相似的文本段落 Embedding queryEmbedding = embeddingModel.embed("Did you ever travel abroad?").content(); EmbeddingSearchRequest embeddingSearchRequest = EmbeddingSearchRequest.builder() .queryEmbedding(queryEmbedding) .maxResults(1) .build(); List<EmbeddingMatch<TextSegment>> matches = embeddingStore.search(embeddingSearchRequest).matches(); EmbeddingMatch<TextSegment> embeddingMatch = matches.get(0); // 输出匹配结果的相似度分数和文本内容 System.out.println(embeddingMatch.score()); System.out.println(embeddingMatch.embedded().text()); }}

这里的嵌入模型用的是all-MiniLM-L6-v2

💡all-MiniLM-L6-v2是 MiniLM 系列的一个小型语言模型,通过「知识蒸馏」技术从更大的模型压缩而来。它的目标是——在保持较高性能的同时,尽可能降低计算资源需求,非常适合本地和轻量场景。

✅ 测试一下

代码里把两条英文句子转成了向量并保存下来:

然后提问:哪句话更适合回答"Did you ever travel abroad?"

结果如下:

0.7378319074630284I've been to France twice.

「我去过法国两次」——完美命中,模型准确理解了「出国旅行」的语义。🎯


五、接口方式:接入阿里百炼向量模型 🔌

模型选择

这里我们选用阿里云百炼平台的向量模型text-embedding-v3

配置类

@Slf4j@Getter@Configurationpublic class LlmConfig { @Value("${ai.dashScope.apiKey}") private String dashScopeApiKey; @Value("${ai.dashScope.modelName}") private String dashScopeModelName; @Value("${ai.dashScope.baseUrl}") private String dashScopeBaseUrl; @Value("${qdrant.host}") private String qdrantHost; @Value("${qdrant.port}") private int qdrantPort; @Value("${qdrant.collectionName}") private String qdrantCollectionName; /** * 创建EmbeddingModel 向量模型 * * @return EmbeddingModel */ @Bean public EmbeddingModel embeddingModel() { return OpenAiEmbeddingModel.builder() .apiKey(dashScopeApiKey) .modelName(dashScopeModelName) .baseUrl(dashScopeBaseUrl) .build(); } /** * 创建Qdrant客户端 * * @return QdrantClient */ @Bean public QdrantClient qdrantClient() { QdrantGrpcClient.Builder grpcClientBuilder = QdrantGrpcClient.newBuilder(qdrantHost, qdrantPort, false); return new QdrantClient(grpcClientBuilder.build()); } @Bean public EmbeddingStore<TextSegment> embeddingStore() { return QdrantEmbeddingStore.builder() .host(qdrantHost) .port(qdrantPort) .collectionName(qdrantCollectionName) .build(); }}

YAML 配置

port: 9008 servlet: encoding: enabled: true force: true charset: UTF-8spring: application: name: langchain_whc_embeddingai: dashScope: # 配置dashScope apiKey: ${AI_DASHSCOPE_API_KEY} modelName: ${AI_DASHSCOPE_MODEL_NAME} baseUrl: ${AI_DASHSCOPE_BASE_URL}qdrant: host: ${QDRANT_HOST} port: ${QDRANT_PORT} collectionName: ${QDRANT_COLLECTION_NAME}

Controller

@Slf4j@RestController@RequiredArgsConstructorpublic class EmbeddingController { private final LlmConfig llmConfig; private final EmbeddingModel embeddingModel; private final QdrantClient qdrantClient; private final EmbeddingStore<TextSegment> embeddingStore; @GetMapping(value = "/embedding/createCollection") public void createCollection() { var vectorParams = Collections.VectorParams.newBuilder() .setDistance(Collections.Distance.Cosine) .setSize(1024) .build(); qdrantClient.createCollectionAsync(llmConfig.getQdrantCollectionName(), vectorParams); }}
✅ 测试新增 Collection

访问接口:http://localhost:9008/embedding/createCollection

测试结果:成功新增了一个 Collection。

✅ 测试新增 Points

在 Controller 中增加接口,这次我们塞一首自创的词进去:

/** * 处理GET请求,向嵌入存储中添加文本段落的嵌入向量 * * @return 添加操作的结果标识符字符串 */ @GetMapping(value = "/embedding/add") public String add() { // 定义《永遇乐・师徒取经》词牌的文本内容作为待处理文本 String prompt = """ 永遇乐・师徒取经 万里西行,风沙漫卷,云路迢递。玄奘慈眉,凝眸西向,志比金石厉。 悟空火眼,金箍掣处,妖魅现形惊悸。笑猪痴、贪眠恋栈,也随杖履千里。 沙憎谨肃,行囊稳护,默默担当行李。九九磨难,千般险厄,终把真经觅。东归胜果,流传千古,漫说佛门真谛。 到如今、传奇犹在,世人共记。 """; // 将文本内容转换为文本段落对象,并添加元数据信息 TextSegment segment = TextSegment.from(prompt); segment.metadata().put("author", "robot"); segment.metadata().put("title", "永遇乐・师徒取经"); // 生成文本段落的嵌入向量 Embedding embedding = embeddingModel.embed(segment).content(); // 将嵌入向量和文本段落添加到嵌入存储中 String result = embeddingStore.add(embedding, segment); log.info(result); return result; }

测试效果:

你可以多塞几条 points 进去,让检索结果更有对比性。

✅ 测试查询
/** * 处理/embedding/query1路径的GET请求,用于查询与"写爱情的诗"相关的嵌入向量匹配结果 * 该方法不接受参数,无返回值 */ @GetMapping(value = "/embedding/query") public void query1() { // 生成查询文本"写爱情的诗"的嵌入向量 Embedding queryEmbedding = embeddingModel.embed("与爱情相关的").content(); // 构建嵌入搜索请求 EmbeddingSearchRequest embeddingSearchRequest = EmbeddingSearchRequest.builder() .queryEmbedding(queryEmbedding) .build(); // 执行嵌入向量搜索 EmbeddingSearchResult<TextSegment> searchResult = embeddingStore.search(embeddingSearchRequest); // 输出查询结果 searchResult.matches().forEach(match -> { log.info("\n匹配度得分---->"+ match.score()); log.info("\n"+match.embedded().text()); }); }

测试结果有点意思——排序跟直觉略有出入:

当然,也可以加上过滤条件,精确锁定某个标题:

EmbeddingSearchRequest embeddingSearchRequest=EmbeddingSearchRequest.builder().queryEmbedding(queryEmbedding).filter(metadataKey("title").isEqualTo("鹧鸪天・别绪")).build();


六、写在最后 📝

从「向量是什么」到「用 LangChain4j + Qdrant 跑通一整套向量检索」,我们走完了从理论到工程的完整闭环:

  • 理论层:向量是有序数字组,是大模型理解世界的语言;
  • 工程层:向量化把文本变成浮点数组,向量数据库负责高效存储与相似度检索;
  • 实践层:无论是本地的 all-MiniLM-L6-v2,还是云端的 text-embedding-v3,都能轻松接入。

理解了向量,你就理解了大模型「思考」的底层方式。剩下的,就是动手把它跑起来。💪

学AI大模型的正确顺序,千万不要搞错了

🤔2026年AI风口已来!各行各业的AI渗透肉眼可见,超多公司要么转型做AI相关产品,要么高薪挖AI技术人才,机遇直接摆在眼前!

有往AI方向发展,或者本身有后端编程基础的朋友,直接冲AI大模型应用开发转岗超合适!

就算暂时不打算转岗,了解大模型、RAG、Prompt、Agent这些热门概念,能上手做简单项目,也绝对是求职加分王🔋

📝给大家整理了超全最新的AI大模型应用开发学习清单和资料,手把手帮你快速入门!👇👇

学习路线:

✅大模型基础认知—大模型核心原理、发展历程、主流模型(GPT、文心一言等)特点解析
✅核心技术模块—RAG检索增强生成、Prompt工程实战、Agent智能体开发逻辑
✅开发基础能力—Python进阶、API接口调用、大模型开发框架(LangChain等)实操
✅应用场景开发—智能问答系统、企业知识库、AIGC内容生成工具、行业定制化大模型应用
✅项目落地流程—需求拆解、技术选型、模型调优、测试上线、运维迭代
✅面试求职冲刺—岗位JD解析、简历AI项目包装、高频面试题汇总、模拟面经

以上6大模块,看似清晰好上手,实则每个部分都有扎实的核心内容需要吃透!

我把大模型的学习全流程已经整理📚好了!抓住AI时代风口,轻松解锁职业新可能,希望大家都能把握机遇,实现薪资/职业跃迁~

这份完整版的大模型 AI 学习资料已经上传CSDN,朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费