Embeddings

Embeddings

Transforme texto em vetores para pesquisa semântica, recuperação (RAG), recomendações, clustering e deduplicação, com os modelos text-embedding-3 da OpenAI a metade do preço da OpenAI. Os mesmos IDs de modelo, o mesmo pedido e a mesma resposta, os mesmos vetores: o código escrito para a OpenAI funciona assim que alterar o 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

Paga o texto que envia, e os vetores são gratuitos. Ambos os 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 das pesquisas e do RAG. O Text Embedding 3 Large é o modelo maior e mais preciso, para quando a qualidade da recuperação importa mais do que o armazenamento e o preço.

Guia 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-o para https://api.zurelay.com/v1, use a sua chave Zurelay e chame o método embeddings. 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 tem um embedding por entrada, pela ordem em que as enviou, cada um com o seu index. usage.prompt_tokens é aquilo sobre o que o pedido foi faturado. Os cabeçalhos também o dizem: x-zurelay-model indica o modelo e x-zurelay-input-tokens os tokens, e x-request-id permite encontrar o pedido no seu registo de pedidos.

Parâmetros

modelstringobrigatório
text-embedding-3-small ou text-embedding-3-large.
inputstring ou arrayobrigatório
O que colocar no embedding: uma string, um array de até 2,048 strings, um array de IDs de tokens (inteiros do tokenizer cl100k_base da OpenAI) ou um array de até 2,048 desses arrays. Cada entrada tem no máximo 8,192 tokens e nenhuma pode ser uma string vazia.
encoding_formatstring
float (predefinição): cada embedding é um array de números. base64: cada um é uma string base64 de números float32 little-endian, mais pequena e mais rápida de descarregar. Ver 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. Omita-o para obter o comprimento total. Ver Vetores mais curtos.
userstring
Opcional: um ID do utilizador final a quem o pedido se destina, como na API da OpenAI. É aceite e não altera nada nos vetores.

Pedidos em lote

Envie muitos textos num só pedido: input aceita um array de até 2,048 e as respostas chegam na mesma ordem. Um pedido com mil textos é muito mais rápido do que mil pedidos, e custa o mesmo: paga por token, não por pedido. Mantenha cada pedido abaixo 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 em fragmentos os documentos com mais de 8,192 tokens antes de gerar os embeddings. Para pesquisa e RAG, fragmentos de algumas centenas de tokens, um parágrafo ou dois, costumam encontrar melhores correspondências do que documentos inteiros.

Vetores mais curtos com dimensions

Os modelos text-embedding-3 foram treinados para que um vetor encurtado continue a funcionar: peça dimensions e recebe a primeira parte do vetor completo, renormalizada para comprimento 1. Vetores mais curtos ocupam menos armazenamento e memória e pesquisam mais depressa, com um pouco menos de exatidão. O Text Embedding 3 Large com 1,024 dimensões ocupa um terço do espaço das suas 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
Use um só modelo e um só tamanho para tudo o que compara: vetores de comprimentos diferentes não podem ser comparados, e muitas bases de dados vetoriais fixam o comprimento quando cria o índice. Vetores mais curtos custam o mesmo: o preço é por token de entrada.

Base64

Com "encoding_format": "base64", cada embedding chega como uma string base64 de números float32 little-endian em vez de um array JSON: mais pequena e mais rápida de descarregar e processar. Se omitir encoding_format, o SDK Python da OpenAI pede base64 por si e descodifica-a, por isso recebe floats simples de qualquer forma. Se pedir base64 você mesmo, recebe a string, para descodificar 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]

Gere os embeddings dos seus documentos uma vez e guarde os vetores. Para pesquisar, gere o embedding da consulta com o mesmo modelo e ordene os documentos pela semelhança de cosseno com ela: quanto mais próximo de 1, mais próximos no significado, mesmo sem palavras em comum. Os vetores text-embedding-3 têm comprimento 1, por isso o produto interno 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 numa base de dados vetorial ou num índice vetorial (pgvector, Qdrant, Pinecone, Weaviate, Chroma e semelhantes) e deixe que seja ela a fazer a ordenação.

Recuperação (RAG)

A geração aumentada por recuperação responde a uma pergunta a partir dos seus próprios documentos: procure as passagens mais próximas da pergunta e depois entregue-as a um modelo de chat juntamente com a pergunta. A mesma chave Zurelay serve para 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

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

