Embeddings

Embeddings

Verwandle Text in Vektoren für semantische Suche, Retrieval (RAG), Empfehlungen, Clustering und Deduplizierung – mit OpenAI’s text-embedding-3 Modellen zum halben OpenAI-Preis. Gleiche Model-IDs, gleicher Request und gleiche Response, gleiche Vektoren: Code, der für OpenAI geschrieben wurde, läuft, sobald du die Base URL und den Key änderst.

POST/v1/embeddings
ModellIDPro 1M Input-TokensPreis bei OpenAIDimensionen
Text Embedding 3 Smalltext-embedding-3-small$0.01$0.021,536 oder weniger
Text Embedding 3 Largetext-embedding-3-large$0.065$0.133,072 oder weniger

Du zahlst für den Text, den du sendest – die Vektoren sind gratis. Beide Modelle lesen bis zu 8,192 Tokens pro Input. Text Embedding 3 Small ist der Einstieg: günstig, schnell und gut für die meisten Such- und RAG-Anwendungen. Text Embedding 3 Large ist das größere, genauere Modell – für Fälle, in denen Retrieval-Qualität mehr zählt als Speicher und Preis.

Schnellstart

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

Jedes OpenAI SDK funktioniert: richte es auf https://api.zurelay.com/v1, nutz deinen Zurelay Key und ruf die Embeddings-Methode auf. Die Antwort ist die von OpenAI:

Antwort (der Vektor gekürzt)
{
"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 enthält ein Embedding pro Input, in der Reihenfolge, in der du sie gesendet hast, jeweils mit seinem index. usage.prompt_tokens ist das, worüber der Request abgerechnet wurde. Die Header verraten es auch: x-zurelay-model nennt das Modell und x-zurelay-input-tokens die Tokens, und mit x-request-id findest du den Request in deinem Request Log.

Parameter

modelstringerforderlich
text-embedding-3-small oder text-embedding-3-large.
inputString oder Arrayerforderlich
Was du einbetten kannst: einen String, ein Array mit bis zu 2,048 Strings, ein Array von Token-IDs (Ganzzahlen aus OpenAI’s cl100k_base Tokenizer) oder ein Array mit bis zu 2,048 solchen Arrays. Jeder Input hat maximal 8,192 Tokens, und keiner darf ein leerer String sein.
encoding_formatstring
float (der Standard): Jedes Embedding ist ein Array aus Zahlen. base64: Jedes ist ein base64-String aus float32-Zahlen in Little-Endian, kleiner und schneller herunterzuladen. Siehe Base64.
dimensionsinteger
Kürze den Vektor: 1 bis 1,536 bei text-embedding-3-small, 1 bis 3,072 bei text-embedding-3-large. Lass den Parameter weg für die volle Länge. Siehe Kürzere Vektoren.
userstring
Optional: eine ID für die Endnutzerin oder den Endnutzer, für die oder den die Anfrage gedacht ist, wie in OpenAIs API. Wird akzeptiert und ändert nichts an den Vektoren.

Mehrere Texte pro Anfrage

Schick viele Texte in einer Anfrage: input nimmt ein Array mit bis zu 2,048, und sie kommen in derselben Reihenfolge zurück. Eine Anfrage mit tausend Texten ist viel schneller als tausend Anfragen und kostet dasselbe: du zahlst pro Token, nicht pro Anfrage. Bleib pro Anfrage insgesamt unter 300,000 Tokens.

# 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

Teile Dokumente, die länger als 8,192 Tokens sind, in Chunks auf, bevor du sie embeddest. Für Suche und RAG finden Chunks von ein paar hundert Tokens, ein oder zwei Absätze, meist bessere Treffer als ganze Dokumente.

Kürzere Vektoren mit dimensions

Die text-embedding-3-Modelle sind so trainiert, dass ein gekürzter Vektor weiter funktioniert: Fragst du dimensions an, bekommst du den ersten Teil des vollen Vektors, neu auf Länge 1 normalisiert. Kürzere Vektoren brauchen weniger Speicher und Arbeitsspeicher und suchen schneller, bei etwas weniger Genauigkeit. Text Embedding 3 Large mit 1,024 Dimensionen braucht ein Drittel des Platzes seiner vollen 3,072.

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
Nutze ein Modell und eine Größe für alles, was du vergleichst: Vektoren unterschiedlicher Länge lassen sich nicht vergleichen, und viele Vektor-Datenbanken legen die Länge beim Anlegen des Index fest. Kürzere Vektoren kosten dasselbe: der Preis gilt pro Input-Token.

Base64

Mit "encoding_format": "base64" kommt jedes Embedding als base64-String aus float32-Zahlen in Little-Endian zurück statt als JSON-Array: kleiner und schneller herunterzuladen und zu parsen. Lässt du encoding_format weg, fragt OpenAIs Python SDK selbst nach base64 und dekodiert es für dich, du bekommst also so oder so normale Floats. Fragst du selbst nach base64, bekommst du den String, den du so dekodierst:

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]

