Embeddings

Embeddings

Convierte texto en vectores para búsqueda semántica, recuperación (RAG), recomendaciones, clustering y deduplicación, con los modelos text-embedding-3 de OpenAI a la mitad del precio de OpenAI. Los mismos IDs de modelo, la misma petición y respuesta, los mismos vectores: el código escrito para OpenAI funciona en cuanto cambias la URL base y la clave.

POST/v1/embeddings
ModeloIDPor 1M de tokens de entradaPrecio de OpenAIDimensiones
Text Embedding 3 Smalltext-embedding-3-small$0.01$0.021,536, o menos
Text Embedding 3 Largetext-embedding-3-large$0.065$0.133,072, o menos

Pagas por el texto que envías y los vectores son gratis. Ambos modelos leen hasta 8,192 tokens por entrada. Text Embedding 3 Small es el ideal para empezar: barato, rápido y bueno para la mayoría de búsquedas y casos de RAG. Text Embedding 3 Large es el modelo más grande y preciso, para cuando la calidad de la recuperación importa más que el almacenamiento y el precio.

Inicio rápido

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

Cualquier SDK de OpenAI sirve: apúntalo a https://api.zurelay.com/v1, usa tu clave de Zurelay y llama a su método embeddings. La respuesta es la de OpenAI:

Respuesta (el vector acortado)
{
"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 devuelve un embedding por entrada, en el orden en que las enviaste, cada uno con su index. usage.prompt_tokens es lo que se facturó por la petición. Las cabeceras también lo dicen: x-zurelay-model indica el modelo y x-zurelay-input-tokens los tokens, y con x-request-id encuentras la petición en tu registro de peticiones.

Parámetros

modelstringobligatorio
text-embedding-3-small o text-embedding-3-large.
inputstring o arrayobligatorio
Qué puedes convertir en embedding: una cadena, un array de hasta 2,048 cadenas, un array de IDs de token (enteros del tokenizer cl100k_base de OpenAI) o un array de hasta 2,048 de esos arrays. Cada entrada tiene como máximo 8,192 tokens y ninguna puede ser una cadena vacía.
encoding_formatstring
float (el valor predeterminado): cada embedding es un array de números. base64: cada uno es una cadena base64 de números float32 little-endian, más pequeños y más rápidos de descargar. Consulta Base64.
dimensionsinteger
Acorta el vector: de 1 a 1,536 en text-embedding-3-small, de 1 a 3,072 en text-embedding-3-large. Omítelo para obtener la longitud completa. Consulta Vectores más cortos.
userstring
Opcional: un ID del usuario final al que va dirigida la solicitud, como en la API de OpenAI. Se acepta y no cambia nada en los vectores.

Procesamiento por lotes

Envía muchos textos en una sola solicitud: input acepta un array de hasta 2,048 y se devuelven en el mismo orden. Una solicitud con mil textos es mucho más rápida que mil solicitudes, y cuesta lo mismo: pagas por token, no por solicitud. Mantén cada solicitud por debajo de 300,000 tokens en 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

Divide en fragmentos los documentos de más de 8,192 tokens antes de generar sus embeddings. Para búsqueda y RAG, los fragmentos de unos cientos de tokens, uno o dos párrafos, suelen encontrar mejores coincidencias que los documentos completos.

Vectores más cortos con dimensions

Los modelos text-embedding-3 están entrenados para que un vector acortado siga funcionando: pide dimensions y obtienes la primera parte del vector completo, renormalizada a longitud 1. Los vectores más cortos ocupan menos almacenamiento y memoria y se buscan más rápido, a cambio de un poco menos de precisión. Text Embedding 3 Large con 1,024 dimensiones ocupa un tercio del espacio de sus 3,072 completas.

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
Usa un solo modelo y un solo tamaño para todo lo que compares: los vectores de distinta longitud no se pueden comparar, y muchas bases de datos vectoriales fijan la longitud al crear el índice. Los vectores más cortos cuestan lo mismo: el precio es por token de entrada.

Base64

Con "encoding_format": "base64" cada embedding llega como una cadena base64 de números float32 little-endian en lugar de un array JSON: más pequeño y más rápido de descargar y procesar. Si omites encoding_format, el SDK de Python de OpenAI pide base64 por su cuenta y lo decodifica, así que obtienes floats normales en ambos casos. Si pides base64 tú mismo, obtienes la cadena, para decodificarla así:

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]

Genera los embeddings de tus documentos una vez y guarda los vectores. Para buscar, genera el embedding de la consulta con el mismo modelo y ordena los documentos por su similitud de coseno con ella: cuanto más cerca de 1, más cerca en significado, aunque no tengan ninguna palabra en común. Los vectores de text-embedding-3 tienen longitud 1, así que el producto escalar da el mismo orden.

# 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

Para más de unos miles de documentos, guarda los vectores en una base de datos vectorial o un índice vectorial (pgvector, Qdrant, Pinecone, Weaviate, Chroma y similares) y deja que haga el orden.

Recuperación (RAG)

La generación aumentada por recuperación responde a una pregunta a partir de tus propios documentos: busca los pasajes más cercanos a la pregunta y luego pásaselos a un modelo de chat junto con la pregunta. La misma clave de Zurelay sirve para las dos cosas.

