Llamadas a funciones: Responses API frente a Chat Completions
Comparación a nivel de protocolo de las definiciones de funciones, los identificadores de llamada, los resultados, los argumentos en streaming, la autorización, la idempotencia y las rutas.

Responses y Chat Completions pueden pedir a una aplicación que ejecute una función, pero representan el ciclo de herramientas de forma distinta. Chat Completions define funciones bajo tools[].function, devuelve message.tool_calls y recibe resultados como mensajes role: "tool". Responses usa definiciones planas, elementos de salida function_call y resultados function_call_output asociados mediante call_id.
El modelo no ejecuta la función. El código de la aplicación debe validar argumentos, autorizar la operación, ejecutarla, devolver el resultado y evitar efectos secundarios duplicados cuando una solicitud se repite.
El ciclo de herramientas tiene cuatro pasos
1. Declarar una función permitida y su esquema de argumentos
2. Recibir una o más llamadas propuestas por el modelo
3. Validar, autorizar y ejecutar cada llamada en la aplicación
4. Devolver cada resultado con el identificador de correlación de la llamada
Solo después del cuarto paso el modelo puede producir una respuesta basada en el resultado. Si solicita otra herramienta, el ciclo continúa con un nuevo ID de llamada. La guía de Function Calling de OpenAI lo define como intercambio de varios pasos; tratar la primera llamada a una herramienta como respuesta final es un error común.
Comparar los contratos de transporte
| Aspecto | Responses API | Chat Completions |
|---|---|---|
| Definición | Elemento plano en tools[] |
Campos dentro de tools[].function |
| Llamada propuesta | Elemento de salida function_call |
tool_calls[] del mensaje del asistente |
| Correlación | call_id |
id, devuelto como tool_call_id |
| Nombre | function_call.name |
tool_calls[].function.name |
| Argumentos | Cadena en function_call.arguments |
Cadena en tool_calls[].function.arguments |
| Resultado | Elemento de entrada function_call_output |
Mensaje role: "tool" |
| Transmisión | Eventos de argumentos tipados | Fragmentos delta.tool_calls[] |
| Texto final | Elementos de salida / función auxiliar | choices[0].message.content |
No relaciones llamadas por posición en la matriz. Las llamadas paralelas y los fragmentos pueden completarse en otro orden. El ID explícito es la clave estable.
Definir una función con un esquema estricto
Esta función de solo lectura consulta un envío:
{
"type": "object",
"properties": {
"order_id": {
"type": "string",
"pattern": "^ORDER-[0-9]{4}$"
}
},
"required": ["order_id"],
"additionalProperties": false
}
strict: true pide al modelo compatible que siga el esquema, pero la aplicación debe analizar y validar los argumentos. La compatibilidad con pattern y otras palabras clave varía, por lo que también debe superarse la prueba de compatibilidad de Structured Outputs.
Implementar el ciclo con Responses API
import OpenAI from "openai";
const client = new OpenAI({
apiKey: process.env.MODELFLARE_API_KEY,
baseURL: "https://modelflare.dev/v1",
});
const model = process.env.MODEL_ID;
if (!model) throw new Error("MODEL_ID es obligatorio");
const orders = new Map([
["ORDER-1001", { status: "in_transit", eta: "2026-08-06" }],
]);
const tools = [
{
type: "function",
name: "get_delivery_status",
description: "Devuelve el estado actual de entrega de un pedido.",
parameters: {
type: "object",
properties: {
order_id: { type: "string", pattern: "^ORDER-[0-9]{4}$" },
},
required: ["order_id"],
additionalProperties: false,
},
strict: true,
},
];
function executeTool(name, rawArguments) {
if (name !== "get_delivery_status") {
throw new Error(`Herramienta no permitida: ${name}`);
}
const args = JSON.parse(rawArguments);
if (!/^ORDER-[0-9]{4}$/.test(args.order_id)) {
throw new Error("order_id no es válido");
}
return orders.get(args.order_id) ?? { status: "not_found" };
}
const first = await client.responses.create({
model,
input: "¿Dónde está el pedido ORDER-1001?",
tools,
parallel_tool_calls: false,
});
const calls = first.output.filter((item) => item.type === "function_call");
if (calls.length === 0) {
console.log(first.output_text);
process.exit(0);
}
const toolOutputs = calls.map((call) => ({
type: "function_call_output",
call_id: call.call_id,
output: JSON.stringify(executeTool(call.name, call.arguments)),
}));
const final = await client.responses.create({
model,
input: [...first.output, ...toolOutputs],
tools,
parallel_tool_calls: false,
});
console.log(final.output_text);
El call.call_id de la salida debe coincidir con el call_id del resultado. En un intercambio sin estado se devuelven los elementos de salida anteriores junto a los resultados. No asumas que cada ruta compatible con OpenAI almacena el estado necesario para continuar una conversación con estado.
Implementar el ciclo con Chat Completions
import OpenAI from "openai";
const client = new OpenAI({
apiKey: process.env.MODELFLARE_API_KEY,
baseURL: "https://modelflare.dev/v1",
});
const model = process.env.MODEL_ID;
if (!model) throw new Error("MODEL_ID es obligatorio");
const orders = new Map([
["ORDER-1001", { status: "in_transit", eta: "2026-08-06" }],
]);
const tools = [
{
type: "function",
function: {
name: "get_delivery_status",
description: "Devuelve el estado actual de entrega de un pedido.",
parameters: {
type: "object",
properties: {
order_id: { type: "string", pattern: "^ORDER-[0-9]{4}$" },
},
required: ["order_id"],
additionalProperties: false,
},
strict: true,
},
},
];
function executeTool(name, rawArguments) {
if (name !== "get_delivery_status") {
throw new Error(`Herramienta no permitida: ${name}`);
}
const args = JSON.parse(rawArguments);
if (!/^ORDER-[0-9]{4}$/.test(args.order_id)) {
throw new Error("order_id no es válido");
}
return orders.get(args.order_id) ?? { status: "not_found" };
}
const messages = [
{ role: "user", content: "¿Dónde está el pedido ORDER-1001?" },
];
const first = await client.chat.completions.create({
model,
messages,
tools,
parallel_tool_calls: false,
});
const assistant = first.choices[0].message;
const calls = assistant.tool_calls ?? [];
if (calls.length === 0) {
console.log(assistant.content);
process.exit(0);
}
messages.push(assistant);
for (const call of calls) {
const result = executeTool(call.function.name, call.function.arguments);
messages.push({
role: "tool",
tool_call_id: call.id,
content: JSON.stringify(result),
});
}
const final = await client.chat.completions.create({
model,
messages,
tools,
parallel_tool_calls: false,
});
console.log(final.choices[0].message.content);
El mensaje del asistente que contiene las llamadas debe añadirse antes de los resultados. Omitirlo o usar un tool_call_id sin pareja crea un historial inválido.
Tratar los argumentos transmitidos como fragmentos
Un fragmento {"order no es JSON inválido, sino incompleto. Mantén un búfer por ID de llamada, añade cada incremento, espera la señal de argumentos completos o de respuesta finalizada, analiza una sola vez y ejecuta después de validar. Responses usa eventos tipados; Chat Completions usa choices[].delta.tool_calls[], donde el índice identifica la entrada en curso. Nunca ejecutes al recibir el primer fragmento. Consulta la guía de transmisión de AI API.
Hacer la ejecución segura e idempotente
- aceptar solo nombres registrados;
- limitar el tamaño y validar el esquema completo;
- autorizar al usuario o carga de trabajo para el recurso;
- separar herramientas de lectura y efectos secundarios;
- eliminar secretos del resultado enviado al modelo;
- establecer plazos y limitar los reintentos hacia servicios dependientes;
- guardar una referencia segura de la operación.
Para efectos secundarios, deriva una clave de idempotencia de la solicitud estable y del ID de llamada. Guarda el resultado antes de devolverlo. Si la red repite la llamada, devuelve el resultado guardado en vez de emitir otro reembolso, mensaje, despliegue o cambio de base de datos.
El nombre y los argumentos generados por el modelo son entradas no fiables. Un esquema válido no concede permisos ni sustituye una transacción.
Decidir cómo tratar múltiples llamadas
Con parallel_tool_calls: false la máquina de estados es más fácil de inspeccionar. Si se habilita el paralelismo:
- relaciona por ID de llamada, nunca por orden de finalización;
- limita las llamadas por respuesta y la concurrencia de la aplicación;
- define el efecto de una herramienta fallida sobre las demás;
- devuelve un resultado por cada llamada aceptada;
- serializa efectos secundarios cuyo orden importe.
El paralelismo reduce la latencia de las lecturas independientes, pero aumenta la complejidad de la autorización, el orden, los reintentos y los fallos parciales.
Comprender el límite de compatibilidad de Modelflare
Modelflare conserva definiciones estrictas y establece correspondencias entre las formas compatibles de Responses y Chat Completions en las rutas OpenAI/Codex aptas. Esta correspondencia cubre deliberadamente menos que el conjunto completo de herramientas de Responses.
Las funciones definidas por la aplicación pueden expresarse en ambos formatos. Las herramientas alojadas que ejecuta el proveedor —búsqueda, ejecución de código u otros tipos— no equivalen a Function Calling de la aplicación y no se deben convertir por suposición. Otras familias compatibles con OpenAI usan Chat Completions sin transformar hasta que se verifiquen; el contrato del proveedor determina la compatibilidad con tools, strict, llamadas paralelas, argumentos transmitidos y tool_choice.
| Prueba | Evidencia necesaria |
|---|---|
| Llamada de lectura | Nombre, argumentos, identificador de llamada, resultado y texto final |
| Argumentos inválidos | Fallo explícito sin ejecución |
| Función desconocida | Rechazo por la lista de permitidos |
| Sin herramienta | Texto normal sin llamada inventada |
| Transmisión | Argumentos reconstruidos e ID correcto |
| Solicitud repetida | Un efecto o un resultado almacenado |
| Varias llamadas | Correlación independiente del orden |
| Ruta de respaldo | Mismo modelo, protocolo, esquema y contrato |
Usa Enrutamiento fiable de AI API para cambios explícitos de ruta y seguridad de las claves de AI API para aislar permisos.
Elegir formato según el contrato de la aplicación
Usa Responses para elementos de salida tipados, su modelo de eventos, continuación de estado u otras funciones verificadas. Usa Chat Completions cuando la aplicación mantiene un historial estable y el contrato de herramientas del proveedor está comprobado. Consulta Responses API y Chat Completions.
El estándar es el mismo: lista de permitidos explícita, esquema estricto, validación en la aplicación, autorización antes de ejecutar, relación por ID de llamada, idempotencia de los efectos y resultado completo para el modelo. Esos controles, no el nombre del endpoint, hacen fiable el flujo de trabajo.