OpenAI Chat Completions
Usa el SDK de OpenAI que ya tienes para llamar a GPT, Claude y Gemini a mitad de precio. Streaming con uso, llamadas a funciones, razonamiento y Responses API.
Cualquier código escrito para el SDK de OpenAI funciona en Half Off Dev cambiando la URL base por https://halfoff.dev/v1 y la clave por la tuya. POST /v1/chat/completions sigue la definición de OpenAI y el cuerpo se reenvía tal cual, así que la referencia de la API de OpenAI vale campo por campo. Pagas la mitad del precio de lista por cada token, y con el mismo cliente llegas también a Claude y a Gemini.
Cambia el cliente#
curl https://halfoff.dev/v1/chat/completions \
-H "Authorization: Bearer $HALFOFF_API_KEY" \
-H "content-type: application/json" \
-d '{
"model": "gpt-6.1-sol",
"messages": [
{"role": "system", "content": "Eres un profesor de SQL que va al grano."},
{"role": "user", "content": "¿Cuándo debo usar un LEFT JOIN?"}
]
}'Como los SDK leen OPENAI_BASE_URL y OPENAI_API_KEY, la mayoría de apps compatibles con el protocolo de OpenAI solo necesitan esos dos valores. En Apps compatibles con OpenAI tienes algunos ejemplos.
Qué modelos puedes llamar aquí#
Todos los modelos de texto de GET /v1/models aceptan hoy Chat Completions, Claude y Gemini incluidos. Es la vía más sencilla si quieres un único cliente para todos los fabricantes.
| Familia | Ids de ejemplo | A tener en cuenta |
|---|---|---|
| GPT | gpt-6.1-sol, gpt-6-astra, gpt-5.6-terra, gpt-5.6-luna | Formato nativo. |
| Claude | claude-sonnet-5.5, claude-opus-5.5 | Para cache_control o ajustes de thinking, llámalos con Anthropic Messages. |
| Gemini | gemini-3.1-pro, gemini-3-flash | Para thinkingConfig y otros campos propios de Gemini, usa la API de Gemini. |
Las opciones exclusivas de un fabricante solo están garantizadas en su propio formato. Consulta formato nativo de cada familia.
Streaming, con el uso al final#
Activa stream: true. El gateway siempre añade stream_options.include_usage: true, de modo que el último fragmento antes de data: [DONE] tiene un array choices vacío y el usage de la llamada. Comprueba que choices no esté vacío antes de leer un delta.
stream = client.chat.completions.create(
model="gpt-6.1-sol",
messages=[{"role": "user", "content": "Enumera los planetas en orden desde el Sol."}],
stream=True,
)
for chunk in stream:
if chunk.choices and chunk.choices[0].delta.content:
print(chunk.choices[0].delta.content, end="", flush=True)
if chunk.usage:
print("\nTokens:", chunk.usage.prompt_tokens, "de entrada,", chunk.usage.completion_tokens, "de salida")El formato exacto en el cable y cómo aparecen los fallos a mitad de stream están en Streaming.
Llamadas a funciones#
Declara tus funciones en tools. Cuando el modelo quiere usar una, finish_reason pasa a tool_calls; ejecútala y devuelve un mensaje tool con el tool_call_id correspondiente.
import json
tools = [{
"type": "function",
"function": {
"name": "check_stock",
"description": "Devuelve cuántas unidades de un producto hay en stock.",
"parameters": {
"type": "object",
"properties": {"sku": {"type": "string"}},
"required": ["sku"],
},
},
}]
messages = [{"role": "user", "content": "¿Nos queda stock del SKU KB-204?"}]
completion = client.chat.completions.create(model="gpt-6.1-sol", messages=messages, tools=tools)
msg = completion.choices[0].message
if msg.tool_calls:
messages.append(msg)
for call in msg.tool_calls:
args = json.loads(call.function.arguments)
result = {"sku": args["sku"], "units": 17} # tu código aquí
messages.append({"role": "tool", "tool_call_id": call.id, "content": json.dumps(result)})
completion = client.chat.completions.create(model="gpt-6.1-sol", messages=messages, tools=tools)
print(completion.choices[0].message.content)tool_choice, parallel_tool_calls y response_format se reenvían sin cambios.
Esfuerzo de razonamiento#
Los modelos GPT-5 y GPT-6 piensan antes de responder. Regula cuánto con reasoning_effort y limita la salida total, razonamiento incluido, con max_completion_tokens. Los valores de esfuerzo permitidos varían según el modelo; los tienes en la guía de razonamiento de OpenAI.
completion = client.chat.completions.create(
model="gpt-6.1-sol",
reasoning_effort="high",
max_completion_tokens=8000,
messages=[{"role": "user", "content": "¿Por qué 0.1 + 0.2 != 0.3 en la mayoría de lenguajes?"}],
)
print(completion.choices[0].message.content)
print("tokens de razonamiento:", completion.usage.completion_tokens_details.reasoning_tokens)Chat Completions no devuelve el texto del razonamiento. Sus tokens se incluyen en usage.completion_tokens y se cobran a la tarifa de salida.
La Responses API#
Para los modelos GPT, POST /v1/responses sirve la Responses API de OpenAI, que es la que habla Codex CLI. Misma URL base, misma clave; client.responses.create() no necesita cambios.
curl https://halfoff.dev/v1/responses \
-H "Authorization: Bearer $HALFOFF_API_KEY" \
-H "content-type: application/json" \
-d '{
"model": "gpt-6.1-sol",
"instructions": "Responde con una sola palabra.",
"input": "¿Qué es lo contrario de una tarea limitada por la latencia?"
}'En streaming (stream: true) recibes los eventos tipados de la API, desde response.created pasando por response.output_text.delta hasta response.completed; el uso que trae response.completed es lo que se factura. Ten en cuenta:
- No se guarda nada en el servidor.
GET,DELETEy cancel sobre/v1/responses/{id}devuelven404, yprevious_response_idsolo funciona mientras la conversación siga en el mismo canal. Envía el historial completo eninputcon"store": false, como hace Codex. - Solo GPT. Cualquier otro modelo responde
404connot served in responses format; usa Chat Completions o su API nativa. instructionsen la respuesta repite lo que enviaste, onullsi no enviaste nada.prompt_cache_keymantiene una conversación en un solo canal, igual que en Chat Completions.
Caché de prompts#
Los modelos de OpenAI guardan en caché los prefijos largos sin que se lo pidas. La parte cacheada aparece en usage.prompt_tokens_details.cached_tokens y se cobra a la tarifa reducida de lectura de caché. Un prompt_cache_key mejora la tasa de aciertos y además le indica al gateway qué peticiones forman una conversación. Consulta Caché de prompts.
No soportado#
Chat Completions y la Responses API son los endpoints con formato de OpenAI. Estos responden 404:
- Respuestas almacenadas:
GET,DELETEo cancel sobre/v1/responses/{id}. - Embeddings:
/v1/embeddings. - Audio:
/v1/audio/*. - Completions heredado, Assistants, archivos, lotes, fine-tuning y moderación.
client.models.list() funciona contra GET /v1/models; pedir un único modelo por id, no.
Cabeceras de la petición#
| Cabecera | Comportamiento |
|---|---|
Authorization: Bearer | Tu clave de Half Off Dev. Obligatoria. |
openai-beta | Se reenvía sin cambios. |
x-halfoff-group | Opcional. Elige el grupo de canales para esta petición, salvo que la clave ya esté vinculada a uno. |
x-session-id | Opcional. Marca peticiones como una misma conversación para el enrutamiento según caché. prompt_cache_key o user en el cuerpo tienen el mismo efecto. |
Los errores usan la forma de OpenAI, {"error": {"message": "...", "type": "...", "code": "..."}}; consulta Errores.