spicyapiDocumentación
Contenido principal

Texto y streaming

Llama a modelos de texto con el formato oficial de OpenAI, Anthropic o Google Gemini y gestiona bien conversaciones, herramientas, streaming y costos.

Elegir un modelo de texto disponible

Consulta /api/v1/models?modality=text&includeSchema=1 con autenticación y envía solicitudes solo a modelos con enabled y available en true. Las herramientas, los mensajes multimodales, los campos de razonamiento y las salidas estructuradas deben estar admitidos también por el inputSchema de ese modelo. Que un protocolo sea compatible no significa que todos los modelos ofrezcan todas las funciones.

Puedes llamar a un mismo ID de modelo con cualquiera de los protocolos siguientes, sin importar qué empresa lo haya desarrollado: el formato de Anthropic sirve para modelos de otras compañías y el de Gemini, para modelos que no son de Google.

Protocolos admitidos

Los cuatro protocolos de texto comparten la misma clave API, el mismo catálogo de modelos y los mismos límites de solicitudes, validación de parámetros y facturación; solo cambia el formato de las solicitudes y las respuestas. Elige el que ya usen tu código o tu SDK.

ProtocoloEndpointCabecera de la clave
OpenAI Chat CompletionsPOST /v1/chat/completionsAuthorization: Bearer
OpenAI ResponsesPOST /v1/responsesAuthorization: Bearer
Anthropic MessagesPOST /v1/messagesx-api-key o Authorization: Bearer
Google GeminiPOST /v1beta/models/{model}:generateContentx-goog-api-key o Authorization: Bearer
Google Gemini en streamingPOST /v1beta/models/{model}:streamGenerateContentx-goog-api-key o Authorization: Bearer
Streaming nativoPOST /api/v1/jobs/streamAuthorization: Bearer

Las respuestas y los errores de /v1 y /v1beta siguen cada uno su propio protocolo, sin la envoltura nativa {code,msg,data,request_id}; no los interpretes con un cliente que solo lea body.data. En el formato Gemini, el ID del modelo va en la ruta de la URL: sus barras se mantienen tal cual y no hace falta codificarlas.

Envía la clave únicamente en las cabeceras de la solicitud. x-api-key y x-goog-api-key solo funcionan en /v1 y /v1beta; la API nativa /api/v1 solo reconoce Authorization: Bearer. El parámetro de consulta ?key= no se acepta en ningún caso: una clave que aparece en una URL queda guardada en el historial del navegador, en los servidores proxy y en los registros de acceso. Los SDK oficiales envían la clave en las cabeceras, así que no les afecta.

Usar los SDK oficiales

Cambia solo la URL base y la clave; todo lo demás se usa como siempre en cada SDK.

SDKURL base
OpenAI (openai)https://api.spicyapi.ai/v1
Anthropic (anthropic)https://api.spicyapi.ai
Google GenAI (google-genai)https://api.spicyapi.ai

Los SDK de Anthropic y Google añaden /v1 o /v1beta por su cuenta, por eso su URL base no lleva ruta de versión; la del SDK de OpenAI, en cambio, debe incluir /v1. Los tres ejemplos de Python siguientes leen de las variables de entorno SPICY_API_KEY y SPICY_MODEL, este último elegido en el catálogo actual.

OpenAI
import os
from openai import OpenAI

client = OpenAI(api_key=os.environ["SPICY_API_KEY"], base_url="https://api.spicyapi.ai/v1")
reply = client.chat.completions.create(
    model=os.environ["SPICY_MODEL"],
    messages=[{"role": "user", "content": "Explain a rainbow in one sentence."}],
)
print(reply.choices[0].message.content)
Anthropic
import os
import anthropic

client = anthropic.Anthropic(api_key=os.environ["SPICY_API_KEY"], base_url="https://api.spicyapi.ai")
message = client.messages.create(
    model=os.environ["SPICY_MODEL"],
    max_tokens=256,
    messages=[{"role": "user", "content": "Explain a rainbow in one sentence."}],
)
print(message.content[0].text)
Google GenAI
import os
from google import genai
from google.genai import types

client = genai.Client(
    api_key=os.environ["SPICY_API_KEY"],
    http_options=types.HttpOptions(base_url="https://api.spicyapi.ai"),
)
response = client.models.generate_content(
    model=os.environ["SPICY_MODEL"],
    contents="Explain a rainbow in one sentence.",
)
print(response.text)

