Transformers documentation
Écrire un modèle de chat
É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 uneTemplateException. 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()parstring|loweroudict.items()pardict|dictitems. La plupart des modifications suivent le même schéma, à l'exception destring.strip(), qui est remplacé parstring|trim. Consultez la liste des filtres intégrés pour obtenir la liste complète des filtres. - Remplacez
True,FalseetNone(spécifiques à Python) partrue,falseetnonerespectivement. - 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 chargentchat_template.jinjade 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èledefaultet un modèletool_usedistinct). Le modèledefaultreste danschat_template.jinjaà la racine du dépôt, mais tout autre modèle nommé est placé dansadditional_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_templatedanstokenizer_config.json. Un format hérité en lecture seule utilisé avant les fichiers.jinjaautonomes. Le modèle est intégré en tant que chaîne JSON danstokenizer_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 unchat_template.jsonhérité avec des fichiers.jinjamodernes 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 :
- Lit tout champ
chat_templateprésent danstokenizer_config.json(ou, pour les processeurs, le fichierchat_template.jsonobsolète). - Reads
chat_template.jinjaat the repo root if it exists, and uses it as thedefaulttemplate, overriding step 1. - Lit chaque fichier
.jinjadansadditional_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)