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

Llamada a funciones

Da a los modelos acceso a nuevas funciones y datos que puedan usar para seguir instrucciones y responder a prompts.

La llamada a funciones (también conocida como llamada a herramientas) ofrece una forma potente y flexible para que los modelos de OpenAI interactúen con sistemas externos y accedan a datos que no forman parte de sus datos de entrenamiento. Esta guía muestra cómo conectar un modelo con los datos y las acciones que ofrece tu aplicación. Mostraremos cómo usar herramientas de función (definidas mediante un esquema JSON) y herramientas personalizadas que trabajan con entradas y salidas de texto libre.

Para las sesiones de la API de agentes, usa Funciones para registrar funciones y gestionar las solicitudes de acciones de la sesión. Los ejemplos de esta guía muestran las integraciones con la API Responses y Chat Completions.

Si tu aplicación tiene muchas funciones o esquemas grandes, puedes combinar la llamada a funciones con la búsqueda de herramientas para diferir la carga de las herramientas que se usan con poca frecuencia y cargarlas solo cuando el modelo las necesite. Solo gpt-5.4 y los modelos posteriores admiten tool_search.

GPT-6 Astra requiere la API Responses para la llamada a herramientas. Los ejemplos de Chat Completions usan GPT-5.6 por compatibilidad. Consulta la guía de migración para actualizar una integración existente.

Cómo funciona

Comencemos por entender algunos términos clave sobre la llamada a herramientas. Una vez que tengamos un vocabulario común, te mostraremos cómo se hace con algunos ejemplos prácticos.

El flujo de llamada a herramientas

La llamada a herramientas es una conversación de varios pasos entre tu aplicación y un modelo a través de la API de OpenAI. Este flujo consta de cinco pasos generales:

  1. Envía una solicitud al modelo con las herramientas a las que podría llamar
  2. Recibe una llamada a una herramienta del modelo
  3. Ejecuta código en la aplicación con los datos de entrada de la llamada a la herramienta
  4. Envía una segunda solicitud al modelo con el resultado de la herramienta
  5. Recibe una respuesta final del modelo (o más llamadas a herramientas)

Diagrama de los pasos de la llamada a funciones

Con Responses, tu aplicación puede continuar este flujo durante tantas llamadas a herramientas como requiera la tarea. Si quieres un framework que encapsule la orquestación recurrente de ese ciclo, consulta la comparación entre la API Responses y el Agents SDK.

Ejemplo de una herramienta de función

Veamos un flujo completo de llamada a herramientas para una función get_horoscope que obtiene el horóscopo diario de un signo del zodiaco.

Ejemplo completo de llamada a herramientas
from openai import OpenAI
import json

client = OpenAI()

# 1. Define a list of callable tools for the model
tools = [
    {
        "type": "function",
        "name": "get_horoscope",
        "description": "Get today's horoscope for an astrological sign.",
        "parameters": {
            "type": "object",
            "properties": {
                "sign": {
                    "type": "string",
                    "description": "An astrological sign like Taurus or Aquarius",
                },
            },
            "required": ["sign"],
        },
    },
]


def get_horoscope(sign):
    return f"{sign}: Next Tuesday you will befriend a baby otter."


# Create a running input list we will add to over time
input_list = [{"role": "user", "content": "What is my horoscope? I am an Aquarius."}]

# 2. Prompt the model with tools defined
response = client.responses.create(
    model="gpt-6-astra",
    tools=tools,
    input=input_list,
)

# Save function call outputs for subsequent requests
input_list += response.output

for item in response.output:
    if item.type == "function_call":
        if item.name == "get_horoscope":
            # 3. Execute the function logic for get_horoscope
            sign = json.loads(item.arguments)["sign"]
            horoscope = get_horoscope(sign)

            # 4. Provide function call results to the model
            input_list.append(
                {
                    "type": "function_call_output",
                    "call_id": item.call_id,
                    "output": horoscope,
                }
            )

print("Final input:")
print(input_list)

response = client.responses.create(
    model="gpt-6-astra",
    instructions="Respond only with a horoscope generated by a tool.",
    tools=tools,
    input=input_list,
)

# 5. The model should be able to give a response!
print("Final output:")
print(response.model_dump_json(indent=2))
print("\n" + response.output_text)

