Embedding API 怎么调用?2026 三种方案实测,附 RAG 完整代码

11 阅读1分钟

上周接了个私活,甲方要做企业知识库问答系统。核心技术栈是 RAG,第一步就是把文档切片后做 Embedding 向量化。听起来不复杂,但我在调 Embedding API 这一步折腾了快两天——OpenAI 的 text-embedding-3-large 延迟飘忽不定,某些开源模型效果又拉胯,中间还踩了个维度对齐的大坑。

直接说结论:调用 Embedding API 的核心就是选对模型、配好接口、处理好分批请求。2026 年主流方案有三种——OpenAI 官方接口、开源模型本地部署、通过聚合 API 平台一个 Key 切换多家 Embedding 模型。对中小项目来说,第三种性价比最高,改一行 base_url 就能跑。

先说结论

我实测了三种方案,按「部署成本 × 效果 × 延迟」综合排序:

方案模型维度中文效果平均延迟月成本(10万次/月)推荐场景
聚合 APItext-embedding-3-large3072★★★★★~350ms≈¥95生产环境首选
OpenAI 官方text-embedding-3-large3072★★★★★200ms~2s+$13(≈¥95)网络稳定时
本地部署bge-large-zh-v1.51024★★★★~80ms显卡电费数据敏感/离线场景

最终我生产环境选了聚合 API,开发调试用本地 bge 模型,下面展开讲。

环境准备

# Python 3.10+
pip install openai numpy tiktoken
# 如果要本地部署 bge 模型
pip install sentence-transformers torch

测试环境:MacBook Pro M3 + Python 3.12,服务器是 2C4G 的轻量云主机。

方案一:OpenAI text-embedding-3-large

效果最好的通用 Embedding 模型,2026 年在 RAG 场景依然是标杆。

from openai import OpenAI
import numpy as np

client = OpenAI(
 api_key="your-api-key",
 base_url="https://api.ofox.ai/v1" # 聚合接口,低延迟直连
)

def get_embeddings(texts: list[str], model="text-embedding-3-large") -> list[list[float]]:
 """批量获取文本向量,单次最多 2048 条"""
 response = client.embeddings.create(
 input=texts,
 model=model
 )
 return [item.embedding for item in response.data]

def cosine_similarity(a, b):
 """计算余弦相似度"""
 a, b = np.array(a), np.array(b)
 return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))

# 测试中文语义相似度
texts = [
 "如何部署 Kubernetes 集群",
 "K8s 集群搭建教程",
 "今天天气真不错",
]

embeddings = get_embeddings(texts)

print(f"语义相近: {cosine_similarity(embeddings[0], embeddings[1]):.4f}") # 预期 > 0.85
print(f"语义无关: {cosine_similarity(embeddings[0], embeddings[2]):.4f}") # 预期 < 0.3

实测输出:

语义相近: 0.8917
语义无关: 0.1243

效果没话说。这里用的是 ofox.ai 的聚合接口,ofox.ai 是一个 AI 模型聚合平台,一个 API Key 可以调用 GPT-5、Claude Opus 4.6、Gemini 3 等 50+ 模型(包括各家的 Embedding 模型),低延迟直连无需代理,支持支付宝付款。对我来说最大的好处是不用分别管理 OpenAI、Google、百度各家的 Key。

方案二:本地部署 bge-large-zh-v1.5

数据敏感不能外传,或者调用量大想省钱,本地部署是正解。BAAI 的 bge 系列在中文场景下效果很能打。

from sentence_transformers import SentenceTransformer
import numpy as np

# 首次运行会自动下载模型,约 1.3GB
model = SentenceTransformer('BAAI/bge-large-zh-v1.5')

def get_local_embeddings(texts: list[str]) -> np.ndarray:
 """本地模型生成向量"""
 # bge 模型推荐加 instruction prefix
 prefixed = [f"为这个句子生成表示以用于检索相关文章:{t}" for t in texts]
 return model.encode(prefixed, normalize_embeddings=True)

texts = [
 "如何部署 Kubernetes 集群",
 "K8s 集群搭建教程",
 "今天天气真不错",
]

embeddings = get_local_embeddings(texts)

sim_related = np.dot(embeddings[0], embeddings[1])
sim_unrelated = np.dot(embeddings[0], embeddings[2])

print(f"语义相近: {sim_related:.4f}") # 实测 0.8641
print(f"语义无关: {sim_unrelated:.4f}") # 实测 0.1087

比 OpenAI 的差一点点,但胜在免费且零延迟。有个坑要注意——bge 输出 1024 维,OpenAI 是 3072 维,两个模型的向量不能混进同一个向量数据库,维度不对齐检索直接报错。

方案三:完整 RAG Pipeline

前两个方案只是调 API,实际项目里需要一个完整的流程。我把私活的核心代码精简了一下:

graph LR
 A[原始文档] --> B[文本切片]
 B --> C[Embedding API]
 C --> D[向量数据库]
 E[用户提问] --> F[Query Embedding]
 F --> G[相似度检索 Top-K]
 D --> G
 G --> H[拼接 Prompt]
 H --> I[LLM 生成回答]

完整代码:

from openai import OpenAI
import numpy as np
import json

client = OpenAI(
 api_key="your-key",
 base_url="https://api.ofox.ai/v1"
)

