news 2026/11/3 8:44:27

Embedding与LLM输出向量的本质区别及RAG实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Embedding与LLM输出向量的本质区别及RAG实战

1. 面试官为什么总爱问 Embedding 和 LLM 输出的关系

1.1 这道题到底在考什么

面 Java AI 岗位的时候,很多人一听到“Embedding 和 LLM 输出向量到底啥关系”就懵了。有人觉得这俩是一回事,都是“把东西变成一串数字”;有人觉得 Embedding 是 Embedding,LLM 输出是 LLM 输出,八竿子打不着。这两种理解都不对,而且面试官恰恰就是冲着这个模糊地带来的。

先把结论摆在前面:Embedding 和 LLM 输出向量,形式上都是浮点数数组,但它们的语义来源、产生方式、用途完全不同。Embedding 是“把输入映射到语义空间”,LLM 输出向量是“模型在生成过程中对下一个 token 的预测分布(logits)或者隐藏状态”。一个是编码器思维,一个是解码器思维。面试官问这道题,本质是在确认三件事:你分不分得清“表示”和“生成”;你知不知道向量在 RAG、语义检索、Agent 记忆里怎么用;你能不能把原理落到 Spring AI 的代码上。

我面过不少人,能把float[]和List<Double>的区别讲清楚,但一问“为什么余弦相似度能算语义相似”,就开始背公式了。所以这篇我打算从原理讲到生产代码,把这条链路彻底打通。

1.2 一个生活化类比先建立直觉

你可以把 Embedding 想成“图书馆的索引卡”。每本书(一段文本、一张图、一段音频)被压缩成一张卡片,卡片上写着它在语义空间里的坐标。内容相近的书,坐标就挨得近。这个坐标是静态的、可复用的,你查一次存下来,下次直接比对就行。

LLM 输出向量则更像“一个人边说话边在大脑里形成的下一个词的概率草图”。它不是一个固定的坐标,而是模型在当前上下文下,对“接下来该说什么”的内部表示。你让它生成一句话,它每一步都会产出一个高维向量(logits),这个向量经过 softmax 才变成词的概率。这个向量是动态的、上下文相关的、一次性的。

所以关键差异在于:Embedding 是“输入侧”的表示,LLM 输出向量是“输出侧”的表示。前者用来检索、聚类、去重;后者用来采样、生成、决策。混为一谈,RAG 就会做歪。

1.3 为什么 Java 开发者尤其容易踩坑

Java 生态里做 AI,主流路径是 Spring AI 或者 Spring AI Alibaba。这两套框架把 Embedding 和 Chat 抽象成了两个不同的接口:EmbeddingModel和ChatModel。但很多同学写代码的时候,看到EmbeddingModel.embed()返回float[],看到ChatModel.call()返回String,就以为 LLM 不产向量。其实 LLM 内部每一步都在产向量,只是框架帮你封装掉了。

面试官问这道题,往往是想引出下一个问题:“那你怎么用 LLM 的输出向量做语义缓存?”或者“RAG 里为什么用 Embedding 而不是直接用 LLM 输出?”能答上来的人,基本就过了 AI 应用开发这一关。

2. 把 Embedding 和 LLM 输出向量的原理彻底拆开

2.1 Embedding 的本质:把离散符号压进连续空间

Embedding 这个词最早来自词向量。Word2Vec 时代,我们把每个词映射成一个 100 到 300 维的稠密向量,让“国王 - 男人 + 女人 ≈ 女王”这种语义运算成立。到了 Transformer 时代,Embedding 不再只是词级别,而是句子、段落、图像、音频都能编码。

它的核心机制是:通过一个可学习的矩阵,把离散的 token id 映射成连续向量,再经过多层编码器(比如 BERT、BGE、text-embedding-3)聚合出整段文本的表示。这个表示通常取[CLS]token 的隐藏状态,或者对所有 token 做 mean pooling。

关键点在于,Embedding 模型是专门为“表示”训练的。它的训练目标可能是对比学习(让相似文本的向量靠近,不相似的远离),也可能是掩码语言建模。不管哪种,最终产出的向量有一个重要性质:语义相似度可以用余弦相似度或点积直接衡量。

这就是为什么 RAG 里能用它做检索。你把知识库所有 chunk 提前 embed 好,存进向量数据库;用户提问时,把问题 embed 成同维度向量,做最近邻搜索,就能召回语义相关的内容。整个过程不需要 LLM 参与,快且便宜。

