国产 Embedding 模型盘点:RAG 场景向量化选型指南
上周有个做知识库问答的朋友找我吐槽:同一份产品手册,换了个 Embedding 模型之后检索命中率直接从 80% 掉到 50%,排查了半天才发现是维度截断没处理对,向量库里新旧向量混在一起,余弦相似度算出来全乱了。这类坑没人提前告诉你,踩一次就得重建整个向量库。
Embedding 模型是 RAG(检索增强生成)架构的基础组件,决定了向量检索的质量上限——生成阶段模型再强,召回的文档片段不对,答案照样跑偏。国产厂商均推出了自家 Embedding API,价格普遍低于 OpenAI text-embedding-3 系列,且对中文语义的把握更准确,这篇把选型、接入、避坑一次讲透。
主流国产 Embedding 模型速查
| 厂商 | 模型名 | 向量维度 | 最大输入 token | 中文优化 | 价格定性 |
|---|---|---|---|---|---|
| 智谱 | embedding-3 | 2048(可截断) | 8192 | 强 | 极低 |
| 通义千问 | text-embedding-v3 | 1024 / 2048 | 8192 | 强 | 低 |
| 文心 | embedding-v1 | 384 | 384 | 强 | 低 |
| 豆包 | doubao-embedding | 2048 | 4096 | 强 | 低 |
| Kimi | 暂无独立 Embedding API | — | — | — | — |
| DeepSeek | 暂无独立 Embedding API | — | — | — | — |
截至 2026-06,以官方为准;部分厂商 Embedding 仍在公测,请以控制台实际可用状态为准。
这张表里最容易被忽略的一列是「向量维度」后面那个「可截断」。智谱 embedding-3 和通义 text-embedding-v3 都是用 MRL(Matryoshka Representation Learning,俄罗斯套娃表示学习)训练出来的——简单说就是模型在训练时刻意让向量的前几百维就已经包含了大部分语义信息,越往后的维度提供的是精度上的边际增益。所以你可以直接从 2048 维向量里截取前 1024 维继续用,语义损失很小,不需要重新调用一次模型。这也是为什么智谱和通义敢在 API 里直接提供 dimensions 参数,而不是让你事后自己降维——传统 Embedding 模型(比如老版 BERT 系)如果硬截断,效果会明显跌。
文心 embedding-v1 维度只有 384,本质是走的轻量模型路线,不是靠 MRL 截断出来的,所以千万别拿文心的向量去截到 128 维再用,效果没保证,官方也没做这个训练目标。
接入示例:OpenAI 兼容调用
大多数国产 Embedding API 同样遵循 OpenAI Embeddings 协议:
from openai import OpenAI
# 以智谱 embedding-3 为例
client = OpenAI(
api_key="sk-xxxxxxxx",
base_url="https://open.bigmodel.cn/api/paas/v4",
)
response = client.embeddings.create(
model="embedding-3",
input="如何用 Python 调用大模型 API?",
)
vector = response.data[0].embedding
print(f"向量维度:{len(vector)}") # 2048
这段代码看着简单,但有两个地方新手常翻车。第一个是 base_url——国产 Embedding API 基本都套了 OpenAI SDK 的壳,靠的就是这一行把请求路由到自家网关,如果你复制了别的厂商代码忘了改这里,报错通常是 401 Unauthorized 或者干脆连不上,第一反应先核对 base_url 和 api_key 是不是配套的,这是最容易被忽略的低级错误,没有之一。第二个是 input 传入的文本长度——embedding-3 最大支持 8192 token,超出部分官方会做静默截断(不报错,但后半段文本直接被丢弃),如果你的文档片段本身就接近这个长度,检索时会发现末尾信息「查不到」,排查起来很隐蔽,建议入库前用 tokenizer 先做一次长度检查,超限的片段主动切分成两段分别入库,而不是指望 API 帮你截好。
通义千问 Embedding 调用(指定维度):
client = OpenAI(
api_key="sk-QWEN_KEY",
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
)
response = client.embeddings.create(
model="text-embedding-v3",
input=["文档片段1", "文档片段2"], # 支持批量
dimensions=1024, # 可选 1024 或 2048
)
注意这里 input 传了个列表——批量传入是国产 Embedding API 都支持的用法,一次请求算多条文本比循环发多次请求快得多,也省网络开销。但批量不是没有上限的,通义单次请求的批量条数和总 token 数都有限制(以官方文档为准,超限直接报 400 参数错误),实际建库时我一般按每批 20~50 条、总长度不超过单条最大 token 的做法来切批次,宁可多发几次请求,也别一次塞太多把请求打回来重来。
还有个容易忽略的细节:同一个知识库里不要混用不同 dimensions 参数生成的向量。比如你先用 dimensions=2048 建的库,后来嫌存储贵改成 dimensions=1024 继续往里写,结果就是向量库里两种维度的向量共存,大多数向量数据库(Milvus、Faiss、pgvector)的索引是按固定维度建的,维度不一致轻则报错、重则相似度计算直接失真却不报错——这也是文章开头那个朋友踩的坑。换维度要么重建整个库,要么至少给新旧向量打上版本标记分开检索。
批量场景下超时和限流也得处理,不能指望一次请求稳过:
import time
from openai import OpenAI, RateLimitError, APITimeoutError
client = OpenAI(api_key="sk-QWEN_KEY", base_url="https://dashscope.aliyuncs.com/compatible-mode/v1")
def embed_with_retry(texts, dimensions=1024, max_retries=3):
for attempt in range(max_retries):
try:
resp = client.embeddings.create(
model="text-embedding-v3",
input=texts,
dimensions=dimensions,
timeout=10, # 单次请求超时(秒),批量越大越容易超时,别用默认值硬扛
)
return [d.embedding for d in resp.data]
except RateLimitError:
wait = 2 ** attempt # 指数退避:1s、2s、4s
print(f"限流了,{wait}s 后重试(第 {attempt + 1} 次)")
time.sleep(wait)
except APITimeoutError:
print("请求超时,减小批量大小重试")
texts = texts[: len(texts) // 2] or texts # 简单降级:批量减半
raise RuntimeError("重试耗尽,建库任务需要人工介入检查")
指数退避不是可有可无的花架子——免费额度并发只有 5~10 路,批量建库跑起来很容易撞到 429,如果你的重试逻辑是「失败就立刻重发」,只会让限流更严重,2 ** attempt 这种退避写法能让请求速率自然降下来,给配额恢复的时间。
RAG 场景选型建议
| 场景 | 推荐模型 | 理由 |
|---|---|---|
| 纯中文知识库检索 | 通义 text-embedding-v3 | 中文 MTEB 榜单表现优秀,2048 维精度高 |
| 中英混合企业文档 | 智谱 embedding-3 | 多语言能力强,价格极低适合大批量 |
| 成本敏感、海量文档 | 文心 embedding-v1 | 维度低(384)存储省,价格最低 |
| 已有豆包 LLM 调用链 | 豆包 doubao-embedding | 同平台统一计费,运维简便 |
向量维度与存储成本
向量维度越高,语义表达越精准,但存储和检索成本也成正比增长:
- 2048 维:1 亿条文档向量约需 800 GB 存储(float32),适合精度优先场景;
- 1024 维:约 400 GB,性价比均衡;
- 384 维:约 150 GB,适合资源受限或成本敏感场景。
大多数 RAG 应用 1024 维已足够,无需盲目追求高维。
相似度计算:别忘了归一化
拿到向量之后,检索阶段通常算的是余弦相似度或者内积。这里有个新手容易漏掉的步骤:部分国产 Embedding 模型返回的向量不是单位向量,也就是模长不是 1。如果你的向量数据库用的是内积(dot product)做相似度而不是余弦,没做归一化的话,模长本身的差异会干扰相似度排序——两条语义接近但模长差很多的向量,算出来的分数反而没有模长接近但语义稍远的向量高。
自查方法很简单,拿到向量先算一下模长:
import numpy as np
vec = np.array(vector)
norm = np.linalg.norm(vec)
print(f"向量模长:{norm:.4f}") # 如果不是 1.0 附近,检索前手动归一化
unit_vec = vec / norm
Milvus、pgvector 这些主流方案大多支持在建索引时指定 cosine 距离,这种情况下引擎内部会自动做归一化,你不用操心;但如果你用的是 inner product 或者自己手写 numpy 算相似度,就一定要在写入库之前手动归一化一遍,不然排序结果会「看起来能跑但精度莫名其妙下降」,这种 bug 最难排查,因为它不报错,只是效果差。
混合检索:Embedding 不是万能的
纯向量检索(dense retrieval)对同义改写、语义相近的查询效果很好,但碰到专有名词、型号、编号这类需要精确匹配的查询反而容易漏召回——比如用户搜「GPT-4o」,向量模型可能把它和「GPT-4」「GPT-4 Turbo」算得很接近,检索出来一堆语义相关但答非所问的结果。这种场景下比较成熟的做法是「混合检索」:向量检索(dense)+ 关键词检索(sparse,比如 BM25)各自出一批候选,再用加权或者 Rerank 模型把两路结果合并排序。国产 Rerank 模型的选型和接入可以参考国产 Rerank 模型盘点,Embedding 负责召回、Rerank 负责精排,这套组合拳在生产环境里比单纯堆高 Embedding 维度更有效。
成本怎么估
建库成本主要看两块:调用费和存储费。调用费按 token 计,国产 Embedding 普遍是「低价定性」,但具体到项目上还是得自己算一遍——假设你有 10 万篇文档,平均每篇切成 3 个片段、每段 500 token,总调用量就是 10 万 × 3 × 500 = 1.5 亿 token,乘以官方单价(不同厂商、不同时段价格会调整,务必以控制台实际计费页为准,别拿旧文章里的数字直接套)就是一次性建库的调用成本。后续增量更新只需要对变化的文档重新 Embedding,成本很低,真正持续花钱的大头其实是存储和检索的机器成本,不是 API 调用费。
存储成本前面提过 float32 精度下每 1 亿条向量的大致体积,实际生产环境很多团队会用 float16 甚至做 PQ(乘积量化)压缩来进一步省空间,但压缩会牺牲一部分检索精度,中小规模知识库(百万级文档以内)通常不需要走到这一步,先把维度选对、把归一化和批量策略做对,比过早优化存储更重要。
常见问题
国产 Embedding 模型比 OpenAI text-embedding-3 差吗?
在中文语义任务上,通义 text-embedding-v3 和智谱 embedding-3 与 text-embedding-3-large 差距不大,且价格低一个数量级。英文及多语言混合场景建议实测对比。
能用国产 Embedding + 海外 LLM 混搭吗? 完全可以。Embedding 模型负责向量化,LLM 负责生成,两者完全解耦。常见做法:用智谱 Embedding 建向量库,用 DeepSeek 做生成,兼顾成本和效果。
Embedding API 有并发限制吗?
有,与 Chat API 类似,免费额度并发较低(约 5~10 路),付费后提升。大批量文档建库时建议用批量传入(input 传列表)减少请求次数,参考 限流策略对比。
相关阅读:国产大模型 API 全景指南 · 国产 Rerank 模型盘点 · 接入 Embedding API 实战
分类导航:国产模型专题
实用工具:价格对比表