A OpenAIEmbeddings do LangChain para Python tokeniza os textos por si mesma por predefinição e envia os IDs dos tokens. A Zurelay aceita arrays de tokens, por isso as predefinições funcionam tal 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, todos os índices e retrievers que criar geram os embeddings através da Zurelay. Para o modelo de chat, consulte 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]));

Aplicações e armazenamentos vetoriais

As aplicações que chamam os embeddings da OpenAI com um URL base configurável funcionam da mesma forma: defina-o como https://api.zurelay.com/v1, use a sua chave Zurelay e um dos IDs de modelo acima. Isto inclui o motor de embeddings OpenAI do Open WebUI para documentos, a API RAG do LibreChat, o nó Embeddings OpenAI do n8n e o fornecedor de modelos compatível com OpenAI do Dify.

Os mesmos vetores da OpenAI

Os pedidos para text-embedding-3-small e text-embedding-3-large correm nos próprios modelos da OpenAI. Verificámos todas as rotas contra a API da OpenAI: para o mesmo texto e modelo, os vetores coincidem com os da OpenAI, com uma semelhança de cosseno de 0.9999 ou mais. Assim, um índice criado com a API da OpenAI continua a funcionar quando gera os embeddings das suas consultas através da Zurelay, e um índice criado através da Zurelay funciona com a OpenAI. Passar de uma para a outra não exige voltar a gerar embeddings.

Mudar de modelo implica gerar novos embeddings

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 coloca o texto no seu próprio espaço. Por isso, para mudar de modelo, volte a gerar os embeddings de tudo o que guardou com o novo modelo e passe a consultar com esse modelo.

Pela mesma razão, um pedido de embeddings é sempre respondido pelo modelo que indicou. O Encaminhamento inteligente nunca o passa a outro modelo, sejam quais forem as suas definições: se o modelo estiver em baixo, recebe 503 model_unavailable, para tentar de novo, em vez de vetores que não correspondem ao seu índice.

Limites

  • Até 2,048 entradas por pedido.
  • Até 8,192 tokens em cada entrada e 300,000 no pedido completo.
  • Sem strings vazias.
  • dimensions de 1 até ao comprimento total do modelo: 1,536 em text-embedding-3-small, 3,072 em text-embedding-3-large.
  • Um pedido que exceda um limite é recusado com um 400 que indica o limite, e não custa nada.
  • Uma chave limitada a alguns modelos precisa de ter o modelo de embeddings na sua lista. Ver Limites e regras das chaves.

Pagar pelos embeddings

Os embeddings são cobrados apenas pelos tokens de entrada, contados como a OpenAI os conta, e os vetores são gratuitos. 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, ou seja 100M de tokens, custa $1.00 em 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 pedido: um pedido de 10 tokens é cobrado como 10 tokens. dimensions mais curtos e base64 custam o mesmo. Os cabeçalhos usage e x-zurelay-input-tokens de cada resposta indicam o que foi cobrado, todos os pedidos ficam no seu registo de pedidos e os pedidos que falham não custam nada.

Erros

Os erros chegam na forma da OpenAI, por isso os SDKs da OpenAI lançam-nos como fariam para a OpenAI.

EstadoCódigoQuando
400invalid_requestFalta um campo ou está errado: uma string vazia, mais de 2,048 inputs, dimensions que não é um número inteiro, ou um encoding_format diferente de float ou base64. A mensagem diz qual.
400invalid_valuedimensions é maior do que o comprimento total do modelo.
400context_length_exceededUm input tem mais de 8,192 tokens, ou o pedido mais de 300,000. Divida o texto ou envie menos inputs de uma vez.
503model_unavailableO modelo está temporariamente indisponível. Tente novamente passados os segundos indicados no cabeçalho retry-after.

As chaves, o crédito, os orçamentos e os limites de taxa dão os mesmos erros que os restantes endpoints: consulte Erros. Os SDKs da OpenAI repetem automaticamente os pedidos que vale a pena repetir.

Experimente primeiro

No Área de testes do dashboard, escolha um modelo de embeddings, escreva alguns textos e gere os embeddings: pode ver cada vetor, os tokens cobrados e a semelhança de cosseno entre cada par. O botão Obter código gera o mesmo pedido para Python, Node.js ou cURL.

Tem dúvidas ou falta alguma coisa? Contacte a nossa equipa de apoio e responderemos por email, normalmente num minuto.