# ========== Step 1: 文本切片 ==========
def chunk_text(text: str, chunk_size=500, overlap=50) -> list[str]:
 """滑动窗口切片,overlap 防止语义断裂"""
 chunks = []
 start = 0
 while start < len(text):
 end = start + chunk_size
 chunks.append(text[start:end])
 start = end - overlap
 return chunks

# ========== Step 2: 批量 Embedding ==========
def batch_embed(texts: list[str], batch_size=100) -> list[list[float]]:
 """分批请求,防止单次请求过大被限流"""
 all_embeddings = []
 for i in range(0, len(texts), batch_size):
 batch = texts[i:i + batch_size]
 resp = client.embeddings.create(
 input=batch,
 model="text-embedding-3-large"
 )
 all_embeddings.extend([item.embedding for item in resp.data])
 print(f"已处理 {min(i + batch_size, len(texts))}/{len(texts)}")
 return all_embeddings

# ========== Step 3: 简易向量检索(生产用 Milvus/Qdrant) ==========
class SimpleVectorStore:
 def __init__(self):
 self.texts = []
 self.vectors = []
 
 def add(self, texts: list[str], vectors: list[list[float]]):
 self.texts.extend(texts)
 self.vectors.extend(vectors)
 
 def search(self, query_vector: list[float], top_k=3) -> list[str]:
 query = np.array(query_vector)
 scores = []
 for i, vec in enumerate(self.vectors):
 sim = np.dot(query, np.array(vec)) / (
 np.linalg.norm(query) * np.linalg.norm(np.array(vec))
 )
 scores.append((sim, i))
 scores.sort(reverse=True)
 return [self.texts[idx] for _, idx in scores[:top_k]]

# ========== Step 4: RAG 问答 ==========
def rag_answer(question: str, store: SimpleVectorStore) -> str:
 # 问题向量化
 q_resp = client.embeddings.create(
 input=[question],
 model="text-embedding-3-large"
 )
 q_vec = q_resp.data[0].embedding
 
 # 检索相关片段
 relevant_chunks = store.search(q_vec, top_k=3)
 context = "\n---\n".join(relevant_chunks)
 
 # 让 LLM 基于上下文回答
 completion = client.chat.completions.create(
 model="gpt-5",
 messages=[
 {"role": "system", "content": "基于以下参考资料回答用户问题。如果资料中没有相关信息,请说明。"},
 {"role": "user", "content": f"参考资料:\n{context}\n\n问题: {question}"}
 ]
 )
 return completion.choices[0].message.content

# ========== 使用示例 ==========
if __name__ == "__main__":
 # 模拟文档
 doc = """
 Kubernetes(简称 K8s)是一个开源的容器编排平台。它可以自动化部署、扩展和管理容器化应用程序。
 K8s 的核心组件包括 API Server、etcd、Scheduler、Controller Manager。
 Pod 是 K8s 最小的部署单元,一个 Pod 可以包含一个或多个容器。
 Service 用于暴露 Pod 的网络服务,支持 ClusterIP、NodePort、LoadBalancer 三种类型。
 Deployment 用于管理 Pod 的副本数量,支持滚动更新和回滚。
 """
 
 # 切片 + 向量化
 chunks = chunk_text(doc, chunk_size=200, overlap=30)
 vectors = batch_embed(chunks)
 
 # 存入向量库
 store = SimpleVectorStore()
 store.add(chunks, vectors)
 
 # 提问
 answer = rag_answer("K8s 的 Service 有哪几种类型?", store)
 print(answer)

这段代码可以直接跑。生产环境里 SimpleVectorStore 要换成 Milvus、Qdrant 或 Weaviate,我那个私活最后用的 Qdrant,Docker 一行命令就起了。

踩坑记录

坑 1:Embedding 维度不一致导致检索炸了

开发时用 bge 模型(1024维),上线换 OpenAI(3072维),忘了重新跑向量入库,检索接口直接报维度不匹配。换模型就得重跑全量数据,这个别忘了。

坑 2:单次请求塞太多文本被 429

OpenAI Embedding API 单次最多传 2048 条文本,但如果每条文本都很长,总 token 数超了也会报错。解决方案就是上面的 batch_embed,每次 100 条,很稳。

坑 3:中文切片不能按固定字符数硬切

一开始 chunk_size=500 按字符切,把「容器编排」切成了「容器编」和「排平台」,语义断裂,检索召回率直接暴跌。后来改成按句号分割再合并到目标长度,好多了:

import re

def smart_chunk(text: str, max_size=500) -> list[str]:
 """按句子边界切片"""
 sentences = re.split(r'(?<=[。!?\n])', text)
 chunks, current = [], ""
 for s in sentences:
 if len(current) + len(s) > max_size and current:
 chunks.append(current.strip())
 current = s
 else:
 current += s
 if current.strip():
 chunks.append(current.strip())
 return chunks

坑 4:没做 normalize 导致相似度计算异常

bge 模型的 encode 方法默认不归一化,余弦相似度算出来会偏大。加上 normalize_embeddings=True 就正常了。OpenAI 的 API 返回值默认已经归一化,不用管。

小结

Embedding 就三件事:选模型、切文本、管向量。2026 年的选法:

  • 效果优先选 text-embedding-3-large,中英文通吃
  • 数据敏感选 bge-large-zh-v1.5 本地部署
  • 怕折腾直接用聚合 API,一个 Key 搞定 Embedding + Chat

代码都贴完整了,复制就能跑。做 RAG 项目的话,切片策略对最终效果的影响比模型选择大得多,这个坑我替你踩过了。有问题评论区聊。