For the complete documentation index, see llms.txt. Markdown versions of documentation pages are available by appending .md to the page URL.
Navigation principale
Theme

Appel de fonction

Donnez aux modèles accès à de nouvelles fonctionnalités et données pour suivre des instructions et répondre aux prompts.

L’appel de fonction, aussi appelé appel d’outil, offre aux modèles OpenAI un moyen puissant et flexible d’interagir avec des systèmes externes et d’accéder à d’autres données que celles de leur entraînement. Ce guide explique comment donner à un modèle accès aux données et aux actions de votre application. Vous y verrez comment utiliser des outils de type fonction, définis par un schéma JSON, ainsi que des outils personnalisés qui acceptent et renvoient du texte libre.

Pour les sessions de l’API Agents, suivez les instructions de la page Fonctions pour enregistrer des fonctions et traiter les demandes d’action d’une session. Les exemples de ce guide illustrent les intégrations avec les API Responses et Chat Completions.

Si votre application comporte de nombreuses fonctions ou des schémas volumineux, vous pouvez associer l’appel de fonction à la recherche d’outils pour différer le chargement des outils rarement utilisés jusqu’à ce que le modèle en ait besoin. Seuls gpt-5.4 et les modèles ultérieurs prennent en charge tool_search.

Avec GPT-6 Astra et GPT-6.1 Sol, les appels d’outils nécessitent l’API Responses. Les exemples de l’API Chat Completions utilisent GPT-5.6 Terra avec le raisonnement désactivé. Consultez le guide de migration pour mettre à jour une intégration existante.

Fonctionnement

Quelques notions permettent de comprendre les appels d’outils. Une fois ces notions définies, nous verrons comment les mettre en pratique à partir d’exemples.

Déroulement d’un appel d’outil

L’appel d’outil est un échange en plusieurs étapes entre votre application et un modèle via l’API OpenAI. Il se déroule en cinq grandes étapes :

  1. Envoyez une requête au modèle en précisant les outils qu’il peut appeler.
  2. Recevez un appel d’outil du modèle.
  3. Exécutez le code côté application avec les données fournies dans l’appel.
  4. Envoyez une deuxième requête au modèle avec le résultat de l’outil.
  5. Recevez la réponse finale du modèle (ou d’autres appels d’outils).

Schéma des étapes d’un appel de fonction

Avec Responses, votre application peut poursuivre cet échange aussi longtemps que la tâche nécessite d’autres appels d’outils. Si vous recherchez un framework qui prend en charge les opérations récurrentes d’orchestration de cette boucle, consultez la comparaison entre l’API Responses et le SDK Agents.

Exemple d’outil de type fonction

Voyons comment se déroule un appel d’outil de bout en bout avec une fonction get_horoscope qui récupère l’horoscope du jour pour un signe astrologique.

Exemple complet d’appel d’outil
from openai import OpenAI
import json

client = OpenAI()

# 1. Define a list of callable tools for the model
tools = [
    {
        "type": "function",
        "name": "get_horoscope",
        "strict": True,
        "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"],
            "additionalProperties": False,
        },
    },
]


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,
                }
            )

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(response.output_text)

Pour les modèles de raisonnement comme GPT-5 ou o4-mini, vous devez également renvoyer avec les résultats des appels d’outils tous les éléments de raisonnement que le modèle a inclus dans les réponses contenant ces appels. Pour savoir comment conserver l’historique des échanges qui ont conduit le modèle à produire ces éléments, consultez les bonnes pratiques pour modifier le préfixe.

Définition des fonctions

En général, vous déclarez les fonctions dans le paramètre tools de chaque requête API. Avec la recherche d’outils, votre application peut aussi charger certaines fonctions plus tard dans l’échange. Dans les deux cas, le schéma de chaque fonction appelable suit la même structure. Une définition de fonction comporte les propriétés suivantes :

Champ Description
type Doit toujours valoir function
name Nom de la fonction (par exemple, get_weather)
description Précisions sur les circonstances et la manière d’utiliser la fonction
parameters Schéma JSON qui définit les arguments d’entrée de la fonction
strict Indique si le mode strict s’applique à l’appel de fonction

Voici un exemple de définition pour la fonction 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
}

