For the complete documentation index, see llms.txt. Markdown versions of documentation pages are available by appending .md to the page URL.
Navegación principal
Theme

Resultados estructurados del modelo

Asegúrate de que las respuestas de texto del modelo sigan el esquema JSON que definas.

JSON es uno de los formatos más usados en el mundo para intercambiar datos entre aplicaciones.

La función de resultados estructurados garantiza que el modelo siempre genere respuestas que cumplan con el esquema JSON que proporciones. Así, no tienes que preocuparte de que el modelo omita una clave obligatoria o invente un valor no válido para una enumeración.

Los resultados estructurados ofrecen varias ventajas:

  1. Garantía de tipos: no necesitas validar las respuestas ni reintentar la solicitud por errores de formato.
  2. Rechazos explícitos: ahora puedes detectar mediante código cuándo el modelo rechaza una solicitud por motivos de seguridad.
  3. Prompts más sencillos: no necesitas insistir en el prompt para obtener un formato uniforme.

Además de la compatibilidad de la API REST con JSON Schema, las bibliotecas de OpenAI para Python y JavaScript te permiten definir esquemas de objetos con pydantic.BaseModel y z.object, respectivamente. El siguiente ejemplo muestra cómo extraer información de un texto no estructurado y obtener un resultado que siga un esquema definido en el código.

El SDK de Ruby admite esquemas definidos con T::Struct de Sorbet y devuelve resultados ya analizados con sus tipos correspondientes.

Obtener una respuesta estructurada
from openai import OpenAI
from pydantic import BaseModel

client = OpenAI()


class CalendarEvent(BaseModel):
    name: str
    date: str
    participants: list[str]


response = client.responses.parse(
    model="gpt-6-astra",
    input=[
        {"role": "system", "content": "Extract the event information."},
        {
            "role": "user",
            "content": "Alice and Bob are going to a science fair on Friday.",
        },
    ],
    text_format=CalendarEvent,
)

event = response.output_parsed

Modelos compatibles

Los resultados estructurados están disponibles en nuestros modelos de lenguaje de gran tamaño más recientes, a partir de GPT-4o. Para proyectos nuevos, empieza con gpt-6-astra. Con modelos más antiguos, como gpt-4-turbo y sus predecesores, puedes usar el modo JSON como alternativa.

Cuándo usar resultados estructurados con la llamada a funciones y cuándo usar text.format

La API de OpenAI ofrece dos formas de usar los resultados estructurados:

  1. Con la llamada a funciones
  2. Con el formato de respuesta json_schema

La llamada a funciones es útil cuando quieres conectar los modelos con las funciones de tu aplicación.

Por ejemplo, puedes darle al modelo acceso a funciones que consultan una base de datos para crear un asistente de IA que ayude a los usuarios con sus pedidos. También puedes darle acceso a funciones que interactúan con la interfaz de usuario.

En cambio, los resultados estructurados con response_format son más adecuados cuando quieres definir un esquema para las respuestas del modelo al usuario, en lugar de definirlo para las llamadas del modelo a una herramienta.

Por ejemplo, si desarrollas una aplicación de tutoría de matemáticas, quizá quieras que el asistente responda al usuario siguiendo un esquema JSON específico. Así puedes generar una interfaz que muestre cada parte del resultado de una manera distinta.

En la práctica:

  • Si conectas el modelo con herramientas, funciones, datos u otros elementos de tu sistema, usa la llamada a funciones. Si quieres estructurar el resultado del modelo cuando responde al usuario, usa un text.format estructurado.

El resto de esta guía se centra en los casos de uso de la API de Responses que no requieren la llamada a funciones. Consulta la guía de

llamada a funciones

para saber cómo usar esta capacidad con los resultados estructurados.

Diferencias entre los resultados estructurados y el modo JSON

Los resultados estructurados son una evolución del modo JSON. Ambos garantizan que se genere JSON válido, pero solo los resultados estructurados garantizan que se cumpla el esquema. Tanto los resultados estructurados como el modo JSON están disponibles en la API de Responses, la API para completar chats, la API de Assistants, la API de ajuste fino y la API de Batch.

Te recomendamos usar resultados estructurados en lugar del modo JSON siempre que sea posible.