2.2 LLM 输出向量的本质:下一个 token 的概率分布

LLM 是自回归模型。给定上下文[t1, t2, ..., tn],它预测下一个 tokentn+1的概率分布。这个分布怎么来的?最后一层 Transformer 输出一个隐藏状态h,然后经过一个线性层(LM Head)映射到词表大小V的向量,也就是 logits。logits 经过 softmax 就变成概率。

所以严格来说,LLM 每一步的输出向量是 logits,维度等于词表大小,比如 32000、128000。这个向量不是用来算语义相似度的,它是用来采样下一个词的。你取 argmax 就是贪心解码,按概率采样就是随机解码,加温度就是调节分布平滑度。

那 LLM 的隐藏状态能不能当 Embedding 用?可以,但效果通常不如专门的 Embedding 模型。因为 LLM 的训练目标是生成,不是表示。它的隐藏状态里混杂了大量生成相关的信息,语义纯度不够。学术界有人拿 LLM 最后一层隐藏状态做检索,效果能打,但工程上不划算——LLM 推理成本比 Embedding 模型高一个数量级。

2.3 一张表看清两者的核心差异

维度EmbeddingLLM 输出向量
产生位置编码器输出 / 池化层解码器最后隐藏状态 / LM Head
典型维度384、768、1024、1536、3072等于词表大小,32000 到 128000+
语义性质静态、可复用、语义空间坐标动态、上下文相关、生成分布
主要用途检索、聚类、去重、推荐采样、生成、决策、打分
相似度计算余弦相似度、点积、欧氏距离一般不直接算相似度
成本低,可批量离线高,逐 token 自回归
框架接口EmbeddingModel.embed()ChatModel.call()内部

这张表建议背下来。面试时能脱口而出,面试官基本就知道你是真做过项目的。

2.4 为什么 RAG 必须用 Embedding 而不是 LLM 输出

有人会问:既然 LLM 也能产向量,为什么 RAG 检索不用 LLM 输出?原因有三。

第一,成本。Embedding 模型参数量通常在 1 亿到 10 亿之间,LLM 动辄几百亿。把 10 万条知识 chunk 全部过一遍 LLM,账单能让你怀疑人生。

第二,语义纯度。Embedding 模型是对比学习训出来的,天生适合算相似度。LLM 的隐藏状态是为生成服务的,里面混了位置、语法、生成策略等信息,直接拿来检索,召回率会掉。

第三,维度与存储。Embedding 通常 768 到 1536 维,向量数据库存起来很轻松。LLM 隐藏状态动辄 4096 到 8192 维,存储和检索成本都高。

所以工程上的标准做法是:检索用 Embedding,生成用 LLM,两者通过向量数据库解耦。这也是 Spring AI 里VectorStore和ChatClient分开设计的原因。

3. Spring AI 生产级代码实战:从 Embedding 到 RAG 全链路

3.1 环境准备与依赖选型

先明确技术栈。我这次用的是 Spring Boot 3.2 + Spring AI 1.0.0-M6 + Spring AI Alibaba,Embedding 模型选的是通义千问的text-embedding-v3,LLM 选的是qwen-plus。向量数据库用 Redis Stack,因为大部分 Java 团队已经有 Redis,加个 RediSearch 模块就能当向量库用,运维成本最低。

Maven 依赖如下:

<dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-core</artifactId> <version>1.0.0-M6</version> </dependency> <dependency> <groupId>com.alibaba.cloud.ai</groupId> <artifactId>spring-ai-alibaba-starter</artifactId> <version>1.0.0-M6.1</version> </dependency> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-redis-store</artifactId> <version>1.0.0-M6</version> </dependency>

配置文件里把 API Key 和模型名配上:

spring: ai: dashscope: api-key: ${DASHSCOPE_API_KEY} embedding: options: model: text-embedding-v3 chat: options: model: qwen-plus temperature: 0.7 data: redis: host: localhost port: 6379

注意:API Key 千万别硬编码进代码,用环境变量或者配置中心。我见过有人把 Key 提交到 Git,第二天就被刷爆了。

3.2 用 EmbeddingModel 把文本变成向量

Spring AI 的EmbeddingModel接口非常干净,核心就两个方法:embed(String)和embed(List<String>)。批量接口很重要,因为生产环境你不可能一条一条 embed,那样网络往返开销太大。

