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

Appel de fonction

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

L’appel de fonction (également 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 à des données autres que leurs données d’entraînement. Ce guide vous montre comment connecter un modèle aux données et aux actions fournies par votre application. Nous verrons comment utiliser les outils de type fonction (définis par un schéma JSON) et les outils personnalisés, qui acceptent du texte libre en entrée et en sortie.

Pour les sessions de l’API Agents, utilisez les Fonctions pour enregistrer des fonctions et traiter les demandes d’action de la session. Les exemples de ce guide présentent les intégrations avec l’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 et ne les charger que lorsque le modèle en a besoin. Seuls gpt-5.4 et les modèles ultérieurs prennent en charge tool_search.

GPT-6 Astra nécessite l’API Responses pour les appels d’outils. Les exemples Chat Completions utilisent GPT-5.6 pour des raisons de compatibilité. Consultez le guide de migration pour mettre à jour une intégration existante.

Fonctionnement

Commençons par quelques termes clés liés aux appels d’outils. Une fois ce vocabulaire commun établi, nous vous montrerons comment procéder à l’aide d’exemples pratiques.

Déroulement des appels d’outils

L’appel d’outil est une conversation 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 avec les outils qu’il pourrait appeler
  2. Recevez un appel d’outil du modèle
  3. Exécutez du code côté application avec les données d’entrée de l’appel d’outil
  4. Envoyez une deuxième requête au modèle avec le résultat de l’outil
  5. Recevez une réponse finale du modèle (ou d’autres appels d’outils)

Schéma des étapes de l’appel de fonction

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

Exemple d’outil de type fonction

Voyons un exemple complet d’appel d’outil 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",
        "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)

Pour les modèles de raisonnement comme GPT-5 ou o4-mini, tous les éléments de raisonnement renvoyés dans les réponses du modèle contenant des appels d’outils doivent également être retransmis avec les résultats de ces appels.

Définition des fonctions

Les fonctions sont généralement déclarées dans le paramètre tools de chaque requête API. Avec la recherche d’outils, votre application peut également charger des fonctions de manière différée au cours de l’interaction. Dans les deux cas, chaque fonction appelable utilise la même structure de schéma. La définition d’une fonction comporte les propriétés suivantes :

ChampDescription
typeLa valeur doit toujours être function
nameLe nom de la fonction (par exemple, get_weather)
descriptionPrécisions sur les situations dans lesquelles utiliser la fonction et sur la manière de l’utiliser
parametersSchéma JSON définissant les arguments d’entrée de la fonction
strictIndique si le mode strict doit être imposé à l’appel de fonction

Voici un exemple de définition pour une 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 parameters est défini par un schéma JSON, vous pouvez utiliser ses nombreuses fonctionnalités, 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 apparentés par domaine, comme crm, billing ou shipping. Les espaces de noms facilitent l’organisation d’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
      }
    }
  ]
}

Si vous devez donner au modèle accès à un vaste écosystème 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 des 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. Consultez le guide de la recherche d’outils pour en savoir plus.