Sin embargo, los resultados estructurados con response_format: {type: "json_schema", ...} solo están disponibles en las versiones gpt-4o-mini, gpt-4o-mini-2024-07-18 y gpt-4o-2024-08-06 de los modelos y en versiones posteriores.

Resultados estructurados Modo JSON
Genera JSON válido Sí Sí
Cumple con el esquema Sí (consulta los esquemas compatibles) No
Modelos compatibles gpt-4o-mini, gpt-4o-2024-08-06 y posteriores gpt-3.5-turbo, gpt-4-*, gpt-4o-* y modelos GPT-5 compatibles
Cómo activarlo text: { format: { type: "json_schema", "strict": true, "schema": ... } } text: { format: { type: "json_object" } }

Ejemplos

Cadena de pensamiento

Puedes pedirle al modelo que organice la respuesta en pasos para guiar al usuario hasta la solución.

Resultados estructurados para tutorías de matemáticas con cadena de pensamiento
from openai import OpenAI
from pydantic import BaseModel

client = OpenAI()


class Step(BaseModel):
    explanation: str
    output: str


class MathReasoning(BaseModel):
    steps: list[Step]
    final_answer: str


response = client.responses.parse(
    model="gpt-6-astra",
    input=[
        {
            "role": "system",
            "content": "You are a helpful math tutor. Guide the user through the solution step by step.",
        },
        {"role": "user", "content": "how can I solve 8x + 7 = -23"},
    ],
    text_format=MathReasoning,
)

math_reasoning = response.output_parsed

Ejemplo de respuesta

{
  "steps": [
    {
      "explanation": "Start with the equation 8x + 7 = -23.",
      "output": "8x + 7 = -23"
    },
    {
      "explanation": "Subtract 7 from both sides to isolate the term with the variable.",
      "output": "8x = -23 - 7"
    },
    {
      "explanation": "Simplify the right side of the equation.",
      "output": "8x = -30"
    },
    {
      "explanation": "Divide both sides by 8 to solve for x.",
      "output": "x = -30 / 8"
    },
    {
      "explanation": "Simplify the fraction.",
      "output": "x = -15 / 4"
    }
  ],
  "final_answer": "x = -15 / 4"
}

Cómo usar resultados estructurados con text.format

Cambiar el formato de respuesta durante la conversación

Para cambiar de esquema entre una respuesta y la siguiente, agrega un elemento configuration_update al final de input antes de agregar el siguiente mensaje del usuario. En cada solicitud, conserva el valor original de text.format en el nivel superior para que el prefijo del prompt anterior siga siendo apto para el almacenamiento en caché de prompts.

Puedes actualizar el formato de respuesta en los modelos compatibles a partir de GPT-6 cuando uses el modo estándar con un solo agente.

Supón que en el turno anterior se describió en texto plano un envío con 17 cajas rojas y 26 cajas azules. Continúa la conversación con previous_response_id y pasa el siguiente arreglo como input.

Pasar de texto plano a un conteo de cajas en formato estructurado
from openai import OpenAI
from pydantic import BaseModel, ConfigDict


class CrateCount(BaseModel):
    model_config = ConfigDict(extra="forbid")
    total_crates: int


client = OpenAI()
model = "gpt-6-astra"

first = client.responses.create(
    model=model,
    input="Summarize this shipment in one sentence: 17 red crates and 26 blue crates.",
    store=True,
)

second = client.responses.parse(
    model=model,
    previous_response_id=first.id,
    input=[
        {
            "type": "configuration_update",
            "text": {
                "format": {
                    "type": "json_schema",
                    "name": "crate_count",
                    "schema": CrateCount.model_json_schema(),
                    "strict": True,
                }
            },
        },
        {"role": "user", "content": "How many crates are in the shipment?"},
    ],
    parse_as=CrateCount,
)

if second.status == "completed" and second.output_parsed is not None:
    print(second.output_parsed.total_crates)

La respuesta esperada es {"total_crates":43}. El campo text.format del objeto de respuesta sigue indicando el formato original especificado en la solicitud. Después de comprobar si hay rechazos o respuestas incompletas, analiza la respuesta con el esquema de la actualización de formato más reciente.

