Introducción a la API de OpenAI: cómo escribir Chat Completions, llamadas a funciones y salida en streaming

Guía básica de APIs Chat Completions compatibles con OpenAI: cómo configurar BASE_URL y OPENAI_API_KEY, organizar messages, tools, tool_choice y stream, y entender respuestas normales y en streaming.

Muchos servicios de modelos grandes ofrecen APIs “compatibles con OpenAI”. Al integrarlos, lo primero que debes confirmar son dos cosas:

  1. BASE_URL: la dirección de API proporcionada por el servicio.
  2. OPENAI_API_KEY: tu clave de acceso.

Si el proveedor es compatible con la API Chat Completions, la forma de la solicitud suele parecerse a /v1/chat/completions de OpenAI. Aun así, OpenAI también está impulsando la Responses API. Chat Completions sigue siendo común, especialmente en APIs compatibles de terceros, pero si estás creando un proyecto nuevo directamente sobre capacidades oficiales de OpenAI, también conviene prestar atención a la Responses API.

Referencias oficiales:

Ejemplo mínimo de solicitud

Una solicitud mínima de Chat Completions se ve aproximadamente así:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
curl https://api.openai.com/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $OPENAI_API_KEY" \
  -d '{
    "model": "gpt-4o-mini",
    "messages": [
      {
        "role": "system",
        "content": "You are a helpful assistant."
      },
      {
        "role": "user",
        "content": "Hello!"
      }
    ]
  }'

Si usas un servicio de terceros compatible con OpenAI, normalmente solo necesitas reemplazar la primera línea por el BASE_URL del proveedor:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
curl $BASE_URL/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $OPENAI_API_KEY" \
  -d '{
    "model": "your-model-id",
    "messages": [
      {
        "role": "user",
        "content": "Hello!"
      }
    ]
  }'

Los tres errores más comunes son:

  • añadir o quitar por error /v1 en BASE_URL;
  • olvidar que Authorization debe usar Bearer;
  • usar un valor de model que el proveedor no soporta realmente.

Los campos más importantes del cuerpo de la solicitud

El cuerpo de una solicitud Chat Completions es un objeto JSON. Los campos más básicos y frecuentes son estos.

model

model es obligatorio e indica el ID del modelo que se va a llamar.

1
2
3
{
  "model": "gpt-4o-mini"
}

En servicios de terceros compatibles con OpenAI, el ID del modelo no siempre será gpt-4o-mini. Algunos proveedores usan nombres propios, como deepseek-chat, qwen-plus o llama-3.1-70b. Debes seguir la documentación o la lista de modelos del proveedor.

messages

messages es obligatorio y representa el historial de conversación de principio a fin. Es un arreglo, y cada elemento es un mensaje.

Los roles comunes son:

  • system: instrucciones del sistema que definen el comportamiento del asistente;
  • user: mensajes del usuario;
  • assistant: respuestas previas del modelo;
  • tool: resultados de ejecución de herramientas.

Una conversación simple de varios turnos puede escribirse así:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
{
  "model": "gpt-4o-mini",
  "messages": [
    {
      "role": "system",
      "content": "You are a concise technical assistant."
    },
    {
      "role": "user",
      "content": "什么是 API?"
    },
    {
      "role": "assistant",
      "content": "API 是应用程序之间约定好的调用接口。"
    },
    {
      "role": "user",
      "content": "用一句话解释给非技术人员听。"
    }
  ]
}

system message

Un mensaje system suele colocarse al principio y le indica al modelo qué rol debe asumir y qué reglas debe seguir.

1
2
3
4
{
  "role": "system",
  "content": "You are a helpful assistant."
}

Campos comunes:

  • role: fijo como system;
  • content: contenido del prompt del sistema;
  • name: opcional, para etiquetar al participante.

No todos los servicios compatibles implementan el comportamiento de system exactamente igual. Si el prompt del sistema parece no funcionar, revisa primero si el proveedor modifica las reglas de roles.

