Gemini API を使い始めた開発者の多くが最初に感じる壁があります。「プロンプトを変えると出力がブレる」「期待した形式で返ってこない」「毎回少しずつ違う回答が出る」という問題です。
これらの問題のほとんどは、システム指示(system_instruction)の設計とプロンプトの構造で解決できます。このガイドでは、Gemini API の出力品質を安定させるための実践的な設計パターンを、動作するコード例と共に紹介します。
システム指示(system_instruction)とは何か
Gemini API では、ユーザーのメッセージとは別に「モデルへの恒久的な指示」を設定できます。これが system_instruction です。OpenAI API でいう system メッセージに相当します。
system_instruction は会話全体を通じて有効であり、ユーザーのメッセージが変わっても適用され続けます。モデルの「人格」「役割」「出力形式」「行動制約」を定義するのに使います。
基本的な使い方
import google.generativeai as genai
genai.configure(api_key="YOUR_GEMINI_API_KEY")
model = genai.GenerativeModel(
model_name="gemini-2.0-flash",
system_instruction="""あなたはシニアバックエンドエンジニアです。
以下のルールに従って回答してください:
- 回答は必ず日本語で書く
- コードを含む場合は必ずコメントを付ける
- 「できません」と言わず、代替案を提示する
- 技術的な回答は根拠を1文で添える"""
)
response = model.generate_content("Python でファイルを非同期に読み込む方法を教えてください")
print(response.text)この例では、「シニアバックエンドエンジニア」という役割と4つの行動ルールを設定しています。これだけで出力の一貫性が大きく向上します。
出力品質に直結するシステム指示の設計パターン
パターン1: 出力形式を明示する
「JSON で返して」だけでは不安定です。スキーマを具体的に記述することで、形式の一貫性が保たれます。
model = genai.GenerativeModel(
model_name="gemini-2.0-flash",
system_instruction="""入力されたテキストを分析し、必ず以下のJSON形式のみで応答してください。
マークダウンのコードブロックや余分なテキストは含めないこと。
{
"sentiment": "positive" | "negative" | "neutral",
"confidence": 0.0から1.0の数値,
"key_phrases": ["フレーズ1", "フレーズ2"],
"summary": "50文字以内の要約"
}"""
)
response = model.generate_content("今日は天気が良くて気分も最高です!仕事も順調に進んでいます。")
print(response.text)
# 出力例: {"sentiment": "positive", "confidence": 0.95, "key_phrases": ["天気が良い", "気分が最高", "仕事が順調"], "summary": "天気と仕事の両方が好調で気分が良い"}重要なのは「コードブロックや余分なテキストを含めない」という明示的な指示です。これがないと、出力が json ... で囲まれて返ってくることがあります。
パターン2: 推論の深さを指定する
ビジネスタスクでは「短い回答」と「詳細な説明」が混在します。タスクに応じて推論の深さを指定することで、不要な冗長さを防げます。
# 短い回答が必要なケース
quick_model = genai.GenerativeModel(
model_name="gemini-2.0-flash",
system_instruction="""質問に対して最大3文で簡潔に回答してください。
前置きや説明は不要です。直接回答から始めてください。"""
)
# 詳細な説明が必要なケース
detail_model = genai.GenerativeModel(
model_name="gemini-2.0-flash",
system_instruction="""質問に対して以下の構成で回答してください:
1. 結論(1文)
2. 理由・根拠(2〜3点)
3. 具体例またはコード
4. 注意点または補足"""
)パターン3: 制約と代替案の両方を指定する
「〜はしない」だけでは、モデルが何をすべきか分からなくなることがあります。禁止事項と共に「代わりにどうするか」を指定するのがコツです。
model = genai.GenerativeModel(
model_name="gemini-2.0-flash",
system_instruction="""セキュリティの専門家として回答してください。
禁止事項と代替対応:
- 機密情報(パスワード・秘密鍵等)を直接記述しない → 代わりに「環境変数から読み込む」「シークレットマネージャーを使う」と案内する
- 古い暗号化手法(MD5・SHA-1等)を推奨しない → 代わりに現在のベストプラクティスを提示する
- 「セキュリティ上の懸念があります」だけで終わらない → 必ず具体的な対処法を続ける"""
)プロンプト設計:Few-Shot Learning で安定性を上げる
システム指示だけでなく、プロンプト側の設計も出力品質に大きく影響します。中でも Few-Shot Learning(例示による学習)は即効性が高い手法です。
Few-Shot プロンプトの実装例
import google.generativeai as genai
genai.configure(api_key="YOUR_GEMINI_API_KEY")
model = genai.GenerativeModel(model_name="gemini-2.0-flash")
def classify_with_few_shot(text: str) -> str:
"""
Few-Shot Learning でサポートチケットを分類する。
例示を含めることで、新しい入力も同じ形式で分類される。
"""
prompt = f"""以下の例を参考に、サポートチケットをカテゴリに分類してください。
例1:
入力: ログインできません。パスワードを入力してもエラーになります。
出力: カテゴリ: 認証エラー, 緊急度: 高
例2:
入力: ダッシュボードの表示が少し遅い気がします。
出力: カテゴリ: パフォーマンス, 緊急度: 低
例3:
入力: 請求書のPDFが開けません。
出力: カテゴリ: ファイル操作エラー, 緊急度: 中
分類対象:
入力: {text}
出力:"""
response = model.generate_content(prompt)
return response.text.strip()
# テスト
result = classify_with_few_shot("メールが送れないというお問い合わせです")
print(result)
# 出力例: カテゴリ: 送信エラー, 緊急度: 高Few-Shot の例示は「形式」を教えるためのものです。入力の多様性をカバーするよう、3〜5個程度の例を用意しておくと安定します。
温度(temperature)パラメータの使い方
出力の「ランダム性」を制御する temperature は、タスクの性質に応じて使い分けます。
# 分類・コード生成・データ抽出 → temperature を低くする(再現性重視)
precise_model = genai.GenerativeModel(
model_name="gemini-2.0-flash",
generation_config=genai.types.GenerationConfig(
temperature=0.1, # ほぼ決定論的な出力
max_output_tokens=512
)
)
# アイデア出し・コピー生成・創作 → temperature を高くする(多様性重視)
creative_model = genai.GenerativeModel(
model_name="gemini-2.0-flash",
generation_config=genai.types.GenerationConfig(
temperature=1.0, # より多様な出力
max_output_tokens=1024
)
)分類や構造化データ抽出のタスクに temperature=1.0 を使うと、毎回微妙に異なる分類になることがあります。これが「出力がブレる」原因の一つです。
エラーハンドリングとフォールバック
本番環境では API エラーへの対処が必須です。Gemini API で発生しやすいエラーと対処法を示します。
import google.generativeai as genai
import time
from google.api_core import exceptions as google_exceptions
def safe_generate(model, prompt: str, max_retries: int = 3) -> str:
"""
エラー処理付きのコンテンツ生成関数。
レート制限・一時エラーは自動リトライ、それ以外は例外を再スローする。
"""
for attempt in range(max_retries):
try:
response = model.generate_content(prompt)
return response.text
except google_exceptions.ResourceExhausted:
# レート制限: 指数バックオフで待機
wait_time = 2 ** attempt * 10 # 10秒 → 20秒 → 40秒
print(f"レート制限。{wait_time}秒待機中... (試行 {attempt + 1}/{max_retries})")
time.sleep(wait_time)
except google_exceptions.ServiceUnavailable:
# 一時的なサービス停止
wait_time = 5 * (attempt + 1)
print(f"サービス一時停止。{wait_time}秒後リトライ...")
time.sleep(wait_time)
except google_exceptions.InvalidArgument as e:
# プロンプトの問題(リトライしても解決しない)
print(f"無効なリクエスト: {e}")
raise
# 全リトライ失敗
raise RuntimeError(f"最大リトライ数({max_retries})を超えました")システム指示が長くなってきたら、置き場所を疑う
システム指示は、書けば書くほど出力が安定します。ただし、その安定と引き換えに、毎リクエストで同じトークンを送り続けることになります。
私の個人開発のパイプラインでは、システム指示が 1,000 トークンを超えたあたりから、この繰り返し送信が費用の無視できない割合を占めるようになりました。処理件数が増えるほど、指示の長さが線形に効いてきます。
私自身が落ち着いた対処は、二段階に分ける方法でした。
- 指示を「不変部分」と「可変部分」に割る。役割定義・出力スキーマ・禁止事項は毎回同じなので不変部分。対象データや日付は可変部分
- 不変部分をキャッシュに載せる。可変部分だけをリクエストごとに送る
この分離をすると、指示を短くするために品質を削る必要がなくなります。個人開発のように予算の上限が自分の財布である環境では、この一手が効きます。むしろ、キャッシュに載せる前提であれば、不変部分はより丁寧に書いたほうが得です。
なお、指示を短くする方向で最適化する前に、そもそも指示で解こうとしている問題が Few-shot の1例で済まないかを確認してください。出力構造の指定は、文章で説明するより例を見せるほうが短く済むことが多くあります。基礎的な技法の切り分けは「Gemini プロンプトエンジニアリング入門」に、キャッシュの実装は「Gemini API キャッシュ戦略の運用ノート」にまとめています。
全体を振り返って:今日から使える改善チェックリスト
Gemini API の出力品質を上げるために、今すぐ試せることをまとめます。
システム指示の改善:
- ロール(役割)を具体的に定義しているか
- 出力形式をスキーマ付きで指定しているか
- 禁止事項だけでなく「代わりにすること」も書いているか
プロンプトの改善:
- 重要なタスクに Few-Shot の例示を加えているか
- タスクの性質に合った
temperatureを設定しているか
エラー処理の改善:
- レート制限エラーへのリトライ処理があるか
- 本番でのエラーをログに残しているか
一つずつ確認して改善するだけで、Gemini API を使ったシステムの安定性と品質が着実に向上します。まずは system_instruction に役割と出力形式を追加するところから始めてみてください。