Cada actualización de formato reemplaza el formato completo. El nuevo formato sigue vigente en los turnos posteriores, incluso si solo actualizas reasoning.effort. Para volver al texto plano, agrega {"type":"configuration_update","text":{"format":{"type":"text"}}} al final del arreglo de entrada. Las actualizaciones solo admiten json_schema y text.

Si vuelves a enviar el historial manualmente, conserva cada actualización en su posición original. Después de una compactación explícita, vuelve a especificar el valor de cada parámetro que hayas cambiado. Consulta la compatibilidad de las actualizaciones de configuración.

Rechazos al usar resultados estructurados

Cuando usas resultados estructurados con datos de entrada generados por los usuarios, los modelos de OpenAI pueden rechazar alguna solicitud por motivos de seguridad. Como el mensaje de rechazo no siempre sigue el esquema que proporcionaste en response_format, la respuesta de la API incluirá un nuevo campo llamado refusal para indicar que el modelo rechazó la solicitud.

Cuando la propiedad refusal aparezca en el objeto de salida, puedes mostrar el mensaje de rechazo en la interfaz de usuario o incluir lógica condicional en el código que procesa la respuesta para manejar las solicitudes rechazadas.

class Step(BaseModel):
    explanation: str
    output: str


class MathReasoning(BaseModel):
    steps: list[Step]
    final_answer: str


response = client.responses.parse(
    model="gpt-6-astra",
    input=[
        {
            "role": "system",
            "content": "You are a helpful math tutor. Guide the user through the solution step by step.",
        },
        {"role": "user", "content": "how can I solve 8x + 7 = -23"},
    ],
    text_format=MathReasoning,
)

for output in response.output:
    if output.type != "message":
        continue

    for item in output.content:
        if item.type == "refusal":
            # If the model refuses to respond, you will get a refusal message
            print(item.refusal)
            continue

        if not item.parsed:
            raise Exception("Could not parse response")

        print(item.parsed)

Si el modelo rechaza la solicitud, la respuesta de la API será similar a esta:

{
  "id": "resp_1234567890",
  "object": "response",
  "created_at": 1721596428,
  "status": "completed",
  "completed_at": 1721596429,
  "error": null,
  "incomplete_details": null,
  "input": [],
  "instructions": null,
  "max_output_tokens": null,
  "model": "gpt-4o-2024-08-06",
  "output": [{
    "id": "msg_1234567890",
    "type": "message",
    "role": "assistant",
    "content": [
      {
        "type": "refusal",
        "refusal": "I'm sorry, I cannot assist with that request."
      }
    ]
  }],
  "usage": {
    "input_tokens": 81,
    "output_tokens": 11,
    "total_tokens": 92,
    "output_tokens_details": {
      "reasoning_tokens": 0,
    }
  },
}

Consejos y buenas prácticas

Cómo manejar los datos de entrada de los usuarios

Si tu aplicación usa datos de entrada generados por los usuarios, asegúrate de que el prompt indique qué hacer cuando esos datos no permitan generar una respuesta válida.

El modelo siempre intentará seguir el esquema que proporciones. Esto puede dar lugar a alucinaciones si los datos de entrada no tienen ninguna relación con el esquema.

Puedes indicar en el prompt que el modelo debe devolver parámetros vacíos o una frase específica si detecta que los datos de entrada son incompatibles con la tarea.

Cómo manejar los errores

Los resultados estructurados también pueden contener errores. Si detectas alguno, prueba a ajustar las instrucciones, incluir ejemplos en las instrucciones del sistema o dividir las tareas en subtareas más sencillas. Consulta la guía de ingeniería de prompts para obtener más información sobre cómo ajustar los datos de entrada.

Cómo mantener el esquema JSON y los tipos sincronizados

Para evitar discrepancias entre el esquema JSON y los tipos correspondientes en tu lenguaje de programación, te recomendamos especialmente que uses las funciones auxiliares nativas del SDK para definir esquemas siempre que estén disponibles.

Si prefieres especificar el esquema JSON directamente, puedes agregar reglas de integración continua que te avisen cuando se modifique el esquema JSON o los objetos de datos subyacentes. También puedes agregar un paso de integración continua que genere automáticamente el esquema JSON a partir de las definiciones de tipos, o viceversa.

Transmisión en tiempo real

Con la transmisión en tiempo real, puedes procesar las respuestas del modelo o los argumentos de las llamadas a funciones a medida que se generan y analizarlos como datos estructurados.