Bonnes pratiques pour définir des fonctions

  1. Choisissez des noms de fonctions clairs et rédigez des descriptions de paramètres et des instructions précises et détaillées.

    • Décrivez explicitement le rôle de la fonction et de chaque paramètre (ainsi que son format), et précisez ce que représente la sortie.
    • Utilisez le prompt système pour décrire quand utiliser chaque fonction et quand ne pas l’utiliser. De manière générale, indiquez au modèle exactement ce qu’il doit faire.
    • Incluez des exemples et des cas limites, notamment pour corriger les échecs récurrents. (Remarque : l’ajout d’exemples peut nuire aux performances des modèles de raisonnement.)
    • Pour les outils à chargement différé, placez les instructions détaillées dans la description de la fonction et gardez une description concise de l’espace de noms. L’espace de noms aide le modèle à choisir ce qu’il doit charger ; la description de la fonction l’aide à utiliser correctement l’outil 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 la structure des objets pour empêcher les états invalides. Par exemple, toggle_light(on: bool, off: bool) autorise des appels invalides.
    • Passez le test du stagiaire. Un stagiaire, ou toute autre personne, peut-il utiliser correctement la fonction avec les seules informations fournies au modèle ? (Si ce n’est pas le cas, quelles questions vous pose-t-il ? Ajoutez les réponses au prompt.)
  3. Allégez la charge 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 obtenu à partir d’un menu précédent, 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 qui sont toujours appelées 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 dès le départ pour améliorer la précision.

    • Évaluez les performances avec différents nombres de fonctions.
    • Visez moins de 20 fonctions disponibles au début de chaque tour , même s’il ne s’agit que d’une recommandation indicative.
    • Utilisez la recherche d’outils pour différer le chargement des ensembles d’outils volumineux ou rarement utilisés, au lieu de tout exposer dès le départ.
  5. Utilisez les ressources d’OpenAI.

    • Générez des schémas de fonctions et améliorez-les progressivement dans le Playground.
    • Envisagez l’affinage pour améliorer la précision des appels de fonction lorsque les fonctions sont nombreuses ou les tâches difficiles. (Cookbook)

Utilisation des tokens

En interne, les fonctions sont injectées dans le message système selon une syntaxe sur laquelle le modèle a été entraîné. Les définitions des fonctions appelables sont donc prises en compte dans la limite de contexte du modèle et facturées comme des tokens d’entrée. Si vous atteignez les limites de tokens, nous vous conseillons de limiter le nombre de fonctions chargées dès le départ, de raccourcir les descriptions lorsque c’est possible ou d’utiliser la recherche d’outils pour ne charger les outils à chargement différé qu’en cas de besoin.

Vous pouvez également recourir à l’affinage pour réduire le nombre de tokens utilisés si votre spécification d’outils définit de nombreuses fonctions.

Gestion des appels de fonction

Lorsque le modèle appelle une fonction, vous devez l’exécuter et renvoyer le résultat. Comme les réponses du modèle peuvent contenir zéro, un ou plusieurs appels, il est recommandé de prévoir le cas où il y en a plusieurs.

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

Exemple de réponse contenant 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 également apparaître avant un function_call. Une fois la fonction chargée, traitez l’appel de fonction de la manière présentée ici.

Exécutez les appels de fonction et ajoutez les 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),
        }
    )

Dans l’exemple ci-dessus, nous utilisons une fonction hypothétique call_function pour acheminer chaque appel. Voici une implémentation possible :

Exécutez les appels de fonction et ajoutez les 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 devrait généralement être une chaîne de caractères, dont vous choisissez le format (JSON, codes d’erreur, texte brut, etc.). Le modèle interprétera cette chaîne selon les besoins.

Pour les fonctions qui renvoient des images ou des fichiers, vous pouvez transmettre un tableau d’objets image ou fichier au lieu d’une chaîne de caractères.

Si votre fonction ne renvoie aucune valeur (par exemple, send_email), renvoyez une chaîne de caractères qui indique 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 les renvoyer au modèle pour obtenir une réponse finale.

Renvoyez les résultats au modèle
response = client.responses.create(
    model="gpt-6-astra",
    input=input_messages,
    tools=responses_tools,
)

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

Configurations supplémentaires

Choix des outils

Par défaut, le modèle détermine 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) Appelez zéro, une ou plusieurs fonctions. tool_choice: "auto"
  2. Obligatoire : Appelez une ou plusieurs fonctions. tool_choice: "required"
  3. Fonction imposée : Appelez exactement une fonction précise. tool_choice: {"type": "function", "name": "get_weather"}
  4. Outils autorisés : Limitez les appels d’outils que le modèle peut effectuer à un sous-ensemble des outils à sa disposition.

Quand utiliser allowed_tools