user message

Un mensaje user representa la entrada del usuario. Lo más común es texto plano:

1
2
3
4
{
  "role": "user",
  "content": "Hello!"
}

En modelos que soportan entrada multimodal, content también puede ser un arreglo con texto e imágenes:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
curl https://api.openai.com/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $OPENAI_API_KEY" \
  -d '{
    "model": "gpt-4o",
    "messages": [
      {
        "role": "user",
        "content": [
          {
            "type": "text",
            "text": "What is in this image?"
          },
          {
            "type": "image_url",
            "image_url": {
              "url": "https://upload.wikimedia.org/wikipedia/commons/thumb/d/dd/Gfp-wisconsin-madison-the-nature-boardwalk.jpg/2560px-Gfp-wisconsin-madison-the-nature-boardwalk.jpg"
            }
          }
        ]
      }
    ],
    "max_completion_tokens": 300
  }'

Que una API compatible soporte imágenes depende del modelo concreto. No asumas soporte multimodal solo porque la ruta del endpoint sea compatible con OpenAI.

assistant message

Un mensaje assistant suele aparecer en el historial de varios turnos y representa lo que el modelo dijo antes.

1
2
3
4
{
  "role": "assistant",
  "content": "Hello, how can I help you today?"
}

Cuando el modelo decide llamar a una herramienta, el mensaje assistant puede no contener texto normal y traer tool_calls en su lugar.

tool message

Un mensaje tool se usa para devolver al modelo el resultado de una herramienta. Debe corresponder a un tool_calls.id del mensaje assistant anterior.

1
2
3
4
5
{
  "role": "tool",
  "tool_call_id": "call_abc123",
  "content": "{\"temperature\": 22, \"unit\": \"celsius\"}"
}

Esto es habitual en flujos de Agent: el modelo decide primero qué función llamar, la aplicación ejecuta realmente esa función, y luego envía el resultado como mensaje tool para que el modelo genere la respuesta final.

tools y tool_choice

tools le dice al modelo qué herramientas puede llamar. El tipo más común es una herramienta de función.

Este es un ejemplo para consultar el clima:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
curl https://api.openai.com/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $OPENAI_API_KEY" \
  -d '{
    "model": "gpt-4o-mini",
    "messages": [
      {
        "role": "user",
        "content": "What is the weather like in Boston today?"
      }
    ],
    "tools": [
      {
        "type": "function",
        "function": {
          "name": "get_current_weather",
          "description": "Get the current weather in a given location",
          "parameters": {
            "type": "object",
            "properties": {
              "location": {
                "type": "string",
                "description": "The city and state, e.g. San Francisco, CA"
              },
              "unit": {
                "type": "string",
                "enum": ["celsius", "fahrenheit"]
              }
            },
            "required": ["location"]
          }
        }
      }
    ],
    "tool_choice": "auto"
  }'

Si el modelo considera que necesita una herramienta, la respuesta puede contener algo así:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
{
  "choices": [
    {
      "index": 0,
      "message": {
        "role": "assistant",
        "content": null,
        "tool_calls": [
          {
            "id": "call_abc123",
            "type": "function",
            "function": {
              "name": "get_current_weather",
              "arguments": "{\"location\":\"Boston, MA\"}"
            }
          }
        ]
      },
      "finish_reason": "tool_calls"
    }
  ]
}

Aquí hay dos detalles importantes:

  1. El modelo solo está sugiriendo llamar a una función. No ejecuta realmente la función por tu programa.
  2. arguments es una cadena JSON generada por el modelo. Debes validarla antes de usarla.

tool_choice controla si el modelo llama herramientas:

  • "none": no llama herramientas y solo genera texto;
  • "auto": el modelo decide entre generar texto o llamar herramientas;
  • "required": obliga al modelo a llamar una o más herramientas;
  • especificar una función: fuerza la llamada a una herramienta concreta.

