Transformers documentation
チャットテンプレートの作成
チャットテンプレートの作成
チャットテンプレートは、トークナイザーの~PreTrainedTokenizer.chat_template属性に保存されるJinjaテンプレートです。Jinjaは、Python風のコードや構文で記述できるテンプレート言語です。
{%- for message in messages %}
{{- '<|' + message['role'] + '|>\n' }}
{{- message['content'] + eos_token }}
{%- endfor %}
{%- if add_generation_prompt %}
{{- '<|assistant|>\n' }}
{%- endif %}しばらく眺めていると、これは実際には Python に非常に似ており、少し奇妙な {%- 構文が使われているだけだと気づくはずです。このテンプレートはメッセージのリストを反復し、各メッセージについてロールとコンテンツを出力し、その後にシーケンス終了トークンを付加します。add_generation_prompt=True の場合、会話の末尾にアシスタントメッセージの開始ヘッダーを追加します。
書き出されたテンプレートを文字列として読み込み、トークナイザーのchat_template属性に代入します。設定が完了すると、apply_chat_template()を呼び出すたびにこのテンプレートが使用されます。また、save_pretrained()またはpush_to_hub()が呼び出された場合、トークナイザーとともに保存されます。テンプレートは、トークナイザーディレクトリ内のchat_template.jinjaファイルに保存されます。このファイルを直接編集してテンプレートを変更することもでき、テンプレート文字列を操作するよりも簡単であることが多いです。Transformersがサポートするその他のディスク上の形式については、以下のチャットテンプレートの保存と読み込みを参照してください。
テンプレート作成のコツ
Jinja テンプレートの作成を始める最も簡単な方法は、既存のテンプレートを参照することです。任意のチャットモデルに対して print(tokenizer.chat_template) を実行すると、そのモデルが使用しているテンプレートを確認できます。ツール呼び出しを行わず、RAG もサポートしていないシンプルなモデルから始めることをお勧めします。ツール使用モデルは非常に複雑なテンプレートを持つことがあるためです。最後に、フォーマットや構文の詳細については、Jinja のドキュメントを参照してください。
チャットテンプレートの作成に特化して、遭遇する可能性のある具体的なヒントや落とし穴がいくつかあります。このセクションでは、それらの一部をより詳しく説明します。
マルチモーダルチャットテンプレートの作成
マルチモーダルテンプレートでは、chat_template 属性はトークナイザーではなく processor に設定されます。メッセージの content キーは、単一の文字列ではなく、多くの場合、コンテンツの辞書のリストです。リスト内の各コンテンツアイテムの型を確認し、それに応じて処理することをお勧めします。
一般的に、テンプレートは画像や動画データに直接アクセスすべきではありません。これは通常、テンプレートのレンダリングが完了した後にプロセッサによって処理されます。代わりに、テンプレートは画像や動画の内容に遭遇した際に、<|image|> や <|video|> のような単一の特殊トークンを出力するようにしてください。プロセッサは、後にこの単一の特殊トークンを画像または動画のトークン列に展開します。出力する具体的なトークンは、使用しているモデルによって異なります。既存のマルチモーダルプロセッサを読み込んで、データがどのように処理されているかを確認することを強くお勧めします。
以下のテンプレート例は、画像とテキストが混在するコンテンツを処理します。
{%- 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 %}このマルチモーダルテンプレートは、上記のよりシンプルなテンプレートと非常に似ていますが、contentリストを確認し、それらを反復して必要に応じて<|image|>トークンをレンダリングします。これにより、ユーザーテキストの「流れの中」に画像を挿入できます。
すべてのモデルがこの方式で動作するわけではありません。たとえば、一部のモデルでは、すべての画像をユーザーメッセージの末尾に移動します。チャットテンプレートは、常にモデルが学習された形式と一致させる必要があります。
空白のトリミング
Jinja は、テキストブロックの前後にある空白をすべて出力します。これはチャットテンプレートにおいて問題となることがあります。モデルの学習時に存在しなかった余分な空白が追加されると、パフォーマンスが低下する可能性があるためです。空白を除去するには、Jinja の行構文に - を追加します。これにより、Python 的なインデントや改行でテンプレートを作成できますが、レンダリングされた出力にインデントが誤って出力されることを防ぐことができます。
以下の例のテンプレートでは-を使用していないため、出力に余分な空白が表示されます。
{% for message in messages %}
{{ message['role'] + message['content'] }}
{% endfor %}意図した内容のみが出力されることを保証するために、-の使用を強く推奨します。
{%- for message in messages %}
{{- message['role'] + message['content'] }}
{%- endfor %}特殊変数とコールラブル
テンプレートにおける定数は、messages 変数と add_generation_prompt ブール値のみです。ただし、apply_chat_template() メソッドに渡される他のすべてのキーワード引数にアクセスできます。
これにより柔軟性が得られ、仕様の設計時に想定していなかったユースケースへの対応も可能になります。最も一般的な追加変数は tools で、JSON スキーマ形式のツールリストを保持します。変数名は自由に指定できますが、標準 API とのテンプレートの互換性を保つため、この用途には慣例に従って tools を使用することを強く推奨します。
tokenizer.special_tokens_map に含まれるトークンにもアクセスできます。これには、bos_token や eos_token のような特殊トークンが含まれていることがよくあります。これらのトークンは、{{- bos_token }} のように、名前を直接指定してアクセスします。
呼び出し可能な関数が2つ利用できます。呼び出すには、{{- function_name(argument) }}を使用します。
raise_exception(msg)はTemplateExceptionを送出します。これは、デバッグや、テンプレートの誤った使用に関するユーザーへの警告に役立ちます。strftime_now(format_str)は、特定の形式で現在の日時を取得します。これはシステムメッセージで頻繁に必要とされる機能です。Python では datetime.now().strftime(format_str) と同等です。
Python 以外の Jinja での互換性
Jinja は複数の言語で実装されており、一般的に同じ構文を使用します。Python でテンプレートを作成すると、文字列に対して lower のような Python のメソッドや、辞書に対して items を使用できます。ただし、テンプレートが Python 以外の実装で使われる場合、例えば Javascript や Rust でデプロイする場合は、これらは機能しません。
すべての Jinja 実装間で互換性を確保するために、以下の変更を行ってください。
- Python のメソッドを Jinja のフィルターに置き換えます。例えば、
string.lower()はstring|lowerに、dict.items()はdict|dictitemsに置き換えます。ほとんどの変更は同じパターンに従いますが、string.strip()はstring|trimに置き換えられます。フィルターの完全なリストについては、組み込みフィルターのリストを参照してください。 True、False、None(これらは Python 固有の記法です)を、それぞれtrue、false、noneに置き換えてください。- dict や list を直接レンダリングすると、他の実装では異なる結果が返される場合があります。例えば、文字列のエントリがシングルクォートからダブルクォートに変更される可能性があります。これを避けるために、tojson フィルターを追加して一貫性を保ちます。
大きなテンプレート
新しいモデルや、tool-callingやRAGといった機能を備えたモデルでは、100行を超える大きなテンプレートが必要になる場合があります。大きなテンプレートは、別ファイルに記述する方が簡単です。別ファイル内の行番号は、テンプレートの解析や実行時のエラーにおける行番号と完全に一致するため、潜在的な問題のデバッグが容易になります。
テンプレートを別ファイルに記述し、チャットテンプレートに抽出します。
open("template.jinja", "w").write(tokenizer.chat_template)編集済みのテンプレートをトークナイザーに読み戻すこともできます。
tokenizer.chat_template = open("template.jinja").read()チャットテンプレートの保存と読み込み
Chat テンプレートは、ディスク上に複数の異なる形式で保存されます。最新のチェックポイントでは、テンプレートが独立した .jinja ファイルとして保存される一方、古いチェックポイントでは、トークナイザーまたはプロセッサーの設定に埋め込まれています。
ストレージ形式
テンプレートは、以下のいずれかの形式で保存できます。
chat_template.jinja(推奨)。リポジトリのルートに配置される独立した Jinja ファイルで、単一のチャットテンプレートが含まれます。save_pretrained() がデフォルトで書き出す形式です。テンプレートを独立したファイルとして保存することで、確認・編集・差分比較が容易になります。tokenizer と processor の両方がchat_template.jinjaを同じ方法で読み込みます。additional_chat_templates/<name>.jinja。モデルが複数の名前付きテンプレート(例:defaultテンプレートと、別のtool_useテンプレート)を同梱する場合に使用される、独立した Jinja ファイルのディレクトリです。defaultテンプレートは引き続きリポジトリのルートにあるchat_template.jinjaに配置されますが、それ以外の名前付きテンプレートはすべてadditional_chat_templates/<name>.jinjaに配置します。ここで、ファイル名のステム(拡張子を除いた部分)がテンプレート名になります。
以下のレガシー形式は、後方互換性のための読み込みのみを目的として保持されています。これらにチャットテンプレートを保存しないでください。
tokenizer_config.json内のchat_templateフィールド。独立した.jinjaファイルが導入される以前に使用されていた、読み取り専用のレガシー形式です。テンプレートはtokenizer_config.json内に JSON 文字列として埋め込まれます。モデルに複数の名前付きテンプレートがある場合、このフィールドは単一の文字列ではなく、{"name": ..., "template": ...}の辞書のリストになります。この形式を使用している既存のリポジトリは引き続き読み込むことができますが、save_pretrained() は代わりに最新の.jinja形式で書き出します。chat_template.json。旧型のマルチモーダルプロセッサチェックポイントで使用される、読み取り専用のレガシー形式です。{"chat_template": "<template string>"}という形式の JSON ファイルです。この形式を使用している既存のリポジトリは引き続き読み込むことができますが、save_pretrained() は代わりに最新の.jinja形式で書き出します。レガシーのchat_template.jsonと最新の.jinjaファイルが混在しているプロセッサのリポジトリでは、読み込み時にエラーが発生します。
読み込みの優先順位
from_pretrained()を呼び出す際、Transformersは固定された優先順位に基づいてストレージ形式を解決します。単体の.jinjaファイルは、configに埋め込まれたテンプレートよりも優先されます。ローダーは以下のようになります。
tokenizer_config.json(プロセッサの場合は、レガシーのchat_template.json)に存在するchat_templateフィールドを読み取ります。- リポジトリのルートに
chat_template.jinjaが存在する場合はそれを読み取り、defaultテンプレートとして使用します。この場合、ステップ1は上書きされます。 additional_chat_templates/内のすべての.jinjaファイルを読み取り、ファイル名の語幹をキーとしてマージします。
結果に単一のdefaultテンプレートがある場合、~PreTrainedTokenizer.chat_templateはその文字列に設定されます。複数の名前付きテンプレートが存在する場合、chat_templateは{name: template_string}の辞書になります。その場合、apply_chat_template()はツールが渡されたときにtool_useエントリを選択し、それ以外の場合はdefaultを選択します。
保存
save_pretrained() および push_to_hub() は、デフォルトで .jinja 形式で書き出します。単一の文字列テンプレートは chat_template.jinja として保存されます。名前付きテンプレートの辞書の場合、default エントリは chat_template.jinja に、残りの各エントリは additional_chat_templates/ 配下に1ファイルずつ書き出されます。重複を避けるため、chat_template フィールドは tokenizer_config.json から削除されます。
レガシー形式のいずれかにテンプレートを保存するサポートされた方法はありません。これらの形式は、古いリポジトリの読み込みにのみ使用されます。
古いリポジトリの更新
tokenizer_config.json または chat_template.json に埋め込まれたテンプレートを、推奨される .jinja 形式に移行するには、再度読み込んで保存してください。
ロードステップは、リポジトリがchat_templateで使用するレガシー形式を正規化し、~PushToHubMixin.push_to_hubはchat_template.jinjaファイルを返します。
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained("your-org/your-model")
tokenizer.push_to_hub("your-org/your-model")ツールのテンプレート
ツール用のテンプレートを書くための特定の形式はありませんが、標準的な API に従うことをお勧めします。これにより、ユーザーがモデルでツールを使用するためにカスタムコードを書く必要がなく、テンプレートが幅広いモデルで利用可能になります。
空白や特殊トークンなどのフォーマットはモデルによって異なります。モデルが学習された際のフォーマットと完全に一致していることを確認してください。
次のセクションでは、ツール用のテンプレートを作成するための標準APIの要素をリストしています。
ツール定義
Tools は Python の関数または JSON スキーマとして渡されます。関数が渡された場合、JSON スキーマが自動的に生成され、テンプレートに渡されます。テンプレートが tools 変数にアクセスする際は、常に JSON スキーマのリストとして扱われます。
テンプレートは常にツールをJSONスキーマとして受け取りますが、モデルが学習時に使用した形式に合わせるためにレンダリングする際、この形式を根本的に変更する必要がある場合があります。例えば、Command-Rは、Pythonの関数ヘッダーで定義されたツールで学習されました。テンプレートは内部でJSONスキーマの型を変換し、入力されたツールをPythonのヘッダーとしてレンダリングします。
以下の例は、JSON スキーマ形式でツールがどのように定義されるかを示しています。
{
"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"]
}
}
}チャットテンプレートにおけるツール定義の処理の例を以下に示します。具体的なトークンやレイアウトは、モデルが学習された際に使用されたものに合わせる必要があります。
{%- 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 %}ツール呼び出し
ツール定義のレンダリングに加えて、テンプレート内でツール呼び出しとツール応答もレンダリングする必要があります。
ツール呼び出しは、一般的に"assistant”メッセージのtool_callsキーに渡されます。ほとんどのツール呼び出しモデルは単一のツール呼び出しのみをサポートしますが、この値は常にリストです。そのため、リストには通常、単一の要素のみが含まれます。
{
"role": "assistant",
"tool_calls": [
{
"type": "function",
"function": {
"name": "multiply",
"arguments": {
"a": 5,
"b": 6
}
}
}
]
}ツール呼び出しを処理するための一般的なパターンを以下に示します。これを起点として使用できますが、テンプレートがモデルの学習時に使用された形式と一致していることを必ず確認してください。
{%- 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 %}ツール応答
Tool responses は tool ロールを持つメッセージの dict です。tool calls と比べてはるかにシンプルで、通常は role、name、content キーのみが含まれます。
{
"role": "tool",
"name": "multiply",
"content": "30"
}一部のテンプレートでは、name キー自体が不要な場合があります。その場合は、テンプレートを content キーのみを読み込むように記述できます。
{%- if message['role'] == 'tool' %}
{{- "<tool_result>" + message['content'] + "</tool_result>" }}
{%- endif %}コントリビュート
テンプレートの準備が整ったら、トークナイザーのchat_template属性に設定し、apply_chat_template()でテストします。期待通りに動作する場合は、push_to_hub()でHubにアップロードします。
モデルの所有者でなくても、空のチャットテンプレートや誤ったチャットテンプレートを持つモデルにテンプレートを追加することは依然として有益です。モデルのリポジトリでプルリクエストを開いて、テンプレートを追加してください!
tokenizer.chat_template = template
tokenizer.push_to_hub("amazing_company/cool_model", commit_message="Add chat template", create_pr=True)