Vous pouvez configurer une liste allowed_tools pour ne rendre disponible qu’un sous-ensemble d’outils au fil des requêtes au modèle, sans modifier la liste d’outils transmise, afin de maximiser les économies réalisées grâce à 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 qui peuvent être appelés à ce stade du tour. C’est particulièrement utile après avoir chargé un sous-ensemble d’outils, lorsque vous souhaitez limiter le modèle à ce sous-ensemble.

Appels de fonction en parallèle

Sur les modèles compatibles à partir de GPT-5, les fonctions peuvent être appelées en parallèle lorsque des outils intégrés sont également disponibles. Les outils intégrés ne peuvent pas être inclus dans un lot d’appels de fonction en parallèle.

Le modèle peut choisir d’appeler plusieurs fonctions au cours d’un même tour. Vous pouvez l’en empêcher en définissant parallel_tool_calls sur false, ce qui garantit qu’aucun outil ou un seul outil sera appelé.

Remarque : Actuellement, si vous utilisez un modèle affiné et que celui-ci appelle plusieurs fonctions au cours d’un même tour, le mode strict sera désactivé pour ces appels.

Remarque concernant gpt-4.1-nano-2025-04-14 : Cette version de gpt-4.1-nano peut parfois inclure plusieurs appels au même outil si les appels d’outils en parallèle sont activés. Il est recommandé de désactiver cette fonctionnalité lorsque vous utilisez cette version.

Mode strict

Définir strict sur true garantit que les appels de fonction respectent le schéma de la fonction, au lieu de simplement tenter de s’y conformer. Nous recommandons de toujours activer le mode strict.

Le mode strict s’appuie sur notre fonctionnalité de sorties structurées et impose donc quelques exigences :

  1. additionalProperties doit être défini sur false pour chaque objet dans parameters.
  2. Tous les champs de properties doivent être marqués comme required.

Vous pouvez indiquer qu’un champ est facultatif en ajoutant null parmi les options de type (voir l’exemple ci-dessous).

Si vous transmettez strict: true et que votre schéma ne respecte pas les exigences ci-dessus, la requête sera rejetée avec des précisions sur les contraintes manquantes. Si vous omettez strict, le comportement par défaut dépend de l’API : les requêtes Responses tentent de normaliser votre schéma pour le rendre conforme au mode strict lorsque c’est possible, et reviennent à des appels de fonction non stricts, sans garantie de conformité, si le schéma ne peut pas être rendu compatible avec le mode strict. Dans ce cas, l’outil présent dans la réponse affichera strict: false. Les requêtes Chat Completions restent non strictes par défaut. Pour désactiver le mode strict dans Responses et conserver des appels de fonction non stricts, sans garantie de conformité, définissez 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.

Bien que nous recommandions d’activer le mode strict, celui-ci présente quelques limites :

  1. Certaines fonctionnalités de JSON Schema ne sont pas prises en charge. (Consultez les schémas pris en charge.)

Pour les modèles affinés en particulier :

  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, cela peut augmenter la latence.
  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.

Streaming

Le streaming permet de suivre la progression en indiquant quelle fonction est appelée à mesure que le modèle renseigne ses arguments, et même en affichant ces arguments en temps réel.

Le streaming des appels de fonction fonctionne de manière très similaire à celui des réponses classiques : définissez stream sur true pour recevoir différents objets event.

Streaming des appels de fonction
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)
É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\"}"}}

Cependant, au lieu d’agréger les fragments dans une seule chaîne content, vous les agrégez dans un objet arguments encodé en JSON.

Lorsque le modèle appelle une ou plusieurs fonctions, un événement de type response.output_item.added est émis pour chaque appel de fonction. Cet événement contient les champs suivants :

ChampDescription
response_idL’identifiant de la réponse à laquelle appartient l’appel de fonction
output_indexL’index de l’élément de sortie dans la réponse. Il permet de distinguer les différents appels de fonction de la réponse.
itemL’élément d’appel de fonction en cours, qui comprend 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 contiennent les champs suivants :

