英語を指示しても日本語が混ざる直接の原因は、Gemini が大量の多言語コーパスで学習されており、長い日本語入力に注意(attention)が強く引かれることにあります。プロンプト末尾に短く Answer in English と添えても、入力本文が長いほど指示の相対的な強度は下がります。加えて、固有名詞や引用のように「そのまま保持したほうが正確だ」とモデルが判断した箇所は、指示を強めない限り原語で残ります。
ここで見落としやすいのが、この現象が決定論的ではないという点です。同じ入力でも、温度やサンプリングの揺らぎによって、混ざる回もあれば混ざらない回もあります。つまり本質は「混入するか否か」という二値ではなく、「1,000 出力あたり何回混ざるか」という確率です。私の翻訳パイプラインの実測では、入力が 2,000 トークンを超えるあたりから Gemini 2.0 Flash で混入が目立ち始め、2.5 Pro でも 3.1 Pro でも完全にゼロにはなりませんでした。「モデルを上げれば直る」という発想を捨てるべき理由がここにあります。相手が確率なら、対策も「計測して閾値まで下げる」という形にせざるを得ません。
計測基盤ができたら、まず効くのは System Instructions です。本文末尾に 英語で と添えるのではなく、System Instructions に強い制約として書きます。System Instructions は本文プロンプトより重く扱われる設計で、ここに許可リストと違反時の動作まで書き切るのがポイントです。
from google import genaifrom google.genai import typesclient = genai.Client(api_key="YOUR_API_KEY")SYSTEM_INSTRUCTION = """You are a professional English-language technical editor.RULES:1. Respond entirely in English. Do not output any hiragana, katakana, or kanji.2. If a Japanese proper noun is essential, transliterate it to romaji and add an English gloss in parentheses.3. If you cannot comply, output exactly "UNABLE_TO_COMPLY" and nothing else.""".strip()resp = client.models.generate_content( model="gemini-2.5-pro", contents="以下の日本語記事を英語で300語以内に要約してください。\n\n" + article_body, config=types.GenerateContentConfig( system_instruction=SYSTEM_INSTRUCTION, temperature=0.2, ),)
私の実測では、末尾添え書きから System Instructions への移行だけで、長文入力の混入率が数分の一まで落ちました。ただしこれは「下がる」であって「消える」ではありません。だからこそ、次の層を測りながら積みます。
Few-shot とスキーマ検証 — どこで効いてどこで破れるか
System Instructions で止まりきらない残りを、Few-shot 例示と response_schema の二層で締めます。ここで大切なのは、どの層がどの条件で効くのかを混同しないことです。実測をまとめると次のようになりました。
対策
よく効く条件
破れやすい条件
コスト
System Instructions 強化
短〜中程度の入力全般
入力が指示より遥かに長いとき
ほぼゼロ
Few-shot 例示(1〜2組)
長文・複雑タスク
トークン予算が厳しいとき
入力トークン増
response_schema 検証
構造化出力を受ける処理
自由文の本文フィールド内の残留
検証コードの保守
Few-shot は「ルールを書く」より「例を見せる」ほうが追従性が高いという一般則をそのまま使います。効果が顕著なのは長文+複雑タスクで、私の翻訳パイプラインでは 2 組入れるだけで混入率が体感で一桁下がりました。ただしトークンを食うので、短い入力なら System Instructions だけで十分です。
EXAMPLES = [ ("東京の天気について教えて", "Tokyo has four distinct seasons, with hot humid summers near 30 degC and mild winters."), ("このアプリの料金プラン", "The app has three tiers: Free, Pro (USD 5/month), and Team (USD 20/month per seat)."),]few_shot = "\n\n".join( f"### Example\nInput (Japanese): {q}\nOutput (English only): {a}" for q, a in EXAMPLES)prompt = f"{few_shot}\n\n### Task\nInput (Japanese): {user_input}\nOutput (English only):"
def daily_contamination_rate(log_lines: list[dict]) -> float: if not log_lines: return 0.0 bad = sum(1 for r in log_lines if r["contaminated"]) return bad / len(log_lines)