Comme le champ parameters contient un schéma JSON, vous pouvez utiliser de nombreuses fonctionnalités de ce format, notamment les types de propriétés, les énumérations, les descriptions, les objets imbriqués et les objets récursifs.

Définition des espaces de noms

Utilisez des espaces de noms pour regrouper les outils par domaine, par exemple crm, billing ou shipping. Ils permettent d’organiser les outils similaires et sont particulièrement utiles lorsque le modèle doit choisir entre des outils destinés à des systèmes ou à des usages différents, par exemple un outil de recherche pour votre CRM et un autre pour votre système de gestion des tickets d’assistance.

{
  "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
      }
    }
  ]
}

Recherche d’outils

Si vous devez donner au modèle accès à un grand nombre d’outils, vous pouvez différer le chargement de tout ou partie de ces outils avec tool_search. L’outil tool_search permet au modèle de rechercher les outils pertinents, de les ajouter à son contexte, puis de les utiliser. Seuls gpt-5.4 et les modèles ultérieurs le prennent en charge. Pour en savoir plus, consultez le guide de recherche d’outils.

Bonnes pratiques pour définir des fonctions

  1. Choisissez des noms de fonctions explicites et rédigez des descriptions de paramètres et des instructions claires et détaillées.

    • Précisez le rôle de la fonction et de chaque paramètre, ainsi que le format de chaque paramètre et la signification du résultat.
    • Indiquez dans le prompt système quand utiliser chaque fonction et quand s’en abstenir. En règle générale, dites au modèle exactement quoi faire.
    • Incluez des exemples et des cas limites, en particulier pour corriger les erreurs récurrentes. (Remarque : l’ajout d’exemples peut nuire aux performances des modèles de raisonnement.)
    • Pour les outils à chargement différé, détaillez les consignes dans la description de la fonction et décrivez brièvement l’espace de noms. L’espace de noms aide le modèle à choisir les outils à charger. La description de la fonction l’aide à utiliser correctement l’outil une fois chargé.
  2. Appliquez les bonnes pratiques du génie logiciel.

    • Rendez les fonctions prévisibles et intuitives (principe de moindre surprise).
    • Utilisez des énumérations et structurez les objets de façon à empêcher les états invalides. Par exemple, toggle_light(on: bool, off: bool) autorise des appels invalides.
    • Faites le test avec un stagiaire. Une personne qui découvre la fonction peut-elle l’utiliser correctement avec les seules informations fournies au modèle ? (Sinon, quelles questions vous pose-t-elle ? Ajoutez les réponses au prompt.)
  3. Allégez le travail du modèle en utilisant du code chaque fois que possible.

    • Ne demandez pas au modèle de renseigner des arguments dont vous connaissez déjà la valeur. Par exemple, si vous disposez déjà d’un order_id à la suite d’un choix dans un menu, n’incluez pas de paramètre order_id. Définissez plutôt submit_refund() sans paramètres et transmettez order_id dans votre code.
    • Regroupez les fonctions que vous appelez toujours l’une après l’autre. Par exemple, si vous appelez toujours mark_location() après query_location(), intégrez simplement la logique de marquage à la fonction de recherche.
  4. Limitez le nombre de fonctions disponibles au départ pour améliorer la précision des appels.

    • Évaluez les performances en faisant varier le nombre de fonctions.
    • Visez moins de 20 fonctions disponibles au début de chaque tour. Ce nombre est indicatif.
    • Utilisez la recherche d’outils pour différer le chargement des groupes d’outils volumineux ou peu utilisés au lieu de tout charger dès le départ.
  5. Tirez parti des ressources OpenAI.

    • Générez et affinez vos schémas de fonctions dans le Playground.
    • Envisagez le fine-tuning pour améliorer la précision des appels de fonction si les fonctions sont nombreuses ou les tâches difficiles (exemple du Cookbook).

Consommation de tokens

Les fonctions sont insérées dans le message système selon une syntaxe que le modèle a apprise pendant son entraînement. Les définitions des fonctions appelables occupent donc une partie de la fenêtre de contexte du modèle et sont facturées en tokens d’entrée. Si vous atteignez les limites de tokens, nous vous conseillons de charger moins de fonctions au départ, de raccourcir les descriptions lorsque c’est possible ou d’utiliser la recherche d’outils pour charger les outils différés uniquement lorsque le modèle en a besoin.

