Documentación

Documentación

completados de chat

El endpoint /v1/chat/completions de Kunavo usa el protocolo Chat Completions de OpenAI para las familias Claude y GPT. El streaming, las herramientas y la salida JSON funcionan con el mismo SDK; el efecto de reasoning_effort depende de la familia, y esta página especifica cuál es.

Endpoint: POST /v1/chat/completions. Los formatos de solicitud y respuesta coinciden exactamente con la API Chat Completions de OpenAI, incluidos el streaming y los campos opcionales tool_calls / reasoning_tokens.

Llamada básica

resp = client.chat.completions.create(
    model="claude-sonnet-5",
    messages=[
        {"role": "system", "content": "You are a senior staff engineer."},
        {"role": "user", "content": "Pros and cons of postgres LISTEN/NOTIFY for a job queue?"},
    ],
    max_tokens=800,
)
print(resp.choices[0].message.content)

Parámetros

Se aceptan todos los parámetros estándar de OpenAI. Algunos solo son pertinentes para ciertos proveedores: el traductor transmite los que admite cada proveedor upstream.

ParámetroTipoNotas
modelcadena (obligatorio)Cualquier identificador habilitado de /v1/models.
messagesmatriz (obligatorio)Formato estándar de mensajes de OpenAI.
temperature0..2Temperatura de muestreo. Valor predeterminado: 1. No tiene efecto en los modelos enumerados debajo de esta tabla.
top_p0..1Muestreo de núcleo. Se aplica la misma excepción que para temperature.
max_tokensintLímite de salida; max_completion_tokens tiene prioridad si se envían ambos. En los modelos GPT, los tokens de razonamiento cuentan para este límite. Si una solicitud a Claude no incluye ninguno de los dos, el valor es 4,096.
streamboolTransmite fragmentos como SSE. Consulta más abajo.
toolsarrayDefiniciones de funciones/herramientas. Tanto Claude como GPT admiten el uso de herramientas.
tool_choiceauto|none|namedFuerza el uso de una herramienta específica o permite que el modelo decida.
response_formatobjectSalida JSON. En Claude se exige un json_schema, pero no un json_object; consulta Salida JSON para ver los detalles de cada familia.
reasoning_effortstringSolo para modelos GPT: se reenvía como reasoning.effort. No se reenvía para Claude. Consulta Razonamiento.
seedintMuestreo determinista cuando se admite.
stopstring|arraySecuencias de parada estrictas.
En Claude Fable 5.1, Claude Fable 5, Claude Opus 5.5, Claude Opus 5, Claude Opus 4.8, Claude Opus 4.7 y Claude Sonnet 5 no se admiten temperature, top_p ni top_k. Kunavo elimina esos tres campos de las solicitudes a esos modelos en lugar de devolverte el error 400 del proveedor; por tanto, no tienen efecto en ellos.

Streaming

Establece stream=True. Kunavo emite eventos enviados por el servidor en el formato exacto de OpenAI: cada fragmento es un chat.completion.chunk con choices[0].delta.content. La carga útil final de uso llega con data: [DONE].

stream = client.chat.completions.create(
    model="claude-haiku-4-5",
    messages=[{"role": "user", "content": "Explain B-trees in one paragraph."}],
    stream=True,
)

for chunk in stream:
    delta = chunk.choices[0].delta.content
    if delta:
        print(delta, end="", flush=True)
El streaming funciona con todos los modelos de chat. Para Claude (cuyo proveedor upstream utiliza el protocolo Anthropic), traducimos al vuelo el flujo de eventos de Anthropic a deltas de OpenAI; tu SDK no nota la diferencia.

Llamadas a herramientas y funciones

Las llamadas a herramientas funcionan con todos los proveedores. Define las herramientas como esquemas JSON; el modelo devuelve tool_calls en su mensaje; las ejecutas y devuelves los resultados en mensajes role: "tool".

tools = [{
    "type": "function",
    "function": {
        "name": "get_current_weather",
        "description": "Get current weather for a city",
        "parameters": {
            "type": "object",
            "properties": {
                "city": {"type": "string"},
                "unit": {"type": "string", "enum": ["c", "f"]},
            },
            "required": ["city"],
        },
    },
}]

resp = client.chat.completions.create(
    model="claude-sonnet-5",
    messages=[{"role": "user", "content": "What's the weather in Tokyo?"}],
    tools=tools,
    tool_choice="auto",
)

# Inspect tool calls the model wants to make
for call in resp.choices[0].message.tool_calls or []:
    print(call.function.name, call.function.arguments)

Salida JSON (response_format)