Ten en cuenta que, en los modelos de razonamiento como GPT-5 u o4-mini, todos los elementos de razonamiento que se devuelvan en las respuestas del modelo con llamadas a herramientas también deben enviarse de vuelta junto con los resultados de esas llamadas.

Definir funciones

Las funciones suelen declararse en el parámetro tools de cada solicitud a la API. Con la búsqueda de herramientas, tu aplicación también puede cargar funciones diferidas más adelante en la interacción. En ambos casos, cada función a la que se puede llamar usa la misma estructura de esquema. La definición de una función tiene las siguientes propiedades:

CampoDescripción
typeSiempre debe ser function
nameEl nombre de la función (por ejemplo, get_weather)
descriptionDetalles sobre cuándo y cómo usar la función
parametersEsquema JSON que define los argumentos de entrada de la función
strictIndica si se debe aplicar el modo estricto a la llamada a la función

Aquí tienes un ejemplo de definición de una función get_weather

{
  "type": "function",
  "name": "get_weather",
  "description": "Retrieves current weather for the given location.",
  "parameters": {
    "type": "object",
    "properties": {
      "location": {
        "type": "string",
        "description": "City and country e.g. Bogotá, Colombia"
      },
      "units": {
        "type": "string",
        "enum": ["celsius", "fahrenheit"],
        "description": "Units the temperature will be returned in."
      }
    },
    "required": ["location", "units"],
    "additionalProperties": false
  },
  "strict": true
}

Al definir parameters mediante un esquema JSON, puedes aprovechar muchas de sus funciones avanzadas, como tipos de propiedades, enumeraciones, descripciones, objetos anidados y objetos recursivos.

Definir espacios de nombres

Usa espacios de nombres para agrupar herramientas relacionadas por dominio, como crm, billing o shipping. Los espacios de nombres ayudan a organizar herramientas similares y son especialmente útiles cuando el modelo debe elegir entre herramientas que sirven a distintos sistemas o propósitos, como una herramienta de búsqueda para tu CRM y otra para tu sistema de tickets de soporte.

{
  "type": "namespace",
  "name": "crm",
  "description": "CRM tools for customer lookup and order management.",
  "tools": [
    {
      "type": "function",
      "name": "get_customer_profile",
      "description": "Fetch a customer profile by customer ID.",
      "parameters": {
        "type": "object",
        "properties": {
          "customer_id": { "type": "string" }
        },
        "required": ["customer_id"],
        "additionalProperties": false
      }
    },
    {
      "type": "function",
      "name": "list_open_orders",
      "description": "List open orders for a customer ID.",
      "defer_loading": true,
      "parameters": {
        "type": "object",
        "properties": {
          "customer_id": { "type": "string" }
        },
        "required": ["customer_id"],
        "additionalProperties": false
      }
    }
  ]
}

Si necesitas darle al modelo acceso a un amplio ecosistema de herramientas, puedes diferir la carga de algunas o de todas ellas con tool_search. La herramienta tool_search permite que el modelo busque herramientas relevantes, las agregue a su contexto y luego las use. Solo gpt-5.4 y los modelos posteriores la admiten. Lee la guía de búsqueda de herramientas para obtener más información.