Para que un reintento de red no genere cargos duplicados, crea un Idempotency-Key por cada acción del usuario, guárdalo de forma persistente y envíalo con la opción de cabeceras personalizadas del SDK (en los SDK de OpenAI y Anthropic es extra_headers).

Correspondencia de parámetros entre protocolos

Los campos de cada protocolo se traducen primero a los nombres de parámetro unificados de la plataforma y después se validan con el inputSchema del modelo elegido. Un parámetro que el modelo no admite devuelve 400: no se ignora en silencio para luego cobrar como si nada. La tabla solo muestra las equivalencias; «—» indica que ese protocolo no tiene un campo equivalente. Si un modelo acepta un parámetro concreto, y con qué rango de valores, lo determina su inputSchema.

Parámetro unificadoChat CompletionsResponsesMessagesGemini
messagesmessagesinstructions, inputsystem, messagessystemInstruction, contents
max_tokensmax_tokens, max_completion_tokensmax_output_tokensmax_tokensgenerationConfig.maxOutputTokens
temperaturetemperaturetemperaturetemperaturegenerationConfig.temperature
top_ptop_ptop_ptop_pgenerationConfig.topP
toolstoolstools (tipo function)tools (herramientas personalizadas)tools[].functionDeclarations
tool_choicetool_choicetool_choicetool_choicetoolConfig.functionCallingConfig
response_formatresponse_formattext.formatgenerationConfig.responseMimeType más responseSchema o responseJsonSchema
reasoning_effortreasoning_effortreasoning.effortgenerationConfig.thinkingConfig

Los campos que no figuran en la tabla también se traducen a parámetros unificados y los valida el modelo; si no están en el inputSchema, devuelven 400. Por ejemplo, si el modelo no acepta stop, devuelven 400 tanto stop en Chat como stop_sequences en Messages y generationConfig.stopSequences en Gemini. Hoy ningún modelo de texto incluye seed en su inputSchema, así que seed en Chat y generationConfig.seed en Gemini también devuelven 400.

Algunas excepciones:

  • Se ignoran, sin afectar a la generación, los campos que solo tienen sentido en la plataforma de origen de cada protocolo: user, metadata y store en Chat; user, metadata, store e include en Responses; y metadata en Messages.
  • Responses no admite previous_response_id: incluye el historial completo en input.
  • En Gemini, safetySettings se acepta pero no tiene efecto; cachedContent y las herramientas que se ejecutan en el servidor, como googleSearch y codeExecution, no se admiten y devuelven 400. Las imágenes pueden enviarse con inlineData (base64) o fileData.fileUri (URL https); si el modelo acepta imágenes lo determina su inputSchema.

Enviar un flujo Chat

Configura en tu servidor SPICY_API_KEY, SPICY_MODEL obtenido del catálogo y un SPICY_IDEMPOTENCY_KEY guardado para esta operación. El ejemplo necesita jq. Puedes cambiar el prompt, pero los campos deben cumplir el esquema vigente.

jq -n --arg model "$SPICY_MODEL" '{
  model: $model,
  messages: [{role: "user", content: "Explain a rainbow in one sentence."}],
  stream: true
}' > chat-request.json

curl --fail-with-body --no-buffer --max-time 120 \
  https://api.spicyapi.ai/v1/chat/completions \
  -H "Authorization: Bearer $SPICY_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $SPICY_IDEMPOTENCY_KEY" \
  --data-binary @chat-request.json

Enviar una solicitud Gemini

Las variables de entorno son las mismas de la sección anterior. El ID del modelo va en la URL y el cuerpo de la solicitud no incluye model. Pon la URL entre comillas dobles y escribe ${SPICY_MODEL} para que la shell no interprete los dos puntos que siguen como un modificador de variable.

jq -n '{
  systemInstruction: {parts: [{text: "Answer in one sentence."}]},
  contents: [{role: "user", parts: [{text: "Explain a rainbow."}]}],
  generationConfig: {temperature: 0.7, maxOutputTokens: 256}
}' > gemini-request.json

curl --fail-with-body --no-buffer --max-time 120 \
  "https://api.spicyapi.ai/v1beta/models/${SPICY_MODEL}:streamGenerateContent?alt=sse" \
  -H "x-goog-api-key: $SPICY_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $SPICY_IDEMPOTENCY_KEY" \
  --data-binary @gemini-request.json

