Embeddings

Embeddings

Transformez du texte en vecteurs pour la recherche sémantique, la récupération (RAG), les recommandations, le clustering et la déduplication, avec les modèles text-embedding-3 d'OpenAI à la moitié du prix d'OpenAI. Mêmes ID de modèles, mêmes requêtes et réponses, mêmes vecteurs : le code écrit pour OpenAI fonctionne dès que vous changez l'URL de base et la clé.

POST/v1/embeddings
ModèleIDPar 1M de tokens d'entréePrix d'OpenAIDimensions
Text Embedding 3 Smalltext-embedding-3-small$0.01$0.021,536, ou moins
Text Embedding 3 Largetext-embedding-3-large$0.065$0.133,072, ou moins

Vous payez pour le texte que vous envoyez, et les vecteurs sont gratuits. Les deux modèles lisent jusqu'à 8,192 tokens par entrée. Text Embedding 3 Small est celui par lequel commencer : bon marché, rapide et efficace pour la plupart des usages de recherche et de RAG. Text Embedding 3 Large est le modèle plus grand et plus précis, à choisir quand la qualité de la récupération compte plus que le stockage et le prix.

Démarrage rapide

import os
from openai import OpenAI
client = OpenAI(
base_url="https://api.zurelay.com/v1",
api_key=os.environ["ZURELAY_API_KEY"],
)
response = client.embeddings.create(
model="text-embedding-3-small",
input="The food was delicious and the waiter was friendly.",
)
vector = response.data[0].embedding # a list of 1,536 floats
print(len(vector), response.usage.prompt_tokens) # 1536 10

N'importe quel SDK OpenAI fonctionne : pointez-le vers https://api.zurelay.com/v1, utilisez votre clé Zurelay et appelez sa méthode embeddings. La réponse est celle d'OpenAI :

Réponse (le vecteur raccourci)
{
"object": "list",
"data": [
{
"object": "embedding",
"index": 0,
"embedding": [-0.0022748, -0.0093183, 0.0158532, ..., -0.0028842]
}
],
"model": "text-embedding-3-small",
"usage": { "prompt_tokens": 10, "total_tokens": 10 }
}

data contient un embedding par entrée, dans l'ordre où vous les avez envoyées, chacun avec son index. C'est usage.prompt_tokens qui a servi de base à la facturation. Les en-têtes le disent aussi : x-zurelay-model indique le modèle, x-zurelay-input-tokens les tokens, et x-request-id permet de retrouver la requête dans votre journal de requêtes.

Paramètres

modelstringrequis
text-embedding-3-small ou text-embedding-3-large.
inputchaîne ou tableaurequis
Ce que vous pouvez envoyer : une chaîne, un tableau de 2,048 chaînes maximum, un tableau d'ID de tokens (entiers issus du tokenizer cl100k_base d'OpenAI), ou un tableau de 2,048 tels tableaux maximum. Chaque entrée fait au plus 8,192 tokens, et aucune ne peut être une chaîne vide.
encoding_formatstring
float (par défaut) : chaque embedding est un tableau de nombres. base64 : chacun est une chaîne base64 de nombres float32 little-endian, plus compacte et plus rapide à télécharger. Voir Base64.
dimensionsinteger
Raccourcissez le vecteur : de 1 à 1,536 sur text-embedding-3-small, de 1 à 3,072 sur text-embedding-3-large. Omettre pour garder la longueur complète. Voir Vecteurs plus courts.
userstring
Facultatif : un ID pour l’utilisateur final à qui la requête s’adresse, comme dans l’API d’OpenAI. Accepté, sans aucun effet sur les vecteurs.

Traitement par lots

Envoyez plusieurs textes en une seule requête : input accepte un tableau allant jusqu’à 2,048 entrées, et elles reviennent dans le même ordre. Une requête avec mille textes est bien plus rapide que mille requêtes, et coûte pareil : vous payez au token, pas à la requête. Gardez chaque requête sous 300,000 tokens au total.