response_format solicita una respuesta en JSON. Lo que garantiza depende de la familia del modelo, porque la solicitud de cada familia se traduce a un mecanismo distinto del proveedor upstream:

Familiajson_schemajson_object
ClaudeObligatorio. Se envía como salida estructurada de Anthropic (output_config.format); la respuesta se ajusta a tu esquema. Verificado en todos los modelos Claude que ofrece Kunavo, 2026-09-24.No obligatorio. Anthropic no tiene un modo JSON sin esquema, así que el campo se acepta y se ignora; la respuesta puede empezar con prosa o un bloque de código.
GPTSe reenvía como text.format de la API Responses, pero el proveedor upstream actual no lo aplica en todas las solicitudes. Valídalo antes de analizar la respuesta.Se reenvía como text.format.
import json

resp = client.chat.completions.create(
    model="claude-haiku-4-5",
    messages=[{"role": "user", "content": "Jane Doe <jane@example.com> wants the Pro plan."}],
    response_format={
        "type": "json_schema",
        "json_schema": {
            "name": "signup",
            "schema": {
                "type": "object",
                "properties": {
                    "name": {"type": "string"},
                    "email": {"type": "string"},
                    "plan": {"type": "string", "enum": ["free", "pro", "enterprise"]},
                },
                "required": ["name", "email", "plan"],
                "additionalProperties": False,
            },
        },
    },
)
signup = json.loads(resp.choices[0].message.content)  # constrained to the schema

Cuatro aspectos que debes conocer sobre Claude:

  • El esquema se envía a Anthropic casi tal como lo escribiste. Anthropic exige additionalProperties: false en los objetos, por lo que Kunavo lo añade a cualquier objeto que enumere sus propiedades y lo deja sin definir, como hace Pydantic con model_json_schema(). Anthropic sigue rechazando lo siguiente con un error 400 que identifica el problema: un objeto abierto (additionalProperties definido con cualquier valor distinto de false, o sin propiedades enumeradas y con additionalProperties sin definir) y oneOf; usa anyOf. La página de salida estructurada de Anthropic enumera los demás casos.
  • Se ignoran strict y name: Anthropic aplica todos los esquemas que acepta, tanto si son estrictos como si no.
  • No se puede combinar un esquema con un mensaje final assistant (un prefill); Anthropic responde con un error 400.
  • Si max_tokens se agota a mitad de un objeto, el JSON queda truncado y finish_reason es "length".

Entrada visual y multimodal

Los modelos con la capacidad vision aceptan bloques de contenido de imagen. Usa una URL HTTPS o una URI base64 data:.

# Pass an image URL or a base64 data URI as part of a multimodal message
resp = client.chat.completions.create(
    model="claude-haiku-4-5",
    messages=[{
        "role": "user",
        "content": [
            {"type": "text", "text": "What's in this image?"},
            {"type": "image_url", "image_url": {
                "url": "https://example.com/cat.jpg"
            }},
        ],
    }],
)
print(resp.choices[0].message.content)

Los modelos con capacidad de visión del catálogo incluyen Claude Opus 5.5; consulta la lista de modelos para ver los demás.

Para una imagen HTTPS, Kunavo descarga el archivo y se lo entrega al modelo en línea, porque algunos proveedores upstream ignoran una URL de imagen sin procesar. Límites: 16 imágenes por solicitud, 5 MB por imagen, 20 MB en total y 10 segundos por descarga. Si no se puede obtener una imagen, se devuelve un error que la identifica en lugar de omitirla.

Razonamiento

El efecto de un control de razonamiento en este endpoint depende de la familia, porque la solicitud de cada familia se traduce a una API distinta del proveedor upstream:

Familiareasoning_effortTokens de razonamiento en el uso
GPTSe reenvía a GPT-6 Astra, GPT-6 Sol, GPT-6 Luna, GPT-5.6 Sol, GPT-5.6 Terra y GPT-5.5 como reasoning.effort de la API Responses, sin alterar el valor. Los valores aceptados son los propios del modelo: en GPT-6 Sol y GPT-6 Luna, none desactiva el razonamiento y max es el nivel máximo.Contabilizados dentro de completion_tokens y desglosados como completion_tokens_details.reasoning_tokens; se facturan a la tarifa de salida.
ClaudeNo se reenvía. La solicitud de Anthropic se construye a partir de una lista fija de campos: model, max_tokens, messages, stream, temperature, top_p, top_k, stop, tools, tool_choice y response_format. Por tanto, reasoning_effort, thinking y cualquier otro campo ajeno a esa lista se descartan aquí.Sin desglose. Anthropic cuenta todo el razonamiento dentro de su output_tokens, que es lo que informa completion_tokens.
# GPT models: reasoning_effort is forwarded as the Responses API's
# reasoning.effort. Reasoning tokens are part of completion_tokens.
resp = client.chat.completions.create(
    model="gpt-6-sol",
    messages=[{"role": "user", "content": "Plan a 3-week MLOps migration."}],
    reasoning_effort="high",
)
details = resp.usage.completion_tokens_details   # absent when no reasoning ran
print(details.reasoning_tokens if details else 0)
El pensamiento extendido en Claude se realiza a través de la API nativa Messages, que transmite thinking a Anthropic sin traducir.