Embede deine Dokumente einmal und speichere die Vektoren. Zum Suchen embedest du die Query mit demselben Modell und sortierst die Dokumente nach Kosinus-Ähnlichkeit dazu: je näher an 1, desto näher in der Bedeutung, auch ohne gemeinsame Wörter. text-embedding-3-Vektoren haben Länge 1, das Skalarprodukt liefert also dieselbe Reihenfolge.

# 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

Bei mehr als ein paar tausend Dokumenten legst du die Vektoren in einer Vektor-Datenbank oder einem Vektor-Index ab (pgvector, Qdrant, Pinecone, Weaviate, Chroma und ähnliche) und lässt sie das Ranking machen.

Retrieval (RAG)

Retrieval-augmented Generation beantwortet eine Frage aus deinen eigenen Dokumenten: Such die Passagen, die der Frage am nächsten sind, und gib sie zusammen mit der Frage an ein Chat-Modell. Derselbe Zurelay-Key macht beides.

# 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 und das AI SDK

Frameworks mit einer OpenAI-Embeddings-Klasse funktionieren mit Zurelays Base-URL und deinem Key, und die Modell-IDs bleiben die von 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"])

LangChains Python-OpenAIEmbeddings tokenisiert Texte standardmäßig selbst und schickt die Token-IDs. Zurelay nimmt Token-Arrays an, die Standardeinstellungen funktionieren also unverändert.

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?")

Mit gesetztem Settings.embed_model embeddet jeder Index und jeder Retriever, den du baust, über Zurelay. Zum Chat-Modell siehe den LlamaIndex-Leitfaden.

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]));

Apps und Vektor-Stores

Apps, die OpenAIs Embeddings mit einer einstellbaren Base-URL aufrufen, funktionieren genauso: setz sie auf https://api.zurelay.com/v1, nutze deinen Zurelay-Key und eine der Modell-IDs oben. Das gilt für Open WebUIs OpenAI-Embedding-Engine für Dokumente, die RAG-API von LibreChat, den Node Embeddings OpenAI von n8n und den OpenAI-kompatiblen Model-Provider von Dify.

Dieselben Vektoren wie OpenAI

Anfragen an text-embedding-3-small und text-embedding-3-large laufen auf OpenAIs eigenen Modellen. Wir haben jede Route gegen OpenAIs API geprüft: bei gleichem Text und Modell stimmen die Vektoren mit denen von OpenAI überein, mit einer Kosinus-Ähnlichkeit von 0.9999 oder mehr. Ein Index, den du mit OpenAIs API gebaut hast, funktioniert also weiter, wenn du deine Queries über Zurelay embeddest, und einer, der über Zurelay gebaut wurde, funktioniert mit OpenAI. Der Wechsel zwischen beiden braucht kein Re-Embedding.

Modellwechsel heißt neu embedden

Vektoren aus verschiedenen Modellen lassen sich nicht vergleichen

