DEV Community

Cover image for OpenAI Responses API: function calling fiable, estado y trabajos en segundo plano
Khavel
Khavel

Posted on Originally published at devaisemanal.com

OpenAI Responses API: function calling fiable, estado y trabajos en segundo plano

Responses API no convierte una función en fiable por sí sola. Esta guía muestra el bucle correcto de tool calls, validación, estado y trabajos largos para que un agente no confunda una respuesta convincente con una acción segura.

OpenAI Responses API es la interfaz unificada para generar respuestas, usar herramientas y conservar estado entre turnos. Un tool call no es una orden que el servidor deba obedecer: es una propuesta del modelo que tu backend debe autorizar, validar, ejecutar de forma idempotente y devolver al modelo como function_call_output.

TL;DR

La keyword principal es OpenAI Responses API. La intención es técnica: un developer que ya puede hacer una llamada básica necesita montar un flujo fiable con function calling, JSON estructurado, streaming, estado conversacional y trabajos que no caben en una petición HTTP corta.

Mi postura: empieza con Responses API antes de introducir una capa de agentes. Es un contrato explícito que te obliga a entender input, output, tools y estado. Un SDK de agentes puede ahorrar orquestación después; no debería ocultar permisos, validación ni efectos externos.

Qué es Responses API y qué no resuelve

Responses API crea un objeto response a partir de un modelo, una entrada y, opcionalmente, herramientas. La salida no tiene por qué ser texto: puede contener mensajes, llamadas de función, resultados de herramientas alojadas, elementos de razonamiento y eventos de streaming. Leer solo output_text es correcto para un chat simple, pero insuficiente para un flujo que actúa sobre sistemas reales.

La API puede encadenar contexto con previous_response_id o con Conversations. Eso evita reenviar un historial manual enorme, pero no sustituye tu modelo de negocio: tú decides qué conversación pertenece a qué usuario, cuánto vive, qué datos se permiten y cuándo hay que resumir o borrar estado.

Tampoco decide si una llamada es segura. El modelo puede proponer create_invoice, send_email o deploy. Tu aplicación sigue siendo el control de autoridad: autentica al usuario, limita el recurso, valida argumentos, exige aprobación cuando corresponde y registra el efecto final.

Diagrama conceptual de un usuario que envía una petición al orquestador de Responses API; el flujo se divide entre una tool validada, salida JSON estructurada y un trabajo asíncrono, con una barrera de autorización, registro de auditoría y estado separado

El modelo propone pasos; el backend conserva la autoridad. Estado, validación, colas y auditoría son piezas distintas del texto generado.

El bucle correcto de function calling

Function calling tiene cuatro etapas: declaras una tool con un schema; el modelo emite uno o más elementos function_call; tu servidor valida y ejecuta solo los que autoriza; devuelves un function_call_output con el call_id original y pides la siguiente respuesta. Si falta el último paso, el modelo no ve el resultado real de la acción y tenderá a completar la conversación con una suposición.

No ejecutes argumentos directamente con json.loads y una llamada a tu SDK interno. El schema reduce salidas mal formadas, pero no prueba que el usuario tenga acceso a project_id, que una fecha exista ni que la acción sea razonable. Valida tipos, rangos, pertenencia al tenant y política de negocio fuera del modelo.

Para una operación con efecto, asocia una clave idempotente a la intención de negocio, no al texto del modelo. Un retry HTTP, una reconexión de streaming o una segunda respuesta no debe enviar dos emails o crear dos facturas. Guarda call_id, usuario, recurso, hash del payload y resultado de la ejecución.

Código: tool estrecha y resultado verificable en Python

Este ejemplo ilustra el bucle. La tool es deliberadamente de lectura y el resultado vuelve como datos, no como instrucciones. En producción, get_release debería imponer autorización y recuperar solo los campos permitidos para el usuario autenticado.

responses_tools.py

import json
from openai import OpenAI

client = OpenAI()
TOOLS = [{
    "type": "function",
    "name": "get_release",
    "description": "Returns approved release metadata for one repository.",
    "parameters": {
        "type": "object",
        "properties": {"repo": {"type": "string", "minLength": 1}},
        "required": ["repo"],
        "additionalProperties": False,
    },
    "strict": True,
}]

def get_release_for_user(user_id: str, repo: str) -> dict:
    assert repo in allowed_repos_for(user_id)  # authz, not a model prompt
    return read_release_metadata(repo)

response = client.responses.create(
    model="gpt-5",
    input="¿Cuál es el último release de api-gateway?",
    tools=TOOLS,
)

tool_outputs = []
for item in response.output:
    if item.type == "function_call" and item.name == "get_release":
        args = json.loads(item.arguments)
        result = get_release_for_user(current_user.id, args["repo"])
        tool_outputs.append({
            "type": "function_call_output",
            "call_id": item.call_id,
            "output": json.dumps(result),
        })

final = client.responses.create(
    model="gpt-5",
    previous_response_id=response.id,
    input=tool_outputs,
)
print(final.output_text)
Enter fullscreen mode Exit fullscreen mode

