Comprensión de elementos versus mensajes en la API de respuestas de OpenAI
Explica cómo la API Responses de OpenAI reorganiza las salidas del modelo en elementos en lugar de mensajes, y por qué ese cambio es importante para la llamada a herramientas y los flujos de trabajo basados en agentes.
Completaciones de chat: basadas en mensajes
Las completaciones de chat han sido durante mucho tiempo el formato preferido para interactuar con los LLM. Una llamada típica se ve así:
response = client.chat.completions.create(
model="...",
messages=[
{
"role": "user",
"content": "Explain RAG"
}
]
)
Envías una lista de mensajes, y el modelo devuelve el siguiente mensaje del asistente. Es bastante sencillo. Por otro lado, la API de respuestas tiene un aspecto ligeramente diferente desde el principio.
response = client.responses.create(
model="...",
input="Explain RAG"
)
A primera vista, podría parecer simplemente un endpoint diferente donde input reemplaza a messages. Pero la verdadera diferencia radica en la abstracción subyacente que utiliza cada API. Las completaciones de chat giran en torno a mensajes, mientras que la API de respuestas está organizada en torno a elementos y respuestas. Una vez que se incluyen herramientas y agentes, esa diferencia comienza a ser mucho más importante.
Una conversación en Chat Completions es simplemente un array de objetos de mensaje.
messages = [
{
"role": "system",
"content": "Act as a helpful AI assistant."
},
{
"role": "user",
"content": "What is a vector database?"
}
]
Cada mensaje lleva un rol y algún contenido.
Los roles típicos que verás son:
systemuserassistant
Puedes imaginar el flujo de la siguiente manera:
Messages (list)-> Model -> Assistant Message
El modelo recibe la conversación hasta el momento y genera el siguiente mensaje del asistente. Un bucle de conversación mínimo se ve así:
# Human message appended to the messages list
messages.append({
"role": "user",
"content": user_input
})
response = client.chat.completions.create(
model="...",
messages=messages
)
# AI message appended to the messages list
messages.append(
response.choices[0].message
)
Tu aplicación es responsable de mantener un registro del historial de mensajes, ya sea en memoria, en una base de datos o como elijas. Cada nuevo mensaje del usuario se agrega a esa lista, toda la lista se envía al modelo y, a su vez, la respuesta del modelo se agrega de nuevo. Este patrón se ajusta naturalmente a cómo funcionan las interfaces de chat.
User Message (str)->
Message History (list[dict])->
Model (llm)->
Assistant Message (str)->
Message History (list[dict])
Para la generación de texto plano y casos de uso típicos de estilo chat, esta configuración es perfectamente adecuada. Pero sus limitaciones comienzan a hacerse notar una vez que una aplicación impulsada por un LLM necesita hacer algo más que simplemente conversar.
Aplicaciones LLM = No solo aplicaciones de chat
Tomemos una solicitud como esta:
Busca los avances recientes en IA, elabora un resumen de los más importantes y envía ese resumen a mi bandeja de entrada.
Para satisfacer esa solicitud, el modelo debe realizar una búsqueda en la web e integrarse con un servicio de correo electrónico.
De repente, el flujo de ejecución implica algo más que un simple intercambio de mensaje del usuario y respuesta del asistente.
User Request ->
Model ->
Web Search ->
Search Results ->
Summarize (Model)->
Send Mail ->
Final Response
Las aplicaciones más complejas pueden depender de varios herramientas diferentes conectadas entre sí.
En este punto, el modelo es un participante activo en una tubería de ejecución más amplia, y no solo un generador de texto. Parte de lo que genera nunca está destinado a llegar al usuario final; existe únicamente para el procesamiento interno. El modelo podría invocar una herramienta, y el resultado de esa llamada podría requerir un tratamiento adicional, lo que a su vez podría desencadenar otra llamada a herramienta. Solo una vez resuelto todo esto el modelo produce una respuesta final, y incluso esa respuesta podría no adoptar la forma de un mensaje de texto.
El flujo ya no puede reducirse a:
Messages (list)-> Model -> Assistant Message
Ahora existen salidas intermedias y acciones a nivel de aplicación que necesitan persistir como parte del contexto, y es precisamente allí donde una abstracción construida únicamente en torno a mensajes comienza a resultar demasiado limitada.
Cada salida del modelo != mensaje
Una vez que un modelo está conectado a las herramientas, gran parte de lo que emite es una llamada a función en lugar de texto conversacional.
Tomemos esto como ejemplo:
Function Call
Name: get_weather
Arguments:
{
"city": "Bengaluru"
}
Esto no está destinado a que lo lea el usuario; es una instrucción dirigida a su aplicación. Su código ejecuta la función correspondiente y devuelve el resultado al modelo, y ese resultado, nuevamente, puede ser o no visible para el usuario.
En la práctica, un modelo puede generar al menos dos tipos distintos de salida durante su ejecución:
- Llamada a función
- Mensaje
Considerar ambos bajo la misma etiqueta de “mensaje del asistente” no refleja lo que realmente ocurre durante la ejecución. Esta discrepancia es el problema de diseño fundamental que la API Responses busca solucionar.
API Responses: una abstracción diferente
En lugar de estar organizada en torno al intercambio de mensajes, la API de Responses se basa en el concepto de una respuesta, que puede agrupar varios elementos de salida.
response = client.responses.create(
model="...",
input="Explain LangGraph"
)
print(response.output)
# response.output is a list of output items.
Para una solicitud de texto plano, esa salida podría ser simplemente un único mensaje. Pero en el caso de cualquier cosa que involucre agentes o herramientas, una respuesta puede incluir varios tipos diferentes de elementos. Una vista simplificada de esa estructura es la siguiente:
Response
| Reasoning Item
| Function Call Item
| Message Item
En este modelo, un mensaje se convierte en solo uno de los varios tipos de salida posibles, y no representa la totalidad de lo que significa una respuesta.
Diferencia:
Diferencia:
Messages (list)-> Model -> Assistant Message
Completado de chat
Input -> Model -> Response
Response:
| Output Item
| Output Item
| Output Item
API de Responses
Con Chat Completions, es la conversación la que se modela, mientras que la API Responses estructura la ejecución del modelo como una respuesta compuesta por elementos de salida. En el caso de una simple completación de texto, esta distinción apenas tiene importancia. Se vuelve significativa una vez que entran en escena herramientas, modelos con capacidad de razonamiento o flujos de trabajo agentes.
Mensajes vs Elementos
La verdadera diferencia entre estas dos APIs se manifiesta en la forma en que cada una estructura su respuesta.
En Chat Completions, todo gira en torno al mensaje.
print(response.choices[0].message.content)
# The generated text is inside the assistant message.
# response
# | choices
# | message
# | content
La API Responses organiza su salida de manera diferente.
print(response.output)
# A simplified structure:
# response
# | output
# | reasoning
# | function_call
# | message
# The important difference is that output is not a list of messages,
# but output items.
Un mensaje es solo un tipo de elemento; una llamada a función es otro tipo; y los modelos que soportan el razonamiento también pueden generar elementos de razonamiento. Esto cambia la forma en que se percibe lo que realmente devuelve un modelo.
Chat Completions
Model Output = Assistant Message
Responses API
Model Output = List of Output Items
- The structure which is useful for tool calling.
Llamadas a herramientas como elementos de salida
Tome una función simple que informa sobre el clima (el ejemplo clásico utilizado en todas partes).
def get_weather(city: str):
return f"The weather in {city} is 28°C"
# The weather is ofcoure hardcoded.
Puede exponer esta función al modelo como una definición de herramienta.
tools = [
{
"type": "function",
"name": "get_weather",
"description": "Get the current weather for a city",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string"
}
},
"required": ["city"]
}
}
]
Esa definición de herramienta se incluye con su solicitud.
response = client.responses.create(
model="...",
tools=tools,
input="What is the weather in Bengaluru?"
)
A partir de aquí, el modelo tiene dos posibles caminos a seguir.
Option 1: Generate a message
Option 2: Call get_weather
Dado que el valor real del clima de la función no está incluido en su definición, el modelo necesita datos en tiempo real, por lo que emite un elemento de llamada a función en lugar de responder directamente.
Function Call Item
name: get_weather
arguments:
{
"city": "Bengaluru"
}
Puede localizar esta llamada a función recorriendo los elementos de salida de la respuesta.
for item in response.output:
if item.type == "function_call":
print(item.name)
print(item.arguments)
Al ejecutarlo, obtendrá:
get_weather
{"city":"Bengaluru"}
En este punto el modelo aún no ha producido una respuesta final, solo ha solicitado que se realice una acción; ejecutar la función en sí corresponde a su aplicación.
result = get_weather("Bengaluru")
Ese resultado luego debe ser devuelto al modelo.
Salida de la llamada a función
Se representa el resultado de una herramienta mediante un elemento del tipo function_call_output.
tool_output = {
"type": "function_call_output",
"call_id": item.call_id,
"output": result
}
El campo call_id relaciona esta salida con la llamada a función específica que la solicitó.
Function Call
| call_id: call_123
Application Executes Tool
Function Call Output
| call_id: call_123
Esta correspondencia se vuelve esencial cuando se invocan varias herramientas al mismo tiempo, por ejemplo, una consulta que solicita el clima en dos ciudades diferentes simultáneamente.
El bucle básico de ejecución de herramientas
Las aplicaciones construidas con herramientas suelen seguir un ciclo repetitivo.
User Input ->
Model ->
Response Output Items ->
Check for Function Calls ->
Execute Functions ->
Create Function Call Outputs ->
Model ->
Final Response
Así es más o menos como se ve en el código.
response = client.responses.create(
model="...",
input=user_input,
tools=tools
)
while True:
function_calls = [
item
for item in response.output
if item.type == "function_call"
]
if not function_calls:
break
tool_outputs = []
for call in function_calls:
result = execute_tool(
call.name,
call.arguments
)
tool_outputs.append({
"type": "function_call_output",
"call_id": call.call_id,
"output": result
})
response = client.responses.create(
model="...",
previous_response_id=response.id,
input=tool_outputs,
tools=tools
)
Este ciclo sigue repitiéndose mientras el modelo siga devolviendo llamadas a funciones. Una vez deja de solicitar llamadas a herramientas, la respuesta contiene la salida final del modelo.
Model ->
Function Call ->
Tool Result ->
Model ->
Function Call ->
Tool Result ->
Model ->
Message
Conclusión
La API Responses no es simplemente una interfaz con un nombre diferente de Chat Completions. Ofrece una estructura que refleja con mayor precisión cómo operan las aplicaciones de LLM contemporáneas cuando intervienen herramientas, razonamiento y múltiples tipos de salida. Chat Completions sigue siendo una opción sólida para casos de uso conversacionales sencillos. Pero en cuanto tu flujo de trabajo comience a adoptar un enfoque basado en agentes, la API Responses es la opción más adecuada. Los mensajes se encargan de la conversación; los elementos, de su ejecución. Esa es toda la idea.
Lectura relacionada
- Comprendiendo los agentes de IA: objetivos, herramientas, memoria y el bucle del agente — Una explicación adecuada para principiantes sobre cómo los agentes de IA difieren de los chatbots, abordando sus componentes esenciales, el bucle de toma de decisiones, los niveles de autonomía y casos de uso en el mundo real.
- Marcos estructurales para agentes de IA: dentro del pipeline ResolveFlow — Explica cómo un agente basado en LangGraph garantiza la separación entre el razonamiento y la ejecución mediante verificaciones a nivel de código en lugar de instrucciones en los prompts, incluyendo un error de recuperación que surgió durante el proceso.