Los campos antiguos functions y function_call fueron reemplazados por tools y tool_choice. En proyectos heredados todavía pueden aparecer, pero en código nuevo conviene usar la forma nueva.

stream salida en streaming

stream es un booleano opcional. Al establecerlo en true, la API devuelve contenido por partes mediante SSE, algo útil para interfaces de chat en tiempo real.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
{
  "model": "gpt-4o-mini",
  "messages": [
    {
      "role": "user",
      "content": "写一句欢迎语"
    }
  ],
  "stream": true
}

Una respuesta sin streaming espera a que el modelo termine y devuelve todo de una vez. Una respuesta en streaming devuelve chunks continuamente. Un fragmento típico se parece a esto:

1
2
3
4
5
data: {"id":"chatcmpl-123","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"role":"assistant"},"finish_reason":null}]}

data: {"id":"chatcmpl-123","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":"Hello"},"finish_reason":null}]}

data: [DONE]

El frontend o backend debe leer continuamente las líneas data:, concatenar cada delta.content y detenerse al recibir [DONE].

Cómo leer una respuesta sin streaming

Una respuesta Chat Completions sin streaming suele contener campos como estos:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
{
  "id": "chatcmpl-123",
  "object": "chat.completion",
  "created": 1677652288,
  "model": "gpt-4o-mini",
  "choices": [
    {
      "index": 0,
      "message": {
        "role": "assistant",
        "content": "Hello there, how may I assist you today?"
      },
      "finish_reason": "stop"
    }
  ],
  "usage": {
    "prompt_tokens": 9,
    "completion_tokens": 12,
    "total_tokens": 21
  }
}

Lo más usado es:

  • choices[0].message.content: respuesta final del modelo;
  • choices[0].message.tool_calls: herramientas que el modelo quiere llamar;
  • choices[0].finish_reason: motivo de parada;
  • usage: uso de tokens.

Valores comunes de finish_reason:

  • stop: parada natural;
  • length: se alcanzó el límite de tokens;
  • tool_calls: el modelo solicitó llamar una herramienta;
  • content_filter: el contenido fue filtrado.

Lista de comprobación para APIs compatibles

Al integrar un servicio de terceros compatible con OpenAI, no basta con mirar que la ruta del endpoint sea igual. Conviene revisar:

  1. Si BASE_URL incluye /v1.
  2. Si OPENAI_API_KEY corresponde realmente al proveedor actual.
  3. Si model es un ID de modelo válido.
  4. Si soporta mensajes system.
  5. Si soporta entrada multimodal image_url.
  6. Si soporta tools y tool_choice.
  7. Si soporta stream: true.
  8. Si el campo de límite de tokens es max_completion_tokens o el antiguo max_tokens.
  9. Si el formato de errores coincide exactamente con OpenAI.
  10. Si hay límites adicionales de velocidad, concurrencia o región.

“Compatible con OpenAI” normalmente significa que la forma de llamada es similar. No significa que todas las capacidades del modelo, semánticas de campos y formatos de error sean idénticos.

Conclusión

Si solo quieres hacer chat básico, para Chat Completions los campos centrales son prácticamente tres:

1
2
3
4
5
{
  "model": "your-model-id",
  "messages": [],
  "stream": false
}

Si quieres construir un Agent o automatización de negocio, necesitas entender además:

  • tools: decirle al modelo qué funciones puede usar;
  • tool_choice: controlar si el modelo llama herramientas;
  • tool_calls: leer qué quiere llamar el modelo;
  • tool message: devolver al modelo los resultados reales de herramientas;
  • stream: convertir la respuesta en salida en tiempo real.

Para APIs compatibles con OpenAI, el camino más seguro es: primero hacer funcionar la solicitud mínima de texto, luego añadir streaming, y por último conectar llamadas a herramientas. Cada vez que agregues una capacidad, pruébala con el modelo real y el proveedor real. No infieras compatibilidad solo por el nombre de los campos.

记录并分享
Creado con Hugo
Tema Stack diseñado por Jimmy