Transformers documentation
レスポンスの解析
レスポンスの解析
チャットモデルが単一の応答文字列だけでなく、構造化された出力を生成することがますます一般的になっています。例えば、推論モデルは推論のトレースを含む思考の連鎖を出力する一方、ツール呼び出しモデルは関数名と引数を出力します。
ただし、構造化出力の問題は、LLMの出力が本質的に構造化されていない点にあります。LLM APIは通常、roleやcontent、reasoning_contentなどのキーを持つメッセージの辞書を受け取り、返却しますが、内部ではLLMは単一のトークンシーケンスを継続しているだけです。ユーザー向けのAPIとモデルの実際のトークンストリームを接続するために、グルーレイヤーを使用します。入力をトークンストリームに変換するには、chat_templatesを使用しますが、これは他のドキュメントで説明されています。このドキュメントでは、そのグルーレイヤーのもう半分、すなわちモデルが生成したトークンを構造化されたレスポンスの辞書に戻すシステムであるレスポンステンプレートについて説明します。
多くの点において、レスポンステンプレートはチャットテンプレートの逆の操作を行います。チャットテンプレートでは、メッセージのリストを入力すると、モデルに入力できるトークンが得られます。一方、レスポンステンプレートでは、モデルの生の出力トークンを入力すると、構造化されたメッセージが得られます。チャットテンプレートと同様に、レスポンステンプレートは、モデルが期待する具体的なフォーマットや制御トークンの煩雑な詳細をユーザーが無視できるようにし、どのモデルでも動作するメッセージ辞書のユニバーサルなAPIを使用することを可能にします。
レスポンステンプレートの理解に最も良い方法は、実際の動作を見ることです。主なエントリポイントはparse_response()メソッドで、単一のシーケンスまたはバッチのどちらを受け入れることもできます:
from transformers import AutoModelForCausalLM, AutoTokenizer
checkpoint = "HuggingFaceTB/SmolLM3-3B"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
model = AutoModelForCausalLM.from_pretrained(checkpoint, dtype="auto", device_map="auto")
messages = [{"role": "user", "content": "Summarize the end of the Cold War, very briefly."}]
input_ids = tokenizer.apply_chat_template(messages, add_generation_prompt=True, return_tensors="pt")["input_ids"].to(model.device)
outputs = model.generate(input_ids, max_new_tokens=1024)[0, input_ids.shape[1]:]
out_text = tokenizer.decode(outputs)
print(tokenizer.parse_response(out_text, prefix=input_ids[0]))
# Outputs a structured dict: {"role": "assistant", "reasoning_content": "...", "content": "..."}トークナイザーにresponse_templateがある場合、parse_responseメソッドは出力メッセージを構造化された辞書にきれいに変換し、チャットに追加できる状態にします。このメソッドには、prefix(プロンプトのトークン)も渡す必要があります。多くのチャットテンプレートは、モデルが応答を開始する前にメッセージを開始したり、思考ブロックを開いたりするため、パーサーはメッセージを理解するためにプロンプトを見る必要があるからです。最終ターン以前のすべてのプレフィックスは破棄され、一度に1つのメッセージのみを解析します。プレフィックスは、最終メッセージ全体を確認し、事前入力されたフィールドを見逃さないようにするために必要です。
プレフィックスの欠落は、事前入力されたメッセージの解析を静かに誤らせる可能性があるため、prefix は必須です。省略するとエラーが発生します。生成結果がすでに完全なメッセージを含んでおり、プレフィックスのコンテキストが不要であると確信できる稀なケースでは、prefix=""(またはトークン ID の空のリスト)を渡して明示的に除外してください。
トークナイザーにレスポンステンプレートが設定されていない場合、parse_responseはエラーを発生させます。より多くのモデルにテンプレートを追加できるよう、最速で対応を進めています。
ストリーミングレスポンスの解析
上記の例では、生成が完了した後にモデルの応答を一度にパースしています。しかし、多くの場合、メッセージが生成されるにつれて部分的なメッセージをパースしたいことがあります。特に、モデルが完了するまで1〜2分間静的なページを表示するだけで済ませたくない、ユーザー向けのアプリケーションではそうです。
ストリーミング解析が必要な場合は、ResponseParser を返す tokenizer.get_response_parser() を呼び出します。
parse_response と同様に、チャットテンプレートによって事前入力されたメッセージの部分をパーサーに認識させるため、チャットプロンプトを prefix= として渡します。返されるオブジェクトはステートフルなパーサーであり、モデルがテキストを生成するにつれて、そのテキストを投入できます。
parser = tokenizer.get_response_parser(prefix=input_ids[0])
for event in parser.initial_events:
render(event) # Display the partial message to the user however you want to
for chunk in model_output:
for event in parser.feed(chunk):
render(event)
message, final_events = parser.finalize()
for event in final_events:
render(event)リクエストにツールが含まれている場合は、それらも渡します(get_response_parser(..., tools=tools))。
ツール呼び出しの引数は、各リージョンがクローズされるたびに、呼び出し元のツールの JSON Schema から型付けされるため、
ストリーミングの消費者は、finalize() の後だけでなく、region_close の時点でスキーマ型付けされた引数を参照できます。
詳細は ツール呼び出しの引数の型付け を参照してください。
パーサーは、生成プロセスからテキストが投入されるにつれてイベントを出力します。これにより、現在どの領域が生成中であるかが示されます。領域の生成が完了すると、完全にパースされた内容が別のイベントとして出力されます。生成が終了すると、finalize()メソッドが残りテキストをフラッシュし、最終的なイベントおよび完全なメッセージ辞書を出力します。
parse_responseはバッチを受け取れますが、ストリーミング解析は常に単一シーケンスです。各ResponseParserは1つの生成の状態を追跡します。
複数の生成を同時にストリーミングしたい場合は、シーケンスごとに1つのResponseParserを作成してください。
ストリーミングイベント
ストリーミング解析の各イベントは、typeキーを持つ辞書です。3つの種類があります。
| 型 | 説明 | 目次 |
|---|---|---|
region_open | contentやreasoning_contentなど、モデルが新しい領域を開始したことを示します。 | field (str): フィールド名。 |
region_chunk | 現在の領域のテキストチャンク。 | field (str): フィールド名。text (str): 新しいチャンク。dirty (bool): チャンクがパースが必要な生テキストの場合にTrue。 |
region_close | リージョンが完了し、そのキーが確定したことを示します。 | field (str): フィールド名。value (any): リージョンの完全に解析された値。 |
region_chunk イベントは、バイトが到着するたびにすべてのリージョンに対して発行されるため、構造化されたリージョンであっても、ストリーミング UI は進捗をレンダリングできます。テキストのようなリージョン(text、int、float、bool)では、チャンクは dirty=False とフラグ付けされます。各チャンクはすでに最終値の一部です(クローズ時に末尾の空白が除去されることを除く)。JSON 形式のツール呼び出しなどの構造化されたリージョンでは、チャンクは dirty=True とフラグ付けされます。これは、テキストが未パースの生データであることを意味します。段階的に表示することは安全ですが、parsed 値(dict、list など)は、対応する region_close イベントでのみ到着します。いずれにせよ、リージョンの確定値は常に region_close によって運ばれるため、中間レンダリングを気にしないコンシューマーは、region_chunk イベントを単に無視すればよいです。
チャットprefixがメッセージに何かを書き込んだ場合(テンプレートが思考ブロックを開いた、またはモデルに引き継ぐ前にアシスタントのプリフィルが応答を開始した、など)、パーサーはそれらのイベントをparser.initial_eventsとして公開します。これは、モデルの出力をレンダラーに渡す前に、レンダラーに再生できるリストです。プレフィックス内で開かれかつ閉じられたリージョンは、region_open / region_chunk / region_closeの完全なシーケンスを生成し、その解析された値は出力辞書に格納されます。これは、モデル自体がそれらを書き込んだ場合とまったく同じです。
典型的なイベントストリームは、次のようになります。
{"type": "region_open", "field": "reasoning_content"}
{"type": "region_chunk", "field": "reasoning_content", "text": "I should ", "dirty": False}
{"type": "region_chunk", "field": "reasoning_content", "text": "greet the user", "dirty": False}
{"type": "region_close", "field": "reasoning_content", "value": "I should greet the user"}
{"type": "region_open", "field": "tool_calls"}
{"type": "region_chunk", "field": "tool_calls", "text": '{"name": "greet_user", ', "dirty": True}
{"type": "region_chunk", "field": "tool_calls", "text": '"arguments": {"greeting": "Hi!"}}', "dirty": True}
{"type": "region_close", "field": "tool_calls", "value": {"type": "function", "function": {"name": "greet_user", "arguments": {"greeting": "Hi!"}}}}reasoning_content が dirty=False で出力される点に注意してください。reasoning_content や content などのフィールドは通常、生のテキストに過ぎないためです。つまり、これらのチャンクを有効な「部分的な出力」として扱うことができます。一方、tool_calls は dirty とフラグ付けされます。これは、生のテキストには大幅なクリーンアップが必要であり、ツールコールは JSON または他の形式としてパースされた後、最終的なツールコールの辞書を生成するために再構成されることが多いからです。その結果、これらの領域の最終的な出力は、生のテキストとは非常に大きく異なることがよくあります。この最終的なパースは、region_close に到達した時点でのみ行われます。それまでの間、dirty チャンクをどう扱うかはあなた次第です。ユーザーに「生の」出力を表示するためにそのまま表示することも、表示できるクリーンな状態になるまで待つこともできます。
これで、レスポンステンプレートの使用に必要な知識の大部分は終了です。このドキュメントの残りの部分は、パーシングシステムの内部構造とレスポンステンプレートの作成方法に焦点を当てています。これは主に開発者やモデル作成者に関連する内容です。ほとんどの人は、ここで安心して読み終えて大丈夫です!
高度なトピック: レスポンステンプレートの作成
レスポンステンプレートの書き方を理解する最善の方法は、具体的な例を挙げることです。SmolLMからの生の応答は、次のようになる可能性があります。
<think>
I should greet the user
</think>
<tool_call>{"name": "greet_user", "arguments": {"greeting": "Hi!"}}</tool_call>この出力を標準的なメッセージ辞書形式でパースすると、次のようになります。
{
"role": "assistant",
"reasoning_content": "I should greet the user",
"tool_calls": [
{"type": "function", "function": {"name": "greet_user", "arguments": {"greeting": "Hi!"}}}
]
}そして、これを解析するテンプレートがこちらです。難しく考えすぎないでください。大部分は比較的自明です!
{
"defaults": {"role": "assistant"},
"start_anchor": "<|im_start|>assistant\n",
"fields": {
"reasoning_content": {"open": "<think>", "close": "</think>", "content": "text"},
"tool_calls": {
"open": "<tool_call>",
"close": "</tool_call>",
"repeats": True,
"content": "json",
"transform": {"type": "function", "function": "{content}"},
},
"content": {
"close": "<|im_end|>",
"content": "text",
},
},
}本質的に、テンプレートはフィールドと区切り文字を定義します。各フィールドは出力辞書のキーに対応します。フィールドには、その区切り文字内のテキストを解析するための情報も含まれます。1つの微妙な点があります。contentフィールドにはopenがありません。これは、SmolLM(およびいくつかの他のモデル)では、特殊トークンによってマークされていないためです。代わりに、contentは他の領域の後のスペースに保存されますが、シーケンスの終了前です。私たちのテンプレートでは、これを他のどの領域にも割り当てられていないテキストを拾い上げる暗黙的 / 残りフィールドとして表現しています。
フィールド名は、モデルのチャットテンプレートが読み取るキーと一致させる必要があります。これにより、解析されたメッセージをチャットに追加し、再度レンダリングできます。特に、推論フィールドの名前はreasoning_contentにしてください。これはメッセージの辞書における推論の標準的なキーであり、ほぼすべてのチャットテンプレートが推論を読み取る際に使用するものです。
fieldsに加えて、テンプレートは2つのトップレベルのキーをサポートしています:
defaults(任意)— 出力に事前設定される値の辞書(例:{"role": "assistant"})。ここに指定されたキーは、フィールドが何も書き込まなくても、解析済み出力に常に保持されます。その他のキーは、対応するフィールドが何も取得しなかった場合に削除されます。start_anchor(str) /start_anchor_pattern(str regex) — チャットプロンプト内で、現在のアシスタントメッセージがどこから始まるかをマークします。prefix=をparse_responseまたはget_response_parserに渡すと、パーサーはこのアンカーの最後の出現箇所以降のプレフィックスを右側から切り捨ててから処理を行います。これにより、マルチターン会話における以前のターンが、現在のメッセージの状態を汚染することがなくなります。アンカーはprefixにのみ適用され、解析対象のレスポンス/生成物には決して適用されません。一部のフォーマットでは、メッセージの途中でアンカーが正当に再出力される場合があります(gpt-oss harmony の出力は、すべてのチャネルを<|start|>assistantで開始します)。そのため、レスポンスをアンカー以降から切り捨てると、モデル自身の推論やツール呼び出しが失われてしまいます。これが、生成物だけでは履歴の混入を防ぐことができない理由です。プロンプトをprefix=として渡してください。ChatML スタイルのモデルでは、アンカーは通常"<|im_start|>assistant\n"です。start_anchorとstart_anchor_patternのうち、ちょうど 1 つを設定する必要があります。
例えば、このマルチターンプレフィックス(2回のアシスタントターンに注意)が与えられた場合:
<|im_start|>user Hi<|im_end|> <|im_start|>assistant Hello!<|im_end|> <|im_start|>user Again?<|im_end|> <|im_start|>assistant
パーサーは最後の<|im_start|>assistant\nまでのすべてを切り捨て、それ以前の"Hello!"ターンを破棄します。ルールは、最後のアシスタントターンを除くすべてが常に破棄されることです。
チャットテンプレートと同様に、レスポンステンプレートはトークナイザーの属性として保存され、トークナイザーとともに保存されます。チャットテンプレートとは異なり、その形式はチャットテンプレートのJinjaスクリプトとは異なりJSONに自然に収まるため、tokenizer_config.json内に保存され、別ファイルとして保存されません。
tokenizer.response_template = template
tokenizer.save_pretrained(...) # Written as a key in tokenizer_config.json詳細: フィールド API リファレンス
各フィールドは複数のキーをサポートしています。これらは2つのタイプに分類できます。まず、フィールドのキャプチャ方法を定義するキーがあります。
| キー | 型 | 目的 |
|---|---|---|
open | str or list[str] | この領域を開始するリテラル文字列。文字列のリストを指定すると、「これらのいずれかに一致する」ことを意味します。 |
open_pattern | str (正規表現) | openの正規表現版です。名前付きグループは、transformで利用可能なキャプチャ変数になります。 |
close | str or list[str] | この領域を閉じるリテラル文字列(または文字列のリスト)。省略すると、ストリームの最後まで実行されます。 |
close_pattern | str (正規表現) | closeの正規表現による代替手段です。名前付きグループは、transformで利用可能なキャプチャ変数になります。 |
repeats | bool | trueの場合、フィールドはリストとなり、各一致が追加されます。デフォルトはfalseです。 |
join | str | repeats を使用すると、リストの代わりに、この区切り文字で一致箇所を1つの文字列に連結します。 |
optional | bool | If false and the region never matches, we raise an error. Default true. |
フィールドにはopenまたはopen_patternのいずれか一方のみを指定する必要があり、両方を指定することはできません。closeとclose_patternについても同様です。
フィールドはclose/close_patternを完全に省略できます。その場合、領域は生成テキストの末尾まで開いた状態のままになります。これは、メッセージの末尾まで続く最後のフィールドに便利です。
neither open nor open_pattern を持たないフィールドは、implicitフィールドです。明示的なリージョンが開かれていない場合常に有効になるため、残りのテキストをキャプチャします。implicitフィールドは最大1つまでです。これは、contentに特殊トークンのタグがなく、他のフィールドの後にプレーンテキストとして記述される場合に最もよく使用されます。
開きと閉じのデリミタの指定に加えて、repeatsを指定することもできます。これはフィールドがリストであることを示し、デリミタが複数回一致することを意味します。これは、モデルが複数のツール呼び出しを同時に出力する並列ツール呼び出しで最も一般的です:
'<tool_call>{"name": "a", ...}</tool_call><tool_call>{"name": "b", ...}</tool_call>'
# Returns `"tool_calls": [{... "a" ...}, {... "b" ...}]` in a template with repeats: trueただし、すべての繰り返しフィールドをリストに変換すべきではありません。一部のモデルは、1つのメッセージ内で複数のreasoning_contentまたはcontentブロックを出力しますが、それらの自然な出力は1つの連結文字列です。join(区切り文字列、通常は""のみ)を設定すると、repeatsフィールドがリストを収集する代わりに、一致したものを連結するように切り替わります:
field = {"reasoning_content": {"open": "<think>", "close": "</think>", "repeats": True, "join": "\n"}}
input = "<think>first</think>...<think>second</think>"
# Returns: {"reasoning_content": "first\nsecond"}joinフィールドの各一致は、文字列としてパースされなければなりません。ストリーミングの場合、region_closeは依然として各ブロック自身の値を保持します。区切り文字は、最終的なメッセージ辞書にのみ出現します。
最後に、必須のフィールドにはoptional: falseを指定できます。このようなフィールドが欠落している場合、メッセージの辞書を返す代わりにエラーを発生させます。
生成が終了すると、閉じ区切り文字が検出されていなくても、開いているすべての領域は閉じられ、確定されます。
フィールドの内容を解析する
フィールドのキャプチャ方法を定義した後、そのキャプチャ内の生テキストをどのようにパースするかを指定する必要があります。これを制御するキーは4つあります。
| キー | 型 | 目的 |
|---|---|---|
content | str | この領域内のコンテンツタイプです。デフォルトは"text"です。各タイプには独自のパーサーがあります。 |
content_args | dict | この領域のコンテンツパーサーに渡す引数。 |
transform | dict/list | 解析済みボディの形状を変更するための任意のポストパーステンプレート(Transformを参照)。 |
transform_each | bool | true の場合、解析されたコンテンツはリストである必要があり、transform が要素ごとに適用されます。 |
最初の(そして最も重要な)キーはcontentです。これはフィールドのコンテンツタイプを示し、フィールド内でキャプチャされた生テキストを最終出力に変換するために使用されるパーサーを決定します。content_argsはパーサーを設定するために使用され、カスタムコードを必要とせずにさまざまなフォーマットの癖に対応できるようにします。
以下では、各パーサーの種類とその引数について順に見ていきます。
基本型
text、int、float、bool は基本型です。これらのコンテンツタイプは、すべて空白を除去し、必要に応じて単純な型変換を行うだけです。content_args は持ちませんが、text だけは引数 strip をサポートしており、これはキャプチャされたテキストの先頭と末尾の空白を除去します。デフォルト値は true です。
field = {"count": {"open": "<n>", "close": "</n>", "content": "int"}}
input = "<n> 42 </n>"
# Returns: {"count": 42}json
jsonパーサーは、キャプチャされたテキストをJSONとして解析します。ツールコールの引数や、その他のネスト構造を持つデータ処理の主力です。モデルが実環境でJSONをどのように壊すかに対応するため、いくつかの任意のcontent_argsを受け取ります。
unquoted_keys(bool, デフォルトfalse): キー名が引用符なしの生データである場合に有効にします (例:{name: "foo"})。厳密な JSON ではなく、Javascript スタイルのオブジェクトリテラルを出力するモデルに便利です。string_delims([open, close]のペアのリスト、省略可): 文字列の値を"..."ではなくカスタムの区切り文字で囲むモデル用です。各ペアは開始マーカーと終了マーカーを指定します。allow_non_json(bool, デフォルトfalse): パースに失敗した場合、例外を送出する代わりに、トリムされた生テキストを返します。モデルが通常は JSON を出力するものの、稀にプレーンテキストに切り替わるフィールドのフォールバックとして便利です。
unquoted_keysとstring_delimsは、どちらも非標準の、JSONにほぼ準拠しているが完全に準拠していない出力を生成するモデルを処理するために存在します。そのため、これらが必要なモデルはごく少数です。
field = {"args": {"open": "<args>", "close": "</args>", "content": "json", "content_args": {"unquoted_keys": True}}}
input = '<args>{city: "London"}</args>'
# Returns: {"args": {"city": "London"}}xml-inline
xml-inlineパーサーは、フラットなシーケンスで構成されるXML風のタグの領域用です。各タグが辞書の1つのエントリになります。tool_callsフィールド内で、JSONブロブではなく各引数を個別のタグとして出力するモデルに最もよく使用されます:
tag_pattern(str, 必須): 単一のタグに一致する正規表現。key(結果の dict のキー) とvalue(dict の値となる生テキスト) という名前付きグループを含める必要があります。value_parser(dict, optional): 各キャプチャされたvalueに適用されるネストされたコンテンツパーサー。name(パーサー、例:"json"、"int")と、任意のargs(そのcontent_args)を持つ辞書です。省略した場合、値は生の文字列のままになります。merge_duplicates(bool, デフォルトはfalse): 同じキーが複数回出現した場合、後続の一致が先行するものを上書きするのを防ぐため、値をリストに集約します。
例えば、Qwen3は各ツール呼び出しの引数をそれぞれ<parameter>タグとして出力し、以下のようにパースします。
"tool_calls": {
"open_pattern": r"<tool_call>\s*<function=(?P<name>\w+)>",
"close": "</tool_call>",
"repeats": True,
"content": "xml-inline",
"content_args": {
"tag_pattern": r"<parameter=(?P<key>\w+)>\s*(?P<value>.*?)\s*</parameter>",
"value_parser": {"name": "json", "args": {"allow_non_json": True}},
},
"transform": {"type": "function", "function": {"name": "{name}", "arguments": "{content}"}},
}ネストされたvalue_parserに注意してください。各パラメータの値は、jsonパーサー(プレーンな文字列がそのまま通るようにallow_non_jsonを指定)で処理されます。上記のtool_callsフィールドにこの入力を渡すと:
input = "<tool_call><function=get_weather><parameter=city>London</parameter><parameter=units>celsius</parameter></function></tool_call>"
# Returns: {"tool_calls": [{"type": "function", "function": {"name": "get_weather", "arguments": {"city": "London", "units": "celsius"}}}]}kv-lines
kv-linesパーサーは、行区切り形式のkey: valueペア(YAML風のメタデータや.envファイルなど)を処理します。各行が結果の辞書内の1つのエントリになります。すべての引数は省略可能です。
line_sep(str, デフォルト"\n"): ペア間の区切り文字。kv_sep(str, デフォルト":"): 1行内のキーと値の間の区切り文字。分割点は最初の出現箇所のみが使用されるため、値自体が区切り文字を含むことができます。strip(bool、デフォルトはtrue): 各キーと値の前後にある空白を除去します。value_parser(dict, 省略可): 各値に適用されるネストされたコンテンツパーサーです。xml-inlineと同じ{"name": ..., "args": ...}形式で指定します。省略した場合、値は生の文字列のままになります。
空行またはkv_sepを含まない行は、無言でスキップされるため、キャプチャされた領域内の余分な空行は許容されます。
field = {"metadata": {"open": "<meta>", "close": "</meta>", "content": "kv-lines"}}
input = "<meta>name: alice\nage: 30</meta>"
# Returns: {"metadata": {"name": "alice", "age": "30"}}age は "30" を文字列のまま保持します。30 に変換するには、value_parser に {"name": "int"} を追加してください。
ツール呼び出し引数の型付け
場合によっては、レスポンスパーサーが誤った型でモデルの出力を解析することがあります。例えば、浮動小数点数1.5を「1.50」として解析してしまう可能性があります。ツールが特定の型の引数を期待しているのに別の型を受け取ると、ツール呼び出しに問題が生じる場合があります。これを回避するには、リクエストのtoolsをparse_response()またはResponseParserに渡し、各ツールのJSONスキーマのparametersを使用してキャストできます。ツールはapply_chat_template()と同じ形式で受け入れられます。JSONスキーマ、または型ヒントとドキュメント文字列を備えたPython関数(スキーマに自動変換されます)です。
tools = [{
"type": "function",
"function": {
"name": "set_alarm",
"parameters": {
"type": "object",
"properties": {
"hour": {"type": "integer"},
"enabled": {"type": "boolean"},
"label": {"type": "string"},
},
},
},
}]
message = parse_response(model_out, template, prefix="", tools=tools)
# message["tool_calls"][0]["function"]["arguments"] ==
# {"hour": 7, "enabled": True, "label": "wake up"}変換
ほとんどのフィールドでは、transformキーは不要です。解析されたボディを最終的な出力に整形する必要がある場合、または区切り文字からの情報を結果にマージする必要がある場合に使用されます。tool_callsフィールドでは、構造が複雑なことが多いため、最もよく使用されます。
transform は テンプレートです。出力の形状を記述する dict(または list)で、"{name}" の形式の文字列は対応する値に置き換えられます。値は content(この領域の解析済みボディ)および open_pattern / close_pattern でキャプチャされた名前付きグループからアクセスできます。非常に一般的なユースケースとして、標準的なツール呼び出し形式の一部である function キーを持つ外側の dict でツール呼び出しの dict をラップすることが挙げられます。
"tool_calls": {
"open": "<tool_call>",
"close": "</tool_call>",
"repeats": True,
"content": "json",
"transform": {"type": "function", "function": "{content}"},
},つまり、この生の出力は:
<tool_call>{"name": "greet_user", "arguments": {"greeting": "Hi!"}}</tool_call>になります(repeats: Trueにより、tool_callsがリストになります):
[{"type": "function", "function": {"name": "greet_user", "arguments": {"greeting": "Hi!"}}}]"{content}" のような文字列全体を占めるプレースホルダーは、参照された値を型を保持したまま返します。つまり上記の例では、パースされた JSON の dict が function の値としてそのまま挿入されます。プレースホルダーは文字列全体を占める必要があります。テキストとプレースホルダーを混在させること("abc {name} def")は許可されていません。これらは f-string ではありません。
プレースホルダーは、"{content.args}" のようなドット区切りパスも受け取れます。これにより、content を参照した上で、解析された辞書の args キーに降下します。キー名が標準形式と一致しないツールコールの本体を整形する場合に、この方法を使用します。例えば、{"name": ..., "args": ...} を出力するモデルでは、args を arguments に変更する必要があります。
"tool_calls": {
"open": "<tool>",
"close": "</tool>",
"repeats": True,
"content": "json",
"transform": {"type": "function", "function": {"name": "{content.name}", "arguments": "{content.args}"}},
},解決できないパス(ここでは、args が欠落しているツール呼び出し)は、不正なツール呼び出しを出力するのではなく、エラーを発生させます。
transform は非常に多機能であり、モデルの出力が標準 API と大きく異なる形式の場合に必要になります。GPT-OSS がその良い例です。GPT-OSS は JSON ボディではなくチャネルヘッダーに関数名を埋め込むため、open_pattern 内の名前付きグループでそれをキャプチャし、transform 内で content とマージする必要があります。open_pattern と close_pattern 内のすべての名前付きグループは、content と並んで変数として利用可能になります。
"tool_calls": {
"open_pattern": r"<\|channel\|>commentary to=functions\.(?P<name>\w+).*?<\|message\|>",
"close": "<|call|>",
"repeats": True,
"content": "json",
"transform": {"type": "function", "function": {"name": "{name}", "arguments": "{content}"}},
},関数名はJSONボディではなくチャネルヘッダーに格納されているため、これ:
<|channel|>commentary to=functions.get_current_weather <|constrain|>json<|message|>{"location": "San Francisco, CA"}<|call|>次のようになります:
[{"type": "function", "function": {"name": "get_current_weather", "arguments": {"location": "San Francisco, CA"}}}]フィールドの解析結果がそれ自体がレコードのリストであり、各要素をリシェイプしたい場合があります。Cohereテンプレートはその良い例です。すべてのツール呼び出しを単一のJSON配列として出力するため、transform_each: Trueを設定して、変換を要素ごとに適用します。配列の各要素のキーはテンプレートのスコープに展開されるため、"{tool_name}"は現在の要素内のtool_nameを参照します。
"tool_calls": {
"open": "<|START_ACTION|>",
"close": "<|END_ACTION|>",
"content": "json",
"transform_each": True,
"transform": {"type": "function", "function": {"name": "{tool_name}", "arguments": "{parameters}"}},
},これにより、次のような出力が変換されます:
[
{"tool_name": "greet_user", "parameters": {"greeting": "Hi!"}},
{"tool_name": "search", "parameters": {"query": "weather tomorrow"}}
]標準的なAPIに適合する、次のような出力に変換します:
[
{"type": "function", "function": {"name": "greet_user", "arguments": {"greeting": "Hi!"}}},
{"type": "function", "function": {"name": "search", "arguments": {"query": "weather tomorrow"}}}
]transform_each フラグは、content がすでにリストである場合にのみ必要です。より一般的なケース、つまり各マッチが1つの要素を寄与し(repeats: True がそれらを蓄積する)、変換がデフォルトで各要素に適用される場合は、このフラグは不要です。
フレームワーク開発者: レギュラー表現の移植可能性
open_pattern、close_pattern、start_anchor_pattern は正規表現の文字列です。ほとんどのユーザー、さらにはほとんどのモデル作成者にとって問題にはなりませんが、別の言語でレスポンス解析の実装を書いている開発者の方は、私たちの実装詳細について知っておく必要があります。このセクションは、Python 以外のチャットテンプレートリングを動作させるために Jinja パーサー全体を実装しなければならなかったすべての人に向けて書かれています。以下の簡単なガイドラインに従っていただければ、レスポンステンプレートの処理がはるかに楽になることを願っています:
- チャット解析で使用されるすべての正規表現には、Pythonの
regexモジュールを使用しています。 Python3のすべての文字列がUnicodeであるため、すべての正規表現のマッチングはUnicode対応です。これは\wのような一般的な文字に特に影響を与えます。エンジンで関連するUnicodeフラグを設定してください。 - すべての正規表現は
re.DOTALLを有効にし、re.MULTILINEを無効にしてコンパイルされるため、.は\nにマッチしますが、^と$は改行ではなく、入力全体のはじまり/おわりにのみマッチします。 - 名前付きグループには
(?P<name>...)構文を使用します。他の正規表現実装では、名前付きキャプチャグループの構文が大きく異なるため、正規表現内のこのパターンを検索して、ローカルの実装に合わせて書き直す必要がある場合があります。 - データの出力タイミングを判断するために、部分正規表現マッチングを使用します。これは、領域の終わりが複数のトークンで構成される可能性があるため、領域の終わりの区切り文字を出力したくない場合に必要です。そのため、トークンが領域の境界の一部ではなく、領域の内部にあると確信できるまで、トークンの出力を保留します。つまり、領域の終わりの正規表現が部分一致した場合、正規表現が完全に一致するか、一致しなくなるまでデータを保留します。正規表現エンジンが部分マッチングをサポートしていない場合でも、レスポンステンプレートの実装は可能ですが、この問題に対処するために別の解決策を見つける必要があるかもしれません。単純なアプローチの一つは、これらの領域を保留するか、
dirtyとして出力し、終わりの区切り文字が明確に確認できるまで待つことです。 - 正規表現エンジンが、
(?!...)などのルックアラウンドをサポートしていない場合があります(稀ですが)。これらはレスポンステンプレートでは一般的に使用されませんが、出現する可能性があり、当方ではサポートしています!そのため、そのような場合はエラーをスローするか、正規表現エンジンが一致候補を検出した際に、ルックアラウンドを手動で抽出してコード内で強制する必要があるかもしれません。 - バックリファレンス、アトミックグループ、ポゼッシブ量子子、再帰などの他の高度な機能は、一般的にレスポンステンプレートでは使用されません。モデル作成者がこれらを使用しないよう促すため、おそらく安全に無視して問題ありません。