Prácticas recomendadas para definir funciones

  1. Escribe nombres de funciones, descripciones de parámetros e instrucciones que sean claros y detallados.

    • Describe explícitamente el propósito de la función y de cada parámetro (y su formato), así como lo que representa la salida.
    • Usa el prompt del sistema para describir cuándo usar cada función y cuándo no. En general, dile al modelo exactamente qué hacer.
    • Incluye ejemplos y casos límite, especialmente para corregir fallas recurrentes. (Nota: agregar ejemplos puede perjudicar el desempeño de los modelos de razonamiento.)
    • Para las herramientas de carga diferida, incluye instrucciones detalladas en la descripción de la función y mantén concisa la descripción del espacio de nombres. El espacio de nombres ayuda al modelo a elegir qué cargar; la descripción de la función lo ayuda a usar correctamente la herramienta cargada.
  2. Aplica las prácticas recomendadas de ingeniería de software.

    • Haz que las funciones sean predecibles e intuitivas. (principio de mínima sorpresa)
    • Usa enumeraciones y la estructura de los objetos para evitar estados no válidos. Por ejemplo, toggle_light(on: bool, off: bool) permite llamadas no válidas.
    • Supera la prueba del pasante. ¿Puede un pasante u otra persona usar correctamente la función con solo la información que le diste al modelo? (Si no, ¿qué preguntas te hace? Agrega las respuestas al prompt.)
  3. Reduce la carga del modelo y usa código cuando sea posible.

    • No hagas que el modelo complete argumentos cuyos valores ya conoces. Por ejemplo, si ya tienes un order_id a partir de un menú anterior, no incluyas un parámetro order_id. En su lugar, define submit_refund() sin parámetros y pasa el order_id en tu código.
    • Combina las funciones que siempre se llaman en secuencia. Por ejemplo, si siempre llamas a mark_location() después de query_location(), simplemente mueve la lógica de marcado a la llamada a la función de consulta.
  4. Mantén un número reducido de funciones disponibles al inicio para lograr mayor precisión.

    • Evalúa el desempeño con distintas cantidades de funciones.
    • Procura que haya menos de 20 funciones disponibles al inicio de cada turno , aunque esto es solo una recomendación orientativa.
    • Usa la búsqueda de herramientas para diferir la carga de grupos grandes de herramientas o de herramientas que se usan con poca frecuencia, en lugar de exponerlas todas desde el principio.
  5. Aprovecha los recursos de OpenAI.

    • Genera esquemas de funciones y mejóralos de forma iterativa en el Playground.
    • Considera el ajuste fino para aumentar la precisión de las llamadas a funciones cuando haya muchas funciones o tareas difíciles. (Cookbook)

Uso de tokens

Internamente, las funciones se insertan en el mensaje del sistema con una sintaxis con la que se entrenó al modelo. Esto significa que las definiciones de las funciones que se pueden llamar cuentan para el límite de contexto del modelo y se facturan como tokens de entrada. Si alcanzas los límites de tokens, te sugerimos limitar la cantidad de funciones que se cargan al inicio, acortar las descripciones cuando sea posible o usar la búsqueda de herramientas para que las herramientas de carga diferida solo se carguen cuando se necesiten.

También puedes usar el ajuste fino para reducir la cantidad de tokens utilizados si tienes muchas funciones definidas en tu especificación de herramientas.

Manejo de llamadas a funciones

Cuando el modelo llama a una función, debes ejecutarla y devolver el resultado. Como las respuestas del modelo pueden incluir cero, una o varias llamadas, la práctica recomendada es asumir que habrá varias.

El arreglo output de la respuesta contiene una entrada cuyo type tiene el valor function_call. Cada entrada incluye un call_id (que se usa después para enviar el resultado de la función), un name y arguments codificados en JSON.

Ejemplo de respuesta con varias llamadas a funciones
[
    {
        "id": "fc_12345xyz",
        "call_id": "call_12345xyz",
        "type": "function_call",
        "name": "get_weather",
        "arguments": "{\"location\":\"Paris, France\"}"
    },
    {
        "id": "fc_67890abc",
        "call_id": "call_67890abc",
        "type": "function_call",
        "name": "get_weather",
        "arguments": "{\"location\":\"Bogotá, Colombia\"}"
    },
    {
        "id": "fc_99999def",
        "call_id": "call_99999def",
        "type": "function_call",
        "name": "send_email",
        "arguments": "{\"to\":\"bob@email.com\",\"body\":\"Hi bob\"}"
    }
]

Si usas la búsqueda de herramientas, también puedes ver elementos tool_search_call y tool_search_output antes de un function_call. Una vez cargada la función, maneja la llamada a la función de la misma manera que se muestra aquí.

Ejecuta las llamadas a funciones y agrega los resultados
input_messages += response.output

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

    name = tool_call.name
    args = json.loads(tool_call.arguments)

    result = call_function(name, args)
    input_messages.append(
        {
            "type": "function_call_output",
            "call_id": tool_call.call_id,
            "output": json.dumps(result),
        }
    )

En el ejemplo anterior, usamos una función hipotética call_function para dirigir cada llamada. Esta es una posible implementación:

Ejecuta las llamadas a funciones y agrega los resultados
def call_function(name, args):
    if name == "get_weather":
        return get_weather(**args)
    if name == "send_email":
        return send_email(**args)
    raise ValueError(f"Unknown function: {name}")

Formato de los resultados

Por lo general, el resultado que pases en el mensaje function_call_output debe ser una cadena de texto cuyo formato puedes elegir (JSON, códigos de error, texto sin formato, etc.). El modelo interpretará esa cadena según sea necesario.