ChampDescription
response_idL’identifiant de la réponse à laquelle appartient l’appel de fonction
item_idL’identifiant de l’élément d’appel de fonction auquel appartient le delta
output_indexL’index de l’élément de sortie dans la réponse. Il permet de distinguer les différents appels de fonction de la réponse.
deltaLe delta du champ arguments.

L’extrait de code ci-dessous montre comment agréger les éléments delta dans un objet tool_call final.

Accumulation des 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] après accumulation
{
    "type": "function_call",
    "id": "fc_1234xyz",
    "call_id": "call_2345abc",
    "name": "get_weather",
    "arguments": "{\"location\":\"Paris, France\"}"
}

Lorsque le modèle a terminé les appels de fonction, un événement de type response.function_call_arguments.done est émis. Cet événement contient l’intégralité de l’appel de fonction, avec les champs suivants :

ChampDescription
response_idL’identifiant de la réponse à laquelle appartient l’appel de fonction
output_indexL’index de l’élément de sortie dans la réponse. Il permet de distinguer les différents appels de fonction de la réponse.
itemL’élément d’appel de fonction, qui comprend les champs name, arguments et id.

Outils personnalisés

Les outils personnalisés fonctionnent de manière très similaire aux outils de type fonction définis par un schéma JSON. Toutefois, au lieu de recevoir des instructions explicites sur les données d’entrée requises par votre outil, le modèle peut lui transmettre une chaîne de caractères quelconque en entrée. Cela permet d’éviter d’encapsuler inutilement une réponse dans du JSON ou d’appliquer une grammaire personnalisée à la réponse (voir ci-dessous).

L’exemple de code suivant montre comment créer un outil personnalisé qui attend en réponse une chaîne de texte contenant du code Python.

Exemple d’appel d’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, toutefois, les données d’entrée de l’appel d’outil sont fournies en 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 non contextuelles

