Embeddings

Embeddings

Transforme texto em vetores para busca semântica, recuperação (RAG), recomendações, agrupamento e deduplicação, com os modelos text-embedding-3 da OpenAI pela metade do preço da OpenAI. Mesmos IDs de modelo, mesma requisição e mesma resposta, mesmos vetores: código feito para a OpenAI funciona assim que você troca a base URL e a chave.

POST/v1/embeddings
ModeloIDPor 1M de tokens de entradaPreço da OpenAIDimensões
Text Embedding 3 Smalltext-embedding-3-small$0.01$0.021,536, ou menos
Text Embedding 3 Largetext-embedding-3-large$0.065$0.133,072, ou menos

Você paga pelo texto que envia, e os vetores são de graça. Os dois modelos leem até 8,192 tokens por entrada. O Text Embedding 3 Small é o indicado para começar: barato, rápido e bom para a maioria dos casos de busca e RAG. O Text Embedding 3 Large é o maior e mais preciso, para quando a qualidade da recuperação importa mais do que armazenamento e preço.

Início 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

Qualquer SDK da OpenAI funciona: aponte para https://api.zurelay.com/v1, use sua chave da Zurelay e chame o método de embeddings dele. A resposta é a da OpenAI:

Resposta (o vetor encurtado)
{
"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 traz um embedding por entrada, na ordem em que você as enviou, cada um com seu index. usage.prompt_tokens é o que foi cobrado na requisição. Os headers também dizem isso: x-zurelay-model indica o modelo e x-zurelay-input-tokens os tokens, e x-request-id localiza a requisição no seu log de requisições.

Parâmetros

modelstringobrigatório
text-embedding-3-small ou text-embedding-3-large.
inputstring ou arrayobrigatório
O que enviar: uma string, um array de até 2,048 strings, um array de IDs de token (inteiros do tokenizer cl100k_base da OpenAI) ou um array de até 2,048 arrays desses. Cada entrada tem no máximo 8,192 tokens, e nenhuma pode ser uma string vazia.
encoding_formatstring
float (o padrão): cada embedding é um array de números. base64: cada um é uma string base64 de números float32 little-endian, menor e mais rápido de baixar. Veja Base64.
dimensionsinteger
Encurte o vetor: de 1 a 1,536 em text-embedding-3-small, de 1 a 3,072 em text-embedding-3-large. Deixe de fora para o comprimento total. Veja Vetores mais curtos.
userstring
Opcional: um ID do usuário final para quem é a requisição, como na API da OpenAI. É aceito e não muda nada nos vetores.

Envio em lote

Envie muitos textos em uma requisição: input aceita um array de até 2,048, e eles voltam na mesma ordem. Uma requisição com mil textos é bem mais rápida que mil requisições, e custa o mesmo: você paga por token, não por requisição. Mantenha cada requisição com menos de 300,000 tokens no 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

Divida documentos com mais de 8,192 tokens em chunks antes de fazer o embedding. Para busca e RAG, chunks de algumas centenas de tokens, um ou dois parágrafos, costumam encontrar resultados melhores do que documentos inteiros.

Vetores mais curtos com dimensions

Os modelos text-embedding-3 são treinados para que um vetor encurtado ainda funcione: peça dimensions e você recebe a primeira parte do vetor completo, renormormalizada para comprimento 1. Vetores mais curtos ocupam menos armazenamento e memória e buscam mais rápido, com um pouco menos de precisão. O Text Embedding 3 Large com 1,024 dimensões ocupa um terço do espaço do seu 3,072 completo.

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
Use um único modelo e um único tamanho para tudo o que for comparar: vetores de comprimentos diferentes não podem ser comparados, e muitos bancos de vetores fixam o comprimento quando você cria o índice. Vetores mais curtos custam o mesmo: o preço é por token de entrada.

Base64

Com "encoding_format": "base64", cada embedding volta como uma string base64 de números float32 little-endian em vez de um array JSON: menor e mais rápido de baixar e processar. Quando você deixa encoding_format de fora, o SDK Python da OpenAI pede base64 por conta própria e decodifica para você, então você recebe floats normais de qualquer jeito. Se você mesmo pedir base64, recebe a string, para decodificar assim:

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]

Faça o embedding dos seus documentos uma vez e armazene os vetores. Para buscar, faça o embedding da consulta com o mesmo modelo e ordene os documentos por similaridade de cosseno com ela: quanto mais perto de 1, mais próximos em significado, mesmo sem nenhuma palavra em comum. Os vetores text-embedding-3 têm comprimento 1, então o produto escalar dá a mesma ordenação.

# 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 mais do que alguns milhares de documentos, guarde os vetores em um banco de dados vetorial ou um índice vetorial (pgvector, Qdrant, Pinecone, Weaviate, Chroma e afins) e deixe que ele faça a ordenação.

Recuperação (RAG)

A geração aumentada por recuperação responde a uma pergunta a partir dos seus próprios documentos: busque os trechos mais próximos da pergunta e então entregue-os a um modelo de chat junto com a pergunta. A mesma chave da Zurelay faz as duas coisas.

