Inferência compatível com OpenAI
Todo projeto tem uma API compatível com a OpenAI, então qualquer cliente ou SDK da OpenAI pode chamar seus modelos trocando a URL base e a chave. O modelo chamado pode ser um modelo hospedado do projeto, um job de treinamento concluído ou um modelo base do catálogo.
Endpoints
GET /api/projects/{owner_slug}/{project_slug}/openai/v1/models
POST /api/projects/{owner_slug}/{project_slug}/openai/v1/chat/completions
POST /api/projects/{owner_slug}/{project_slug}/openai/v1/completions
POST /api/projects/{owner_slug}/{project_slug}/openai/v1/embeddings
Aponte um SDK da OpenAI para a URL base do projeto:
from openai import OpenAI
client = OpenAI(
base_url="https://reopenly.com/api/projects/<owner>/<project>/openai/v1",
api_key="<sua chave de API da ReOpenly>",
)
GET /models retorna os modelos hospedados que você pode chamar:
{
"object": "list",
"data": [
{
"id": "support-assistant",
"object": "model",
"created": 1764547200,
"owned_by": "reopenly"
}
]
}
Use um id retornado como model. A lista não inclui modelos base nem jobs de
treinamento: leia GET /api/v1/base-models para o catálogo base e use um modelo
base como <catalog-model-id>, e um job concluído de um projeto que você
acessa como training-job#<job-id>.
Respostas de chat
curl "https://reopenly.com/api/projects/<owner>/<project>/openai/v1/chat/completions" \
-H "Authorization: Bearer $REOPENLY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "<catalog-model-id>",
"messages": [
{"role": "user", "content": "Give me one sentence about datasets."}
],
"max_tokens": 64,
"temperature": 0.2,
"stream": false
}'
A requisição aceita model, messages, max_tokens, temperature, top_p,
reasoning_effort, tools de função, stream e
stream_options.include_usage. A resposta é uma chat completion da OpenAI com
choices, um motivo de término e o uso:
{
"id": "chatcmpl-…",
"object": "chat.completion",
"created": 1764547200,
"model": "<catalog-model-id>",
"choices": [
{
"index": 0,
"message": {"role": "assistant", "content": "…"},
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 18,
"completion_tokens": 12,
"total_tokens": 30,
"reasoning_tokens": 0,
"cached_tokens": 0
}
}
As contagens de tokens vêm do modelo quando ele as informa. Uma contagem não
informada é 0; não a leia como estimativa.
Padrões para campos omitidos
| Campo | Aplicado quando omitido |
|---|---|
max_tokens | 1024 |
temperature | 1.0 |
top_p | 1.0 |
stream | false |
reasoning_effort | nada: o padrão do próprio modelo |
GET /api/v1/base-models publica a mesma tabela como um bloco inference
(defaults e capabilities), para que um cliente a leia em vez de fixá-la no
código. O playground parte desse bloco, então um prompt digitado ali e o mesmo
prompt enviado por esta API são a mesma requisição.
reasoning_effort: "none" desliga o raciocínio explicitamente, enquanto omitir
o campo pede ao modelo o que ele faz por padrão: são requisições diferentes. Os
valores explícitos são none, low e high; o "padrão do modelo" do
playground não envia campo nenhum.
Streaming
Use stream: true para receber quadros text/event-stream. Um stream bem
sucedido termina com um quadro de término seguido de:
data: [DONE]
Peça o quadro final de uso padrão quando precisar:
{
"model": "support-assistant",
"messages": [{"role": "user", "content": "Summarize this."}],
"stream": true,
"stream_options": {"include_usage": true}
}
O quadro de uso tem um array choices vazio e um objeto usage. Nem todo
quadro traz papel ou conteúdo: deltas de chamada de ferramenta e o quadro de
término têm outros formatos.
Uma requisição que falha antes de o stream começar recebe a resposta de erro
JSON normal. Se a geração falhar depois de 200 OK e dos cabeçalhos do stream,
o último quadro é um envelope de erro JSON e o stream termina sem [DONE]. Veja
Erros e novas tentativas.
Completions legadas
POST /completions aceita uma string ou uma lista de strings em prompt e
retorna um text_completion da OpenAI. Uma lista é unida com espaços e tratada
como um único prompt; ela não produz uma escolha por prompt, então envie
requisições separadas quando precisar disso. Aceita model, prompt,
max_tokens, temperature, top_p e reasoning_effort, e não faz streaming.
Embeddings
POST /embeddings serve os modelos de embeddings hospedados de um projeto de
embeddings.
curl "https://reopenly.com/api/projects/<owner>/<project>/openai/v1/embeddings" \
-H "Authorization: Bearer $REOPENLY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "citation-embedding",
"input": ["A short passage to embed.", "A second passage."],
"encoding_format": "float",
"input_type": "document"
}'
A requisição recebe uma string não vazia ou uma lista de strings, codificação
float ou base64 (base64, que o SDK Python oficial da OpenAI pede por
padrão, retorna bytes float32 little-endian codificados em base64), o tamanho de
vetor do modelo como dimensions, e o input_type opcional query ou
document. Um modelo treinado com query_instruction a aplica só às
consultas.
Limites. Cada entrada pode ter até 512 tokens, cada requisição até duas
entradas, e uma requisição até 512 tokens no total. Um modelo que você treinou
com max_seq_length 1024 ou 2048 aceita entradas até esse comprimento e aumenta
o orçamento de tokens da requisição para o mesmo limite. O teto de duas entradas
continua: uma entrada no comprimento máximo ocupa todo o orçamento, enquanto
duas entradas menores podem caber juntas. Um modelo treinado com 512 ou menos
mantém 512. O limite de entrada é
separado do tamanho da saída: o modelo base 0.6B retorna um vetor de 1.024
dimensões qualquer que seja o comprimento da entrada. Uma requisição acima
desses limites responde 400 invalid_request com error.param="input" e sem
Retry-After: divida ou encurte, porque repetir sem mudar não vai ajudar.
Um valor de tipo ou formato errado responde 422 com detail (campos
desconhecidos são ignorados); um dimensions bem formado mas não suportado
responde 400 invalid_request com error.param="dimensions". Um modelo
inativo ou desconhecido responde 404, e um projeto sem embeddings responde
409.
Por padrão, uma entrada acima do limite é recusada com 400, como faz a
OpenAI. Use truncate: true para cortar cada entrada longa demais no limite: o
fim do texto é removido, exatamente como os modelos foram treinados, e o item é
marcado com "truncated": true. Uma entrada cortada ocupa todo o orçamento de
entrada, então envie-a em uma requisição própria. O campo truncated só aparece
quando a entrada foi cortada; a ausência dele significa false. Pelo SDK
Python da OpenAI:
response = client.embeddings.create(
model="citation-embedding",
input=["A long document. " * 600],
extra_body={"truncate": True},
)
print(getattr(response.data[0], "truncated", False))
A resposta mantém a ordem das entradas:
{
"object": "list",
"data": [
{"object": "embedding", "embedding": [0.01, -0.02], "index": 0}
],
"model": "citation-embedding",
"usage": {"prompt_tokens": 12, "total_tokens": 12}
}
Tempo limite e capacidade. Um modelo que ficou ocioso pode levar um momento
para responder à primeira requisição, então use um tempo limite no cliente de
pelo menos 60 segundos. Se o modelo não conseguir responder nesse tempo, ou
se chegarem mais requisições ao mesmo tempo do que ele comporta, a resposta é
503 capacity_unavailable com Retry-After: espere esse tempo e tente de novo.
Toda resposta é verificada antes de ser devolvida (quantidade de vetores,
tamanho, ordem e uso), e uma requisição é cobrada no máximo uma vez, mesmo
quando é repetida internamente.
Chamadas de embeddings nunca são salvas em um dataset. A ReOpenly não guarda o texto da requisição nem os vetores retornados como dados de treino: um vetor não diz qual trecho respondeu a uma consulta, então seria no máximo metade de um par de treino. Para montar pares de treino a partir de buscas reais, informe-as pelo Feedback de busca. Para experimentar um modelo de forma interativa ou classificar um dataset com ele, veja Playground e Busca.
Limites de contexto
Uma requisição de chat precisa caber no contexto do modelo: o
max_context_tokens de GET /api/v1/base-models (4.096 para Qwen3.5 9B),
contando o prompt e o max_tokens. A verificação acontece antes de o modelo
rodar, e uma requisição que não cabe responde invalid_request. Leia o limite
do catálogo em vez desta página e deixe espaço para max_tokens.
Cobrança e novas tentativas
Uma requisição só é cobrada depois que o modelo a executou, e uma requisição que
falha não é cobrada. Uma requisição bem-sucedida em uma rota paga é cobrada por
pelo menos 100 milissegundos no preço congelado (0.000050 créditos quando o preço é
0.03 crédito por minuto de GPU). Tente de novo só quando a resposta for um
erro model_warming com Retry-After. Uma resposta ambiguous_transport pode
significar que a requisição foi atendida, então repetir às cegas pode duplicar
trabalho e cobrança.
O playground mantém a conversa original, faz no máximo três novas tentativas automáticas
depois da requisição original e então deixa um Tentar de novo manual. Ele
nunca repete um erro que chega no meio do stream. O playground guarda o projeto
na URL da página (/app/playground?project=owner/project) e chama a mesma API de
projeto mostrada acima; não há rota de inferência fora de um projeto.
Inferência e ajuste fino são cobrados em unidades de 100 ms, arredondadas para cima a partir do tempo ativo medido. Créditos usam seis casas decimais. Um treino que nunca inicia permanece gratuito.
Modelos hospedados também lista os modelos base de cada projeto. Não é necessário ajuste fino: use ReOpenly/Qwen3.5-9B para texto ou ReOpenly/Qwen3-Embedding-0.6B / ReOpenly/Qwen3-Embedding-4B para embeddings. Chamadas ao modelo base exigem o proprietário do projeto e uma rota de inferência disponível. Texto respeita o salvamento automático; vetores de embeddings exigem feedback explícito para virar exemplos de treino.