caché de prompts

Un prompt de sistema extenso, un documento de referencia o un bloque de ejemplos: cualquier prefijo estable puede almacenarse en caché en el proveedor y reutilizarse en llamadas posteriores por una fracción del precio de entrada. Los aciertos de caché aparecen en el objeto de uso como prompt_tokens_details.cached_tokens.

Los modelos GPT almacenan en caché automáticamente; no hace falta modificar la solicitud. cached_tokens es un subconjunto de prompt_tokens y se factura a una tarifa reducida de lectura de caché.

Claude solo almacena en caché hasta un punto de interrupción cache_control, y en un prompt largo este endpoint establece los puntos de interrupción por ti: en el prompt de sistema, en las definiciones de herramientas y en el último mensaje en cuanto la conversación contiene un turno del asistente. Para elegir tú el prefijo, adjunta cache_control a un bloque de contenido, como se muestra abajo; Kunavo conserva tus puntos de interrupción y solo añade los suyos en las posiciones que dejaste vacías:

# Claude caches prefixes you mark with cache_control. Attach it to a content
# block; later calls reusing that prefix read it at ~10% of the input price.
resp = client.chat.completions.create(
    model="claude-sonnet-5",
    messages=[{
        "role": "user",
        "content": [
            {
                "type": "text",
                "text": LONG_DOCUMENT,            # the stable, reused prefix
                "cache_control": {"type": "ephemeral"},
            },
            {"type": "text", "text": "Summarize the document above."},
        ],
    }],
)
print(resp.usage.prompt_tokens_details.cached_tokens)
Para tener control total sobre el almacenamiento en caché de prompts de Claude —el prompt de sistema y las definiciones de herramientas, varios puntos de interrupción y los campos de uso nativos cache_creation_input_tokens / cache_read_input_tokens— usa la API nativa Messages. Este endpoint también almacena en caché el prompt de sistema y las definiciones de herramientas, admite varios puntos de interrupción en una misma solicitud e informa las escrituras en caché como cache_creation_input_tokens y las lecturas de caché como prompt_tokens_details.cached_tokens; lo que solo el endpoint nativo hace es transmitir el cuerpo de tu solicitud sin añadir ningún punto de interrupción.

Objeto de uso

Cada respuesta (y el fragmento final de la transmisión) incluye un objeto usage:

CampoSignificado
prompt_tokensTokens de entrada facturados, incluidos los tokens almacenados en caché.
prompt_tokens_details.cached_tokensEntrada almacenada en caché: un subconjunto de prompt_tokens, facturado a una tarifa reducida de lectura de caché.
completion_tokensTokens de salida facturados. En los modelos GPT esto incluye los tokens de razonamiento; en Claude es output_tokens de Anthropic.
completion_tokens_details.reasoning_tokensModelos GPT: la parte de completion_tokens correspondiente al razonamiento. Se omite cuando es cero y no se informa para Claude.
total_tokensprompt_tokens + completion_tokens.

Compatibilidad de Ollama con OpenAI — /v1/chat/completions

Ollama ofrece sus modelos locales mediante una API compatible con OpenAI en http://localhost:11434/v1, que implementa el mismo contrato /chat/completions documentado en esta página. Así, ambas opciones son intercambiables desde el cliente: cualquier código escrito para la API v1 de Ollama funciona con la de Kunavo con solo cambiar la URL base y el modelo.

from openai import OpenAI

# Before — Ollama's OpenAI-compatible API, local models
client = OpenAI(
    base_url="http://localhost:11434/v1",
    api_key="ollama",                       # ignored locally
)
resp = client.chat.completions.create(
    model="llama3.2",
    messages=[{"role": "user", "content": "Summarize this changelog."}],
)

# After — the same call against hosted frontier models.
# Two lines changed. Everything below them is untouched.
client = OpenAI(
    base_url="https://api.kunavo.com/v1",   # <- was localhost:11434/v1
    api_key=os.environ["KUNAVO_API_KEY"],
)
resp = client.chat.completions.create(
    model="claude-sonnet-5",  # <- was llama3.2
    messages=[{"role": "user", "content": "Summarize this changelog."}],
)

