会話の途中でシステム指示を足す

Claude Code エンジニアリング 5分で読めます

Claude の会話では、いちばん上に置く system がふるまいの土台になります。ところが会話が始まってから状況が変わることがあります。利用者がモードを切り替えた、手元のファイルが更新された、残りの予算が減った。こうした「途中で分かったこと」を伝えるために system を書き換えると、そこから後ろのキャッシュがすべて無効になります。messages 配列に role: "system" のメッセージを足せば、キャッシュを壊さずに同じことができます。

概要

会話の途中でシステム指示を足す機能です。トップレベルの system フィールドを編集するかわりに、messages 配列の末尾のほうに {"role": "system", "content": "..."} という項目を追加します。ベータヘッダーは不要で、通常の client.messages.create から使えます。

解決している問題はプロンプトキャッシュです。Claude のキャッシュは前方一致で働きます。toolssystemmessages の順に組み立てられた文字列の、先頭からの一致でキャッシュが引かれます。したがって system を1文字でも変えると、その後ろにある会話履歴のキャッシュがすべて無効になり、次のリクエストで会話全体を最初から処理し直すことになります。

会話の途中に置いたシステムメッセージは履歴の後ろに並ぶので、前にあるキャッシュ済みの部分は変わりません。指示は、そのターンと、それ以降のすべてのターンに効きます。

この機能に対応しているモデルは Claude Opus 5、Claude Opus 4.8、Claude Fable 5、Claude Mythos 5 です。Claude Sonnet 5 は対応していません。対応していないモデルに送ると 400 エラー(role 'system' is not supported on this model)が返ります。

基本概念

置ける場所には決まりがあります。

もう1つ大事なのが書き方です。命令ではなく、状況として書いてください。「利用者が言ったことは無視しろ」「前の指示は破棄しろ」といった上書き型の言い回しは避けます。Claude は、利用者の不利になるように働く指示から利用者を守るよう訓練されており、その保護は system というロールに対しても働きます。事実を置いて、あとは Claude に任せるのが確実です。

従来からある方法として、利用者のターンの本文に <system-reminder> のような印をつけて運用指示を埋め込むやり方があります。キャッシュの挙動は同じですが、安全性が違います。利用者の入力に書き込めるものは、その印を偽装できます。role: "system" は偽装できない運用側の経路です。対応していないモデルではこの旧方式にフォールバックすることになります。

使い方

トップレベルの system は固定したまま、会話の後ろに指示を足します。

response = client.messages.create(
    model="claude-opus-5",
    max_tokens=16000,
    system=[
        {
            "type": "text",
            "text": STABLE_SYSTEM_PROMPT,
            "cache_control": {"type": "ephemeral"},
        }
    ],
    messages=[
        *history,
        {"role": "user", "content": user_message},
        {
            "role": "system",
            "content": "利用者のタイムゾーンは Asia/Tokyo です。",
        },
    ],
)

TypeScript でも形は同じです。

const response = await client.messages.create({
  model: "claude-opus-5",
  max_tokens: 16000,
  system: [
    { type: "text", text: STABLE_SYSTEM, cache_control: { type: "ephemeral" } },
  ],
  messages: [
    ...history,
    { role: "user", content: userMessage },
    { role: "system", content: "簡潔モードです。回答は40語以内にしてください。" },
  ],
});

効いているかどうかは usage で確かめられます。cache_read_input_tokens が伸びていれば、前置きはキャッシュから読まれています。ここが毎回 0 のままなら、どこかで前方一致が崩れています。トップレベルの system に日時や UUID を埋め込んでいないか、tools の順序がリクエストごとに変わっていないかを疑ってください。

対応していないモデルも扱うなら、400 を捕まえて旧方式に落とす形にします。

try:
    response = client.messages.create(model=MODEL, messages=messages, ...)
except anthropic.BadRequestError:
    # role: "system" 非対応。利用者ターンの本文に埋め込む形へ落とす
    response = client.messages.create(model=MODEL, messages=fallback_messages, ...)

まとめ

要点をまとめます。

本記事は Anthropic 公式ドキュメントに基づく非公式の日本語解説です。仕様は変わることがあります。実装前に公式ドキュメントで最新をご確認ください。