# 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 y el AI SDK

Los frameworks con una clase de embeddings de OpenAI funcionan con la URL base y la clave de Zurelay, y los ID de modelo siguen siendo los de 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"])

La clase Python OpenAIEmbeddings de LangChain tokeniza los textos por su cuenta de forma predeterminada y envía los ID de token. Zurelay acepta arrays de tokens, así que la configuración predeterminada funciona tal cual.

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

Con Settings.embed_model configurado, cada índice y cada retriever que crees genera sus embeddings a través de Zurelay. Para el modelo de chat, consulta la guía de 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]));

Aplicaciones y almacenes vectoriales

Las aplicaciones que llaman a los embeddings de OpenAI con una URL base configurable funcionan igual: ponla en https://api.zurelay.com/v1, usa tu clave de Zurelay y uno de los ID de modelo de arriba. Esto incluye el motor de embeddings de OpenAI para documentos de Open WebUI, la API de RAG de LibreChat, el nodo Embeddings OpenAI de n8n y el proveedor de modelos compatible con OpenAI de Dify.

Los mismos vectores que OpenAI

Las solicitudes a text-embedding-3-small y text-embedding-3-large se ejecutan en los propios modelos de OpenAI. Comprobamos cada ruta contra la API de OpenAI: para el mismo texto y el mismo modelo, los vectores coinciden con los de OpenAI, con una similitud de coseno de 0.9999 o más. Así que un índice creado con la API de OpenAI sigue funcionando cuando generas los embeddings de tus consultas a través de Zurelay, y uno creado a través de Zurelay funciona con los de OpenAI. Pasar de uno a otro no exige volver a generar los embeddings.

Cambiar de modelo obliga a regenerar los embeddings

Los vectores de modelos distintos no se pueden comparar

Un vector de text-embedding-3-small no significa nada junto a uno de text-embedding-3-large, ni junto a uno de cualquier otro modelo: cada modelo sitúa el texto en su propio espacio. Así que, para cambiar de modelo, vuelve a generar los embeddings de todo lo que tengas guardado con el nuevo modelo y consulta con ese modelo de ahí en adelante.

Por la misma razón, a una solicitud de embeddings siempre responde el modelo que indicaste. El enrutamiento inteligente nunca la pasa a otro modelo, sean cuales sean tus ajustes: si el modelo está caído recibes 503 model_unavailable, para reintentar, en lugar de vectores que no encajan con tu índice.

Límites

  • Hasta 2,048 entradas por solicitud.
  • Hasta 8,192 tokens en cada entrada, y 300,000 en la solicitud completa.
  • No se admiten cadenas vacías.
  • dimensions de 1 a la longitud máxima del modelo: 1,536 en text-embedding-3-small, 3,072 en text-embedding-3-large.
  • Una solicitud que supera un límite se rechaza con un 400 que indica el límite, y no cuesta nada.
  • Una clave limitada a ciertos modelos necesita el modelo de embeddings en su lista. Consulta Límites y reglas de las claves.

Cuánto cuestan los embeddings

Los embeddings se facturan solo por tokens de entrada, contados como los cuenta OpenAI, y los vectores son gratis. Text Embedding 3 Small cuesta $0.01 por 1M de tokens y Text Embedding 3 Large $0.065, la mitad del precio de OpenAI. Un millón de textos de 100 tokens cada uno, 100M de tokens, cuesta $1.00 generar sus embeddings con Text Embedding 3 Small y $6.50 con Text Embedding 3 Large, frente a $2.00 y $13.00 en OpenAI.

No hay mínimo por solicitud: una solicitud de 10 tokens se factura como 10 tokens. Los dimensions más cortos y base64 cuestan lo mismo. Las cabeceras usage y x-zurelay-input-tokens de cada respuesta indican sobre qué se facturó, todas las solicitudes aparecen en tu registro de solicitudes y las que fallan no cuestan nada.

Errores

Los errores llegan con el formato de OpenAI, así que los SDK de OpenAI los lanzan igual que lo harían con OpenAI.

EstadoCódigoCuándo
400invalid_requestFalta un campo o es incorrecto: una cadena vacía, más de 2,048 inputs, un dimensions que no es un número entero o un encoding_format distinto de float o base64. El mensaje indica cuál.
400invalid_valuedimensions es mayor que la longitud completa del modelo.
400context_length_exceededUn input supera los 8,192 tokens, o la solicitud supera 300,000. Divide el texto o envía menos inputs a la vez.
503model_unavailableEl modelo no está disponible por un momento. Reintenta después de los segundos indicados en el encabezado retry-after.

Las claves, el crédito, los presupuestos y los límites de frecuencia dan los mismos errores que cualquier otro endpoint: consulta Errores. Los SDK de OpenAI reintentan los que vale la pena reintentar.

Pruébalo primero

En el Playground del dashboard, elige un modelo de embeddings, escribe algunos textos y genera sus embeddings: verás cada vector, los tokens facturados y la similitud coseno de cada par. Su botón Obtener código genera la misma petición para Python, Node.js o cURL.

¿Tienes dudas o falta algo? Escribe a nuestro equipo de soporte y te responderemos por email, normalmente en menos de un minuto.