@Service public class EmbeddingService { private final EmbeddingModel embeddingModel; public EmbeddingService(EmbeddingModel embeddingModel) { this.embeddingModel = embeddingModel; } public float[] embedSingle(String text) { return embeddingModel.embed(text); } public List<float[]> embedBatch(List<String> texts) { return embeddingModel.embed(texts); } }

这里有个细节:text-embedding-v3默认输出 1024 维,但支持通过dimensions参数降到 512 或 256。降维能省存储和检索时间,但会损失一点精度。我的经验是,知识库小于 10 万条时用 1024 维,超过 100 万条再考虑降维。

批量 embed 的时候要注意单次请求的 token 上限。通义千问的 Embedding 接口单次最多 25 条文本,每条不超过 2048 token。所以生产代码里要自己做分批:

public List<float[]> embedInBatches(List<String> texts, int batchSize) { List<float[]> result = new ArrayList<>(); for (int i = 0; i < texts.size(); i += batchSize) { int end = Math.min(i + batchSize, texts.size()); List<String> batch = texts.subList(i, end); result.addAll(embeddingModel.embed(batch)); } return result; }

3.3 向量入库与相似度检索

拿到float[]之后,下一步是存进向量数据库。Spring AI 的VectorStore抽象帮你屏蔽了底层差异,Redis、Milvus、PgVector 都能用同一套 API。

@Service public class KnowledgeBaseService { private final VectorStore vectorStore; public KnowledgeBaseService(VectorStore vectorStore) { this.vectorStore = vectorStore; } public void addDocuments(List<String> chunks) { List<Document> documents = chunks.stream() .map(chunk -> new Document(chunk, Map.of("source", "manual"))) .toList(); vectorStore.add(documents); } public List<Document> search(String query, int topK) { SearchRequest request = SearchRequest.query(query) .withTopK(topK) .withSimilarityThreshold(0.7); return vectorStore.similaritySearch(request); } }

similarityThreshold这个参数很关键。设太低会召回一堆不相关的内容,污染 LLM 的上下文;设太高又可能漏掉关键信息。我的经验值是 0.65 到 0.75 之间,具体要看 Embedding 模型的分布。你可以先用一批标注数据跑一下召回率和准确率,再定阈值。

3.4 把检索结果喂给 LLM 生成答案

RAG 的最后一步是把召回的 chunk 拼进 prompt,让 LLM 基于这些上下文回答。Spring AI 的ChatClient提供了 fluent API,写起来很顺。

@Service public class RagService { private final ChatClient chatClient; private final KnowledgeBaseService knowledgeBaseService; public RagService(ChatClient.Builder builder, KnowledgeBaseService knowledgeBaseService) { this.chatClient = builder.build(); this.knowledgeBaseService = knowledgeBaseService; } public String answer(String question) { List<Document> docs = knowledgeBaseService.search(question, 5); String context = docs.stream() .map(Document::getContent) .collect(Collectors.joining("\n\n")); String prompt = """ 你是一个严谨的技术助手。请仅基于以下上下文回答问题。 如果上下文没有相关信息,直接说"我不知道",不要编造。 上下文: %s 问题:%s """.formatted(context, question); return chatClient.prompt() .user(prompt) .call() .content(); } }

这段代码里有几个生产级的考量。第一,prompt 里明确要求“仅基于上下文”,减少幻觉。第二,要求“不知道就说不知道”,避免模型硬编。第三,topK 设 5 是经验值,太多会超上下文窗口,太少召回不足。

3.5 用 LLM 输出向量做语义缓存

前面说了 LLM 输出向量一般不直接用于检索,但有一个场景例外:语义缓存。传统缓存用 key 精确匹配,用户问“Java 怎么创建线程”和“Java 线程创建方式”会命中两次 LLM,浪费钱。语义缓存的做法是:把用户问题 embed 成向量,先在缓存里找相似问题,如果相似度超过阈值,直接返回缓存答案。

@Service public class SemanticCacheService { private final EmbeddingModel embeddingModel; private final Map<String, CachedAnswer> cache = new ConcurrentHashMap<>(); public Optional<String> get(String question) { float[] queryVec = embeddingModel.embed(question); return cache.values().stream() .filter(entry -> cosineSimilarity(queryVec, entry.vector()) > 0.92) .map(CachedAnswer::answer) .findFirst(); } public void put(String question, String answer) { float[] vec = embeddingModel.embed(question); cache.put(question, new CachedAnswer(vec, answer)); } private double cosineSimilarity(float[] a, float[] b) { double dot = 0, normA = 0, normB = 0; for (int i = 0; i < a.length; i++) { dot += a[i] * b[i]; normA += a[i] * a[i]; normB += b[i] * b[i]; } return dot / (Math.sqrt(normA) * Math.sqrt(normB)); } }