Así puedes empezar a procesar la respuesta sin esperar a recibirla completa. Esto resulta especialmente útil si quieres mostrar los campos JSON uno por uno o procesar los argumentos de las llamadas a funciones en cuanto estén disponibles.

Te recomendamos usar los SDK para gestionar la transmisión en tiempo real con resultados estructurados.

from openai import OpenAI
from pydantic import BaseModel


class EntitiesModel(BaseModel):
    attributes: list[str]
    colors: list[str]
    animals: list[str]


client = OpenAI()

with client.responses.stream(
    model="gpt-6-astra",
    input=[
        {"role": "system", "content": "Extract entities from the input text"},
        {
            "role": "user",
            "content": "The quick brown fox jumps over the lazy dog with piercing blue eyes",
        },
    ],
    text_format=EntitiesModel,
) as stream:
    for event in stream:
        if event.type == "response.refusal.delta":
            print(event.delta, end="")
        elif event.type == "response.output_text.delta":
            print(event.delta, end="")
        elif event.type == "response.error":
            print(event.error, end="")
        elif event.type == "response.completed":
            print("Completed")  # print(event.response.output)

    final_response = stream.get_final_response()
    print(final_response)

Esquemas compatibles

Los resultados estructurados admiten un subconjunto del lenguaje JSON Schema.

Tipos admitidos

Los resultados estructurados admiten los siguientes tipos:

  • Cadena
  • Número
  • Booleano
  • Entero
  • Objeto
  • Arreglo
  • Enumeración
  • anyOf

Propiedades admitidas

Además de especificar el tipo de una propiedad, puedes definir restricciones adicionales:

Propiedades admitidas para string:

  • pattern — Expresión regular con la que debe coincidir la cadena.
  • format — Formato predefinido para cadenas. Actualmente se admiten los siguientes formatos:
    • date-time
    • time
    • date
    • duration
    • email
    • hostname
    • ipv4
    • ipv6
    • uuid

Propiedades admitidas para number:

  • multipleOf — El número debe ser múltiplo de este valor.
  • maximum — El número debe ser menor o igual que este valor.
  • exclusiveMaximum — El número debe ser menor que este valor.
  • minimum — El número debe ser mayor o igual que este valor.
  • exclusiveMinimum — El número debe ser mayor que este valor.

Propiedades admitidas para array:

  • minItems — El arreglo debe contener al menos esta cantidad de elementos.
  • maxItems — El arreglo debe contener como máximo esta cantidad de elementos.

Estos ejemplos muestran cómo aplicar las restricciones de cada tipo:

{
    "name": "user_data",
    "strict": true,
    "schema": {
        "type": "object",
        "properties": {
            "name": {
                "type": "string",
                "description": "The name of the user"
            },
            "username": {
                "type": "string",
                "description": "The username of the user. Must start with @",
                "pattern": "^@[a-zA-Z0-9_]+$"
            },
            "email": {
                "type": "string",
                "description": "The email of the user",
                "format": "email"
            }
        },
        "additionalProperties": false,
        "required": [
            "name", "username", "email"
        ]
    }
}

La raíz del esquema debe ser un objeto y no puede usar anyOf

La raíz de un esquema debe ser un objeto y no puede usar anyOf. En Zod, por ejemplo, usar una unión discriminada genera un anyOf en el nivel raíz. Por eso, el siguiente código no funcionará:

import { z } from "zod";
import { zodResponseFormat } from "openai/helpers/zod";

const BaseResponseSchema = z.object({
  /* ... */
});
const UnsuccessfulResponseSchema = z.object({
  /* ... */
});

const finalSchema = z.discriminatedUnion("status", [
  BaseResponseSchema,
  UnsuccessfulResponseSchema,
]);

// Invalid JSON Schema for Structured Outputs
const json = zodResponseFormat(finalSchema, "final_schema");

Todos los campos deben marcarse como required

Para usar los resultados estructurados, debes marcar todos los campos o parámetros de función como required.

