Saltar al contenido

AI Gateway

Chat Completions

POST /v1/chat/completions — el endpoint de chat compatible con OpenAI de Clipia AI Gateway. Respuestas con streaming y normales, parámetros, roles, herramientas y errores.

POST /v1/chat/completions es el endpoint principal de Clipia AI Gateway, compatible con OpenAI Chat Completions. La clave necesita el permiso generate, que tienen todas las claves, así que no necesita una clave aparte para el chat. Recibe un array messages y devuelve la respuesta del asistente en modo sin streaming o con streaming, con soporte para tool calling y salida estructurada mediante esquema JSON.

POST/v1/chat/completions

Respuesta normal

import os
from openai import OpenAI

client = OpenAI(
    api_key=os.environ["CLIPIA_KEY"],
    base_url="https://api.clipia.ai/v1",
)

resp = client.chat.completions.create(
    model="claude-opus-5-5",
    messages=[
        {"role": "system", "content": "You are a concise assistant."},
        {"role": "user", "content": "Tell me three facts about Mars."},
    ],
    max_tokens=512,
)

print(resp.choices[0].message.content)
print(resp.usage)
import OpenAI from "openai";

const client = new OpenAI({
  apiKey: process.env.CLIPIA_KEY,
  baseURL: "https://api.clipia.ai/v1",
});

const resp = await client.chat.completions.create({
  model: "claude-opus-5-5",
  messages: [
    { role: "system", content: "You are a concise assistant." },
    { role: "user", content: "Tell me three facts about Mars." },
  ],
  max_tokens: 512,
});

console.log(resp.choices[0].message.content);
curl https://api.clipia.ai/v1/chat/completions \
  -H "Authorization: Bearer $CLIPIA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "claude-opus-5-5",
    "messages": [
      { "role": "system", "content": "You are a concise assistant." },
      { "role": "user", "content": "Tell me three facts about Mars." }
    ],
    "max_tokens": 512
  }'

Respuesta 200

{
  "id": "chatcmpl-3f9a1c7e2b41",
  "object": "chat.completion",
  "created": 1782300000,
  "model": "claude-opus-5-5",
  "provider": "Clipia",
  "choices": [
    {
      "index": 0,
      "message": { "role": "assistant", "content": "1. ...\n2. ...\n3. ..." },
      "finish_reason": "stop"
    }
  ],
  "usage": {
    "prompt_tokens": 28,
    "completion_tokens": 64,
    "total_tokens": 92,
    "cost": 0.046
  }
}

usage.cost es el coste de la petición en créditos. El id de la respuesta tiene la forma chatcmpl-…, y provider siempre es "Clipia". finish_reason se normaliza al enum de OpenAI: stop, length, tool_calls, content_filter.

Respuesta con streaming

Pase stream: true: la respuesta llega de forma incremental como Server-Sent Events. Cada evento es un chat.completion.chunk con un delta; el stream termina con el literal data: [DONE].

stream = client.chat.completions.create(
    model="claude-opus-5-5",
    messages=[{"role": "user", "content": "Write a haiku about the sea."}],
    stream=True,
)

for chunk in stream:
    delta = chunk.choices[0].delta
    if delta.content:
        print(delta.content, end="", flush=True)
const stream = await client.chat.completions.create({
  model: "claude-opus-5-5",
  messages: [{ role: "user", content: "Write a haiku about the sea." }],
  stream: true,
});

for await (const chunk of stream) {
  process.stdout.write(chunk.choices[0]?.delta?.content ?? "");
}
curl -N https://api.clipia.ai/v1/chat/completions \
  -H "Authorization: Bearer $CLIPIA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "claude-opus-5-5",
    "messages": [{ "role": "user", "content": "Write a haiku about the sea." }],
    "stream": true
  }'

Flujo de eventos

data: {"id":"chatcmpl-...","object":"chat.completion.chunk","created":1782300000,"model":"claude-opus-5-5","choices":[{"index":0,"delta":{"role":"assistant"},"finish_reason":null}]}

data: {"id":"chatcmpl-...","object":"chat.completion.chunk","model":"claude-opus-5-5","choices":[{"index":0,"delta":{"content":"Waves "},"finish_reason":null}]}

data: {"id":"chatcmpl-...","object":"chat.completion.chunk","model":"claude-opus-5-5","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}

data: [DONE]

Uso final en un stream

Para recibir usage durante el streaming, pase stream_options: { "include_usage": true }. En ese caso se envía un chunk adicional con usage completo y choices: [] vacío justo antes de data: [DONE].

Parámetros de la petición

Prop

Type

Compatibilidad de parámetros

Los campos n, parallel_tool_calls, logprobs, top_logprobs, logit_bias, service_tier y metadata se aceptan, pero no afectan a la respuesta: siempre recibe una sola opción. Los campos desconocidos de nivel superior tampoco provocan un 400, de modo que el código escrito para OpenAI funciona sin cambios.

La compatibilidad de los parámetros depende del modelo: consulte supported_parameters en GET /v1/models. Los modelos actuales pueden ignorar temperature, top_p y las penalizaciones por repetición. DeepSeek V4.1 Flash no llama a herramientas. Para una salida estricta por esquema JSON, elija un modelo con response_format, como GPT-6.1 Sol. Claude 5.5 sigue las instrucciones de salida estructurada sin garantizar una conformidad estricta con el esquema.

Roles de los mensajes

RolCómo se trata
systeminstrucción de sistema
developerse trata como system
usermensaje del usuario
assistantuna respuesta anterior del modelo, incluida una con tool_calls
toolel resultado de una llamada a herramienta; requiere tool_call_id
cualquier otrose trata como user

Un array messages vacío, un mensaje sin role de tipo cadena o una petición sin model devuelven 400 invalid_request_error.

Reintentos de peticiones

Este endpoint no admite la cabecera Idempotency-Key: un reintento tras un fallo de red ejecuta y cobra la petición de nuevo. El chat está sujeto a los límites de frecuencia y de concurrencia comunes.

Llamadas a herramientas

Ciclo completo: usted describe las herramientas en tools, el modelo devuelve tool_calls, usted ejecuta la función por su parte y devuelve el resultado como un mensaje con role: "tool", tras lo cual el modelo genera la respuesta final.

import json

tools = [
    {
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "Current weather in a city",
            "parameters": {
                "type": "object",
                "properties": {
                    "city": {"type": "string", "description": "City name"},
                },
                "required": ["city"],
            },
        },
    }
]