Para las funciones que devuelven imágenes o archivos, puedes pasar un arreglo de objetos de imagen o archivo en lugar de una cadena de texto.

Si tu función no tiene un valor de retorno (por ejemplo, send_email), devuelve una cadena de texto que indique si la operación tuvo éxito o falló, como "success".

Incorporación de los resultados en la respuesta

Después de agregar los resultados a input, puedes enviarlos de vuelta al modelo para obtener una respuesta final.

Envía los resultados de vuelta al modelo
response = client.responses.create(
    model="gpt-6-astra",
    input=input_messages,
    tools=responses_tools,
)

print(response.output_text)
Respuesta final
"It's about 15°C in Paris, 18°C in Bogotá, and I've sent that email to Bob."

Configuraciones adicionales

Selección de herramientas

De forma predeterminada, el modelo determinará cuándo usar herramientas y cuántas usar. Puedes forzar un comportamiento específico con el parámetro tool_choice.

  1. Automática: (Predeterminada) llamar a cero, una o varias funciones. tool_choice: "auto"
  2. Obligatoria: llamar a una o más funciones. tool_choice: "required"
  3. Función forzada: llamar exactamente a una función específica. tool_choice: {"type": "function", "name": "get_weather"}
  4. Herramientas permitidas: restringir las llamadas a herramientas que puede hacer el modelo a un subconjunto de las herramientas disponibles para el modelo.

Cuándo usar allowed_tools

Puedes configurar una lista allowed_tools si quieres que solo esté disponible un subconjunto de herramientas en las solicitudes al modelo, sin modificar la lista de herramientas que envías, para maximizar el ahorro del almacenamiento de prompts en caché.

"tool_choice": {
    "type": "allowed_tools",
    "mode": "auto",
    "tools": [
        { "type": "function", "name": "get_weather" },
        { "type": "function", "name": "search_docs" }
    ]
  }
}

También puedes establecer tool_choice en "none" para simular el comportamiento de no pasar ninguna función.

Cuando usas la búsqueda de herramientas, tool_choice sigue aplicándose a las herramientas que se pueden llamar en ese momento del turno. Esto resulta especialmente útil después de cargar un subconjunto de herramientas, cuando quieres limitar al modelo a ese subconjunto.

Llamadas a funciones en paralelo

En los modelos compatibles a partir de GPT-5, se puede llamar a funciones en paralelo cuando también hay herramientas integradas disponibles. Las herramientas integradas no pueden incluirse en un lote de llamadas a funciones en paralelo.

El modelo puede optar por llamar a varias funciones en un solo turno. Puedes evitarlo estableciendo parallel_tool_calls en false, lo que garantiza que se llame a una sola herramienta o a ninguna.

Nota: actualmente, si usas un modelo con ajuste fino y este llama a varias funciones en un turno, el modo estricto se desactivará para esas llamadas.

Nota sobre gpt-4.1-nano-2025-04-14: esta versión de gpt-4.1-nano a veces puede incluir varias llamadas a la misma herramienta si las llamadas a herramientas en paralelo están habilitadas. Se recomienda desactivar esta función al usar esta versión.

Modo estricto

Establecer strict en true garantiza que las llamadas a funciones cumplan de manera confiable el esquema de la función, en lugar de solo intentar cumplirlo sin garantías. Recomendamos activar siempre el modo estricto.

Internamente, el modo estricto utiliza nuestra función de resultados estructurados y, por lo tanto, introduce un par de requisitos:

  1. additionalProperties debe establecerse en false para cada objeto de parameters.
  2. Todos los campos de properties deben marcarse como required.

Puedes indicar que un campo es opcional agregando null como opción de type (consulta el ejemplo a continuación).

Si envías strict: true y tu esquema no cumple con los requisitos anteriores, la solicitud se rechazará con detalles sobre las restricciones faltantes. Si omites strict, el comportamiento predeterminado depende de la API: las solicitudes a Responses intentarán normalizar tu esquema para usar el modo estricto cuando sea posible y recurrirán a llamadas a funciones no estrictas, que intentan cumplir el esquema sin garantizarlo, si este no puede hacerse compatible con el modo estricto. Cuando esto ocurra, la herramienta en la respuesta mostrará strict: false. Las solicitudes a Chat Completions siguen siendo no estrictas de forma predeterminada. Para desactivar el modo estricto en Responses y mantener las llamadas a funciones no estrictas, que intentan cumplir el esquema sin garantizarlo, establece explícitamente strict: false.