El detalle importante no es el nombre de la función: es que allowed_repos_for vive en tu backend. Si el modelo propone otro repo, la autorización falla antes de tocar la fuente de datos. Devuelve un error de dominio breve y deja que el modelo explique el límite al usuario, en vez de darle una excepción cruda o inventar una respuesta.

Structured Outputs: contrato de interfaz, no control de seguridad

¿Te está sirviendo? Hay una dosis cada semana

Te resumo herramientas de IA para devs, agentes, MCP, seguridad y workflows en un email de 5 minutos. En español y sin ruido.

Suscribirme gratis

Cuando necesitas una salida que otro sistema consuma, usa Structured Outputs con JSON Schema estricto. En Responses API el formato se configura dentro de text.format; para tools, define strict: true y limita propiedades. Eso hace que el contrato sea más predecible que pedir «devuelve JSON válido» en un prompt.

Lo que conviene comprobar

Un schema debe representar una decisión pequeña y verificable. Para triage, por ejemplo: categoría de una allowlist, confianza acotada, evidencia citada y needs_human_review. Evita un objeto genérico tipo action: string que luego se convierte en una puerta trasera de comandos para cualquier integración.

Trata cualquier campo generado como entrada no confiable al cruzar una frontera. strict evita muchas formas inválidas; no sustituye escape HTML, validación de URLs, autorización, control de concurrencia, límites de tamaño ni saneamiento para SQL o shell. Un JSON impecable puede describir una acción equivocada.

Estado: previous_response_id frente a Conversations

previous_response_id es útil para enlazar el siguiente turno al anterior con una relación explícita. Es cómodo en una conversación corta o en un workflow donde tu base de datos guarda el último response ID por sesión. Las instrucciones de una llamada anterior no se arrastran automáticamente si pasas instrucciones nuevas: verifica ese comportamiento antes de asumir que una política quedó vigente.

Conversations es una entidad de estado reutilizable para añadir y recuperar ítems entre respuestas. Encaja cuando necesitas una conversación estable que pueda sobrevivir a distintos dispositivos o workers. Aun así, no conviertas la Conversation en tu única fuente de verdad: conserva en tu base la identidad del usuario, el tenant, el estado de aprobación y referencias de auditoría.

Mi regla: guarda solo IDs y contexto mínimo de producto; vuelve a resolver permisos, herramientas permitidas y policy en cada petición. El estado puede recordar la conversación, pero no debe heredar autoridad. Un usuario que pierde acceso a un proyecto no debería mantenerlo porque una conversación vieja lo mencionaba.

Streaming y background mode son flujos distintos

Streaming usa eventos server-sent para pintar progreso o texto parcial con baja latencia. Es una decisión de experiencia de usuario; no conviertas cada delta en un registro de negocio ni ejecutes una tool al primer fragmento. Espera al elemento de function call completo y conserva una ruta clara de cancelación del cliente.

Background mode sirve para respuestas largas que deben continuar aunque la petición web se corte. Creas la respuesta con background=true, persistes su ID y consultas su estado o recibes el evento webhook correspondiente. El frontend no debería mantener una conexión abierta durante minutos solo para fingir que un job asíncrono es streaming.

La consecuencia operativa importa: background mode conserva datos para poder hacer polling y no es compatible con Zero Data Retention. Revisa data controls, retención y la región de datos de tu organización antes de activarlo en flujos con información sensible. Para una tarea larga sin datos que deban salir, una cola propia y una llamada normal puede ser una alternativa más controlable.

Herramientas alojadas, MCP y límites de datos

Responses API puede combinar funciones de tu aplicación con herramientas alojadas, como web search, file search, code interpreter o image generation, según modelo y disponibilidad. Cada una introduce otra frontera: cuota, tiempo, datos enviados y resultados que pueden estar equivocados o contener instrucciones externas.

Los servidores MCP remotos son servicios de terceros. No les pases un token o documento solo porque una tool description sea atractiva. Delimita por servidor la URL, identidad, scopes, datos que puede recibir, rate limits y la aprobación para efectos externos. MCP conecta capacidades; no valida automáticamente su confianza.

Usa allowed_tools o un conjunto de tools por tarea cuando sea posible. Un agente de triage no necesita la misma superficie que uno de release. Reducir opciones también mejora la calidad: al modelo le cuesta menos elegir una tool cuando no le ofreces quince acciones parecidas con permisos distintos.

Arquitectura mínima que llevaría a producción

Entrada autenticada → policy de tenant → creación de response → parser de output → validador de schema y negocio → executor idempotente → auditoría → function_call_output → respuesta final. Si hay una acción sensible, añade una transición explícita de propuesta a aprobación: el modelo prepara payload y evidencia; una persona o regla independiente habilita la mutación.

Mantén los executors fuera del prompt. Una tool debería ser una función estrecha, con nombre que explique el efecto, parámetros mínimos y un resultado redactado. update_customer es demasiado grande; propose_customer_address_change y apply_approved_address_change dejan una frontera revisable.

