GEMINI LABEN
NANOLITE — Nano Banana 2 Liteが登場しました。Googleで最も速く、最もコスト効率の高いGemini Imageモデルで、軽量な画像生成を安く回したい用途に向いていますOMNIFLASH — Gemini Omni Flashがpublic previewになりました。ネイティブにマルチモーダルなモデルで、企業や開発者が独自の動的な動画ワークフローを構築できますAGENTS — Managed Agentsが拡張されました。background: trueでサーバー側の非同期実行とポーリング、リモートMCPサーバー連携、対話をまたぐ認証情報のリフレッシュに対応しますMEMORY — Memory BankのIngestEvents APIが一般提供になりました。イベントの取り込みとメモリ生成を分離し、コンテンツを継続的にストリームできますTHROUGHPUT — Provisioned Throughputで、同一モデル・同一リージョンに対して最大7件の保留オーダーを提出できるようになりましたDEPRECATE — 画像生成モデルは8月17日に、Gemini Enterprise Agent PlatformのGrok 4.1系は8月20日に停止される予定ですNANOLITE — Nano Banana 2 Liteが登場しました。Googleで最も速く、最もコスト効率の高いGemini Imageモデルで、軽量な画像生成を安く回したい用途に向いていますOMNIFLASH — Gemini Omni Flashがpublic previewになりました。ネイティブにマルチモーダルなモデルで、企業や開発者が独自の動的な動画ワークフローを構築できますAGENTS — Managed Agentsが拡張されました。background: trueでサーバー側の非同期実行とポーリング、リモートMCPサーバー連携、対話をまたぐ認証情報のリフレッシュに対応しますMEMORY — Memory BankのIngestEvents APIが一般提供になりました。イベントの取り込みとメモリ生成を分離し、コンテンツを継続的にストリームできますTHROUGHPUT — Provisioned Throughputで、同一モデル・同一リージョンに対して最大7件の保留オーダーを提出できるようになりましたDEPRECATE — 画像生成モデルは8月17日に、Gemini Enterprise Agent PlatformのGrok 4.1系は8月20日に停止される予定です
記事一覧/API / SDK
API / SDK/2026-05-01中級

Gemini APIの『contents must alternate between user and model』エラーが消えない理由と直し方

Gemini API で頻発する『contents must alternate between user and model』エラーの正体と、role 順序・assistant 表記・Function Calling 履歴の落とし穴を、すぐ使える修正コード付きで解説します。

gemini-api279troubleshooting57chat4function-calling20python103javascript3

google.api_core.exceptions.InvalidArgument: 400 Please ensure that multiturn requests alternate between user and model. ── Gemini API でマルチターン会話を組み立てている方なら、一度はこのエラーで足止めされた経験があるのではないでしょうか。私も自前のチャットボットを Vertex AI へ移行する作業で、まさにこのメッセージと半日格闘しました。

ややこしいのは、エラー文だけ見ると「会話が交互になっていない」という意味に読めるのに、実際には別の原因(role の表記ゆれ、Function Calling のレスポンス構造、system_instruction の混入など)で同じ例外が投げられる点です。ここでは私がプロダクトのリリース前に踏んだ落とし穴を中心に、原因別の見分け方と修正パターンを整理しました。

エラーが起きる仕組み — Gemini が要求する 2 つのルール

Gemini API の generateContent エンドポイントは、contents 配列に対して 2 つの厳格なルールを課しています。1 つは「roleusermodel の 2 種類のみで、必ず交互に並ぶ」こと。もう 1 つは「最後のターンは必ず user で終わる」ことです。OpenAI 系 API のように assistantsystemcontents の中に置くと、Gemini はそれを許容せず即座に 400 を返します。

最初に、最小構成で正しい形を見ておきます。

# 動く最小例(Python / google-genai SDK)
from google import genai
 
client = genai.Client(api_key="YOUR_GEMINI_API_KEY")
 