Si votre spécification d’outils définit de nombreuses fonctions, vous pouvez aussi recourir au fine-tuning pour réduire le nombre de tokens utilisés.

Traitement des appels de fonction

Lorsque le modèle appelle une fonction, vous devez l’exécuter et renvoyer le résultat. Une réponse peut contenir zéro, un ou plusieurs appels. Prévoyez donc de traiter plusieurs appels.

Le tableau output de la réponse contient une entrée dont le champ type vaut function_call. Chaque entrée de ce type comprend un call_id (utilisé ensuite pour transmettre le résultat de la fonction), un champ name et des arguments encodés en JSON.

Exemple de réponse avec plusieurs appels de fonction
[
    {
        "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 vous utilisez la recherche d’outils, des éléments tool_search_call et tool_search_output peuvent aussi apparaître avant un élément function_call. Une fois la fonction chargée, traitez l’appel comme dans cet exemple.

Exécution des appels de fonction et ajout des résultats
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),
        }
    )

L’exemple ci-dessus suppose l’existence d’une fonction call_function qui dirige chaque appel vers la fonction correspondante. Voici une implémentation possible :

Exécution des appels de fonction et ajout des résultats
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}")

Format des résultats

Le résultat transmis dans le message function_call_output est généralement une chaîne de caractères dont vous choisissez le format (JSON, code d’erreur, texte brut, etc.). Le modèle interprète cette chaîne selon les besoins.

Pour les fonctions qui renvoient des images ou des fichiers, vous pouvez transmettre un tableau d’objets représentant des images ou des fichiers à la place d’une chaîne.

Si votre fonction n’a pas de valeur de retour (par exemple, send_email), renvoyez une chaîne indiquant la réussite ou l’échec, comme "success".

Intégration des résultats à la réponse

Après avoir ajouté les résultats à input, vous pouvez renvoyer le tout au modèle pour obtenir une réponse finale.

Renvoi des résultats au modèle
response = client.responses.create(
    model="gpt-6-astra",
    input=input_messages,
    tools=responses_tools,
)
Réponse finale
"It's about 15°C in Paris, 18°C in Bogotá, and I've sent that email to Bob."

Options de configuration supplémentaires

Choix des outils

Par défaut, le modèle décide quand utiliser des outils et combien en utiliser. Vous pouvez imposer un comportement précis avec le paramètre tool_choice.

  1. Automatique : (par défaut) le modèle peut appeler zéro, une ou plusieurs fonctions. tool_choice: "auto"
  2. Appel obligatoire : le modèle doit appeler au moins une fonction. tool_choice: "required"
  3. Fonction imposée : le modèle doit effectuer exactement un appel à la fonction spécifiée. tool_choice: {"type": "function", "name": "get_weather"}
  4. Outils autorisés : le modèle peut uniquement appeler un sous-ensemble des outils mis à sa disposition.

Quand utiliser allowed_tools

Configurez une liste allowed_tools si vous souhaitez rendre seulement certains outils accessibles selon la requête tout en conservant la même liste d’outils en entrée. Vous pouvez ainsi maximiser les économies liées à la mise en cache des prompts.

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

Vous pouvez également définir tool_choice sur "none" pour obtenir le même comportement que si vous ne transmettiez aucune fonction.

Lorsque vous utilisez la recherche d’outils, tool_choice s’applique toujours aux outils appelables à ce stade du tour. Cette option est particulièrement utile lorsque vous venez de charger un sous-ensemble d’outils et souhaitez limiter le modèle à ces outils.

Appels de fonction en parallèle

À partir de GPT-5, les modèles compatibles peuvent appeler des fonctions en parallèle même si des outils intégrés sont également disponibles. Un lot d’appels de fonction parallèles ne peut pas inclure d’outils intégrés.

Le modèle peut choisir d’appeler plusieurs fonctions au cours d’un même tour. Pour l’en empêcher, définissez parallel_tool_calls sur false. Le modèle appellera alors au plus un outil.

Remarque : actuellement, si un modèle ayant fait l’objet d’un fine-tuning appelle plusieurs fonctions au cours d’un même tour, le mode strict est désactivé pour ces appels.