{
    "type": "function",
    "name": "get_weather",
    "description": "Retrieves current weather for the given location.",
    "strict": true,
    "parameters": {
        "type": "object",
        "properties": {
            "location": {
                "type": "string",
                "description": "City and country e.g. Bogotá, Colombia"
            },
            "units": {
                "type": ["string", "null"],
                "enum": ["celsius", "fahrenheit"],
                "description": "Units the temperature will be returned in."
            }
        },
        "required": ["location", "units"],
        "additionalProperties": false
    }
}

Todos los esquemas generados en el Playground tienen el modo estricto activado.

Aunque recomendamos activar el modo estricto, tiene algunas limitaciones:

  1. Algunas características de los esquemas JSON no son compatibles. (Consulta los esquemas compatibles.)

Específicamente para los modelos con ajuste fino:

  1. Los esquemas pasan por un procesamiento adicional en la primera solicitud (y luego se almacenan en caché). Si tus esquemas varían entre solicitudes, esto puede generar latencias más altas.
  2. Los esquemas se almacenan en caché para mejorar el rendimiento y no pueden acogerse a la retención cero de datos.

Streaming

El streaming permite mostrar el progreso indicando qué función se llama mientras el modelo completa sus argumentos, e incluso mostrar los argumentos en tiempo real.

El streaming de llamadas a funciones es muy similar al de respuestas normales: estableces stream en true y recibes distintos objetos event.

Streaming de llamadas a funciones
from openai import OpenAI

client = OpenAI()

tools = [
    {
        "type": "function",
        "name": "get_weather",
        "description": "Get current temperature for a given location.",
        "parameters": {
            "type": "object",
            "properties": {
                "location": {
                    "type": "string",
                    "description": "City and country e.g. Bogotá, Colombia",
                }
            },
            "required": ["location"],
            "additionalProperties": False,
        },
    }
]

stream = client.responses.create(
    model="gpt-6-astra",
    input=[{"role": "user", "content": "What's the weather like in Paris today?"}],
    tools=tools,
    stream=True,
)

for event in stream:
    print(event)
Eventos de salida
{"type":"response.output_item.added","response_id":"resp_1234xyz","output_index":0,"item":{"type":"function_call","id":"fc_1234xyz","call_id":"call_1234xyz","name":"get_weather","arguments":""}}
{"type":"response.function_call_arguments.delta","response_id":"resp_1234xyz","item_id":"fc_1234xyz","output_index":0,"delta":"{\""}
{"type":"response.function_call_arguments.delta","response_id":"resp_1234xyz","item_id":"fc_1234xyz","output_index":0,"delta":"location"}
{"type":"response.function_call_arguments.delta","response_id":"resp_1234xyz","item_id":"fc_1234xyz","output_index":0,"delta":"\":\""}
{"type":"response.function_call_arguments.delta","response_id":"resp_1234xyz","item_id":"fc_1234xyz","output_index":0,"delta":"Paris"}
{"type":"response.function_call_arguments.delta","response_id":"resp_1234xyz","item_id":"fc_1234xyz","output_index":0,"delta":","}
{"type":"response.function_call_arguments.delta","response_id":"resp_1234xyz","item_id":"fc_1234xyz","output_index":0,"delta":" France"}
{"type":"response.function_call_arguments.delta","response_id":"resp_1234xyz","item_id":"fc_1234xyz","output_index":0,"delta":"\"}"}
{"type":"response.function_call_arguments.done","response_id":"resp_1234xyz","item_id":"fc_1234xyz","output_index":0,"arguments":"{\"location\":\"Paris, France\"}"}
{"type":"response.output_item.done","response_id":"resp_1234xyz","output_index":0,"item":{"type":"function_call","id":"fc_1234xyz","call_id":"call_1234xyz","name":"get_weather","arguments":"{\"location\":\"Paris, France\"}"}}

Sin embargo, en lugar de combinar los fragmentos en una sola cadena content, los combinas en un objeto JSON arguments codificado.

Cuando el modelo llama a una o más funciones, se emite un evento de tipo response.output_item.added por cada llamada a función. Este evento contiene los siguientes campos:

