概述
嵌入模型将原始文本(如句子、段落或推文)转换为固定长度的数字向量,捕捉其 **语义含义**。这些向量使机器能够基于含义而非精确词汇来比较和搜索文本。
在实践中,这意味着具有相似想法的文本在向量空间中彼此靠近放置。例如,不仅匹配短语 *"机器学习"*,嵌入可以呈现讨论相关概念的文件,即使使用了不同的措辞。
工作原理
- **向量化** — 模型将每个输入字符串编码为高维向量。
- **相似度评分** — 使用数学度量比较向量,以衡量底层文本之间的相关程度。
相似度度量
几种常见的度量用于比较嵌入:
- * **余弦相似度** — 测量两个向量之间的角度。
- * **欧几里得距离** — 测量点之间的直线距离。
- * **点积** — 测量一个向量在另一个向量上的投影量。
以下是计算两个向量之间余弦相似度的示例:
def cosine_similarity(vec1, vec2):
dot = np.dot(vec1, vec2)
return dot / (np.linalg.norm(vec1) * np.linalg.norm(vec2))
similarity = cosine_similarity(query_embedding, document_embedding)
print("Cosine Similarity:", similarity)
接口
LangChain 通过 Embeddings 接口为文本嵌入模型(如 OpenAI、Cohere、Hugging Face)提供标准接口。
两个主要方法可用:
- *
embed_documents(texts: List[str]) → List[List[float]]:嵌入文档列表。 - *
embed_query(text: str) → List[float]:嵌入单个查询。
热门集成
常见部署模式
在实践中,大多数团队收敛于四种模式之一:
- 托管、旗舰:OpenAI
text-embedding-3-large、Cohereembed-english-v3、Googlegemini-embedding-001、Voyagevoyage-3。一次 API 调用,开箱即用的最佳质量,无需本地基础设施。每次调用成本和数据出口依赖。 - 本地、开源:
BAAI/bge-*,mixedbread-ai/mxbai-embed-*,Qwen/Qwen3-Embedding-*,nomic-ai/modernbert-embed-*,sentence-transformers/all-*一次下载,随处运行。无按调用计费成本,数据永不离开您的环境。在小规模下可能比托管API慢;如果有GPU,则速度相当或更快。 - 本地、开源、专业化:一个针对您的特定领域、语言或任务进行微调的模型。以强大的开源基础模型为起点(例如
BAAI/bge-m3) and fine-tuning on even a few thousand in-domain query/document pairs often beats hosted flagships on retrieval accuracy for that domain. - 在生产规模上自托管:通过 Text Embeddings Inference (TEI) 或Ollama提供相同的开源模型(基础版或微调版)。让您享有本地推理的经济效益,同时具备托管服务商的水平扩展能力和API易用性。
LangChain对四种方式一视同仁:您实例化一个 Embeddings 子类并将其交给向量存储或检索器。模式(2)和(3)使用 HuggingFaceEmbeddings;模式(4)使用 HuggingFaceEndpointEmbeddings or OllamaEmbeddings.
需要权衡的因素
质量
从 MTEB 排行榜开始。MTEB 在检索、聚类、分类和重排序任务上对嵌入模型进行基准测试,是事实上的行业参考标准。按您的语言和任务类型进行筛选(检索是RAG最常见的场景)。
排行榜数据并非总能直接迁移,因此在做决定之前请在您自己的数据上进行小规模评估。LangSmith 提供了相关工具;请参阅 评估指南.
成本
托管嵌入服务的定价通常在每百万token几美分到约0.15美元之间。对于仅嵌入一次但每天查询数千次的语料库,成本通常由查询端主导。
本地推理无按调用计费成本,但需要CPU(速度慢)或GPU(硬件或云服务成本)。盈亏平衡点取决于工作负载:低流量的个人项目在CPU上基本免费;对于中等规模的生产环境,通过TEI使用单块GPU运行本地模型在单位经济效益上通常优于托管服务。
延迟
托管嵌入API每次请求会增加约50-200毫秒的网络延迟。本地模型在CPU上处理短查询,小型模型需要10-100毫秒(all-MiniLM-L6-v2级),大型模型需要50-500毫秒。在GPU上,本地推理通常比往返托管API更快。
对于批量索引,每请求延迟不如吞吐量重要。TEI和多进程本地推理会进行大批量处理。在GPU上运行时,请考虑例如 encode_kwargs={"batch_size": 64} 或更高。 HuggingFaceEmbeddings 当在GPU上运行时。
维度
嵌入维度会影响向量存储的存储和查询计算。典型尺寸:
- - 384(小型Sentence Transformer模型,
all-MiniLM-L6-v2) - - 768(中型ST模型,
all-mpnet-base-v2,bge-base) - - 1024 (
bge-large、Cohere v3、Voyage) - - 1536(OpenAI
text-embedding-3-small、Qwen3-Embedding-0.6B) - - 3072+(OpenAI
text-embedding-3-large, Qwen3-Embedding-4B/8B)
较大的向量通常更准确,但会消耗更多存储空间和查询计算资源。多个现代模型(OpenAI text-embedding-3-*, mixedbread-ai/mxbai-embed-large-v1、Matryoshka训练的ST模型、Qwen3-Embedding)支持 **截断**:将向量截取至更小维度,同时保持优雅的质量降级。可用于将更多向量适配到更小的索引中。
上下文长度
大多数经典嵌入模型的上下文上限为512个token(all-mpnet-base-v2、经典BGE)。较新的模型支持更长的上下文:
- -
nomic-ai/modernbert-embed-base:8192个token - -
Alibaba-NLP/gte-multilingual-base: 8192 tokens - -
BAAI/bge-m3: 8192 tokens - - OpenAI
text-embedding-3-*: 8191 tokens
如果您的文本块较长(全页技术文档、法律段落),请优先选择长上下文模型。对于较短的文本块,512个token的限制很少会构成约束。
多语言支持
对于多语言检索,请选择针对您的语言进行训练的模型。推荐默认值:
- - Open:
BAAI/bge-m3,intfloat/multilingual-e5-*,Alibaba-NLP/gte-multilingual-*,Qwen/Qwen3-Embedding-*(通过HuggingFaceEmbeddings) - - 托管版:Cohere
embed-multilingual-v3、OpenAItext-embedding-3-*
查询和文档提示词
多个现代开源模型(如E5、BGE、Qwen3-Embedding、GTE)针对查询和文档使用不同的文本前缀进行训练。在查询时使用错误的前缀是常见的质量下降原因。当使用 HuggingFaceEmbeddings时,请显式传递提示词:
from langchain_huggingface import HuggingFaceEmbeddings
embeddings = HuggingFaceEmbeddings(
model_name="intfloat/e5-large-v2",
encode_kwargs={"prompt": "passage: "},
query_encode_kwargs={"prompt": "query: "},
)
请查阅Hugging Face上各模型的模型卡,获取推荐的提示词字符串。
许可协议
大多数流行的开源嵌入模型采用宽松的许可协议(Apache 2.0、MIT)。部分最新的专业模型在生产环境中使用需要商业许可。在发布前,请检查每个模型的许可协议。
超越单向量密集嵌入
每个块使用单个密集向量是默认设置,但并非唯一选择。
稀疏检索与混合检索
密集嵌入在处理精确匹配查询(产品代码、命名实体、代码标识符)方面不如基于关键词的索引。混合检索将密集索引与BM25或稀疏神经索引(SPLADE、 BAAI/bge-m3的稀疏输出)相结合,以覆盖两种情况。
晚期交互与多向量
ColBERT风格的模型为每个token生成一个向量,而不是为每个块生成一个向量,然后通过晚期交互对查询和文档进行评分。这通常比单向量密集检索在复杂查询上更准确,但代价是更高的存储需求和更复杂的索引。当前该领域的开源模型包括 jinaai/jina-colbert-v2, answerdotai/answerai-colbert-small-v1,以及更新的晚期交互变体,例如 lightonai/LateOn。LangChain的内置检索器针对单向量嵌入;晚期交互通常需要专门的索引(Vespa、Qdrant的多向量支持或PyLate)。
起点
如果您只需要一个可用的起点:
- - 快速原型,托管版:
OpenAIEmbeddings(model="text-embedding-3-small") - - 快速原型,本地版,无需API密钥:
HuggingFaceEmbeddings(model_name="sentence-transformers/all-mpnet-base-v2", encode_kwargs={"normalize_embeddings": True}) - - 生产环境,托管版,质量优先:
VoyageAIEmbeddings(model="voyage-3")orOpenAIEmbeddings(model="text-embedding-3-large") - - 生产环境,开源版,质量优先:
HuggingFaceEmbeddings(model_name="BAAI/bge-m3", encode_kwargs={"normalize_embeddings": True})通过TEI服务 - - 多语言,开源版:
HuggingFaceEmbeddings(model_name="intfloat/multilingual-e5-large")已配置查询和文档提示词
在您自己的数据上衡量检索质量,然后进行迭代。
缓存
可以存储或临时缓存嵌入向量,以避免重新计算。
可以使用 CacheBackedEmbeddings。此包装器将嵌入向量存储在键值存储中,其中文本被哈希处理,哈希值用作缓存中的键。
初始化 CacheBackedEmbeddings is from_bytes_store的主要支持方式是:它接受以下参数:
- - **
underlying_embedder**:用于嵌入的嵌入器。 - - **
document_embedding_cache**:任意ByteStore用于缓存文档嵌入 - - **
batch_size**:(可选,默认为None)存储更新之间要嵌入的文档数量 - - **
namespace**:(可选,默认为"")文档缓存要使用的命名空间。有助于避免冲突(例如,将其设置为嵌入模型名称) - - **
query_embedding_cache**:(可选,默认为None) AByteStore用于缓存查询嵌入,或True来重用与document_embedding_cache. - - 始终设置
namespace参数以避免使用不同嵌入模型时发生冲突 - -
CacheBackedEmbeddings默认不缓存查询嵌入。要启用此功能,请指定一个query_embedding_cache.
from langchain_classic.embeddings import CacheBackedEmbeddings # [!code highlight]
from langchain_classic.storage import LocalFileStore # [!code highlight]
from langchain_core.vectorstores import InMemoryVectorStore
# Create your underlying embeddings model
underlying_embeddings = ... # e.g., OpenAIEmbeddings(), HuggingFaceEmbeddings(), etc.
# Store persists embeddings to the local filesystem
# This isn't for production use, but is useful for local
store = LocalFileStore("./cache/") # [!code highlight]
cached_embedder = CacheBackedEmbeddings.from_bytes_store(
underlying_embeddings,
store,
namespace=underlying_embeddings.model
)
# Example: caching a query embedding
tic = time.time()
print(cached_embedder.embed_query("Hello, world!"))
print(f"First call took: {time.time() - tic:.2f} seconds")
# Subsequent calls use the cache
tic = time.time()
print(cached_embedder.embed_query("Hello, world!"))
print(f"Second call took: {time.time() - tic:.2f} seconds")
在生产环境中,通常会使用更强大的持久存储,如数据库或云存储。请参阅 存储集成 了解选项