Transformers documentation

Écrire un modèle de chat

You are viewing main version, which requires installation from source. If you'd like regular pip install, checkout the latest stable version (v5.22.2).
Hugging Face's logo
Join the Hugging Face community

and get access to the augmented documentation experience

to get started

Écrire un modèle de chat

Un modèle de chat est un modèle Jinja stocké dans l'attribut ~PreTrainedTokenizer.chat_template du tokenizer. Jinja est un langage de modélisation qui permet d'écrire du code et une syntaxe similaires à Python.

{%- for message in messages %}
    {{- '<|' + message['role'] + '|>\n' }}
    {{- message['content'] + eos_token }}
{%- endfor %}
{%- if add_generation_prompt %}
    {{- '<|assistant|>\n' }}
{%- endif %}

Si vous observez ce code un moment, vous devriez vous rendre compte qu’il est en réalité très proche de Python, à quelques syntaxes étranges de type {%- près. Le modèle itère sur une liste de messages et, pour chaque message, il affiche le rôle et le contenu du message, suivis d’un jeton de fin de séquence. Si add_generation_prompt=True, il ajoute l’en-tête de début d’un message d’assistant à la fin de la conversation.

Chargez le modèle écrit en tant que chaîne de caractères et affectez-le à l'attribut chat_template du tokenizer. Une fois défini, le modèle est utilisé à chaque appel de apply_chat_template(). Il est également enregistré avec le tokenizer à chaque appel de save_pretrained() ou push_to_hub(). Le modèle est enregistré dans le fichier chat_template.jinja du répertoire du tokenizer. Vous pouvez modifier directement ce fichier pour changer le modèle, ce qui est souvent plus simple que de manipuler une chaîne de caractères de modèle. Consultez Storing and loading chat templates ci-dessous pour les autres formes sur disque prises en charge par Transformers.

Conseils pour la rédaction de modèles

La façon la plus simple de commencer à écrire des modèles Jinja est de se référer aux modèles existants. Utilisez print(tokenizer.chat_template) sur n’importe quel modèle de chat pour voir le modèle qu’il utilise. Commencez par des modèles simples qui n’appellent aucun outil et ne prennent pas en charge le RAG, car les modèles d’utilisation d’outils peuvent avoir des modèles très complexes. Enfin, consultez la documentation Jinja pour plus de détails sur le formatage et la syntaxe.

Il existe toutefois quelques conseils et pièges spécifiques que vous pourriez rencontrer lors de l’écriture de modèles de chat, et cette section en abordera certains en détail.

Rédaction de modèles de chat multimodaux

Pour les modèles multimodaux, l'attribut chat_template est défini sur le processor, et non sur le tokenizer. La clé content d'un message est souvent une liste de dictionnaires de contenu, plutôt qu'une simple chaîne de caractères. Vous pouvez vérifier le type de chaque élément de contenu dans la liste et le traiter en conséquence.

En général, le modèle de rendu ne doit pas accéder directement aux données d’images ou de vidéos. Cette tâche est normalement prise en charge par le processeur une fois le rendu du modèle terminé. À la place, votre modèle de rendu doit émettre un jeton spécial unique comme <|image|> ou <|video|> lorsqu’il rencontre du contenu image ou vidéo. Le processeur développera ensuite ce jeton spécial unique en une séquence de jetons d’image ou de vidéo. Les jetons exacts à émettre dépendent du modèle avec lequel vous travaillez. Nous recommandons vivement de charger un processeur multimodal existant pour voir comment il gère les données.

Le modèle d'exemple ci-dessous gère un contenu mixte d'images et de texte.

{%- for message in messages %}
    {%- if loop.index0 == 0 %}
        {{- bos_token }}
    {%- endif %}
    {{- '<|start_header_id|>' + message['role'] + '<|end_header_id|>\n\n' }}
    {%- if message['content'] is string %}
        {{- message['content'] }}
    {%- else %}
        {%- for content in message['content'] %}
            {%- if content['type'] == 'image' %}
                {{- '<|image|>' }}
            {%- elif content['type'] == 'text' %}
                {{- content['text'] }}
            {%- endif %}
        {%- endfor %}
    {%- endif %}
    {{- '<|eot_id|>' }}
{%- endfor %}
{%- if add_generation_prompt %}
    {{- '<|start_header_id|>assistant<|end_header_id|>\n\n' }}
{%- endif %}

Ce modèle multimodal est très similaire au modèle plus simple ci-dessus, mais il vérifie les listes content et les parcourt pour afficher les jetons <|image|> lorsque nécessaire. Cela permet d’insérer les images « dans le flux » du texte de l’utilisateur.

Tous les modèles ne fonctionnent pas ainsi : certains déplacent par exemple toutes les images à la fin du message de l'utilisateur. Le modèle de conversation doit toujours correspondre au format utilisé pour l'entraînement du modèle.

Suppression des espaces

Jinja affiche tout espace blanc avant ou après un bloc de texte. Cela peut poser problème pour les modèles de discussion, car l’ajout d’espaces blancs supplémentaires qui n’étaient pas présents lors de l’entraînement du modèle peut nuire aux performances. Pour supprimer les espaces blancs, ajoutez - à la syntaxe de ligne Jinja. Cela vous permet d’écrire votre modèle avec un indentation et des retours à la ligne de type Python, sans imprimer accidentellement une indentation dans la sortie rendue.

Le modèle d’exemple ci-dessous n’utilise pas -, ce qui entraîne l’affichage d’espaces supplémentaires dans la sortie.

{% for message in messages %}
    {{ message['role'] + message['content'] }}
{% endfor %}

Nous recommandons vivement d'utiliser - pour s'assurer que seul le contenu prévu est affiché.

{%- for message in messages %}
    {{- message['role'] + message['content'] }}
{%- endfor %}

Variables et appelables spéciaux

Les seules constantes d'un modèle sont la variable messages et le booléen add_generation_prompt. Toutefois, vous avez accès à tous les autres arguments nominaux qui sont transmis à la méthode apply_chat_template().

Cela offre une flexibilité et permet de prendre en charge des cas d’usage que nous n’avions peut-être pas envisagés lors de la conception de la spécification. La variable supplémentaire la plus courante est tools, qui contient une liste d’outils au format JSON schema. Bien que vous puissiez utiliser n’importe quel nom de variable, nous vous recommandons vivement de respecter la convention et d’utiliser tools à cette fin. Cela rend les modèles plus compatibles avec l’API standard.

Vous avez également accès à tous les jetons contenus dans tokenizer.special_tokens_map, qui inclut souvent des jetons spéciaux comme bos_token et eos_token. Accédez-y directement par leur nom, comme {{- bos_token }}.

Deux fonctions appelables sont à votre disposition. Pour les appeler, utilisez {{- function_name(argument) }}.

  • raise_exception(msg) lève une TemplateException. Cela est utile pour le débogage ou pour avertir les utilisateurs d’une utilisation incorrecte du modèle.
  • strftime_now(format_str) récupère la date et l’heure actuelles dans un format spécifique, ce qui est souvent requis dans les messages système. Il est équivalent à datetime.now().strftime(format_str) en Python.

Compatibilité avec Jinja non-Python

Jinja est implémenté dans plusieurs langages et ils partagent généralement la même syntaxe. L’écriture d’un modèle en Python permet d’utiliser des méthodes Python telles que lower sur les chaînes de caractères ou items sur les dictionnaires. Cependant, cela ne fonctionnera pas si le modèle est utilisé dans une implémentation non-Python, par exemple lors d’un déploiement avec Javascript ou Rust.

Apportez les modifications ci-dessous pour garantir la compatibilité avec toutes les implémentations Jinja.

  • Remplacez les méthodes Python par des filtres Jinja. Par exemple, remplacez string.lower() par string|lower ou dict.items() par dict|dictitems. La plupart des modifications suivent le même schéma, à l'exception de string.strip(), qui est remplacé par string|trim. Consultez la liste des filtres intégrés pour obtenir la liste complète des filtres.
  • Remplacez True, False et None (spécifiques à Python) par true, false et none respectivement.
  • Le rendu direct d'un dictionnaire ou d'une liste peut renvoyer des résultats différents selon les implémentations. Par exemple, les entrées de type chaîne peuvent passer de guillemets simples à des guillemets doubles. Pour éviter cela, ajoutez le filtre tojson afin de maintenir la cohérence.

Grosses templates

Les modèles plus récents ou les modèles dotés de fonctionnalités telles que tool-calling et RAG nécessitent des gabarits plus volumineux, pouvant dépasser 100 lignes. Il est souvent plus simple de rédiger de grands gabarits dans un fichier distinct. Les numéros de ligne dans ce fichier correspondent exactement aux numéros de ligne indiqués lors des erreurs d’analyse ou d’exécution du gabarit, ce qui facilite le débogage des éventuels problèmes.

Écrivez le modèle dans un fichier séparé et extrayez-le dans le modèle de chat.

open("template.jinja", "w").write(tokenizer.chat_template)

Vous pouvez également recharger un modèle modifié dans le tokenizer.

tokenizer.chat_template = open("template.jinja").read()

Stockage et chargement des modèles de chat

Les modèles de chat sont stockés sur disque dans plusieurs formats différents. Les points de contrôle modernes enregistrent les modèles sous forme de fichiers .jinja autonomes, tandis que les points de contrôle plus anciens les intègrent dans la configuration du tokeniseur ou du processeur.

Formats de stockage

Les modèles peuvent être stockés dans l'un des formats suivants.

  • chat_template.jinja (recommandé). Un fichier Jinja autonome à la racine du dépôt, contenant un seul modèle de chat. C’est ce que save_pretrained() écrit par défaut. Stocker le modèle dans son propre fichier facilite son inspection, son édition et sa comparaison. Les tokenizers et les processors chargent chat_template.jinja de la même manière.
  • additional_chat_templates/<name>.jinja. Un répertoire de fichiers Jinja autonomes utilisé lorsqu’un modèle est livré avec plusieurs modèles nommés (par exemple, un modèle default et un modèle tool_use distinct). Le modèle default reste dans chat_template.jinja à la racine du dépôt, mais tout autre modèle nommé est placé dans additional_chat_templates/<name>.jinja, où la tige du nom de fichier correspond au nom du modèle.

Les formats legacy ci-dessous sont conservés uniquement pour la compatibilité ascendante lors du chargement. N’écrivez pas de modèles de chat dans l’un ou l’autre de ces formats.

  • champ chat_template dans tokenizer_config.json. Un format hérité en lecture seule utilisé avant les fichiers .jinja autonomes. Le modèle est intégré en tant que chaîne JSON dans tokenizer_config.json. Lorsqu’un modèle possède plusieurs modèles nommés, le champ est une liste de dictionnaires {"name": ..., "template": ...} au lieu d’une seule chaîne. Les dépôts existants qui utilisent ce format continuent de se charger, mais save_pretrained() écrit le format moderne .jinja à la place.

  • chat_template.json. Un format hérité en lecture seule utilisé par d’anciens points de contrôle de processeurs multimodaux. Un fichier JSON de la forme {"chat_template": "<template string>"}. Les dépôts existants qui utilisent ce format continuent de se charger, mais save_pretrained() écrit désormais le format moderne .jinja. Un dépôt de processeur qui mélange un chat_template.json hérité avec des fichiers .jinja modernes lève une erreur au chargement.

Priorité de chargement

Lors de l’appel de from_pretrained(), Transformers résout les formats de stockage avec une priorité fixe. Les fichiers .jinja autonomes ont la priorité sur les modèles intégrés dans la configuration. Le chargeur :

  1. Lit tout champ chat_template présent dans tokenizer_config.json (ou, pour les processeurs, le fichier chat_template.json obsolète).
  2. Reads chat_template.jinja at the repo root if it exists, and uses it as the default template, overriding step 1.
  3. Lit chaque fichier .jinja dans additional_chat_templates/, indexé par le nom de fichier sans extension, et les fusionne.

Si le résultat comporte un seul modèle default, ~PreTrainedTokenizer.chat_template est défini sur cette chaîne. Si plusieurs modèles nommés existent, chat_template devient un dictionnaire de {name: template_string}. Dans ce cas, apply_chat_template() sélectionne l’entrée tool_use lorsque des outils sont passés, et default sinon.

Enregistrement

save_pretrained() et push_to_hub() écrivent par défaut au format .jinja. Un modèle de chaîne unique devient chat_template.jinja. Un dictionnaire de modèles nommés écrit l’entrée default dans chat_template.jinja et un fichier par entrée restante sous additional_chat_templates/. Le champ chat_template est supprimé de tokenizer_config.json pour éviter les doublons.

Il n'existe pas de méthode prise en charge pour enregistrer un modèle dans l'un des formats hérités. Ils sont conservés uniquement pour charger les anciens dépôts.

Mettre à jour un ancien dépôt

Migrer un modèle intégré dans tokenizer_config.json ou chat_template.json vers le format .jinja recommandé en le chargeant puis en le sauvegardant à nouveau.

L'étape de chargement normalise le format legacy utilisé dans chat_template, et ~PushToHubMixin.push_to_hub renvoie un fichier chat_template.jinja.

from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained("your-org/your-model")
tokenizer.push_to_hub("your-org/your-model")

Modèles pour les outils

Il n’existe pas de format spécifique pour rédiger des modèles de prompts destinés aux outils, mais il est préférable de suivre l’API standard. Cela garantit que le modèle de prompt est largement accessible sur différents modèles, sans exiger des utilisateurs d’écrire du code personnalisé pour utiliser des outils avec votre modèle.

La mise en forme, comme les espaces et les jetons spéciaux, est propre à chaque modèle. Assurez-vous que tout correspond exactement au format utilisé lors de l’entraînement du modèle.

La section suivante répertorie les éléments de l'API standard pour écrire des modèles pour les outils.

Définitions des outils

Les outils sont transmis sous forme de fonctions Python ou de schéma JSON. Lorsque des fonctions sont transmises, un schéma JSON est généré automatiquement et passé au modèle. Lorsqu'un modèle accède à la variable tools, il s'agit toujours d'une liste de schémas JSON.

Bien qu’un template reçoive toujours les outils sous forme de schéma JSON, il peut être nécessaire de modifier radicalement ce format lors de leur rendu afin de correspondre au format utilisé pour l’entraînement du modèle. Par exemple, Command-R a été entraîné avec des outils définis par des en-têtes de fonctions Python. Le template convertit en interne les types de schéma JSON et rend les outils d’entrée sous forme d’en-têtes Python.

L'exemple ci-dessous montre comment un outil est défini au format JSON schema.

{
  "type": "function",
  "function": {
    "name": "multiply",
    "description": "A function that multiplies two numbers",
    "parameters": {
      "type": "object",
      "properties": {
        "a": {
          "type": "number",
          "description": "The first number to multiply"
        },
        "b": {
          "type": "number",
          "description": "The second number to multiply"
        }
      },
      "required": ["a", "b"]
    }
  }
}

Un exemple de gestion des définitions d’outils dans un modèle de chat est présenté ci-dessous. Les jetons et les mises en page spécifiques doivent être modifiés pour correspondre à ceux avec lesquels le modèle a été entraîné.

{%- if tools %}
    {%- for tool in tools %}
        {{- '<tool>' + tool['function']['name'] + '\n' }}
        {%- for argument in tool['function']['parameters']['properties'] %}
            {{- argument + ': ' + tool['function']['parameters']['properties'][argument]['description'] + '\n' }}
        {%- endfor %}
        {{- '\n</tool>' }}
    {%- endfor %}
{%- endif %}

Appels d'outils

En plus de l’affichage des définitions d’outils, vous devez également afficher les appels d’outils et les réponses d’outils dans le modèle.

Les appels d’outils sont généralement transmis dans la clé tool_calls d’un message "assistant”. Il s’agit toujours d’une liste, même si la plupart des modèles d’appel d’outils ne prennent en charge que des appels d’outils uniques, ce qui signifie que la liste ne contient généralement qu’un seul élément.

{
  "role": "assistant",
  "tool_calls": [
    {
      "type": "function",
      "function": {
        "name": "multiply",
        "arguments": {
          "a": 5,
          "b": 6
        }
      }
    }
  ]
}

Un schéma courant pour la gestion des appels d’outils est présenté ci-dessous. Vous pouvez l’utiliser comme point de départ, mais assurez-vous que votre gabarit correspond bien au format avec lequel le modèle a été entraîné !

{%- if message['role'] == 'assistant' and 'tool_calls' in message %}
    {%- for tool_call in message['tool_calls'] %}
            {{- '<tool_call>' + tool_call['function']['name'] + '\n' + tool_call['function']['arguments']|tojson + '\n</tool_call>' }}
    {%- endfor %}
{%- endif %}

Réponses des outils

Les réponses d’outils sont des dictionnaires de messages avec le rôle tool. Ils sont beaucoup plus simples que les appels d’outils et contiennent généralement uniquement les clés role, name et content.

{
  "role": "tool",
  "name": "multiply",
  "content": "30"
}

Certains modèles n'ont peut-être même pas besoin de la clé name, auquel cas vous pouvez écrire votre modèle pour ne lire que la clé content.

{%- if message['role'] == 'tool' %}
    {{- "<tool_result>" + message['content'] + "</tool_result>" }}
{%- endif %}

Contribuer

Une fois le modèle prêt, définissez-le sur l'attribut chat_template du tokenizer et testez-le avec apply_chat_template(). S'il fonctionne comme prévu, téléchargez-le sur le Hub avec push_to_hub().

Même si vous n’êtes pas le propriétaire du modèle, il est utile d’ajouter un modèle pour un modèle dont le modèle de chat est vide ou incorrect. Ouvrez une pull request sur le dépôt du modèle pour ajouter le modèle !

tokenizer.chat_template = template
tokenizer.push_to_hub("amazing_company/cool_model", commit_message="Add chat template", create_pr=True)
Update on GitHub