contents = [
    {"role": "user", "parts": [{"text": "東京の天気は?"}]},
    {"role": "model", "parts": [{"text": "東京は晴れです。"}]},
    {"role": "user", "parts": [{"text": "明日は?"}]},  # 末尾は必ず user
]
 
response = client.models.generate_content(
    model="gemini-2.5-pro",
    contents=contents,
)
print(response.text)

この形を「正解」として頭に置いておくと、以降のケース別エラーが読み解きやすくなります。

ケース1: 同じ role が連続している(最頻出パターン)

実装の現場で一番多いのは、ユーザーの発話を受け取る前に履歴へ先回りで model のメッセージを足してしまうケースです。たとえば「初期挨拶」を model ロールで埋め込み、続けて UI から来た最初のユーザー発話を user で push する設計にすると、その後 model の応答が返ってきたときに意図せず usermodelusermodelmodel の並びになってしまうことがあります。

# NG: 同じ role が連続
contents = [
    {"role": "model", "parts": [{"text": "こんにちは。何をお手伝いしましょう?"}]},  # 先頭が model
    {"role": "user",  "parts": [{"text": "こんにちは"}]},
    {"role": "model", "parts": [{"text": "はい"}]},
    {"role": "model", "parts": [{"text": "どうされましたか?"}]},  # 連続して model
]

修正の方針は単純で、「初期挨拶を system_instruction に逃がす」「連続するターンは 1 つに統合する」のいずれかです。私のプロジェクトでは前者のほうが管理コストが低くおすすめです。

# OK: system_instruction で挨拶を表現し、contents は user 始まり
config = genai.types.GenerateContentConfig(
    system_instruction="あなたは親しみやすいアシスタントです。最初の応答で挨拶してください。"
)
contents = [{"role": "user", "parts": [{"text": "こんにちは"}]}]
 
response = client.models.generate_content(
    model="gemini-2.5-pro",
    contents=contents,
    config=config,
)

ケース2: assistant を渡している(OpenAI からの移植で頻出)

OpenAI Chat Completions の感覚で role: "assistant" のまま contents を組むと、Gemini はそれを未知のロールとして弾きます。エラーメッセージは「alternate」と表示されることがあり、原因が掴みにくいのが厄介です。

# NG: OpenAI 互換の role 名
contents = [
    {"role": "user",      "parts": [{"text": "Hi"}]},
    {"role": "assistant", "parts": [{"text": "Hello!"}]},  # ← Gemini では model
]

移植する場合は、メッセージ整形の段階で role を写像する小さな関数を 1 つ用意しておくと、以降の混入事故を防げます。

ROLE_MAP = {"user": "user", "assistant": "model", "model": "model"}
 
def to_gemini_contents(messages):
    out = []
    for m in messages:
        role = ROLE_MAP.get(m["role"])
        if role is None:
            continue  # system は別扱い
        out.append({"role": role, "parts": [{"text": m["content"]}]})
    return out

ケース3: Function Calling の応答を間違った role で詰めている

Function Calling を使い始めて最初にぶつかるのが、ツール実行結果の格納方法です。Gemini では「ツールから返ってきた結果」も role: "user" 側に乗せ、functionResponse という part を入れる仕様になっています。role: "function"role: "tool" を使うと、その時点で例外が出ます。

# OK: functionResponse は user ロールに置く
from google.genai import types
 
contents = [
    {"role": "user",  "parts": [{"text": "東京の天気を取得して"}]},
    {"role": "model", "parts": [
        types.Part(function_call=types.FunctionCall(name="get_weather", args={"city": "Tokyo"}))
    ]},
    {"role": "user", "parts": [
        types.Part(function_response=types.FunctionResponse(
            name="get_weather",
            response={"weather": "sunny", "temp": 22},
        ))
    ]},
]

「ツール呼び出しなのに user 扱い?」と直感に反するため、ここはつまずきやすい仕様です。Function Calling 全般についてはGemini API で Function Calling が動かないときの調査手順も参考になります。

ケース4: system_instruction を contents に混ぜている