Si no necesitas streaming, cambia :streamGenerateContent?alt=sse por :generateContent y lee el texto de la respuesta en candidates[0].content.parts[].text; el consumo está en usageMetadata. Si una solicitud de streaming no lleva alt=sse, la respuesta es un array JSON que se va escribiendo progresivamente; los SDK oficiales añaden alt=sse por su cuenta.

Conversaciones y herramientas

En Chat sin streaming lee choices[0].message; con streaming, lee choices[].delta en cada evento SSE completo. No interpretes cada fragmento TCP como JSON. Los argumentos de las herramientas pueden llegar repartidos en varios fragmentos: une los tool_calls por index y valida los argumentos completos antes de ejecutarlos. Conserva el mensaje assistant y devuelve cada resultado con su tool_call_id correspondiente. Responses requiere el historial completo en input y no acepta previous_response_id; devuelve los resultados de funciones con function_call_output y el call_id correspondiente. Messages trabaja con bloques text, tool_use y tool_result dentro de content; no le sirve tal cual el formato de mensajes de Chat.

En Gemini, contents alterna dos roles: user y model. Cuando el modelo quiere llamar a una herramienta, devuelve una parte functionCall; después de ejecutarla, convierte el resultado en una parte functionResponse y envíala en el siguiente contenido user, con el mismo name que en la llamada.

Finalización, errores y desconexiones

ProtocoloFin normalConsumo
Chat Completionsdata: [DONE]usage del último chunk
ResponsesEvento response.completedresponse.usage dentro del evento
Messagesmessage_stop después de message_deltausage de message_delta
GeminiÚltimo evento con finishReason; no hay [DONE]usageMetadata

Antes de leer el flujo, comprueba el estado HTTP y el Content-Type; los errores llegan en el JSON propio de cada protocolo:

ProtocoloCuerpo del error
Chat Completions, Responses{"error": {"message", "type", "param", "code"}}
Messages{"type": "error", "error": {"type", "message"}}
Gemini{"error": {"code", "message", "status"}}

Los códigos de estado HTTP son los mismos en los cuatro protocolos. El campo status de Gemini usa los nombres de estado de Google: 400 es INVALID_ARGUMENT, 401 es UNAUTHENTICATED, 402 es FAILED_PRECONDITION, 403 es PERMISSION_DENIED, 404 es NOT_FOUND, 429 es RESOURCE_EXHAUSTED y 503 es UNAVAILABLE. En tu código, decide según el código de estado y el tipo de error; no compares el texto de message.

Si el error se produce con el flujo ya iniciado, Chat y Gemini envían un evento con un objeto error, Responses envía un evento error y Messages envía event: error; el cliente debe manejar estos eventos de error, los timeouts y los cortes prematuros. Desactiva el buffering de respuestas en el proxy. Cerrar el navegador o usar AbortSignal solo detiene la recepción; no equivale a cancelar ni garantiza un reembolso. Si el servidor dispone de un consumo fiable, una desconexión iniciada por el cliente se liquida igualmente según el consumo real ya generado; sin datos de consumo, cerrar la conexión tampoco puede darse por una finalización gratuita.

Cotización y cobro

Para que el usuario confirme antes la cotización exacta y el límite de cargo, puedes usar la vía nativa jobs/quote → jobs/stream con el mismo model/input, además de quoteId y expectedCost. /v1 y /v1beta no aceptan estos datos de cotización nativos y aplican el precio vigente en el momento de aceptar la solicitud. El importe final lo determinan el registro de la tarea y la facturación; la marca de fin del texto no acredita el cobro. reasoning_tokens ya está incluido en completion_tokens y no se suma otra vez.

Otras solicitudes

Estos ejemplos muestran la estructura del protocolo. Sustituye MODEL_ID_FROM_CATALOG por un modelo vigente que admita los campos; no envíes el marcador.

POST /v1/responses
{
  "model": "MODEL_ID_FROM_CATALOG",
  "input": "Explain a rainbow in one sentence.",
  "stream": false
}
POST /v1/messages
{
  "model": "MODEL_ID_FROM_CATALOG",
  "max_tokens": 256,
  "messages": [{"role": "user", "content": "Explain a rainbow in one sentence."}],
  "stream": false
}

Más información

Cotizaciones y compatibilidad · Facturación · Integración en producción · Diagnóstico y recuperación

En esta página