Ein text-embedding-3-small-Vektor bedeutet nichts neben einem text-embedding-3-large-Vektor oder einem aus irgendeinem anderen Modell: jedes Modell ordnet Text in seinem eigenen Raum an. Um das Modell zu wechseln, embeddest du also alles Gespeicherte neu mit dem neuen Modell und suchst ab dann mit diesem Modell.

Aus demselben Grund beantwortet immer das Modell deine Embeddings-Anfrage, das du angegeben hast. Smart Routing gibt sie nie an ein anderes Modell weiter, egal welche Einstellungen du hast: Ist das Modell nicht erreichbar, bekommst du 503 model_unavailable, zum erneuten Versuchen, statt Vektoren, die nicht zu deinem Index passen.

Limits

  • Bis zu 2,048 Inputs pro Anfrage.
  • Bis zu 8,192 Tokens pro Input und 300,000 in der ganzen Anfrage.
  • Keine leeren Strings.
  • dimensions von 1 bis zur vollen Länge des Modells: 1,536 bei text-embedding-3-small, 3,072 bei text-embedding-3-large.
  • Eine Anfrage über einem Limit wird mit einem 400 abgelehnt, das das Limit nennt, und kostet nichts.
  • Ein Key, der auf bestimmte Modelle beschränkt ist, braucht das Embedding-Modell auf seiner Liste. Siehe Key-Limits und Regeln.

Was Embeddings kosten

Embeddings werden nur auf Input-Tokens abgerechnet, gezählt wie OpenAI zählt, und die Vektoren sind gratis. Text Embedding 3 Small kostet $0.01 pro 1M Tokens und Text Embedding 3 Large $0.065, die Hälfte von OpenAIs Preis. Eine Million Texte mit je 100 Tokens, also 100M Tokens, kosten $1.00 zum Embedden mit Text Embedding 3 Small und $6.50 mit Text Embedding 3 Large – gegenüber $2.00 und $13.00 bei OpenAI.

Es gibt kein Minimum pro Request: Ein Request mit 10 Tokens wird mit 10 Tokens abgerechnet. Kürzere dimensions und base64 kosten gleich viel. Die Header usage und x-zurelay-input-tokens jeder Antwort sagen, worauf abgerechnet wurde, jeder Request steht in deinem Request-Log, und fehlgeschlagene Requests kosten nichts.

Fehler

Fehler kommen in OpenAIs Format, deshalb werfen OpenAIs SDKs sie genau so, wie sie es für OpenAI tun würden.

StatusCodeWann
400invalid_requestEin Feld fehlt oder ist falsch: ein leerer String, mehr als 2,048 Inputs, ein dimensions-Wert, der keine ganze Zahl ist, oder ein encoding_format, das nicht float oder base64 ist. Die Meldung sagt, welches.
400invalid_valuedimensions ist größer als die volle Länge des Modells.
400context_length_exceededEin Input hat über 8,192 Tokens, oder der Request über 300,000. Teile den Text auf oder schicke weniger Inputs auf einmal.
503model_unavailableDas Modell ist kurzzeitig nicht verfügbar. Versuch es nach der Anzahl Sekunden im retry-after-Header erneut.

Keys, Guthaben, Budgets und Rate Limits liefern dieselben Fehler wie jeder andere Endpoint: siehe Fehler. Die SDKs von OpenAI wiederholen die Anfragen, bei denen sich ein neuer Versuch lohnt.

Probier es zuerst aus

Im Playground im Dashboard wählst du ein Embedding-Modell, schreibst ein paar Texte und embeddest sie: Du siehst jeden Vektor, die abgerechneten Tokens und die Kosinus-Ähnlichkeit jedes Paares. Der Button „Code holen“ erzeugt dieselbe Anfrage für Python, Node.js oder cURL.

Fragen oder fehlt etwas? Frag unser Support-Team und wir antworten per E-Mail, meist innerhalb einer Minute.