ChoTensai_V3 / scripts /review_math_with_gemini.py
TOMOCHIN4
release: v2.0.0 STABLE リリース完了
c71b90b
#!/usr/bin/env python3
"""
数学問題JSONファイルをGemini APIで添削するスクリプト
- JSONファイル単位(40問)で1回のAPI呼び出し
- モデル: gemini-2.5-pro-preview-06-05
"""
import os
import sys
import json
import time
import argparse
from pathlib import Path
from google import genai
from google.genai import types
# APIキー
API_KEY = os.environ.get("GEMINI_API_KEY")
MODEL_NAME = "gemini-3-pro-preview"
# 添削プロンプト
REVIEW_PROMPT = """あなたは中学受験の算数教育専門家です。以下の算数問題({count}問)を添削してください。
【重要:中学受験算数のルール】
この問題集は中学受験用です。以下のルールを厳守してください:
★ 方程式(xを使った式)は絶対に使わないでください ★
- 中学受験では方程式を使わず、以下の方法で解きます:
- 特殊算(つるかめ算、旅人算、仕事算、ニュートン算、差集め算など)
- 計算の工夫(分配法則、逆算、比の利用)
- 線分図、面積図、ダイヤグラム
- 和差算、消去算
- 比と割合の考え方
【添削基準】
1. 学術的整合性
- answerが算数的に正確か(計算結果が正しいか)
- question_hintが問題として適切か(何を求めるか明確か)
- source_contextが中学受験向けの解法になっているか
2. 問題文の明確性
- 曖昧な表現がないか
- 解答が一意に定まるか
- 単位や条件が明記されているか
3. 難易度の妥当性
- 基本: 小学4-5年レベル(単純な計算、基本公式)
- 標準: 小学6年〜中学受験標準レベル(複数ステップ、特殊算)
- 応用: 難関中学受験レベル(複合問題、発展的思考)
【重要な修正ポイント】
- source_contextに方程式(x=〜など)がある場合は、特殊算や図を使った解法に書き換え
- 計算結果が間違っている場合は正しい値に修正
- question_hintが曖昧な場合は具体的な問題文に修正
- 難易度が不適切な場合は適切なレベルに変更
【出力形式】
- 入力と同じJSON配列形式で全{count}問を返してください
- 各問題は以下のフィールドを含むこと:
- answer_id: そのまま維持
- answer: 正確な解答
- question_hint: 明確な問題文
- difficulty: 基本/標準/応用のいずれか
- source_context: 方程式を使わない中学受験向けの解法説明
【入力データ】
```json
{questions}
```
【出力】
添削済みの全{count}問をJSON配列形式で出力してください。"""
def load_json(filepath: str) -> list:
"""JSONファイルを読み込み"""
with open(filepath, 'r', encoding='utf-8') as f:
data = json.load(f)
# 配列形式とオブジェクト形式の両方に対応
if isinstance(data, list):
return data
return data.get('questions', [])
def save_json(filepath: str, questions: list):
"""JSONファイルを保存(配列形式)"""
with open(filepath, 'w', encoding='utf-8') as f:
json.dump(questions, f, ensure_ascii=False, indent=2)
def review_with_gemini(questions: list) -> list:
"""Gemini APIで問題を添削"""
if not API_KEY:
raise RuntimeError("GEMINI_API_KEY not set")
client = genai.Client(api_key=API_KEY)
# プロンプト作成
prompt = REVIEW_PROMPT.format(
count=len(questions),
questions=json.dumps(questions, ensure_ascii=False, indent=2)
)
print(f" Gemini API呼び出し中(モデル: {MODEL_NAME})...")
# API呼び出し
response = client.models.generate_content(
model=MODEL_NAME,
contents=prompt,
config=types.GenerateContentConfig(
response_mime_type="application/json",
temperature=0.1, # 低めで正確性重視
)
)
# レスポンス解析
result_text = response.text
if result_text is None:
raise RuntimeError("Gemini API returned empty response")
print(f" レスポンス長: {len(result_text)}文字")
# JSON部分を抽出(```json ... ``` で囲まれている場合に対応)
if "```json" in result_text:
start = result_text.find("```json") + 7
end = result_text.find("```", start)
result_text = result_text[start:end].strip()
elif "```" in result_text:
start = result_text.find("```") + 3
end = result_text.find("```", start)
result_text = result_text[start:end].strip()
# JSONパース
reviewed = json.loads(result_text)
return reviewed
def main():
parser = argparse.ArgumentParser(description='Gemini APIで数学問題を添削')
parser.add_argument('input_file', help='入力JSONファイル')
parser.add_argument('--output', '-o', help='出力ファイル(省略時は入力ファイルを上書き)')
parser.add_argument('--dry-run', action='store_true', help='実際に保存しない')
args = parser.parse_args()
input_path = Path(args.input_file)
output_path = Path(args.output) if args.output else input_path
print(f"=== {input_path.name} 添削開始 ===")
# 読み込み
print(f" 読み込み中: {input_path}")
questions = load_json(str(input_path))
print(f" 問題数: {len(questions)}問")
# 添削
try:
reviewed = review_with_gemini(questions)
print(f" 添削完了: {len(reviewed)}問")
except Exception as e:
print(f" エラー: {e}")
sys.exit(1)
# 検証
if len(reviewed) != len(questions):
print(f" 警告: 問題数が変化しました({len(questions)}{len(reviewed)})")
# 難易度分布確認
dist = {"基本": 0, "標準": 0, "応用": 0}
for q in reviewed:
diff = q.get('difficulty', '')
if diff in dist:
dist[diff] += 1
print(f" 難易度分布: 基本{dist['基本']} / 標準{dist['標準']} / 応用{dist['応用']}")
# 保存
if args.dry_run:
print(f" [dry-run] 保存スキップ")
else:
save_json(str(output_path), reviewed)
print(f" 保存完了: {output_path}")
print(f"=== {input_path.name} 添削完了 ===\n")
return reviewed
if __name__ == "__main__":
main()