Remarque concernant gpt-4.1-nano-2025-04-14 : cette version de gpt-4.1-nano peut parfois appeler plusieurs fois le même outil si les appels parallèles sont activés. Nous vous recommandons de désactiver cette fonctionnalité avec cette version.

Mode strict

Définissez strict sur true pour garantir que les appels de fonction respectent le schéma de la fonction. Sans cette garantie, le modèle fait au mieux pour s’y conformer. Nous vous recommandons de toujours activer le mode strict.

Le mode strict s’appuie sur les sorties structurées et impose donc deux contraintes :

  1. additionalProperties doit valoir false pour chaque objet dans parameters.
  2. Tous les champs de properties doivent figurer dans required.

Pour rendre un champ facultatif, ajoutez null aux valeurs possibles de type (voir l’exemple ci-dessous).

Si vous envoyez strict: true et que votre schéma ne respecte pas ces exigences, l’API rejette la requête et indique les contraintes manquantes. Si vous omettez strict, le comportement par défaut dépend de l’API. L’API Responses tente d’adapter votre schéma au mode strict. Si elle ne peut pas le rendre compatible, elle utilise le mode non strict, dans lequel le modèle fait au mieux pour respecter le schéma. Dans ce cas, l’outil figurant dans la réponse indique strict: false. Les requêtes Chat Completions utilisent par défaut le mode non strict. Pour désactiver le mode strict dans Responses et conserver les appels de fonction sans garantie de conformité, spécifiez explicitement 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
    }
}

Le mode strict est activé pour tous les schémas générés dans le Playground.

Nous vous recommandons d’activer le mode strict, mais il présente quelques limites :

  1. Certaines fonctionnalités des schémas JSON ne sont pas prises en charge (voir les schémas pris en charge).

Les limites suivantes concernent spécifiquement les modèles ayant fait l’objet d’un fine-tuning :

  1. Les schémas font l’objet d’un traitement supplémentaire lors de la première requête, puis sont mis en cache. Si vos schémas varient d’une requête à l’autre, la latence peut augmenter.
  2. Les schémas sont mis en cache pour améliorer les performances et ne sont pas éligibles à la politique de non-conservation des données.

Diffusion en continu

La diffusion en continu permet de suivre la progression d’un appel : vous pouvez afficher la fonction appelée pendant que le modèle renseigne ses arguments, et même afficher les arguments en temps réel.

La diffusion en continu des appels de fonction fonctionne comme celle des réponses ordinaires : définissez stream sur true pour recevoir différents objets event.

Diffusion en continu des appels de fonction
from openai import OpenAI

client = OpenAI()

tools = [
    {
        "type": "function",
        "name": "get_weather",
        "strict": True,
        "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)
Événements de sortie
{"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\"}"}}

Au lieu d’assembler les fragments en une seule chaîne content, vous les assemblez pour reconstituer l’objet arguments encodé en JSON.

Lorsque le modèle appelle une ou plusieurs fonctions, l’API émet pour chaque appel un événement de type response.output_item.added contenant les champs suivants :

Champ Description
response_id Identifiant de la réponse à laquelle appartient l’appel de fonction.
output_index Indice de l’élément de sortie dans la réponse. Il permet de distinguer les différents appels de fonction dans cette réponse.
item Élément représentant l’appel de fonction en cours, avec les champs name, arguments et id.

Vous recevez ensuite une série d’événements de type response.function_call_arguments.delta, qui contiennent le delta du champ arguments. Ces événements comportent les champs suivants :

Champ Description
response_id Identifiant de la réponse à laquelle appartient l’appel de fonction.
item_id Identifiant de l’élément d’appel de fonction auquel appartient le fragment.
output_index Indice de l’élément de sortie dans la réponse. Il permet de distinguer les différents appels de fonction dans cette réponse.
delta Fragment du champ arguments.

L’extrait de code suivant montre comment assembler les delta pour obtenir l’objet tool_call final.

Assemblage des fragments de tool_call
final_tool_calls = {}

for event in stream:
    if (
        event.type == "response.output_item.added"
        and event.item.type == "function_call"
    ):
        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] après assemblage
{
    "type": "function_call",
    "id": "fc_1234xyz",
    "call_id": "call_2345abc",
    "name": "get_weather",
    "arguments": "{\"location\":\"Paris, France\"}"
}