{
    "name": "get_weather",
    "description": "Fetches the weather in the given location",
    "strict": true,
    "parameters": {
        "type": "object",
        "properties": {
            "location": {
                "type": "string",
                "description": "The location to get the weather for"
            },
            "unit": {
                "type": "string",
                "description": "The unit to return the temperature in",
                "enum": ["F", "C"]
            }
        },
        "additionalProperties": false,
        "required": ["location", "unit"]
    }
}

Aunque todos los campos deben ser obligatorios (y el modelo devolverá un valor para cada parámetro), puedes simular un parámetro opcional mediante un tipo de unión que incluya null.

{
    "name": "get_weather",
    "description": "Fetches the weather in the given location",
    "strict": true,
    "parameters": {
        "type": "object",
        "properties": {
            "location": {
                "type": "string",
                "description": "The location to get the weather for"
            },
            "unit": {
                "type": ["string", "null"],
                "description": "The unit to return the temperature in",
                "enum": ["F", "C"]
            }
        },
        "additionalProperties": false,
        "required": [
            "location", "unit"
        ]
    }
}

Límites de tamaño y profundidad de anidamiento de los objetos

Un esquema puede tener hasta 5000 propiedades de objeto en total y hasta 10 niveles de anidamiento.

Límite de longitud total de las cadenas

En un esquema, la suma de las longitudes de todos los nombres de propiedades, nombres de definiciones, valores de enum y valores de const no puede superar los 120 000 caracteres.

Límites de tamaño de las enumeraciones

Un esquema puede tener hasta 1000 valores de enumeración en total entre todas sus propiedades de enumeración.

Si una propiedad de enumeración contiene más de 250 valores y estos son cadenas, la suma de sus longitudes no puede superar los 15 000 caracteres.

Siempre debes establecer additionalProperties: false en los objetos

additionalProperties determina si un objeto puede contener pares de clave y valor adicionales que no estén definidos en el esquema JSON.

Los resultados estructurados solo pueden generar los pares de clave y valor especificados, por lo que debes establecer additionalProperties: false para activar esta función.

{
    "name": "get_weather",
    "description": "Fetches the weather in the given location",
    "strict": true,
    "schema": {
        "type": "object",
        "properties": {
            "location": {
                "type": "string",
                "description": "The location to get the weather for"
            },
            "unit": {
                "type": "string",
                "description": "The unit to return the temperature in",
                "enum": ["F", "C"]
            }
        },
        "additionalProperties": false,
        "required": [
            "location", "unit"
        ]
    }
}

Orden de las claves

Cuando usas los resultados estructurados, las claves aparecen en el mismo orden que en el esquema.

Algunas palabras clave específicas de ciertos tipos aún no se admiten

  • Composición: allOf, not, dependentRequired, dependentSchemas, if, then, else

Los modelos con ajuste fino tampoco admiten las siguientes palabras clave:

  • Para cadenas: minLength, maxLength, pattern, format
  • Para números: minimum, maximum, multipleOf
  • Para objetos: patternProperties
  • Para arreglos: minItems, maxItems

Si activas los resultados estructurados con strict: true y llamas a la API con un esquema JSON no admitido, recibirás un error.

Con anyOf, cada esquema anidado debe ser válido según el subconjunto admitido de JSON Schema

Este es un ejemplo de un esquema admitido que usa anyOf:

{
    "type": "object",
    "properties": {
        "item": {
            "anyOf": [
                {
                    "type": "object",
                    "description": "The user object to insert into the database",
                    "properties": {
                        "name": {
                            "type": "string",
                            "description": "The name of the user"
                        },
                        "age": {
                            "type": "number",
                            "description": "The age of the user"
                        }
                    },
                    "additionalProperties": false,
                    "required": [
                        "name",
                        "age"
                    ]
                },
                {
                    "type": "object",
                    "description": "The address object to insert into the database",
                    "properties": {
                        "number": {
                            "type": "string",
                            "description": "The number of the address. Eg. for 123 main st, this would be 123"
                        },
                        "street": {
                            "type": "string",
                            "description": "The street name. Eg. for 123 main st, this would be main st"
                        },
                        "city": {
                            "type": "string",
                            "description": "The city of the address"
                        }
                    },
                    "additionalProperties": false,
                    "required": [
                        "number",
                        "street",
                        "city"
                    ]
                }
            ]
        }
    },
    "additionalProperties": false,
    "required": [
        "item"
    ]
}

Se admiten definiciones