阈值 0.92 是我实测下来的经验值。低于这个值,容易把不同问题误判为相同;高于 0.95,又太严格,缓存命中率低。生产环境建议用 Redis 存向量,别用ConcurrentHashMap,否则重启就丢。

4. 常见问题与排查技巧实录

4.1 Embedding 维度对不上怎么办

这是新手最容易遇到的问题。你用一个模型 embed 了知识库,换模型之后查询,报维度不匹配。原因是不同 Embedding 模型输出维度不同,text-embedding-v3是 1024 维,text-embedding-v2是 1536 维,BGE 是 768 维。

解决办法只有一个:换模型必须重新 embed 整个知识库。没有捷径。所以选模型的时候要慎重,尽量选稳定、长期维护的。我一般会在项目初期就把 Embedding 模型定死,写进技术文档,避免后期换模型带来的全量重建。

4.2 相似度很高但答案不对

这种情况通常是 chunk 切分有问题。你把一整篇文档 embed 成一个向量,语义被平均掉了,检索时召回的是“整体相关”但“细节不对”的内容。正确做法是按语义边界切分,比如按段落、按标题层级切,每个 chunk 控制在 200 到 500 token。

Spring AI 提供了TokenTextSplitter:

TokenTextSplitter splitter = new TokenTextSplitter(500, 100, 5, 10000, true); List<Document> chunks = splitter.apply(documents);

参数含义分别是:目标 chunk 大小、重叠 token 数、最小 chunk 数、最大 chunk 数、是否保留分隔符。重叠 100 token 是为了避免句子被切断导致语义丢失。

4.3 LLM 不按上下文回答

模型幻觉是 RAG 的老大难。除了 prompt 里明确约束,还有几个技巧。第一,把上下文放在问题前面,模型对前面的内容注意力更强。第二,给每个 chunk 加编号,让模型引用来源。第三,用 few-shot 示例告诉模型“不知道就说不知道”。

如果还是不行,考虑换更强的 LLM,或者降低 temperature。temperature=0时模型最保守,适合事实性问答;temperature=0.7适合创意生成。RAG 场景我一般设 0.1 到 0.3。

4.4 常见问题速查表

问题现象可能原因排查方向解决方案
维度不匹配换过 Embedding 模型检查模型名和维度配置全量重新 embed
召回不准chunk 切分过大看 chunk 平均 token 数按语义边界切分,加重叠
答案幻觉prompt 约束不足检查 prompt 模板加“仅基于上下文”约束
响应慢向量库没建索引看 Redis 慢查询日志建 HNSW 或 IVF 索引
缓存命中低阈值设太高统计相似度分布调到 0.90 到 0.93
成本高每次全量 embed看 API 调用量加语义缓存,批量 embed

4.5 几个我踩过的坑

第一个坑:批量 embed 时没控制并发。我一开始用parallelStream并发调 Embedding 接口,结果触发限流,一半请求失败。后来改成固定线程池,并发度控制在 5 以内,稳定了。

第二个坑:向量没归一化。余弦相似度和点积在向量归一化后等价,但如果你用点积检索又没归一化,结果会偏。Spring AI 的VectorStore内部会处理,但自己写相似度计算时要注意。

第三个坑:Redis 向量索引没预热。Redis Stack 建 HNSW 索引后,第一次查询会慢,因为要构建图。生产环境建议启动后跑一批预热查询。

第四个坑:prompt 太长超上下文。topK 设 10,每个 chunk 500 token,加上问题本身,轻松超过 8000 token。后来我把 topK 降到 5,chunk 控制在 300 token,问题解决。

5. 面试怎么答这道题才能加分

5.1 标准回答框架

如果面试官问“Embedding 和 LLM 输出向量啥关系”,你可以这样答:

“两者形式上都是浮点数组,但语义来源不同。Embedding 是编码器对输入的语义压缩,静态可复用,专门为相似度计算训练,维度通常 768 到 1536。LLM 输出向量是解码器对下一个 token 的预测分布,动态上下文相关,维度等于词表大小,用于采样生成。工程上,RAG 检索用 Embedding,生成用 LLM,两者通过向量数据库解耦。Spring AI 里分别对应EmbeddingModel和ChatModel。”

这段话覆盖了定义、差异、用途、框架落地,面试官基本满意。

5.2 加分项:主动聊语义缓存和 Agent 记忆

如果面试官继续追问,你可以主动引出语义缓存:“LLM 输出向量虽然不直接用于检索,但可以用于语义缓存。把用户问题 embed 后比对历史问题,相似度超过 0.92 直接返回缓存答案,能省不少 token 成本。”

再进一步,可以聊 Agent 记忆:“Agent 的长期记忆也可以用 Embedding 实现。把历史对话 embed 存起来,每轮对话前检索相关记忆注入上下文,让 Agent 记住用户偏好。”

这两个点一抛出来,面试官就知道你不只是背八股,是真做过项目。

5.3 反问环节可以问什么

面试快结束时,你可以反问:“咱们团队的 RAG 系统用的是什么 Embedding 模型?有没有做语义缓存?”这个问题既显示你的专业度,又能帮你判断团队的技术水平。如果对方答不上来,说明 AI 应用还在早期,你进去有机会主导;如果对方讲得头头是道,说明团队有积累,你能学到东西。

6. 写在最后的一些个人体会

我做 Java AI 应用这两年,最大的感受是:原理不清,代码白写。很多人上来就抄 RAG demo,跑通了就以为会了。结果一上生产,召回不准、幻觉严重、成本爆炸,全是坑。根子就在于没搞懂 Embedding 和 LLM 输出向量的本质区别。

Spring AI 把复杂度封装得很好,但封装不等于你可以不懂。面试官问这道题,不是要你背概念,是要确认你有没有能力在出问题时定位到根因。维度不对、召回不准、缓存失效,这些问题的答案都藏在对向量的理解里。

最后分享一个小技巧:如果你要深入学这块,别只看文档,自己动手 embed 一批文本,算一下相似度矩阵,看看哪些该近的近了、哪些该远的没远。跑一遍,比看十篇文章都管用。向量这东西,直觉是跑出来的,不是看出来的。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/11/3 8:44:26

Windows下RabbitMQ安装与配置实战指南

1. Windows系统下RabbitMQ安装全攻略 RabbitMQ作为目前最流行的开源消息中间件之一&#xff0c;在分布式系统架构中扮演着重要角色。我在多个微服务项目中都深度使用过RabbitMQ&#xff0c;今天就来分享在Windows环境下的完整安装配置指南。不同于官方文档的简略说明&#xff…

作者头像 李华
网站建设 2026/11/3 8:42:48

告别商业软件!六条图像处理高效路子,从轻量工具到脚本自动化

图像处理这件事&#xff0c;很多人第一反应就是打开那个体积庞大、订阅费不便宜的商业软件。但说实话&#xff0c;日常八成的活儿——裁个图、压个体积、批量改尺寸、去个水印、做个封面——根本用不上那么重的工具。我这些年折腾下来&#xff0c;电脑里装过的图像工具没有二十…

作者头像 李华
网站建设 2026/11/3 8:42:09

DeepSeek学习指南:推理模型与通用模型的分工逻辑及提示语设计

简介&#xff1a;这份《DeepSeek 从入门到精通 清华大学版》由清华大学新闻与传播学院新媒体研究中心元宇宙文化实验室团队编写&#xff0c;面向希望系统掌握 DeepSeek 的普通用户与开发者&#xff0c;帮助读者从基础认知走向高效应用。内容围绕 DeepSeek 是什么、能做什么、如…

作者头像 李华
网站建设 2026/11/3 8:41:54

PY32F002B量产烧录与UID加密全流程实战指南

1. 为什么一颗几毛钱的MCU也值得认真做烧录与加密 手里攥着一颗PY32F002B&#xff0c;TSSOP20封装&#xff0c;零售价不到一块钱&#xff0c;很多人第一反应是“这种片子随便烧烧就行了&#xff0c;哪值得写什么全流程”。我最初也是这么想的&#xff0c;直到某次小批量出货后&…

作者头像 李华
网站建设 2026/11/3 8:41:52

操作系统期末复习指南:从进程到文件系统的知识地图与核心考点

简介&#xff1a;这份资源是面向山东大学操作系统课程期末备考学生的复习笔记&#xff0c;以PDF形式系统梳理了课程核心考点&#xff0c;适合需要快速回顾知识框架、查漏补缺的本科生使用。压缩包内共1个PDF文件&#xff0c;大小约2.91MB&#xff0c;内容按章节组织&#xff0c…

作者头像 李华