Lorsque le modèle a fini d’appeler les fonctions, l’API émet un événement de type response.function_call_arguments.done. Cet événement contient l’appel de fonction complet, avec les champs suivants :

Champ Description
response_id Identifiant de la réponse à laquelle appartient l’appel de fonction.
output_index Indice de l’élément de sortie dans la réponse. Il permet de distinguer les différents appels de fonction dans cette réponse.
item Élément représentant l’appel de fonction, avec les champs name, arguments et id.

Outils personnalisés

Les outils personnalisés fonctionnent de manière similaire aux outils de type fonction définis par un schéma JSON. Toutefois, vous n’avez pas à préciser explicitement la structure des données attendues : le modèle peut transmettre à l’outil une chaîne de caractères arbitraire en entrée. Vous évitez ainsi d’encapsuler inutilement une réponse dans du JSON. Vous pouvez aussi appliquer une grammaire personnalisée à la réponse, comme expliqué plus loin.

L’exemple suivant montre comment créer un outil personnalisé qui attend du modèle une chaîne de caractères contenant du code Python.

Exemple d’appel d’un outil personnalisé
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)

Comme précédemment, le tableau output contient un appel d’outil généré par le modèle. Cette fois, l’entrée de l’appel est fournie sous forme de texte brut.

[
  {
    "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"
  }
]

Grammaires hors contexte

Une grammaire hors contexte (CFG) est un ensemble de règles qui définissent comment produire du texte valide dans un format donné. Pour un outil personnalisé, vous pouvez fournir une CFG afin de contraindre le texte que le modèle lui transmet en entrée.

Lors de la configuration d’un outil personnalisé, utilisez le paramètre grammar pour fournir votre CFG. Deux syntaxes sont actuellement prises en charge pour définir les grammaires : lark et regex.

CFG Lark

Exemple de grammaire hors contexte avec 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 sortie obtenue pour l’outil devrait alors respecter la CFG Lark que vous avez définie :

[
  {
    "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"
  }
]

Les grammaires utilisent une variante de Lark. LLGuidance contraint l’échantillonnage du modèle. Certaines fonctionnalités de Lark ne sont pas prises en charge :

  • Les assertions avant et arrière dans les expressions régulières de l’analyseur lexical
  • Les quantificateurs non gourmands (*?, +?, ??) dans les expressions régulières de l’analyseur lexical
  • Les priorités des terminaux
  • Les modèles de règles
  • Les imports (sauf l’import intégré %import common)
  • Les directives %declare

Nous vous recommandons d’utiliser l’IDE Lark pour expérimenter avec vos grammaires personnalisées.

Limitez la complexité de la grammaire

Limitez votre grammaire aux règles et aux motifs nécessaires à votre outil. L’API OpenAI peut renvoyer une erreur si la grammaire est trop complexe. Vérifiez donc sa compatibilité avant de l’utiliser dans l’API.

La mise au point d’une grammaire Lark peut être délicate. Les grammaires simples sont les plus fiables. Pour une grammaire complexe, il faut souvent ajuster à plusieurs reprises sa définition, le prompt et la description de l’outil pour éviter que le modèle ne produise des sorties hors distribution.

Motifs corrects et incorrects

Exemple correct (un seul terminal délimité) :

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, ]*\./

Ne découpez PAS le motif en plusieurs règles ou terminaux comme ci-dessous. Cette approche suppose que les règles peuvent répartir le texte libre entre les terminaux. Or l’analyseur lexical retient la correspondance la plus longue pour chaque fragment de texte libre et vous perdez la maîtrise du découpage :

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

Seules les définitions des terminaux déterminent comment l’analyseur lexical découpe l’entrée. Les règles en minuscules n’influencent pas ce découpage. Si vous devez reconnaître du « texte libre entre des points d’ancrage », définissez un seul terminal dont l’expression régulière couvre tout le motif. L’analyseur lexical le reconnaîtra ainsi en une seule fois, selon la structure voulue.

Terminaux et règles