Mide más que éxito HTTP: porcentaje de tool calls válidas, denegadas por policy, reintentos idempotentes, aprobaciones, errores por tipo, latencia p50/p95, coste por workflow y tareas resueltas sin escalado. Una respuesta fluida puede ocultar que el modelo llama tres veces a una API o que el 20% de acciones queda bloqueado al final.

Checklist antes de habilitar una tool con efecto

La tool expresa una única capacidad y no acepta campos libres que acaben en SQL, shell o URLs arbitrarias.

El backend autentica al usuario y comprueba autorización por tenant, recurso y operación; el modelo no decide permisos.

Los argumentos pasan JSON Schema y validación de negocio antes de llegar a un executor.

Las mutaciones tienen una clave idempotente y un registro de resultado por operación de negocio.

Las acciones externas o irreversibles muestran destino, payload, evidencia y riesgo antes de la aprobación.

Las tools disponibles se reducen por tarea y se revisan al cambiar de modelo, prompt o integración.

El estado conversacional no concede permisos persistentes y tiene una política de retención explícita.

Streaming, background jobs y webhooks tienen timeouts, cancelación, reintentos y observabilidad propios.

Hay evals con entradas ambiguas, argumentos inválidos, recursos de otro tenant y prompt injection indirecta.

Conclusión

Responses API es una buena base cuando quieres control fino: te enseña exactamente cuándo el modelo habló, cuándo pidió una tool y cuándo tu sistema produjo un resultado verificable. Esa claridad vale más que una demo de agente que parece autónoma hasta que intenta escribir en producción.

Empieza por una tool de lectura, un schema pequeño y una traza completa. Añade estado cuando haya una razón de producto, y background mode cuando el trabajo de verdad sea largo. La autonomía útil no consiste en dar más funciones al modelo: consiste en hacer que cada capacidad tenga una frontera, una evidencia y una forma segura de fallar.

Preguntas frecuentes

¿Qué es OpenAI Responses API?

Es la API unificada de OpenAI para crear respuestas con input multimodal, herramientas, streaming y estado conversacional. La salida puede incluir texto y elementos de tool calling, no solo una cadena.

¿Responses API sustituye a OpenAI Agents SDK?

No necesariamente. Responses API ofrece el contrato de bajo nivel; Agents SDK puede ayudar a orquestar agentes. Si necesitas permisos y efectos controlados, debes implementar validación y autorización en cualquiera de las dos capas.

¿Function calling ejecuta mi función automáticamente?

No. El modelo devuelve una propuesta de llamada; tu aplicación interpreta el output, valida argumentos y permisos, ejecuta si procede y devuelve un function_call_output.

¿Para qué sirve previous_response_id?

Enlaza una respuesta nueva con el contexto de la respuesta anterior. Es útil para turnos cortos, pero no sustituye una política de identidad, autorización o retención de datos.

¿Cuándo uso background mode?

Cuando una respuesta puede durar más que la petición HTTP normal y quieres consultar su estado o recibir un webhook. Revisa antes su efecto en retención de datos y compatibilidad con Zero Data Retention.

¿Structured Outputs hace segura una acción?

No. Hace más predecible el formato. Todavía debes validar negocio, scopes, tenant, recursos, límites, idempotencia y aprobación humana cuando exista efecto externo.

Cómo llevar una tool de Responses API de demo a producción

  1. Elegir una capacidad de lectura. Empieza por una consulta reversible con un recurso claro, como recuperar metadata de un release aprobado.
  2. Diseñar el schema. Declara campos mínimos, tipos, allowlists y additionalProperties: false; activa modo estricto cuando sea compatible.
  3. Separar autorización. Resuelve usuario, tenant, scopes y recurso en el backend antes de llamar a la fuente de datos.
  4. Crear el primer response. Envía el input y solo las tools necesarias para esa tarea; registra el response ID y la versión de policy.
  5. Interpretar function calls. Procesa únicamente elementos completos de tipo function call; no ejecutes texto libre ni deltas de streaming.
  6. Validar y ejecutar. Comprueba schema y reglas de negocio, aplica rate limits e idempotencia y captura un resultado redactado.
  7. Devolver function_call_output. Usa el call ID original y datos estructurados para que el siguiente response pueda explicar el resultado real.
  8. Añadir aprobación. Separa propuesta y mutación cuando la acción escriba, envíe, despliegue o transfiera información.
  9. Preparar fallos. Define errores de autorización, validación, proveedor y timeout; cada uno debe tener una recuperación distinta.
  10. Medir y evaluar. Prueba tenants cruzados, argumentos hostiles y retries; mide tools inválidas, bloqueos, coste, latencia y resolución.

Fuentes y referencias

También te puede interesar

Recibe una lectura semanal de herramientas IA para devs

Cada semana te resumo herramientas de IA para devs, agentes, MCP, seguridad y workflows en un email de 5 minutos. En español y sin ruido.

Suscribirme gratis

Top comments (0)