Gemini 3.7 Flashの下書き混在をPythonで点検
Googleは8月13日、Gemini 3.7 Flashを発表しました。3.6 Flashの発表から約3週間です。モデルの更新がここまで短いと、営業資料やメールの下書きを作る側は、同じ案件の中で古い版と新しい版を混ぜない仕組みを先に持っておきたいです。
モデル名を空欄にした下書きは、顧客向けの比較には使わないほうがいいです。同じプロンプトで作ったつもりでも、出力の言い回し、表の埋まり方、根拠の拾い方が変わるからです。提案の反応を後から見返した時も、どの条件で作った文だったか分からなくなります。
今日は、スプレッドシートからCSVで書き出した下書き一覧を点検し、同じ案件・同じプロンプト版に複数のモデル名が混ざっていないか出すPythonを置きます。APIは使いません。手元のPython 3.14.6で動かしました。標準ライブラリだけなので、社内PCへ追加で入れるものもありません。
下書き一覧に残す列は5つだけ
シートには次の列を置きます。campaign は案件や施策の識別子です。顧客名そのものを入れず、社内の案件番号にしておくと扱いやすいです。
campaign,model,prompt_version,generated_at,draft_id
FY26-Q3-014,gemini-3.6-flash,v4,2026-08-12T09:10:00+09:00,D-101
FY26-Q3-014,gemini-3.7-flash,v4,2026-08-14T09:20:00+09:00,D-102
FY26-Q3-014,gemini-3.7-flash,v5,2026-08-14T09:30:00+09:00,D-103
FY26-Q3-021,gemini-3.7-flash,v2,2026-08-14T10:00:00+09:00,D-201
ポイントは、モデルが違ってもプロンプト版まで同じなら、同じ条件の下書きとして比較しないことです。上の例なら FY26-Q3-014 の v4 が要確認です。v5 は指示を直した後の別条件なので、混在としては扱いません。
混在した案件だけを出すPython
次のコードを audit_models.py として保存します。普段は --csv に書き出しファイルを渡します。--demo は記事内のサンプルで試すためのものです。
import argparse
import csv
import sys
from collections import defaultdict
from io import StringIO
REQUIRED = {"campaign", "model", "prompt_version", "generated_at", "draft_id"}
DEMO_CSV = """campaign,model,prompt_version,generated_at,draft_id
FY26-Q3-014,gemini-3.6-flash,v4,2026-08-12T09:10:00+09:00,D-101
FY26-Q3-014,gemini-3.7-flash,v4,2026-08-14T09:20:00+09:00,D-102
FY26-Q3-014,gemini-3.7-flash,v5,2026-08-14T09:30:00+09:00,D-103
FY26-Q3-021,gemini-3.7-flash,v2,2026-08-14T10:00:00+09:00,D-201
"""
def read_rows(handle):
reader = csv.DictReader(handle)
if reader.fieldnames is None:
raise ValueError("CSVのヘッダーがありません")
missing_columns = REQUIRED - set(reader.fieldnames)
if missing_columns:
raise ValueError("不足している列: " + ", ".join(sorted(missing_columns)))
rows = []
for line_no, row in enumerate(reader, start=2):
empty = [name for name in REQUIRED if not row[name].strip()]
if empty:
raise ValueError(f"{line_no}行目が空です: {', '.join(sorted(empty))}")
rows.append(row)
return rows
def find_mixed_batches(rows):
batches = defaultdict(list)
for row in rows:
batches[(row["campaign"], row["prompt_version"])].append(row)
mixed = []
for key, batch in batches.items():
models = {row["model"] for row in batch}
if len(models) > 1:
mixed.append((key, sorted(batch, key=lambda row: row["generated_at"])))
return sorted(mixed)
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--csv", help="スプレッドシートから書き出したCSV")
parser.add_argument("--demo", action="store_true", help="記事内のサンプルで実行")
args = parser.parse_args()
if args.demo:
rows = read_rows(StringIO(DEMO_CSV))
elif args.csv:
with open(args.csv, encoding="utf-8-sig", newline="") as handle:
rows = read_rows(handle)
else:
parser.error("--csv または --demo を指定してください")
mixed = find_mixed_batches(rows)
if not mixed:
print("モデル混在はありません")
return
print(f"要確認: {len(mixed)}件")
for (campaign, prompt_version), batch in mixed:
print(f"\n{campaign} / prompt={prompt_version}")
for row in batch:
print(f" {row['generated_at']} {row['model']} {row['draft_id']}")
sys.exit(1)
if __name__ == "__main__":
main()
手元では、記事のコードを --demo で実行して次の出力になりました。終了コードが 1 になるので、定期点検や共有前チェックに組み込む時も、混在を見落とさずに済みます。
要確認: 1件
FY26-Q3-014 / prompt=v4
2026-08-12T09:10:00+09:00 gemini-3.6-flash D-101
2026-08-14T09:20:00+09:00 gemini-3.7-flash D-102
実ファイルなら、こう実行します。
python audit_models.py --csv drafts.csv
なぜ案件だけでまとめないのか
最初は案件単位で見れば足りると思いがちです。ただ、プロンプトを直した後の下書きまで止めると、現場では面倒になって列が埋まらなくなります。そこで、campaign と prompt_version の組で見ています。
モデル更新の影響と、こちらが指示を変えた影響を分けられるからです。反応の良かったメールを横展開する時も、「モデルを替えたから良くなった」のか、「件名の条件を足したから良くなった」のかを切り分けられます。ここ地味に効きます。
もう一つ、モデル名は人が読む表示名より、実際に呼び出した識別子をそのまま入れるのがおすすめです。たとえば社内で「Flash」と呼んでいても、切替前後で同じ文字列のままでは今回の点検が働きません。モデルを変更する日に、シートの入力規則も一緒に更新しておくと事故が減ります。
先に止めるのは送信ではなく比較
このスクリプトは下書きを削除しません。混ざったことを知らせるだけです。その後に、どちらかのモデルで作り直すか、別バッチとして残すかを案件の担当者が決めます。提案内容の正しさまで機械に決めさせると、かえって説明しにくくなります。
モデルの更新はこれからも速くなります。だからこそ、下書きの品質だけでなく、どの条件で作ったかを一行で追える状態にしておくと、試す速さと検証の速さを両立できます。新しいモデルを触った日に、まず1案件だけモデル列を埋めて比べる。それで十分です。