最近在做一个智能问答系统时,我发现OpenAI的Embeddings API调用成本实在太高了。每次请求都要按token数计费,项目规模一大,账单数字就蹭蹭往上涨。更让人头疼的是,当调用量激增时,API响应速度会明显变慢,严重影响了用户体验。
这让我开始思考:有没有可能用其他方案来替代OpenAI的Embeddings服务?经过调研,我发现Cohere的embedding模型效果不错,而且价格更实惠。但直接替换会遇到一个问题:项目中已经大量使用了OpenAI的标准API接口,如果全部重写,工作量太大。
于是,我决定开发一个兼容OpenAI Embeddings API的私有化服务。这样既能降低成本,又不需要修改现有代码。下面我就把这个实践过程分享给大家,特别是那些和我一样关注成本效益的开发者们。
在开始之前,我们需要选择一个合适的embedding模型作为OpenAI的替代品。目前市面上有几个不错的选择:
我最终选择了Cohere,主要考虑以下几点:
首先创建一个Python虚拟环境:
bash复制python -m venv embedding_env
source embedding_env/bin/activate # Linux/Mac
embedding_env\Scripts\activate # Windows
安装必要依赖:
bash复制pip install fastapi httpx python-dotenv uvicorn
创建项目结构:
code复制embedding_service/
├── .env
├── app.py
├── config.py
└── requirements.txt
在.env文件中添加你的Cohere API Key:
env复制COHERE_API_KEY=your_cohere_api_key_here
WORKER_API_TIMEOUT=30
OpenAI的Embeddings API有两个主要端点:
/v1/embeddings/v1/engines/{model_name}/embeddings请求体通常包含以下字段:
json复制{
"input": "要嵌入的文本",
"model": "text-embedding-ada-002"
}
响应格式为:
json复制{
"object": "list",
"data": [
{
"object": "embedding",
"embedding": [...],
"index": 0
}
],
"model": "text-embedding-ada-002",
"usage": {
"prompt_tokens": 5,
"total_tokens": 5
}
}
我们需要创建一个适配器,将OpenAI格式的请求转换为Cohere格式,再将Cohere的响应转换回OpenAI格式。下面是核心代码实现:
python复制import os
import httpx
from fastapi import FastAPI, Request
from dotenv import load_dotenv
load_dotenv()
app = FastAPI()
@app.post("/v1/embeddings")
async def create_embedding(request: Request):
payload = await request.json()
# 转换请求格式
cohere_payload = {
"texts": [payload["input"]] if isinstance(payload["input"], str) else payload["input"],
"truncate": "END"
}
# 调用Cohere API
headers = {
'Authorization': f'Bearer {os.getenv("COHERE_API_KEY")}',
'Content-Type': 'application/json'
}
async with httpx.AsyncClient() as client:
response = await client.post(
"https://api.cohere.ai/v1/embed",
headers=headers,
json=cohere_payload,
timeout=float(os.getenv("WORKER_API_TIMEOUT", 30))
)
cohere_data = response.json()
# 转换响应格式
embeddings = []
for i, embedding in enumerate(cohere_data["embeddings"]):
embeddings.append({
"object": "embedding",
"embedding": embedding,
"index": i
})
return {
"object": "list",
"data": embeddings,
"model": payload.get("model", "cohere-embed"),
"usage": {
"prompt_tokens": len(payload["input"]) if isinstance(payload["input"], str) else sum(len(text) for text in payload["input"]),
"total_tokens": 0 # Cohere不返回token计数
}
}
在实际应用中,我们经常需要同时处理多个文本的嵌入。上面的代码已经支持批量输入,但我们可以进一步优化性能:
python复制# 在调用Cohere API前添加批量处理逻辑
MAX_BATCH_SIZE = 96 # Cohere单次请求最大支持96个文本
texts = [payload["input"]] if isinstance(payload["input"], str) else payload["input"]
if len(texts) > MAX_BATCH_SIZE:
# 分批处理
all_embeddings = []
for i in range(0, len(texts), MAX_BATCH_SIZE):
batch = texts[i:i+MAX_BATCH_SIZE]
cohere_payload = {"texts": batch, "truncate": "END"}
# ...调用Cohere API...
all_embeddings.extend(cohere_data["embeddings"])
cohere_data["embeddings"] = all_embeddings
为了减少重复计算和API调用,我们可以引入缓存:
python复制from functools import lru_cache
import hashlib
@lru_cache(maxsize=10000)
def get_text_hash(text: str) -> str:
return hashlib.md5(text.encode()).hexdigest()
# 在create_embedding函数中添加缓存检查
text_hash = get_text_hash(str(payload["input"]))
if text_hash in embedding_cache:
return embedding_cache[text_hash]
添加监控和日志可以帮助我们了解服务运行状况:
python复制import logging
import time
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
@app.middleware("http")
async def log_requests(request: Request, call_next):
start_time = time.time()
response = await call_next(request)
process_time = (time.time() - start_time) * 1000
logger.info(f"{request.method} {request.url.path} - {response.status_code} - {process_time:.2f}ms")
return response
我们可以使用Uvicorn来运行这个服务:
bash复制uvicorn app:app --host 0.0.0.0 --port 8000 --workers 4
对于生产环境,建议使用:
将配置集中管理是个好习惯。创建config.py:
python复制import os
from dotenv import load_dotenv
load_dotenv()
class Config:
COHERE_API_KEY = os.getenv("COHERE_API_KEY")
WORKER_API_TIMEOUT = float(os.getenv("WORKER_API_TIMEOUT", 30))
MAX_BATCH_SIZE = 96
CACHE_SIZE = 10000
确保服务安全需要注意以下几点:
python复制from fastapi import HTTPException
@app.post("/v1/embeddings")
async def create_embedding(request: Request):
payload = await request.json()
# 输入验证
if "input" not in payload:
raise HTTPException(status_code=400, detail="Missing 'input' field")
if not payload["input"]:
raise HTTPException(status_code=400, detail="'input' cannot be empty")
# 继续原有逻辑...
使用pytest编写测试用例:
python复制import pytest
from fastapi.testclient import TestClient
from app import app
client = TestClient(app)
def test_embedding_single_text():
response = client.post(
"/v1/embeddings",
json={"input": "测试文本", "model": "cohere-embed"}
)
assert response.status_code == 200
data = response.json()
assert "data" in data
assert len(data["data"]) == 1
assert len(data["data"][0]["embedding"]) > 0
def test_embedding_multiple_texts():
response = client.post(
"/v1/embeddings",
json={"input": ["文本1", "文本2"], "model": "cohere-embed"}
)
assert response.status_code == 200
data = response.json()
assert len(data["data"]) == 2
使用locust进行负载测试:
python复制from locust import HttpUser, task, between
class EmbeddingUser(HttpUser):
wait_time = between(1, 5)
@task
def get_embedding(self):
self.client.post(
"/v1/embeddings",
json={"input": "性能测试文本", "model": "cohere-embed"}
)
运行测试:
bash复制locust -f locustfile.py
在实际项目中,我对比了使用OpenAI和Cohere的成本差异。以一个中等规模的应用为例,每月大约需要处理100万次嵌入请求:
OpenAI text-embedding-ada-002:
Cohere Embed:
看起来OpenAI更便宜?其实不然。OpenAI按token计费,而Cohere按请求计费。如果每次请求包含大量文本,Cohere可能更划算。比如:
比OpenAI便宜60%!所以关键在于如何优化使用方式。