CampoDescripción
response_idEl identificador de la respuesta a la que pertenece la llamada a función
output_indexEl índice del elemento de salida en la respuesta. Representa las llamadas a funciones individuales de la respuesta.
itemEl elemento de llamada a función en curso que incluye los campos name, arguments y id

Después recibirás una serie de eventos de tipo response.function_call_arguments.delta que contendrán el delta del campo arguments. Estos eventos contienen los siguientes campos:

CampoDescripción
response_idEl identificador de la respuesta a la que pertenece la llamada a función
item_idEl identificador del elemento de llamada a función al que pertenece el delta
output_indexEl índice del elemento de salida en la respuesta. Representa las llamadas a funciones individuales de la respuesta.
deltaEl delta del campo arguments.

El siguiente fragmento de código muestra cómo combinar los delta en un objeto tool_call final.

Acumulación de deltas de tool_call
final_tool_calls = {}

for event in stream:
    if event.type == "response.output_item.added":
        final_tool_calls[event.output_index] = event.item
    elif event.type == "response.function_call_arguments.delta":
        index = event.output_index

        if final_tool_calls[index]:
            final_tool_calls[index].arguments += event.delta
final_tool_calls[0] acumulado
{
    "type": "function_call",
    "id": "fc_1234xyz",
    "call_id": "call_2345abc",
    "name": "get_weather",
    "arguments": "{\"location\":\"Paris, France\"}"
}

Cuando el modelo termine de llamar a las funciones, se emitirá un evento de tipo response.function_call_arguments.done. Este evento contiene la llamada a función completa, incluidos los siguientes campos:

CampoDescripción
response_idEl identificador de la respuesta a la que pertenece la llamada a función
output_indexEl índice del elemento de salida en la respuesta. Representa las llamadas a funciones individuales de la respuesta.
itemEl elemento de llamada a función que incluye los campos name, arguments y id.

Herramientas personalizadas

Las herramientas personalizadas funcionan de forma muy similar a las herramientas de función basadas en esquemas JSON. Sin embargo, en lugar de darle al modelo instrucciones explícitas sobre la entrada que requiere tu herramienta, el modelo puede pasarle una cadena arbitraria como entrada. Esto permite evitar envolver innecesariamente una respuesta en JSON o aplicar una gramática personalizada a la respuesta (más detalles a continuación).

El siguiente ejemplo de código muestra cómo crear una herramienta personalizada que espera recibir como respuesta una cadena de texto con código Python.

Ejemplo de llamada a una herramienta personalizada
from openai import OpenAI

client = OpenAI()

response = client.responses.create(
    model="gpt-6-astra",
    input="Use the code_exec tool to print hello world to the console.",
    tools=[
        {
            "type": "custom",
            "name": "code_exec",
            "description": "Executes arbitrary Python code.",
        }
    ],
)
print(response.output)

Como antes, el arreglo output contendrá una llamada a herramienta generada por el modelo. La diferencia es que esta vez la entrada de la llamada a herramienta se proporciona como texto sin formato.

[
  {
    "id": "rs_6890e972fa7c819ca8bc561526b989170694874912ae0ea6",
    "type": "reasoning",
    "content": [],
    "summary": []
  },
  {
    "id": "ctc_6890e975e86c819c9338825b3e1994810694874912ae0ea6",
    "type": "custom_tool_call",
    "status": "completed",
    "call_id": "call_aGiFQkRWSWAIsMQ19fKqxUgb",
    "input": "print(\"hello world\")",
    "name": "code_exec"
  }
]

Gramáticas libres de contexto

Una gramática libre de contexto (CFG) es un conjunto de reglas que definen cómo producir texto válido en un formato determinado. Para las herramientas personalizadas, puedes proporcionar una CFG que restrinja el texto que el modelo envía como entrada a una herramienta personalizada.

Puedes proporcionar una CFG personalizada mediante el parámetro grammar al configurar una herramienta personalizada. Actualmente, admitimos dos formas de sintaxis de CFG para definir gramáticas: lark y regex.

CFG de Lark

Ejemplo de gramática libre de contexto de Lark
from openai import OpenAI

client = OpenAI()

grammar = """
start: expr
expr: term (SP ADD SP term)* -> add
| term
term: factor (SP MUL SP factor)* -> mul
| factor
factor: INT
SP: " "
ADD: "+"
MUL: "*"
%import common.INT
"""