Los elementos que suelen fallar al cambiar de proveedor aquí no fallan, porque el formato en la red es el mismo:

FunciónOllama v1Kunavo v1
Ruta del endpoint/v1/chat/completions/v1/chat/completions
StreamingSSE, chat.completion.chunkIdénticos: misma estructura de delta
Llamadas a herramientastools / tool_callsIdénticas en Claude y GPT
Entrada visualBloques image_url en modelos locales multimodalesLos mismos bloques; URL o URI de datos en base64
api_keyMarcador de posición, ignoradoUna clave real de Kunavo
modelUna etiqueta descargada (llama3.2)Un identificador alojado (claude-sonnet-5)
extras de usoSolo recuentos de tokensAñade cached_tokens y reasoning_tokens
Más allá del chatcompletions, embeddingsAdemás, endpoints de imágenes, vídeo y audio
¿Usas ambos? Lee base_url, model y la clave desde variables de entorno, y cambia según el entorno: modelos locales para iteraciones sin conexión y modelos alojados cuando importa la calidad, con una sola ruta de código. La guía completa, que incluye lo que la interfaz OpenAI de Ollama implementa y lo que no, está en la guía de la API de Ollama compatible con OpenAI.

¿Dónde está la documentación oficial del endpoint /v1/chat/completions compatible con OpenAI de Ollama?

Ollama documenta su capa de compatibilidad con OpenAI en su propio repositorio, en docs/openai.md, que es la referencia oficial de los campos de OpenAI que implementa el servidor local. Esta página es la referencia equivalente para el endpoint alojado /v1/chat/completions de Kunavo: el mismo contrato de Chat Completions de OpenAI, documentado campo por campo más arriba, para que puedas comparar directamente ambas interfaces. En resumen, los formatos de solicitud y respuesta son iguales: Ollama lo ofrece en http://localhost:11434/v1 para modelos descargados localmente, y Kunavo en https://api.kunavo.com/v1 para modelos Claude y GPT alojados.

¿Es /v1/chat/completions de Kunavo compatible con la API de OpenAI de Ollama?

Sí: ambos implementan el mismo contrato de Chat Completions de OpenAI, por lo que un cliente diseñado para Ollama en http://localhost:11434/v1 funciona sin cambios en https://api.kunavo.com/v1. Los campos de solicitud (messages, temperature, max_tokens, stream, tools, tool_choice, response_format, stop, seed) y los campos de respuesta (choices[].message, choices[].delta en streaming, usage) coinciden. Las diferencias son las esperables en modelos alojados: api_key es una clave real, no un marcador de posición; model recibe un identificador alojado, como claude-sonnet-5, en lugar de una etiqueta descargada, como llama3.2; y usage incluye cached_tokens y reasoning_tokens, que los modelos locales no informan.

¿Cómo migro una aplicación de Ollama a Claude o GPT alojado?

Cambia dos líneas: establece base_url de http://localhost:11434/v1 a https://api.kunavo.com/v1 y establece model con el identificador alojado en lugar de la etiqueta local. Usa una clave real de Kunavo en lugar del marcador de posición que Ollama ignora. No hace falta cambiar el código de streaming, llamadas a herramientas ni visión, porque los formatos SSE y tool_calls son idénticos.

¿Puedo seguir usando Ollama en desarrollo y modelos alojados en producción?

Sí, es una configuración habitual: lee la URL base, el modelo y la clave de las variables de entorno y cámbialos según el entorno. Como ambos extremos usan el mismo protocolo OpenAI, no hay que mantener una segunda ruta de código: Llama o Qwen locales para iterar sin conexión, y Claude o GPT alojados cuando importa la calidad. Las definiciones de herramientas y los controladores de streaming se comparten literalmente.

¿Qué endpoints de OpenAI añade Kunavo a la interfaz compatible de Ollama?

La interfaz de Ollama compatible con OpenAI cubre /chat/completions, /completions, /embeddings y /models. Kunavo añade endpoints multimedia con la misma clave: /v1/images/generations, /v1/images/edits, /v1/video/generations, /v1/videos y /v1/audio/music, además de Claude en el endpoint nativo de Anthropic /v1/messages. Hay dos salvedades claras en sentido contrario: /v1/audio/speech, /v1/audio/transcriptions y /v1/embeddings están implementados como formatos de transmisión, pero actualmente no tienen ningún modelo habilitado, por lo que no funcionarán por ahora; y, a diferencia de Ollama, Kunavo no ejecuta modelos localmente. GET /v1/models es siempre la referencia definitiva de lo que se puede invocar.

Próximos pasos