OpenAI 系の習慣で role: "system"contents の先頭に入れる実装がそのまま残っているケースです。Gemini の場合、システム指示は GenerateContentConfig.system_instruction(または旧 SDK の system_instruction 引数)で渡すのが正解で、contents に入れると「user 始まりではない」と判定されます。

# NG
contents = [
    {"role": "system", "parts": [{"text": "あなたは丁寧なサポート担当です"}]},  # ← ここが原因
    {"role": "user",   "parts": [{"text": "返金したいです"}]},
]
 
# OK
config = genai.types.GenerateContentConfig(
    system_instruction="あなたは丁寧なサポート担当です"
)
contents = [{"role": "user", "parts": [{"text": "返金したいです"}]}]

履歴を壊さないためのバリデータを 1 つ持っておく

長期運用で一番効くのは、API へ投げる直前に履歴を検査するバリデータです。私はどのプロジェクトでも下のような 20 行ほどの関数を用意し、テストケースとして「連続 user」「連続 model」「末尾 model」「assistant 混入」をカバーしています。

def validate_contents(contents):
    if not contents:
        raise ValueError("contents が空です")
    if contents[0]["role"] != "user":
        raise ValueError("先頭は user である必要があります")
    if contents[-1]["role"] != "user":
        raise ValueError("末尾は user で終わる必要があります")
    for i in range(len(contents) - 1):
        if contents[i]["role"] == contents[i + 1]["role"]:
            raise ValueError(f"role が連続: index {i}{i+1} がともに {contents[i]['role']}")
    for c in contents:
        if c["role"] not in ("user", "model"):
            raise ValueError(f"未知の role: {c['role']}")
    return True

CI でこのバリデータを通しておけば、SDK バージョンを上げたときの仕様変更や、コントリビュータの誤実装にも気付きやすくなります。会話履歴の保存設計そのものに不安がある方は、Gemini API のチャット履歴管理エラーまとめも合わせてご覧ください。

本書の「対話モデルの履歴設計」の章は、Gemini に限らず役割設計の基礎として読み応えがあります。

次にすること

このエラーは、慣れてしまえば 1 分で原因を特定できる種類のものです。今日からできる一歩としては、お使いのプロジェクトに先ほどの validate_contents を組み込み、ユニットテストとして「連続ロール」「末尾 model」のケースを 1 件ずつ追加してみてください。エラーの再発防止が驚くほど楽になります。Function Calling を本格的に運用しているなら、functionResponse を含むサンプルもテストに加えておくと盤石です。

シェア

お読みいただきありがとうございます

Gemini Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • コピー&ペーストで使える実装コード付き
  • 毎日新しい上級ガイドを追加
  • ¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

もしこの記事がお役に立ちましたら、チップ(¥150)で応援いただけると大変励みになります。広告なしでの運営を続けるため、皆さまのご支援が大きな力になっています。

関連記事

API / SDK2026-05-28
Gemini API の chat で send_message ごとに温度やトークン上限が変わらない原因
google-genai SDK の chat セッションで send_message ごとに generationConfig や temperature を渡しているのに、応答の挙動が変わらない・前の設定が引きずられる場合の切り分け手順をまとめました。SDK の設計意図を踏まえた回避策と、現場で使っているラッパー実装まで動くコードで整理しています。
API / SDK2026-04-19
Gemini APIのマルチターン会話が壊れる:チャット履歴管理の落とし穴と解決策
Gemini APIでマルチターン会話を実装すると会話が長くなるにつれてトークン超過・応答遅延・文脈消失が起きます。ChatSessionの正しい使い方と履歴管理の実践的な解決策をコード付きで解説します。
API / SDK2026-06-14
Gemini API の本文が途中で切れる — finish_reason: MAX_TOKENS を検知して続きを継ぎ直す実装メモ
長文生成で末尾が文の途中でぷつりと切れる。原因の多くは finish_reason: MAX_TOKENS です。例外も 200 のまま静かに混入するこの事故を検知し、続きを継ぎ足して全文を取り戻す実装を、思考トークンの落とし穴とあわせてまとめました。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →