Embedding:文本怎么变成向量?语义检索为什么能工作?
10 / 16 章
Embedding 不是可有可无的组件,它是 RAG 的地基。地基歪了,后面 Retriever、Rerank、Prompt 再漂亮也救不回来。 一、Embedding 到底是什么? Embedding,就是把一段文字变成一串数字。 这串数
Embedding 不是可有可无的组件,它是 RAG 的地基。地基歪了,后面 Retriever、Rerank、Prompt 再漂亮也救不回来。
一、Embedding 到底是什么?
Embedding,就是把一段文字变成一串数字。
这串数字不是随机数,而是语义坐标。意思相近的文本,向量距离更近;意思差得远的文本,距离更远。
LangChain 官方文档对 Embedding 的解释也很直接:Embedding 模型会把原始文本转换成固定长度的数字向量,这些向量能表达语义含义,让机器按“意思”比较文本,而不是只按关键词匹配。
这就是语义检索能工作的原因。
用户问“提前还款怎么操作”,知识库里写的是“如何结清贷款”,关键词不一样,但意思接近。Embedding 能把它们拉到同一个语义区域,向量库就能召回。
如果没有 Embedding,RAG 很容易退化成普通搜索。只能找关键词,找不到意思。
二、Embedding 在 RAG 里处在哪一层?
前面几章已经讲过:Loader 把资料读进来,Splitter 把长文档切成 Chunk。接下来,就轮到 Embedding。
它做一件事:把每个 Chunk Document 的 page_content 转成向量,然后交给 VectorStore 入库。
离线建库和在线查询两条链路,都要经过 Embedding
离线建库时,Embedding 面向的是一批文档。在线查询时,Embedding 面向的是用户的一句话。
所以 LangChain 在接口上故意拆成两个方法:
embed_documents(texts):文档批量向量化。
embed_query(text):问题单条向量化。
这两个方法看起来简单,但这是整个语义检索的分水岭。文档侧是“建索引”,问题侧是“查索引”。
三、Embeddings 抽象接口
打开 LangChain Core 的源码,可以看到 Embeddings 是一个抽象基类。它不是 OpenAI,也不是 HuggingFace。它只是规定:一个 Embedding 模型必须能把文档和查询变成向量。
源码的设计非常克制。核心就是四个方法:
class Embeddings(ABC):
def embed_documents(self, texts: list[str]) -> list[list[float]]: ...
def embed_query(self, text: str) -> list[float]: ...
async def aembed_documents(self, texts: list[str]) -> list[list[float]]: ...
async def aembed_query(self, text: str) -> list[float]: ...
这里最关键的是返回类型。
文档是 list[str],所以返回 list[list[float]]。一个文本,对应一个向量。
查询是 str,所以返回 list[float]。一个问题,对应一个向量。
这就是 LangChain 抽象层的好处:上层 VectorStore 和 Retriever 不需要知道你底下接的是哪个模型。只要实现这个接口,就能被接入 RAG 流水线。
四、为什么要区分 embed_documents 和 embed_query?
很多人第一次看源码会疑惑:文档是文本,问题也是文本,为什么不统一叫 embed_text?
不同 Embedding 模型可能会对“文档”和“查询”使用不同提示方式。比如某些检索模型会要求:
文档侧:把这段内容编码成可检索资料。
查询侧:把这个问题编码成检索意图。
有些模型内部路径一样,有些模型内部路径不一样。LangChain 先把接口拆开,给不同模型留下扩展空间。
这就是好的框架设计:看起来多写了一个方法,实际是在给未来兼容性留口子。
五、OpenAIEmbeddings 怎么实现?
以 OpenAIEmbeddings 为例,源码链路很清晰。
它的 embed_documents 大致分两种情况:
如果不检查上下文长度:直接按 chunk_size 分批调用 embedding API。
如果检查上下文长度:先 tokenize,再按 embedding_ctx_length 切分,再批量请求。
源码里有一个细节很重要:长文本不会直接硬塞给 embedding API。它会被分成 token chunks,再请求向量,最后把多段结果合并。
embed_query 更直接。源码逻辑可以压缩成一句话:
def embed_query(self, text: str) -> list[float]:
return self.embed_documents([text])[0]
也就是说:问题向量化,本质上还是复用文档向量化能力。先把单个问题包装成列表,再取第一个结果。
这不是偷懒。恰恰是工程复用。
六、从源码看 VectorStore 为什么能自动调用 Embedding?
很多人写代码时会发现,自己明明只写了 from_texts、add_documents 或 retriever.invoke,为什么 Embedding 就自动被调用了?
因为 VectorStore 和 Retriever 在内部已经接上了 Embeddings 接口。
文档入库时:
VectorStore.add_documents(documents)
-> 取出 page_content
-> embeddings.embed_documents(texts)
-> 向量 + metadata + id 入库
查询检索时:
retriever.invoke(query)
-> vectorstore.similarity_search(query)
-> embeddings.embed_query(query)
-> 用 query_vector 去向量库查近邻
-> 返回 List[Document]
所以,Embedding 是隐藏在 RAG 后面的发动机。你看不到它转,但它一直在转。
七、向量怎么比较?三个常见相似度指标
文本变成向量后,下一步就是比较。LangChain 官方文档列出了常见的三类距离或相似度指标:余弦相似度、欧氏距离、点积。
余弦相似度:看两个向量方向是否接近。常用于语义相似度。
欧氏距离:看两个点在空间里的直线距离。
点积:看一个向量在另一个方向上的投影强度。
实际项目里,你不一定要自己手写这些公式。向量库会帮你算。但你必须知道:不同指标会影响召回结果,向量是否归一化也会影响结果。
八、Embedding 模型怎么选?这张图比参数更重要
选 Embedding 模型,不要只看榜单。
真正上线时,最关键的是业务评测。你要拿自己的问题、自己的知识库、自己的标准答案去测。
比如智能客服要测:用户口语化提问能不能召回制度文档。股票研报助手要测:同一公司不同公告、行业新闻、研报摘要之间能不能建立语义关联。
如果只拿通用 benchmark 选模型,很容易线上翻车。
九、缓存为什么重要?Embedding 不是一次性成本
很多人以为 Embedding 只在建库时花钱。错。
查询也要 embed_query。每个用户问题都可能触发一次向量化。高并发下,延迟和成本都会上来。
所以生产系统至少要做三类缓存:
文档向量缓存:同一 Chunk 内容不重复向量化。
查询向量缓存:高频问题不重复调用 embedding 模型。
索引版本缓存:模型版本、切分版本、向量库版本要绑定。
只要文档内容、切分策略、Embedding 模型版本任何一个变了,历史向量就可能需要重建。
十、企业级落地:Embedding 不该只是一个函数
如果只是 Demo,Embedding 可以写成一行代码。
如果是企业系统,Embedding 必须进入数据流水线。
Java 主服务负责文档上传、任务调度、权限审计、索引版本。
Python AI 服务负责 Loader、Splitter、Embedding、Retriever。
Milvus / ES 负责向量和关键词索引。
Redis 做缓存,MySQL 记录元数据和版本。
日志系统记录每次向量化耗时、模型名称、维度、token、失败原因。
Embedding 是基础设施,不是工具函数。
十一、Embedding 最容易踩的坑
RAG 效果差,很多时候不是大模型的问题,而是 Embedding 链路的问题。
最常见的就是:文档切得不好、模型版本乱换、向量没有重建、metadata 没带、只做向量检索不做关键词融合。
尤其是模型版本。
同一个向量库里,不能混着放不同 Embedding 模型生成的向量。维度可能不同,语义空间也可能不同。哪怕维度一样,距离也不一定有可比性。
十二、总结
Embedding 把文本变成向量,VectorStore 按距离找资料,Retriever 把相关 Document 交给 Prompt,Model 再基于资料生成答案。
所以,RAG 的第一性原理不是“把资料塞给大模型”,而是:先把资料变成可计算的语义坐标,再按问题找到最相关的上下文。
学 LangChain,Embedding 这一章必须吃透。因为从这里开始,RAG 才真正从“文本处理”进入“语义计算”。
相关推荐
LangChain Tool Calling 原理:模型是怎么决定调用哪个工具的?
一、模型不会真的调用工具 很多人第一次看 Tool Calling,会以为模型自己会查数据库、调接口、执行函数。错。模型没有直接执行你代码的权限。它能做的,是看懂你给它的工具说明,然后输出一段结构化数据。 这段结构化数据就是 tool_ca
LangChain 系列之Tools:让大模型真正连接业务系统
前面几章,我们把 RAG 的底层链路讲完了:文档进来,切分,向量化,入库,检索,重排,最后把上下文交给模型。 但这还不够。 RAG 让模型会“查资料”。Tools 让模型能“办事情”。 没有 Tools,大模型只是一个会聊天的脑子。它能分析
LangChain 系列:从 0 搭一个企业知识库问答系统
这一章不再单讲一个组件,而是把前面所有 RAG 组件合起来:从文件上传,到向量入库,再到用户提问、检索证据、模型回答、日志追踪。 01 先搞清楚:企业知识库问答不是 Demo Demo 只要能回答。企业系统要能上传、能解析、能检索、能引用、
Tensor:PyTorch 世界里的一切都是张量
1. Tensor 是 PyTorch 的基本单位 PyTorch 里,模型吃进去的是 Tensor,参数保存的是 Tensor,梯度也是 Tensor,GPU 上跑的还是 Tensor。 你可以把 Tensor 理解成“加强版数组”。但这
Rerank 与上下文压缩:为什么召回 TopK 后还要重排?
RAG 最容易踩的坑,不是“没有召回”。 而是召回了一堆看起来相关、实际会干扰模型的资料。 所以,成熟的 RAG 链路不会把 TopK 直接塞进 Prompt。 它会先召回,再重排,再压缩。 1. 为什么召回 TopK 后还要重排? 向量召
环境安装与开发姿势:CPU、CUDA、ROCm、MPS 怎么选
学 PyTorch,第一个门槛不是模型。是环境。环境装错,代码还没开始跑,报错已经堆满屏。 这一章只讲一件事:把 PyTorch 环境一次性讲清楚。不是背安装命令,而是看懂安装背后的逻辑。 因为命令会随着版本变化。逻辑不会。你只要能判断 C
阅读补充
一句话看懂
Embedding 不是可有可无的组件,它是 RAG 的地基。地基歪了,后面 Retriever、Rerank、Prom
事件背景
这篇内容围绕“Embedding”展开,热闻岛基于公开信息整理事件背景、主要进展与可继续关注的方向。
事件时间线
发布
相关信息进入公开传播
更新
热闻岛对内容进行整理与补充。
看点
- · Embedding的最新进展是什么
- · 相关信息对用户或行业会带来哪些影响
- · 后续是否会有新的回应或处理结果
后续关注
- · 后续官方回应或权威通报
- · 相关主体的进一步说明
- · 事件对普通用户和平台传播的持续影响
免责声明:本文仅代表作者观点,不构成投资建议、法律建议、医疗建议。财经类内容尤其需要注意风险;爆料类信息请以权威通报为准。
评论 (0)
登录后即可发表评论
去登录