# pip install tiktoken
import tiktoken
# The tokenizer text-embedding-3 counts with, to keep each request under the limits
enc = tiktoken.get_encoding("cl100k_base")
def batches(texts, max_inputs=2048, max_tokens=300_000):
batch, tokens = [], 0
for text in texts:
count = len(enc.encode(text))
if batch and (len(batch) == max_inputs or tokens + count > max_tokens):
yield batch
batch, tokens = [], 0
batch.append(text)
tokens += count
if batch:
yield batch
vectors = []
for batch in batches(texts):
response = client.embeddings.create(model="text-embedding-3-small", input=batch)
vectors += [item.embedding for item in response.data] # in the order sent

Découpez les documents de plus de 8,192 tokens en morceaux avant de les transformer en embeddings. Pour la recherche et le RAG, des morceaux de quelques centaines de tokens, un paragraphe ou deux, trouvent généralement de meilleures correspondances que des documents entiers.

Des vecteurs plus courts avec dimensions

Les modèles text-embedding-3 sont entraînés pour qu’un vecteur raccourci fonctionne toujours : demandez dimensions et vous obtenez la première partie du vecteur complet, renormalisée à une longueur de 1. Les vecteurs plus courts prennent moins de stockage et de mémoire et la recherche est plus rapide, pour un peu moins de précision. Text Embedding 3 Large en 1,024 dimensions occupe un tiers de l’espace de ses 3,072 dimensions complètes.

response = client.embeddings.create(
model="text-embedding-3-large",
input="The food was delicious and the waiter was friendly.",
dimensions=1024,
)
print(len(response.data[0].embedding)) # 1024
Utilisez un seul modèle et une seule taille pour tout ce que vous comparez : des vecteurs de longueurs différentes ne sont pas comparables, et beaucoup de bases de données vectorielles fixent la longueur à la création de l’index. Les vecteurs plus courts coûtent autant : le prix est par token d’entrée.

Base64

Avec "encoding_format": "base64", chaque embedding revient sous forme de chaîne base64 de nombres float32 little-endian au lieu d’un tableau JSON : plus compacte, et plus rapide à télécharger et à analyser. Si vous omettez encoding_format, le SDK Python d’OpenAI demande lui-même le base64 et le décode pour vous : vous obtenez des floats dans les deux cas. Si vous demandez le base64 vous-même, vous recevez la chaîne, à décoder comme ceci :

import base64
import numpy as np
response = client.embeddings.create(
model="text-embedding-3-small",
input=["First text", "Second text"],
encoding_format="base64",
)
# Each embedding is a base64 string of little-endian float32 numbers
vectors = [np.frombuffer(base64.b64decode(item.embedding), dtype="<f4") for item in response.data]

Transformez vos documents en embeddings une fois et stockez les vecteurs. Pour chercher, transformez la requête avec le même modèle et classez les documents par similarité cosinus avec elle : plus la valeur est proche de 1, plus le sens est proche, même sans aucun mot en commun. Les vecteurs text-embedding-3 ont une longueur de 1, donc le produit scalaire donne le même classement.

# pip install openai numpy
import os
import numpy as np
from openai import OpenAI
client = OpenAI(base_url="https://api.zurelay.com/v1", api_key=os.environ["ZURELAY_API_KEY"])
MODEL = "text-embedding-3-small"
docs = [
"To change your password, open Settings, choose Security and click Change password.",
"Invoices are emailed on the first day of each month and can be downloaded under Billing.",
"Invite teammates from the Team page; each person signs in with their own email.",
"API keys can be limited to a monthly budget, a rate limit and a list of models.",
]
def embed(texts):
response = client.embeddings.create(model=MODEL, input=texts)
return np.array([item.embedding for item in response.data])
doc_vectors = embed(docs) # embed once, and store the vectors beside the text
def search(query, k=2):
query_vector = embed([query])[0]
# The vectors have length 1, so the dot product is the cosine similarity
scores = doc_vectors @ query_vector
best = np.argsort(-scores)[:k]
return [(docs[i], float(scores[i])) for i in best]
for text, score in search("How can I change my password?"):
print(f"{score:.3f} {text}") # the password article comes first

Au-delà de quelques milliers de documents, gardez les vecteurs dans une base de données vectorielle ou un index vectoriel (pgvector, Qdrant, Pinecone, Weaviate, Chroma et autres) et laissez-le faire le classement.

Récupération (RAG)

La génération augmentée par récupération répond à une question à partir de vos propres documents : cherchez les passages les plus proches de la question, puis transmettez-les à un modèle de chat avec la question. La même clé Zurelay fait les deux.

# After the search example above: retrieve, then answer from what was found
question = "How do I stop a key from spending too much?"
notes = "\n".join(text for text, _ in search(question))
answer = client.chat.completions.create(
model="gpt-6-luna",
messages=[
{
"role": "system",
"content": "Answer from these notes only. If they don't say, say so.\n\n" + notes,
},
{"role": "user", "content": question},
],
)
print(answer.choices[0].message.content)

LangChain, LlamaIndex et le AI SDK

Les frameworks dotés d’une classe d’embeddings OpenAI fonctionnent avec l’URL de base et la clé de Zurelay, et les ID de modèles restent ceux d’OpenAI.

LangChain

# pip install langchain-openai
import os
from langchain_openai import OpenAIEmbeddings
embeddings = OpenAIEmbeddings(
model="text-embedding-3-small",
base_url="https://api.zurelay.com/v1",
api_key=os.environ["ZURELAY_API_KEY"],
)
query_vector = embeddings.embed_query("How can I change my password?")
doc_vectors = embeddings.embed_documents(["First document", "Second document"])

Le OpenAIEmbeddings Python de LangChain tokenise les textes lui-même par défaut et envoie les ID de tokens. Zurelay accepte les tableaux de tokens, donc les réglages par défaut fonctionnent tels quels.

LlamaIndex

# pip install llama-index-embeddings-openai
import os
from llama_index.core import Settings
from llama_index.embeddings.openai import OpenAIEmbedding
Settings.embed_model = OpenAIEmbedding(
model="text-embedding-3-small",
api_base="https://api.zurelay.com/v1",
api_key=os.environ["ZURELAY_API_KEY"],
)
vector = Settings.embed_model.get_text_embedding("How can I change my password?")

Une fois Settings.embed_model défini, chaque index et retriever que vous créez calcule ses embeddings via Zurelay. Pour le modèle de chat, voir le guide LlamaIndex.

Vercel AI SDK

// npm install ai @ai-sdk/openai
import { createOpenAI } from "@ai-sdk/openai";
import { cosineSimilarity, embed, embedMany } from "ai";
const zurelay = createOpenAI({
baseURL: "https://api.zurelay.com/v1",
apiKey: process.env.ZURELAY_API_KEY,
});
const model = zurelay.embedding("text-embedding-3-small");
const { embeddings } = await embedMany({ model, values: ["First document", "Second document"] });
const { embedding } = await embed({ model, value: "How can I change my password?" });
console.log(cosineSimilarity(embedding, embeddings[0]));

Applications et bases vectorielles

Les applications qui appellent les embeddings d’OpenAI avec une URL de base configurable fonctionnent de la même façon : mettez https://api.zurelay.com/v1, utilisez votre clé Zurelay et l’un des ID de modèles ci-dessus. C’est le cas du moteur d’embeddings OpenAI d’Open WebUI pour les documents, de l’API RAG de LibreChat, du nœud Embeddings OpenAI de n8n et du fournisseur de modèles compatible OpenAI de Dify.

Les mêmes vecteurs qu’OpenAI

Les requêtes vers text-embedding-3-small et text-embedding-3-large passent par les modèles d’OpenAI eux-mêmes. Nous avons vérifié chaque route contre l’API d’OpenAI : pour le même texte et le même modèle, les vecteurs correspondent à ceux d’OpenAI, avec une similarité cosinus de 0.9999 ou plus. Un index construit avec l’API d’OpenAI continue donc de fonctionner quand vous transformez vos requêtes en embeddings via Zurelay, et un index construit via Zurelay fonctionne avec celle d’OpenAI. Passer de l’une à l’autre ne demande aucune ré-indexation.