response = client.responses.create(
    model="gpt-6-astra",
    input="Use the math_exp tool to add four plus four.",
    tools=[
        {
            "type": "custom",
            "name": "math_exp",
            "description": "Creates valid mathematical expressions",
            "format": {
                "type": "grammar",
                "syntax": "lark",
                "definition": grammar,
            },
        }
    ],
)
print(response.output)

La salida de la herramienta debería ajustarse entonces a la CFG de Lark que definiste:

[
  {
    "id": "rs_6890ed2b6374819dbbff5353e6664ef103f4db9848be4829",
    "type": "reasoning",
    "content": [],
    "summary": []
  },
  {
    "id": "ctc_6890ed2f32e8819daa62bef772b8c15503f4db9848be4829",
    "type": "custom_tool_call",
    "status": "completed",
    "call_id": "call_pmlLjmvG33KJdyVdC4MVdk5N",
    "input": "4 + 4",
    "name": "math_exp"
  }
]

Las gramáticas se especifican mediante una variante de Lark. El muestreo del modelo se restringe mediante LLGuidance. Algunas funciones de Lark no son compatibles:

  • Aserciones de anticipación y retrospección en las expresiones regulares del analizador léxico
  • Modificadores no codiciosos (*?, +?, ??) en las expresiones regulares del analizador léxico
  • Prioridades de los terminales
  • Plantillas
  • Importaciones (excepto la instrucción integrada %import common)
  • Directivas %declare

Recomendamos usar el Lark IDE para experimentar con gramáticas personalizadas.

Limitar la complejidad de la gramática

Limita tu gramática a las reglas y los patrones que necesita tu herramienta. La API de OpenAI puede devolver un error si la gramática es demasiado compleja, por lo que debes asegurarte de que la gramática que quieres usar sea compatible antes de utilizarla en la API.

Perfeccionar las gramáticas de Lark puede ser difícil. Las gramáticas menos complejas funcionan con mayor fiabilidad, mientras que las complejas suelen requerir ajustes sucesivos en la propia definición de la gramática, el prompt y la descripción de la herramienta para garantizar que el modelo no se salga de la distribución.

Patrones correctos e incorrectos

Correcto (un solo terminal acotado):

start: SENTENCE
SENTENCE: /[A-Za-z, ]*(the hero|a dragon|an old man|the princess)[A-Za-z, ]*(fought|saved|found|lost)[A-Za-z, ]*(a treasure|the kingdom|a secret|his way)[A-Za-z, ]*\./

NO hagas esto (dividir entre reglas o terminales). Este enfoque intenta que las reglas repartan el texto libre entre terminales. El analizador léxico buscará coincidencias de forma voraz en los fragmentos de texto libre y perderás el control:

start: sentence
sentence: /[A-Za-z, ]+/ subject /[A-Za-z, ]+/ verb /[A-Za-z, ]+/ object /[A-Za-z, ]+/

Las reglas en minúsculas no influyen en cómo se extraen los terminales de la entrada; solo lo hacen las definiciones de los terminales. Cuando necesites “texto libre entre anclas”, exprésalo como un único terminal con una gran expresión regular para que el analizador léxico lo reconozca exactamente una vez con la estructura que buscas.

Terminales y reglas

Lark usa terminales para los tokens del analizador léxico (por convención, UPPERCASE) y reglas para las producciones del analizador sintáctico (por convención, lowercase). La forma más práctica de mantenerse dentro del subconjunto admitido y evitar sorpresas es definir la gramática de forma explícita, evitar la complejidad innecesaria y usar terminales y reglas con una clara separación de responsabilidades.

La sintaxis de expresiones regulares que usan los terminales es la sintaxis del crate regex de Rust, no la del módulo re de Python.

Conceptos clave y prácticas recomendadas

El analizador léxico se ejecuta antes que el analizador sintáctico

El analizador léxico reconoce los terminales de forma voraz (prevalece la coincidencia más larga) antes de que se aplique la lógica de cualquier regla de la CFG. Si intentas “dar forma” a un terminal dividiéndolo entre varias reglas, esas reglas no pueden guiar al analizador léxico; solo pueden hacerlo las expresiones regulares de los terminales.

Prefiere un solo terminal para extraer texto de fragmentos de formato libre

Si necesitas reconocer un patrón dentro de un texto arbitrario (por ejemplo, lenguaje natural con “cualquier cosa” entre anclas), exprésalo como un solo terminal. No intentes intercalar terminales de texto libre con reglas del analizador sintáctico; el analizador léxico voraz no respetará los límites que pretendes establecer y es muy probable que el modelo se salga de la distribución.