Puedes usar definiciones para crear subesquemas y hacer referencia a ellos en distintas partes del esquema. Este es un ejemplo sencillo:

{
    "type": "object",
    "properties": {
        "steps": {
            "type": "array",
            "items": {
                "$ref": "#/$defs/step"
            }
        },
        "final_answer": {
            "type": "string"
        }
    },
    "$defs": {
        "step": {
            "type": "object",
            "properties": {
                "explanation": {
                    "type": "string"
                },
                "output": {
                    "type": "string"
                }
            },
            "required": [
                "explanation",
                "output"
            ],
            "additionalProperties": false
        }
    },
    "required": [
        "steps",
        "final_answer"
    ],
    "additionalProperties": false
}

Se admiten esquemas recursivos

Ejemplo de un esquema recursivo que usa # para hacer referencia a la raíz:

{
    "name": "ui",
    "description": "Dynamically generated UI",
    "strict": true,
    "schema": {
        "type": "object",
        "properties": {
            "type": {
                "type": "string",
                "description": "The type of the UI component",
                "enum": ["div", "button", "header", "section", "field", "form"]
            },
            "label": {
                "type": "string",
                "description": "The label of the UI component, used for buttons or form fields"
            },
            "children": {
                "type": "array",
                "description": "Nested UI components",
                "items": {
                    "$ref": "#"
                }
            },
            "attributes": {
                "type": "array",
                "description": "Arbitrary attributes for the UI component, suitable for any element",
                "items": {
                    "type": "object",
                    "properties": {
                        "name": {
                            "type": "string",
                            "description": "The name of the attribute, for example onClick or className"
                        },
                        "value": {
                            "type": "string",
                            "description": "The value of the attribute"
                        }
                    },
                    "additionalProperties": false,
                    "required": ["name", "value"]
                }
            }
        },
        "required": ["type", "label", "children", "attributes"],
        "additionalProperties": false
    }
}

Ejemplo de un esquema recursivo con recursión explícita:

{
    "type": "object",
    "properties": {
        "linked_list": {
            "$ref": "#/$defs/linked_list_node"
        }
    },
    "$defs": {
        "linked_list_node": {
            "type": "object",
            "properties": {
                "value": {
                    "type": "number"
                },
                "next": {
                    "anyOf": [
                        {
                            "$ref": "#/$defs/linked_list_node"
                        },
                        {
                            "type": "null"
                        }
                    ]
                }
            },
            "additionalProperties": false,
            "required": [
                "next",
                "value"
            ]
        }
    },
    "additionalProperties": false,
    "required": [
        "linked_list"
    ]
}

Modo JSON

El modo JSON es una versión más básica de la función de resultados estructurados. El modo JSON garantiza que el modelo genere JSON válido, mientras que los resultados estructurados garantizan que la salida del modelo cumpla con el esquema que especifiques. Te recomendamos usar resultados estructurados si son compatibles con tu caso de uso.

Cuando activas el modo JSON, el modelo genera JSON válido, salvo en algunos casos límite que debes detectar y manejar de forma adecuada.

Para activar el modo JSON en la API de Responses, puedes establecer text.format en { "type": "json_object" }. Si usas la llamada a funciones, el modo JSON siempre está activado.

Notas importantes:

  • Cuando uses el modo JSON, siempre debes indicarle al modelo que genere JSON en algún mensaje de la conversación, por ejemplo, en el mensaje del sistema. Si no incluyes una instrucción explícita para generar JSON, el modelo podría generar caracteres de espacio en blanco sin parar y la solicitud podría seguir ejecutándose hasta alcanzar el límite de tokens. Para evitar que se te olvide, la API devolverá un error si la cadena “JSON” no aparece en el contexto.
  • El modo JSON solo garantiza que la salida sea JSON válido y se pueda analizar sin errores. Para garantizar que cumpla con un esquema específico, usa resultados estructurados. Si no es posible, usa una biblioteca de validación y, si es necesario, reintenta la solicitud hasta obtener un resultado que cumpla con el esquema que necesitas.
  • Tu aplicación debe detectar y manejar los casos límite en los que el modelo podría generar un objeto JSON incompleto (consulta la sección siguiente).

Recursos

Para saber más sobre los resultados estructurados, te recomendamos consultar estos recursos: