面试官问Embedding和LLM输出向量关系?Spring AI实战拆解RAG检索链路
1. 面试官到底想考什么从一道题看穿 Embedding 与 LLM 的关系Java 后端这两年面试的风向变得很快。前几年还在问 HashMap 扩容、JVM 调优、Spring 循环依赖现在稍微像样点的 AI 相关岗位面试官张口就是“Embedding 和 LLM 输出的向量到底什么关系”。很多人第一反应是懵的——这俩不都是向量吗不都是模型吐出来的浮点数数组吗能有什么关系我一开始也这么想直到自己用 Spring AI 搭了一套知识库问答系统被一个检索不准的问题折磨了整整两天才真正把这两个东西掰扯清楚。这道题表面在问概念实际上考的是你有没有真正动手做过 RAG检索增强生成有没有踩过“拿 LLM 输出向量去算相似度”这种坑。先把结论摆出来Embedding 模型和 LLM 都会输出向量但它们的向量完全不是一回事用途、语义空间、维度含义都不一样。Embedding 输出的是“语义坐标”LLM 输出的是“下一个词的打分分布”或者“隐藏层状态”。面试官想听的不是背定义而是你能不能讲清楚为什么 RAG 里检索必须用 Embedding 模型而不是随便找个 LLM 的输出凑合。这篇文章我打算按面试答题的思路来写但不会写成八股文。我会把原理拆开把 Spring AI 的生产级代码贴出来再把我实际踩过的坑和排查过程讲清楚。适合正在准备 Java AI 岗位面试的朋友也适合已经上手 Spring AI 但还没搞明白底层逻辑的开发者。看完你至少能做到两件事面试时把这道题讲出层次感写代码时不再把两种向量混用。2. 把两种向量拆开看语义坐标 vs 概率分布2.1 Embedding 模型输出的向量到底是什么Embedding 模型的核心任务只有一个把一段文本映射到一个固定维度的稠密向量空间里并且保证语义相近的文本在这个空间里的距离也相近。比如“Java 面试”和“Java 求职”这两个短语经过 Embedding 之后它们的余弦相似度会很高可能到 0.9 以上而“Java 面试”和“今天天气不错”的相似度就会很低。这个向量空间是怎么来的训练的时候模型见过海量文本对通过对比学习的方式让语义相似的样本在向量空间里靠近不相似的推远。最终每个维度并不对应某个具体的、人类能解释的含义而是整个向量共同编码了语义信息。你可以把它理解成给每段文本在一个高维地图上打了一个坐标点维度可能是 768、1024、1536 甚至更高。关键点在于Embedding 向量的设计目标就是“可比”。它天生就是为了计算相似度、做检索、做聚类、做分类而存在的。你拿两个 Embedding 向量算余弦相似度得到的结果是有意义的能反映语义接近程度。2.2 LLM 输出的向量又是什么LLM 的输出向量要分两种情况说很多人混淆就混淆在这里。第一种是 LLM 最后一层输出的 logits经过 softmax 之后变成词表上的概率分布。假设词表有 15 万个 token那这个向量就是 15 万维的每一维代表“下一个 token 是这个词”的概率。这个向量的含义非常明确它是用来做下一个词预测的不是用来做语义相似度计算的。你拿两个句子的 logits 向量算余弦相似度得到的数字没有任何语义检索意义。第二种是 LLM 中间隐藏层的输出也就是 hidden states。比如你让 LLM 处理一句话取最后一层 hidden state 的最后一个 token 对应的向量维度可能是 4096、8192。这个向量确实编码了上下文信息理论上也能拿来做一些相似度计算但它不是为这个任务训练的。它的语义空间和 Embedding 模型的语义空间完全是两套坐标系不能混用。我打个比方。Embedding 模型输出的向量像是地图上的经纬度坐标专门用来定位和测距的。LLM 输出的 logits 像是考试时每道选择题的选项概率分布告诉你它觉得哪个答案最可能对。你拿两份概率分布去算地理距离逻辑上就不成立。2.3 一张表把两者的区别钉死对比维度Embedding 模型输出LLM 输出logitsLLM 输出hidden states向量维度768/1024/1536 等固定维度等于词表大小通常 10 万等于隐藏层维度4096/8192 等语义含义文本的语义坐标下一个 token 的概率分布上下文编码表示设计目标相似度计算、检索、聚类下一个词预测语言建模的中间表示能否直接算相似度能且结果有意义不能无检索意义勉强能但效果远不如 Embedding典型用途RAG 检索、去重、推荐文本生成特征提取、微调是否归一化通常归一化到单位长度softmax 后和为 1一般不归一化这张表建议面试前多看几遍。面试官如果追问“那 LLM 的 hidden states 能不能替代 Embedding”你就可以从“训练目标不同导致语义空间不可比”这个角度回答直接打到点上。3. Spring AI 实战从零搭一套 RAG 检索链路3.1 项目依赖与模型选型光讲原理不够面试官下一句往往是“你实际怎么用的”。我用 Spring AI 搭一个最小可用的 RAG 示例把 Embedding 和 LLM 的分工展示清楚。先看依赖。Spring AI 目前迭代很快我用的版本是 1.0.0 系列搭配 Spring Boot 3.3.x。核心依赖就两个一个负责 Embedding一个负责 Chat。dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-openai-spring-boot-starter/artifactId version1.0.0/version /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency模型选型上Embedding 我建议用专门的 Embedding 模型比如 text-embedding-3-small 或者开源的 bge-large-zh。Chat 模型用 GPT-4o-mini 或者通义千问都行。这里有个关键原则Embedding 模型和 Chat 模型是两套独立的模型各司其职不要想着用一个模型全包了。配置写进 application.ymlspring: ai: openai: api-key: ${OPENAI_API_KEY} embedding: options: model: text-embedding-3-small chat: options: model: gpt-4o-mini temperature: 0.2temperature 设 0.2 是为了让回答更稳定RAG 场景不需要太多创造性。3.2 文档入库Embedding 模型的主战场RAG 的第一步是把知识库文档切块、向量化、存进向量数据库。这一步全程用 Embedding 模型LLM 完全不参与。Service public class DocumentIngestService { private final VectorStore vectorStore; public DocumentIngestService(VectorStore vectorStore) { this.vectorStore vectorStore; } public void ingest(ListString rawDocuments) { ListDocument documents new ArrayList(); for (String raw : rawDocuments) { // 按 500 字切块重叠 50 字避免语义被切断 ListString chunks splitText(raw, 500, 50); for (String chunk : chunks) { documents.add(new Document(chunk)); } } // 这一步内部调用 Embedding 模型把每个 chunk 转成向量 vectorStore.add(documents); } private ListString splitText(String text, int size, int overlap) { ListString result new ArrayList(); int start 0; while (start text.length()) { int end Math.min(start size, text.length()); result.add(text.substring(start, end)); start (size - overlap); } return result; } }vectorStore.add()这一行是关键。Spring AI 内部会调用 Embedding 模型把每个 Document 的文本转成向量然后连同原文一起存进向量数据库。存进去的是“文本 向量”的组合检索时先用向量找相似的再把原文拿出来给 LLM。切块参数 500 和 50 不是拍脑袋定的。500 字大约对应 300 到 400 个 token能容纳一个完整的知识点重叠 50 字是为了防止一个句子正好被切在边界上导致语义断裂。这两个参数我调过好几轮切太小检索碎片化切太大检索精度下降。3.3 检索阶段用 Embedding 找相似别碰 LLM用户提问来了之后第一步是把问题也转成 Embedding 向量然后去向量数据库里找最相似的几个 chunk。Service public class RetrievalService { private final VectorStore vectorStore; public RetrievalService(VectorStore vectorStore) { this.vectorStore vectorStore; } public ListDocument retrieve(String query, int topK) { SearchRequest request SearchRequest.builder() .query(query) .topK(topK) .similarityThreshold(0.7) .build(); return vectorStore.similaritySearch(request); } }注意这里SearchRequest的 query 是原始文本Spring AI 内部会调用 Embedding 模型把它转成向量再和库里存的向量算相似度。整个检索过程 LLM 一次都没被调用。这就是面试官想听到的分工Embedding 负责“找”LLM 负责“答”。similarityThreshold 设 0.7 是个经验值。设太低会召回一堆不相关的设太高可能什么都召不回。我一般会先跑一批测试问题看召回结果的相似度分布再定这个阈值。3.4 生成阶段LLM 登场把检索结果揉进回答检索到相关 chunk 之后把它们拼进 prompt交给 LLM 生成最终回答。Service public class RagChatService { private final ChatClient chatClient; private final RetrievalService retrievalService; public RagChatService(ChatClient.Builder builder, RetrievalService retrievalService) { this.chatClient builder.build(); this.retrievalService retrievalService; } public String chat(String userQuestion) { ListDocument docs retrievalService.retrieve(userQuestion, 5); String context docs.stream() .map(Document::getText) .collect(Collectors.joining(\n\n---\n\n)); String prompt 你是一个严谨的技术助手。请仅根据下面提供的参考资料回答问题。 如果参考资料中没有相关信息直接说“资料中没有找到相关内容”不要编造。 参考资料 %s 用户问题%s .formatted(context, userQuestion); return chatClient.prompt() .user(prompt) .call() .content(); } }这段代码里LLM 接收的是“检索结果 用户问题”拼成的 prompt输出的是自然语言回答。LLM 在这里输出的不是向量是文本。如果你非要看它内部的向量那是 logits 或者 hidden states和检索用的 Embedding 向量没有任何关系。4. 生产环境踩坑实录那些面试官爱追问的细节4.1 坑一拿 LLM 的 embedding 接口当 Embedding 模型用有些 LLM 平台提供了 embedding 接口名字里带 embedding但底层用的模型和专门的 Embedding 模型不是一回事。我早期图省事直接用某个 Chat 模型平台附带的 embedding 接口结果检索效果很差相似度分数普遍偏低召回的内容经常跑偏。后来换成专门的 Embedding 模型同样的文档和问题召回准确率肉眼可见地提升。原因在于专门训练的 Embedding 模型在对比学习阶段见过大量语义相似/不相似的样本对它的向量空间是为相似度计算优化的。而 Chat 模型附带的 embedding 接口很多只是把 hidden states 拿出来用没有经过针对性的对比学习训练。面试时如果被问到“怎么选 Embedding 模型”你可以从这几个维度答看公开榜单上的检索任务排名、看是否支持中文、看维度和成本、看是否支持自定义维度压缩。别只看模型大小。4.2 坑二向量维度不一致导致检索报错有一次我把 Embedding 模型从 1536 维换成了 1024 维但向量数据库里还存着旧模型生成的 1536 维向量。检索的时候直接抛异常维度对不上。这个坑的教训是换 Embedding 模型必须重建整个向量库。因为不同模型的向量空间完全不可比旧向量在新模型的空间里没有任何意义。生产环境换模型要走的流程是新建一个 collection用新模型重新灌数据验证效果后再切流量最后删旧 collection。4.3 坑三相似度阈值设错导致召回为空similarityThreshold 这个参数很微妙。不同 Embedding 模型的相似度分布不一样有的模型相似度普遍偏高有的普遍偏低。我一开始照搬网上的 0.8结果大部分问题都召回为空LLM 只能回答“资料中没有找到相关内容”。后来我写了个小脚本拿 50 个测试问题跑一遍打印每个问题 top5 的相似度分数观察分布之后再定阈值。实测下来text-embedding-3-small 在中文技术文档上相关 chunk 的相似度大多在 0.6 到 0.85 之间所以我把阈值定在 0.65 左右比较合适。4.4 常见问题速查表问题现象可能原因排查方向解决方法检索结果完全不相关Embedding 模型选错或维度不匹配检查模型名称和向量库维度换专用 Embedding 模型重建向量库召回为空相似度阈值过高打印相似度分布降低阈值到合理区间回答编造内容prompt 没约束好检查 prompt 是否要求“仅根据资料回答”加强 prompt 约束加兜底话术检索慢向量库没建索引检查向量库索引配置建 HNSW 或 IVF 索引切块语义断裂chunk size 太小或重叠不足检查切块参数增大 chunk size增加 overlap同一问题每次回答差异大temperature 过高检查 chat 模型参数降低 temperature 到 0.1-0.35. 面试答题框架把这道题讲出层次感5.1 三层回答法如果面试官问“Embedding 和 LLM 输出向量什么关系”我建议按三层来答。第一层先定性两者都是向量但用途和语义空间完全不同。Embedding 向量是语义坐标为相似度计算而生LLM 输出向量是概率分布或隐藏状态为文本生成而生。第二层讲区别从维度、训练目标、是否可比较三个角度展开。重点强调“不同模型的向量空间不可比”这是很多人忽略的点。第三层落到实践在 RAG 系统里Embedding 负责检索LLM 负责生成两者通过“检索结果拼进 prompt”这个环节衔接。检索阶段绝不调用 LLM生成阶段绝不拿 LLM 输出向量去做相似度。5.2 面试官可能追问的问题“为什么不能用 LLM 的 hidden states 做检索”答训练目标不同hidden states 没有经过对比学习语义空间不适合相似度计算实测效果远不如专用 Embedding 模型。“Embedding 模型怎么选”答看检索榜单、看中文支持、看维度和成本、看是否支持维度压缩。“向量数据库怎么选”答小规模用内存版或 pgvector大规模用 Milvus、Qdrant 等看团队运维能力。“怎么评估 RAG 效果”答构造问答对看召回率、准确率人工评估回答质量必要时上 RAGAS 等评估框架。“Spring AI 和 LangChain4j 怎么选”答Java 生态里 Spring AI 和 Spring Boot 集成更顺LangChain4j 功能更偏 Python LangChain 的风格看团队技术栈。5.3 一个容易被忽略的加分点面试时如果能提到“Embedding 模型也可以微调”会显得你研究得更深。通用 Embedding 模型在垂直领域比如医疗、法律、专利上效果可能不够好这时候可以用领域数据做对比学习微调让向量空间更贴合业务语义。这个点很多人不知道说出来就是加分项。6. 我实际做项目时的一些体会这套 RAG 链路我在两个项目里落地过一个是内部技术文档问答一个是客服知识库。最大的体会是Embedding 的质量决定了 RAG 的上限LLM 的质量决定了下限。检索不准再强的 LLM 也答不对检索准了普通 LLM 也能给出不错的回答。所以如果你要上手做 RAG我建议把 70% 的精力花在文档切块、Embedding 模型选型、检索参数调优上30% 花在 prompt 工程上。很多人反过来天天调 prompt结果检索召回的都是垃圾怎么调都没用。另外一个小技巧检索的时候可以同时用向量检索和关键词检索做混合检索再重排序。纯向量检索对专有名词、型号、代码标识符不敏感加一路关键词检索能明显提升召回质量。Spring AI 目前对混合检索的支持还在完善中可以自己组合实现。最后说一句这道面试题真正想筛的是“有没有动手做过”的人。背概念谁都会但踩过坑、调过参、知道为什么这么设计的人一开口就能听出来。希望这篇内容能帮你把这道题讲透也把 RAG 这条路走顺。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →