金色の配線パターンが広がる基板の接写
現場の実践

LLMのチャットテンプレートが勝手に足す「システムプロンプト」の正体

目次を見る

社内のRAGシステムやチャットボットに、オープンソースのLLM(大規模言語モデル)を組み込んでいる方に向けた内容です。システムプロンプトを意図的に空にしているのに、モデルが「自分はChatGPTです」のような発言をして困ったことはないでしょうか。原因は多くの場合、チャットテンプレート(モデルに付属する変換処理)にあります。

Hugging Face上でダウンロード数上位300モデルのうち189モデルのテンプレートを調べた検証によると、52モデルが「何も書いていないはずの会話」に、モデルの名前・開発元・学習データの締め切り日・今日の日付・会話の許可範囲などを勝手に差し込んでいました。重複を除いた96種類のテンプレートで数えても約5分の1にあたる20種類です。業務システムに組み込む前に確認しておかないと、思わぬ形で挙動に影響します。

チャットテンプレートとは何か

LLMは本来、ロールごとに分かれた会話の配列ではなく、1本の長い文字列しか読めません。チャットテンプレートは、システム・ユーザー・アシスタントといったロール付きのメッセージ一覧を、モデルが実際に読む1本の文字列に組み立てる変換処理です。

実装にはJinja(Pythonでよく使われるテンプレートエンジン)が使われており、モデルのリポジトリにtokenizer_config.jsonの一部として同梱されています。多くの開発者は「システムプロンプトを空にすれば、ユーザーの発言とロールマーカー(役割を示す特殊トークン)だけが渡る」と考えがちですが、実際はテンプレート側が独自の文言を追記しているケースがあります。

たとえばLlama 3.1 8B Instructに「Who made you?」という1文だけを送ると、実際にモデルへ渡る文字列には次のようなシステムブロックが自動生成されます。

<|begin_of_text|><|start_header_id|>system<|end_header_id|>

Cutting Knowledge Date: December 2023
Today Date: 26 Jul 2024
<|eot_id|><|start_header_id|>user<|end_header_id|>

Who made you?<|eot_id|>

開発者が一切書いていない「学習データの締め切り」と「今日の日付」が、テンプレート側で勝手に挿入されています。

どのモデルが何を足しているか

調査では、189モデルのうち114モデルは何も追加せず、23モデルは「あなたは親切なアシスタントです」程度の無難な一文のみでした。残り52モデルは、モデルの素性や日付、会話範囲の制限といった踏み込んだ内容を加えていました。

主なパターンは以下の通りです。

  • Qwen2.5シリーズ: 「あなたはAlibaba Cloudが開発したQwenです」という1行が、サイズ違いや量子化(モデルを軽量化する処理)コピーにも継承されている
  • gpt-oss 20B: 「あなたはOpenAIが訓練したChatGPTです」と学習データの締め切り・今日の日付を明記
  • Granite 3.3 2B: IBM開発である旨と、締め切り日・今日の日付を明記
  • DeepSeek Coder 7B v1.5: コンピュータサイエンス関連以外の質問や、政治的・セキュリティ関連の質問への回答拒否を指示

オープンな重み(モデルのパラメータ)を公開しているモデルでも、ライセンス元のブランド名や回答範囲の制約が、ユーザーの目に触れない形で埋め込まれている点に注意が必要です。業務システムで「このモデルはOpenAI製ではない」と説明していても、モデル自身がChatGPTを名乗ってしまうと、利用者への説明と矛盾が生じます。

日付の扱いは実行基盤によって変わる

さらに厄介なのは、日付情報の扱いが実行基盤(推論エンジン)ごとに異なる点です。多くのテンプレートはstrftime_nowという関数で実行時の日付を取得する設計ですが、Meta製のLlama 3.1・3.3テンプレートはこの関数を使わず、次のように固定値を埋め込んでいます。

{%- if not date_string is defined %}
{%- set date_string = "26 Jul 2024" %}
{%- endif %}

これはLlama 3.1のリリースから3日後の日付です。実行基盤側が日付を渡さない限り、モデルは「今日は2024年7月26日」と思い込み続けます。

検証では、同じLlama 3.1テンプレートでも実行基盤によって挙動が異なることが確認されています。

実行基盤モデルに伝わる日付
transformers2024年7月26日(固定)
vLLM 0.322024年7月26日(固定)
llama.cpp(llama-server)実行時の今日の日付
Ollama 0.40(公式pull)システムブロックなし
Ollama 0.40(GGUFを自前インポート)実行時の今日の日付

Ollamaの公式配布モデルはMetaのJinjaテンプレートをそのまま使わず、Go言語で書かれた独自テンプレートを採用しており、システムプロンプトやツール定義を送らない限りシステムブロック自体を開きません。一方で、GGUF形式(量子化モデルの配布形式)を自前でインポートすると、同梱のMeta製テンプレートがllama-server経由で処理され、実行時の日付が入ります。同じ「Llama 3.1」でも配布経路によって挙動が変わるということです。

業務システムで今日確認できること

社内システムに組み込んでいるモデルが、意図しない情報を発言していないか確認する手順は難しくありません。

python -c "
from transformers import AutoTokenizer
tok = AutoTokenizer.from_pretrained('モデル名')
msgs = [{'role': 'user', 'content': 'テスト'}]
print(tok.apply_chat_template(msgs, tokenize=False, add_generation_prompt=True))
"

このコードを使い、システムプロンプトを渡さない状態でテンプレートの出力をそのまま表示すれば、何が自動追加されているか一目で分かります。確認すべきポイントは次の3点です。

  • モデル名・開発元の自称: 社内向けの説明と矛盾する自己紹介をしていないか
  • 日付情報の有無: 固定値か、実行基盤が渡す動的な値かをtokenizer_config.jsonのchat_templateフィールドで確認
  • 回答範囲の制限文: 業務用途で不要な制約(特定分野限定など)が入っていないか

tokenizer_config.jsonはHugging Faceのモデルページの「Files」タブから直接閲覧できます。Jinjaの文法が読めなくても、{% set %}や{% if %}の中に書かれた平文のメッセージ部分だけ追えば、何が足されているかは把握できます。

社内のRAGパイプラインやカスタムのエージェント基盤を自前実装している場合、推論エンジンの選定(vLLM・llama.cpp・Ollamaなど)によって同じモデルでも挙動が変わる点は、移行時の回帰テストの観点としても押さえておく価値があります。

まとめ

チャットテンプレートは、システムプロンプトを空にしても「空の会話」をモデルに渡しているとは限りません。モデルの自己紹介や日付、回答範囲の制約が暗黙に挿入されているケースが一定数あります。

導入前・移行前に確認すべきことは次の通りです。

  • 使用するモデルのtokenizer_config.jsonを開き、chat_templateの中身を実際に読む
  • apply_chat_templateで空のユーザーメッセージを変換し、出力文字列を目視で確認する
  • 推論エンジンを切り替える予定がある場合、日付の扱いが変わらないか実機で比較する

業務要件として「モデルの素性を正確に利用者へ伝える」「学習データの鮮度に関する誤解を避ける」ことが必要であれば、テンプレートの中身はコードレビューの対象に含める価値があります。

参考

Skip the system prompt and the template writes one

この記事について: 本記事は AI を活用して作成し、forva AI 編集部が内容を確認・監修しています。

AI 駆動開発のご相談は forva AI へ。まずはお気軽にどうぞ。