messages = [{"role": "user", "content": "What's the weather in Moscow right now?"}]

# 1) The model decides to call a tool
resp = client.chat.completions.create(
    model="claude-opus-5-5", messages=messages, tools=tools,
)
msg = resp.choices[0].message
# resp.choices[0].finish_reason == "tool_calls"

# 2) Run the function on your side
call = msg.tool_calls[0]
args = json.loads(call.function.arguments)   # {"city": "Moscow"}
result = {"temp_c": 14, "condition": "cloudy"}

# 3) Send the result back and get the final answer
messages.append(msg)  # assistant echo with tool_calls
messages.append({
    "role": "tool",
    "tool_call_id": call.id,
    "content": json.dumps(result),
})

final = client.chat.completions.create(
    model="claude-opus-5-5", messages=messages, tools=tools,
)
print(final.choices[0].message.content)

Con streaming, los fragmentos de tool_calls llegan agrupados por index: el nombre de la función y el id llegan en el primer delta de ese índice y, después, los fragmentos de function.arguments, que usted concatena hasta formar un JSON válido. Una llamada a herramienta completa se marca con finish_reason: "tool_calls".

Salidas estructuradas

Para obligar a que la respuesta se ajuste a un esquema JSON, pase response_format con type: "json_schema" y strict: true.

resp = client.chat.completions.create(
    model="gpt-6.1-sol",
    messages=[{"role": "user", "content": "Extract name and age: Anna is 30."}],
    response_format={
        "type": "json_schema",
        "json_schema": {
            "name": "person",
            "strict": True,
            "schema": {
                "type": "object",
                "properties": {
                    "name": {"type": "string"},
                    "age": {"type": "integer"},
                },
                "required": ["name", "age"],
                "additionalProperties": False,
            },
        },
    },
)

print(resp.choices[0].message.content)  # {"name": "Anna", "age": 30}

json_object frente a json_schema

response_format: { "type": "json_object" } garantiza un JSON válido, pero sin un esquema concreto, y exige que la palabra «json» aparezca en messages. Para una conformidad estructural estricta, utilice json_schema con strict: true.

Errores

Los errores usan el sobre de OpenAI { "error": { "message", "type", "param", "code" } }, siempre con Content-Type: application/json y el estado HTTP correspondiente.

EstadocodetypeCuándo
400invalid_request_errorinvalid_request_errorcuerpo mal formado: sin model, messages vacío, esquema JSON no válido en response_format
400context_length_exceededinvalid_request_errorla entrada supera el contexto del modelo; se comprueba antes de cobrar nada
401invalid_api_keyinvalid_request_errorla clave falta, es incorrecta o está revocada
402insufficient_quotainsufficient_quotano hay créditos suficientes en el saldo
402subscription_requiredinsufficient_quotael modelo requiere una suscripción activa
402insufficient_quotainsufficient_quotase ha agotado el presupuesto de créditos de la clave para el periodo: consulte Límites de frecuencia
403insufficient_scope o permission_errorinvalid_request_errora la clave le falta un permiso o la petición no está permitida
404model_not_foundinvalid_request_errormodel desconocido o no disponible
422model_input_invalidinvalid_request_errorel modelo rechazó la petición por su contenido; reformule la entrada
429rate_limit_exceeded o concurrency_limit_exceededrate_limit_errorse ha superado el límite de peticiones, de tokens o de concurrencia
500, 502, 503, 504nullapi_errorfallo temporal del modelo; reintente más tarde
{
  "error": {
    "message": "Insufficient credits. Please top up your balance.",
    "type": "insufficient_quota",
    "param": null,
    "code": "insufficient_quota"
  }
}

En los errores 5xx el mensaje siempre es genérico: "The upstream model is temporarily unavailable. Please retry." para 502, 503 y 504, y "An internal error occurred. Please retry or contact support." para 500.

Errores dentro de un stream

Si el fallo se produce cuando el stream ya ha empezado, el estado HTTP ya es 200. En ese caso, el último chunk antes de data: [DONE] incluye finish_reason: "error" y un objeto error:

{
  "id": "chatcmpl-...",
  "object": "chat.completion.chunk",
  "choices": [{ "index": 0, "delta": {}, "finish_reason": "error" }],
  "error": { "message": "The model timed out. Please retry.", "code": "upstream_timeout", "type": "api_error" }
}
codeQué ha ocurrido
upstream_errorel modelo no respondió
upstream_timeoutse agotó el tiempo de espera del modelo
service_unavailableel modelo no está disponible temporalmente
empty_responseel modelo no devolvió ninguna salida utilizable
incomplete_streamel stream del modelo terminó antes de tiempo
invalid_structured_outputla salida no se ajusta al response_format solicitado
model_input_invalidel modelo rechazó la petición por su contenido

Compruebe finish_reason en el último chunk: los clientes del SDK de OpenAI no siempre convierten ese chunk en una excepción. Los errores que se producen antes de que empiece el stream, como 402 o 404, llegan como JSON normal con un estado HTTP.