# 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 e o AI SDK

Frameworks com uma classe de embeddings da OpenAI funcionam com a base URL e a chave da Zurelay, e os IDs dos modelos continuam sendo os da 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"])

O OpenAIEmbeddings do LangChain em Python tokeniza os textos por conta própria por padrão e envia os IDs dos tokens. A Zurelay aceita arrays de tokens, então as configurações padrão funcionam como estão.

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

Com Settings.embed_model definido, todo índice e retriever que você criar faz o embedding pela Zurelay. Para o modelo de chat, veja o guia do 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]));

Apps e bancos de vetores

Apps que chamam os embeddings da OpenAI com uma base URL que você pode definir funcionam do mesmo jeito: defina-a como https://api.zurelay.com/v1, use sua chave da Zurelay e um dos IDs de modelo acima. Isso inclui o motor de embeddings da OpenAI do Open WebUI para documentos, a RAG API do LibreChat, o nó Embeddings OpenAI do n8n e o provedor de modelos compatível com OpenAI do Dify.

Os mesmos vetores da OpenAI

As requisições para text-embedding-3-small e text-embedding-3-large rodam nos próprios modelos da OpenAI. Conferimos cada rota contra a API da OpenAI: para o mesmo texto e modelo, os vetores batem com os da OpenAI, com similaridade de cosseno de 0.9999 ou mais. Então um índice que você criou com a API da OpenAI continua funcionando quando você faz o embedding das suas consultas pela Zurelay, e um criado pela Zurelay funciona com a da OpenAI. Mudar de uma para a outra não exige refazer o embedding.

Trocar de modelo significa refazer o embedding

Vetores de modelos diferentes não podem ser comparados

Um vetor de text-embedding-3-small não significa nada ao lado de um de text-embedding-3-large, ou de qualquer outro modelo: cada modelo posiciona o texto em seu próprio espaço. Então, para trocar de modelo, refaça o embedding de tudo o que você armazenou com o novo modelo, e consulte com esse modelo daí em diante.

Pelo mesmo motivo, uma requisição de embeddings é sempre respondida pelo modelo que você indicou. O Roteamento inteligente nunca a entrega a outro modelo, sejam quais forem suas configurações: se o modelo estiver fora do ar, você recebe 503 model_unavailable, para tentar de novo, em vez de vetores que não batem com o seu índice.

Limites

  • Até 2,048 entradas por requisição.
  • Até 8,192 tokens em cada entrada, e 300,000 na requisição inteira.
  • Nenhuma string vazia.
  • dimensions de 1 até o comprimento total do modelo: 1,536 em text-embedding-3-small, 3,072 em text-embedding-3-large.
  • Uma requisição acima de um limite é recusada com um 400 que indica o limite, e não custa nada.
  • Uma chave limitada a alguns modelos precisa ter o modelo de embeddings na lista dela. Veja Limites e regras de chaves.

Pagar por embeddings

Embeddings são cobrados só pelos tokens de entrada, contados do jeito que a OpenAI conta, e os vetores são grátis. Text Embedding 3 Small custa $0.01 por 1M de tokens e Text Embedding 3 Large $0.065, metade do preço da OpenAI. Um milhão de textos de 100 tokens cada, 100M de tokens, custa $1.00 para gerar embeddings com Text Embedding 3 Small e $6.50 com Text Embedding 3 Large, contra $2.00 e $13.00 na OpenAI.

Não há mínimo por requisição: uma requisição de 10 tokens é cobrada como 10 tokens. Um dimensions mais curto e base64 custam o mesmo. Os headers usage e x-zurelay-input-tokens de cada resposta dizem sobre o que foi cobrado, toda requisição fica no seu request log, e requisições que falham não custam nada.

Erros

Os erros vêm no formato da OpenAI, então os SDKs da OpenAI os lançam como lançariam para a OpenAI.

StatusCódigoQuando
400invalid_requestUm campo está faltando ou errado: uma string vazia, mais de 2,048 inputs, um dimensions que não é um número inteiro, ou um encoding_format diferente de float ou base64. A mensagem diz qual.
400invalid_valuedimensions é maior que o tamanho máximo do modelo.
400context_length_exceededUm input passa de 8,192 tokens, ou a requisição passa de 300,000. Divida o texto ou envie menos inputs de uma vez.
503model_unavailableO modelo está indisponível por um breve período. Tente novamente após os segundos indicados no header retry-after.

Chaves, crédito, orçamentos e limites de taxa geram os mesmos erros de todos os outros endpoints: veja Erros. Os SDKs da OpenAI repetem automaticamente as requisições que vale a pena repetir.

Experimente primeiro

No Playground do dashboard, escolha um modelo de embedding, escreva alguns textos e gere os embeddings: você vê cada vetor, os tokens cobrados e a similaridade de cosseno de cada par. O botão Gerar código monta a mesma requisição para Python, Node.js ou cURL.

Tem dúvidas ou falta algo? Fale com nossa equipe de suporte e responderemos por e-mail, geralmente em até um minuto.