Changer de modèle impose de recalculer les embeddings

Les vecteurs de modèles différents ne sont pas comparables

Un vecteur text-embedding-3-small ne signifie rien à côté d’un vecteur text-embedding-3-large, ou d’un vecteur de n’importe quel autre modèle : chaque modèle place le texte dans son propre espace. Pour changer de modèle, recalculez donc les embeddings de tout ce que vous avez stocké avec le nouveau modèle, et interrogez avec ce modèle ensuite.

Pour la même raison, une requête d’embeddings est toujours traitée par le modèle que vous avez nommé. Le routage intelligent ne la confie jamais à un autre modèle, quels que soient vos réglages : si le modèle est indisponible, vous recevez 503 model_unavailable, à réessayer, plutôt que des vecteurs qui ne correspondent pas à votre index.

Limites

  • Jusqu’à 2,048 entrées par requête.
  • Jusqu’à 8,192 tokens par entrée, et 300,000 pour la requête entière.
  • Pas de chaînes vides.
  • dimensions de 1 à la longueur complète du modèle : 1,536 sur text-embedding-3-small, 3,072 sur text-embedding-3-large.
  • Une requête qui dépasse une limite est refusée avec 400 qui précise la limite, et ne coûte rien.
  • Une clé limitée à certains modèles doit avoir le modèle d’embeddings dans sa liste. Voir Limites et règles des clés.

Payer les embeddings

Les embeddings sont facturés sur les tokens d’entrée uniquement, comptés comme OpenAI les compte, et les vecteurs sont gratuits. Text Embedding 3 Small coûte $0.01 par 1M tokens et Text Embedding 3 Large $0.065, la moitié du prix d’OpenAI. Un million de textes de 100 tokens chacun, soit 100M tokens, coûtent $1.00 à vectoriser avec Text Embedding 3 Small et $6.50 avec Text Embedding 3 Large, contre $2.00 et $13.00 chez OpenAI.

Aucun minimum par requête : une requête de 10 tokens est facturée 10 tokens. Les plus courts dimensions et le base64 coûtent pareil. Les en-têtes usage et x-zurelay-input-tokens de chaque réponse indiquent ce qui a été facturé, chaque requête apparaît dans votre journal des requêtes, et les requêtes en échec ne coûtent rien.

Erreurs

Les erreurs ont le format d’OpenAI, donc les SDK d’OpenAI les lèvent comme ils le feraient pour OpenAI.

StatutCodeQuand
400invalid_requestUn champ manque ou est incorrect : chaîne vide, plus de 2,048 entrées, dimensions qui n’est pas un entier, ou un encoding_format autre que float ou base64. Le message précise lequel.
400invalid_valuedimensions est supérieur à la longueur maximale du modèle.
400context_length_exceededUne entrée dépasse 8,192 tokens, ou la requête dépasse 300,000. Découpez le texte, ou envoyez moins d’entrées à la fois.
503model_unavailableLe modèle est brièvement indisponible. Réessayez après le nombre de secondes indiqué par l’en-tête retry-after.

Les clés, le crédit, les budgets et les limites de débit renvoient les mêmes erreurs que tous les autres endpoints : voir Erreurs. Les SDK d’OpenAI relancent automatiquement celles qui valent la peine d’être relancées.

Essayez d’abord

Dans le Playground du dashboard, choisissez un modèle d'embedding, saisissez quelques textes et vectorisez-les : vous voyez chaque vecteur, les tokens facturés et la similarité cosinus de chaque paire. Le bouton Get code génère la même requête pour Python, Node.js ou cURL.

Une question ou un élément manquant ? Contactez notre équipe d’assistance et nous vous répondrons par e-mail, généralement en moins d’une minute.