Lark utilise les terminaux pour les tokens de l’analyseur lexical (par convention, UPPERCASE) et les règles pour les productions de l’analyseur syntaxique (par convention, lowercase). Pour vous en tenir aux fonctionnalités prises en charge et éviter les surprises, définissez une grammaire explicite, évitez toute complexité superflue et attribuez des rôles distincts aux terminaux et aux règles.

Les expressions régulières des terminaux suivent la syntaxe de la crate regex de Rust. Cette syntaxe diffère de celle de re, le module Python.

Principes et bonnes pratiques

L’analyse lexicale précède l’analyse syntaxique

L’analyseur lexical reconnaît les terminaux en retenant la correspondance la plus longue avant toute application des règles de la CFG. Si vous tentez de « façonner » un terminal en le répartissant sur plusieurs règles, l’analyseur lexical tiendra uniquement compte des expressions régulières des terminaux, sans suivre ces règles.

Privilégiez un seul terminal pour extraire un motif de passages en texte libre

Pour reconnaître un motif dans du texte arbitraire (par exemple, une phrase en langage naturel qui peut contenir « n’importe quoi » entre des points d’ancrage), exprimez-le sous la forme d’un seul terminal. Évitez d’alterner terminaux de texte libre et règles syntaxiques : l’analyseur lexical retient la correspondance la plus longue sans respecter les limites voulues, et le modèle risque fortement de produire des sorties hors distribution.

Utilisez les règles pour combiner des tokens distincts

Les règles conviennent pour assembler des terminaux explicitement délimités (nombres, mots-clés, signes de ponctuation) en structures plus grandes. Elles ne permettent pas de contraindre efficacement « ce qui se trouve entre » deux terminaux.

Définissez des terminaux ciblés, bornés et autonomes

Privilégiez les classes de caractères explicites et les quantificateurs bornés ({0,10} plutôt que * sans limite partout). Pour reconnaître « tout texte jusqu’au prochain point », préférez un motif comme /[^.\n]{0,10}*\./ à /.+\./ pour éviter que la longueur du texte n’augmente de façon incontrôlée.

Utilisez les règles pour combiner les tokens, sans intervenir dans le fonctionnement des expressions régulières

Exemple d’utilisation appropriée des règles :

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

Traitez explicitement les espaces blancs

Évitez les directives %ignore sans limite. Elles peuvent rendre la grammaire trop complexe ou amener le modèle à produire des sorties hors distribution, voire les deux. Insérez plutôt des terminaux explicites partout où les espaces blancs sont autorisés.

Résolution des problèmes

  • Si l’API rejette une grammaire trop complexe, simplifiez les règles et les terminaux, puis supprimez les directives %ignore sans limite.
  • Si les appels d’outils personnalisés contiennent des tokens inattendus, vérifiez que les terminaux ne se chevauchent pas et examinez les correspondances retenues par l’analyseur lexical.
  • Si le modèle produit des sorties « hors distribution » (excessivement longues ou répétitives, syntaxiquement valides mais sémantiquement incorrectes) :
    • Rendez la grammaire plus restrictive.
    • Affinez le prompt en ajoutant des exemples few-shot. Revoyez aussi la description de l’outil : expliquez la grammaire et demandez au modèle de raisonner et de s’y conformer.
    • Essayez un effort de raisonnement plus élevé, par exemple en passant d’un niveau moyen à un niveau élevé.

CFG avec expression régulière

Exemple de grammaire hors contexte avec une expression régulière
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 sortie obtenue pour l’outil devrait alors respecter la CFG que vous avez définie avec une expression régulière :

[
  {
    "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"
  }
]

Comme avec Lark, les expressions régulières suivent la syntaxe de la crate regex de Rust. Cette syntaxe diffère de celle de re, le module Python.

Certaines fonctionnalités des expressions régulières ne sont pas prises en charge :

  • Les assertions avant et arrière
  • Les quantificateurs non gourmands (*?, +?, ??)

Principes et bonnes pratiques

Le motif doit tenir sur une seule ligne

Pour reconnaître un saut de ligne dans l’entrée, utilisez la séquence d’échappement \n. N’utilisez pas le mode détaillé ou étendu, qui permet de répartir les motifs sur plusieurs lignes.

Fournissez le motif de l’expression régulière sous forme de chaîne simple

N’entourez pas le motif de //.