Usa reglas para combinar tokens discretos

Las reglas son ideales para combinar terminales delimitados explícitamente (números, palabras clave, signos de puntuación) en estructuras más grandes. No son la herramienta adecuada para restringir “lo que hay entre” dos terminales.

Mantén los terminales específicos, acotados y autocontenidos

Prioriza las clases de caracteres explícitas y los cuantificadores acotados ({0,10}, en lugar de usar * sin límites en todas partes). Si necesitas “cualquier texto hasta un punto”, usa algo como /[^.\n]{0,10}*\./ en lugar de /.+\./ para evitar un crecimiento descontrolado.

Usa reglas para combinar tokens, no para controlar el funcionamiento interno de las expresiones regulares

Ejemplo de uso adecuado de reglas:

start: expr
NUMBER: /[0-9]+/
PLUS: "+"
MINUS: "-"
expr: term (("+"|"-") term)*
term: NUMBER

Maneja los espacios en blanco de forma explícita

No dependas de directivas %ignore sin límites. Usar directivas de omisión sin límites puede hacer que la gramática sea demasiado compleja o que el modelo se salga de la distribución, o ambas cosas. Prefiere insertar terminales explícitos en cada lugar donde se permitan espacios en blanco.

Solución de problemas

  • Si la API rechaza la gramática porque es demasiado compleja, simplifica las reglas y los terminales y elimina las directivas %ignore sin límites.
  • Si se llama a las herramientas personalizadas con tokens inesperados, confirma que los terminales no se superpongan; revisa el comportamiento voraz del analizador léxico.
  • Cuando el modelo se desvía “fuera de la distribución” (esto se manifiesta en salidas excesivamente largas o repetitivas, sintácticamente válidas pero semánticamente incorrectas):
    • Restringe más la gramática.
    • Ajusta de forma iterativa el prompt (agrega unos pocos ejemplos) y la descripción de la herramienta (explica la gramática e indica al modelo que razone y se ajuste a ella).
    • Prueba un mayor esfuerzo de razonamiento (por ejemplo, pasa de Media a Alta).

CFG con expresiones regulares

Ejemplo de gramática libre de contexto con expresiones regulares
from openai import OpenAI

client = OpenAI()

grammar = r"^(?P<month>January|February|March|April|May|June|July|August|September|October|November|December)\s+(?P<day>\d{1,2})(?:st|nd|rd|th)?\s+(?P<year>\d{4})\s+at\s+(?P<hour>0?[1-9]|1[0-2])(?P<ampm>AM|PM)$"

response = client.responses.create(
    model="gpt-6-astra",
    input="Use the timestamp tool to save a timestamp for August 7th 2025 at 10AM.",
    tools=[
        {
            "type": "custom",
            "name": "timestamp",
            "description": "Saves a timestamp in date + time in 24-hr format.",
            "format": {
                "type": "grammar",
                "syntax": "regex",
                "definition": grammar,
            },
        }
    ],
)
print(response.output)

La salida de la herramienta debería ajustarse entonces a la CFG con expresiones regulares que definiste:

[
  {
    "id": "rs_6894f7a3dd4c81a1823a723a00bfa8710d7962f622d1c260",
    "type": "reasoning",
    "content": [],
    "summary": []
  },
  {
    "id": "ctc_6894f7ad7fb881a1bffa1f377393b1a40d7962f622d1c260",
    "type": "custom_tool_call",
    "status": "completed",
    "call_id": "call_8m4XCnYvEmFlzHgDHbaOCFlK",
    "input": "August 7th 2025 at 10AM",
    "name": "timestamp"
  }
]

Al igual que con la sintaxis de Lark, las expresiones regulares usan la sintaxis del crate regex de Rust, no la del módulo re de Python.

Algunas funciones de las expresiones regulares no son compatibles:

  • Aserciones de anticipación y retrospección
  • Modificadores no voraces (*?, +?, ??)

Conceptos clave y prácticas recomendadas

El patrón debe estar en una sola línea

Si necesitas reconocer un salto de línea en la entrada, usa la secuencia de escape \n. No uses el modo detallado o extendido, que permite que los patrones abarquen varias líneas.

Proporciona la expresión regular como una cadena que contenga solo el patrón

No encierres el patrón entre //.