Une grammaire non contextuelle (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 qui contraint le texte que le modèle lui transmet en entrée.

Vous pouvez fournir une CFG personnalisée à l’aide du paramètre grammar lors de la configuration d’un outil personnalisé. Nous prenons actuellement en charge deux syntaxes de CFG pour définir les grammaires : lark et regex.

CFG Lark

Exemple de grammaire non contextuelle 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 de 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 sont définies à l’aide d’une variante de Lark. L’échantillonnage du modèle est contraint à l’aide de LLGuidance. Certaines fonctionnalités de Lark ne sont pas prises en charge :

  • Assertions avant et arrière dans les expressions régulières de l’analyseur lexical
  • Quantificateurs non gourmands (*?, +?, ??) dans les expressions régulières de l’analyseur lexical
  • Priorités des terminaux
  • Gabarits
  • Imports (autres que l’import intégré %import common)
  • Directives %declare

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

Limitez la complexité de la grammaire

Limitez votre grammaire aux règles et aux motifs dont votre outil a besoin. L’API OpenAI peut renvoyer une erreur si la grammaire est trop complexe. Vérifiez donc que la grammaire souhaitée est compatible avant de l’utiliser dans l’API.

La mise au point des grammaires Lark peut être délicate. Les grammaires les plus simples offrent le fonctionnement le plus fiable. Les grammaires complexes nécessitent souvent des ajustements successifs de leur définition, du prompt et de la description de l’outil pour éviter que le modèle ne se retrouve hors distribution.

Motifs corrects et incorrects

Correct (un seul terminal, borné) :

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 faites PAS ceci (répartition entre plusieurs règles ou terminaux). Cette approche tente de laisser les règles répartir le texte libre entre les terminaux. L’analyseur lexical reconnaîtra les portions de texte libre de manière gloutonne et vous perdrez le contrôle :

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

Les règles en minuscules n’influencent pas le découpage de l’entrée en terminaux : seules les définitions des terminaux le font. Lorsque vous avez besoin de « texte libre entre des ancres », regroupez l’ensemble dans un seul terminal défini par une expression régulière, afin que l’analyseur lexical le reconnaisse en une seule fois, avec la structure voulue.

Terminaux et règles

Lark utilise des terminaux pour les tokens de l’analyseur lexical (par convention, UPPERCASE) et des règles pour les productions de l’analyseur syntaxique (par convention, lowercase). Pour rester dans le sous-ensemble pris en charge et éviter les surprises, le plus pratique est de garder une grammaire explicite, d’éviter toute complexité inutile et de séparer clairement les rôles des terminaux et des règles.

Les terminaux utilisent la syntaxe de la crate Rust regex, et non celle du module re de Python.

Notions clés et bonnes pratiques

L’analyseur lexical s’exécute avant l’analyseur syntaxique

L’analyseur lexical reconnaît les terminaux de manière gloutonne (la correspondance la plus longue l’emporte) avant l’application de toute logique des règles de la CFG. Si vous essayez de « façonner » un terminal en le répartissant entre plusieurs règles, celles-ci ne pourront pas guider l’analyseur lexical : seules les expressions régulières des terminaux le peuvent.

Privilégiez un seul terminal pour extraire du texte à partir de portions de texte libre

Si vous devez reconnaître un motif au sein d’un texte quelconque (par exemple, du langage naturel avec « n’importe quoi » entre les ancres), exprimez-le dans un seul terminal. N’essayez pas d’alterner des terminaux de texte libre et des règles de l’analyseur syntaxique : l’analyseur lexical glouton ne respectera pas les limites prévues et le modèle risque fortement de se retrouver hors distribution.

Utilisez les règles pour combiner des tokens distincts

Les règles sont idéales pour combiner des terminaux explicitement délimités (nombres, mots-clés, ponctuation) en structures plus grandes. Elles ne conviennent pas pour contraindre « 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 des * non bornés partout). Si vous avez besoin de reconnaître « n’importe quel texte jusqu’à un point », préférez une expression comme /[^.\n]{0,10}*\./ à /.+\./ pour éviter une croissance incontrôlée.

Utilisez les règles pour combiner les tokens, pas pour piloter le fonctionnement interne des expressions régulières

Exemple de bonne utilisation des règles :

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

Traitez explicitement les caractères d’espacement

Ne vous appuyez pas sur des directives %ignore sans limites. Des directives d’exclusion non bornées peuvent rendre la grammaire trop complexe ou faire sortir le modèle de sa distribution, voire les deux. Insérez plutôt des terminaux explicites partout où les caractères d’espacement sont autorisés.

Dépannage

  • Si l’API rejette la grammaire parce qu’elle est trop complexe, simplifiez les règles et les terminaux et supprimez les directives %ignore non bornées.
  • Si les outils personnalisés sont appelés avec des tokens inattendus, vérifiez que les terminaux ne se chevauchent pas et examinez le comportement glouton de l’analyseur lexical.
  • Lorsque le modèle dérive « hors distribution » (il produit des sorties excessivement longues ou répétitives, syntaxiquement valides mais sémantiquement incorrectes) :
    • Rendez la grammaire plus restrictive.
    • Ajustez progressivement le prompt (ajoutez des exemples few-shot) et 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 supérieur (par exemple, passez du niveau Médium au niveau Élevé).

CFG à base d’expressions régulières

Exemple de grammaire hors contexte à base d’expressions régulières
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 de l’outil devrait alors respecter la CFG à base d’expressions régulières que vous avez définie :

[
  {
    "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 la syntaxe Lark, les expressions régulières utilisent la syntaxe de la crate Rust regex, et non celle du module re de Python.

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

  • Assertions avant et arrière
  • Quantificateurs non gloutons (*?, +?, ??)

Notions clés 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 verbeux ou étendu, qui permet de répartir les motifs sur plusieurs lignes.

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

N’encadrez pas le motif avec //.