Spaces:
Sleeping
Sleeping
| #!/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() | |