report-review

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | Submodules | README | LICENSE

structuring-training-data.py (5782B)


      1 import glob
      2 import json
      3 import os
      4 from typing import Optional
      5 
      6 from google import genai
      7 from google.genai import types
      8 
      9 
     10 def generate_structured_json(
     11     typst_file_path: str,
     12     feedback_file_path: str,
     13     output_dir: str,
     14     sequence_num: int,  # 新しい引数: 入力ファイルから抽出した連番
     15     base_output_name: str,  # 出力ファイル名のベース部分を保持
     16 ):
     17     client: Optional[genai.Client] = None
     18     try:
     19         # クライアントの初期化はtryブロック内に移動してAPIキーのチェックをスムーズに
     20         client = genai.Client()
     21     except Exception as e:
     22         print(
     23             f"エラー: Geminiクライアントの初期化に失敗しました。APIキーを確認してください: {e}"
     24         )
     25         return
     26 
     27     # 連番を使用して直接出力ファイル名を生成 (例: structured_data_001.json)
     28     output_file_path = os.path.join(
     29         output_dir, f"{base_output_name}_{sequence_num:03d}.json"
     30     )
     31 
     32     if os.path.exists(output_file_path):
     33         print(f"警告: 出力ファイル {output_file_path} は既に存在します。上書きします。")
     34 
     35     with open(typst_file_path, "r", encoding="utf-8") as f:
     36         typst_content = f.read()
     37 
     38     with open(feedback_file_path, "r", encoding="utf-8") as f:
     39         feedback_content = f.read()
     40 
     41     system_instruction = "あなたは、提供されたTypstレポートとそのフィードバックを構造化するための専門家です。入力テキストから、指摘事項を分析し、指定されたJSONスキーマに厳密に従って、指摘事項のリストを生成してください。応答はJSON形式のみとし、Markdownの装飾や説明文は一切含めないでください。"
     42 
     43     json_schema = {
     44         "type": "array",
     45         "items": {
     46             "type": "object",
     47             "properties": {
     48                 "type": {
     49                     "type": "string",
     50                     "description": "指摘のカテゴリ(例:format, logic, grammar, citationなど)。",
     51                 },
     52                 "location": {
     53                     "type": "string",
     54                     "description": "Typstファイル内の指摘箇所(例:line 25-30, 全体)。",
     55                 },
     56                 "text": {
     57                     "type": "string",
     58                     "description": "フィードバックの具体的な内容。",
     59                 },
     60             },
     61             "required": ["type", "location", "text"],
     62         },
     63     }
     64 
     65     prompt = (
     66         f"Typstファイルの内容:\n---\n{typst_content}\n---\n\n"
     67         f"対応するフィードバックの生テキスト:\n---\n{feedback_content}\n---"
     68     )
     69 
     70     try:
     71         response = client.models.generate_content(
     72             model="gemini-2.5-flash",
     73             contents=[prompt],
     74             config=types.GenerateContentConfig(
     75                 system_instruction=system_instruction,
     76                 response_mime_type="application/json",
     77                 response_schema=json_schema,
     78                 temperature=0.1,
     79             ),
     80         )
     81 
     82         structured_json_data = json.loads(response.text)
     83 
     84         with open(output_file_path, "w", encoding="utf-8") as f:
     85             json.dump(structured_json_data, f, ensure_ascii=False, indent=2)
     86 
     87         print(f"構造化されたJSONを {output_file_path} に保存しました。")
     88 
     89     except Exception as e:
     90         print(f"Gemini API呼び出しまたはJSON保存中にエラーが発生しました: {e}")
     91 
     92 
     93 if __name__ == "__main__":
     94     REPORTS_DIR = "reports"
     95     OUTPUT_DIR = "structured-feedback"
     96     OUTPUT_BASE_NAME = "structured_data"
     97 
     98     os.makedirs(OUTPUT_DIR, exist_ok=True)
     99 
    100     if not os.getenv("GEMINI_API_KEY"):
    101         print("エラー: 環境変数 GEMINI_API_KEY が設定されていません。")
    102     else:
    103         feedback_files = glob.glob(os.path.join(REPORTS_DIR, "feedback_*.txt"))
    104         for feedback_file_path in feedback_files:
    105             base_filename = os.path.basename(feedback_file_path)
    106 
    107             try:
    108                 # 連番を文字列として抽出 (例: "001")
    109                 sequence_num_str = base_filename.split("_")[-1].split(".")[0]
    110 
    111                 # Typstファイル名の構築
    112                 typst_file_name = f"report{sequence_num_str}.typ"
    113                 typst_file_path = os.path.join(REPORTS_DIR, typst_file_name)
    114 
    115                 # 連番を数値に変換
    116                 sequence_num = int(sequence_num_str)
    117 
    118                 if os.path.exists(typst_file_path):
    119                     print(
    120                         f"Processing {typst_file_path} and {feedback_file_path}. Sequence number: {sequence_num:03d}"
    121                     )
    122                     generate_structured_json(
    123                         typst_file_path=typst_file_path,
    124                         feedback_file_path=feedback_file_path,
    125                         output_dir=OUTPUT_DIR,
    126                         sequence_num=sequence_num,  # 抽出した連番を渡す
    127                         base_output_name=OUTPUT_BASE_NAME,
    128                     )
    129                 else:
    130                     print(
    131                         f"警告: 対応するTypstファイル {typst_file_path} が見つかりませんでした。スキップします。"
    132                     )
    133             except ValueError:
    134                 print(
    135                     f"警告: ファイル名 {base_filename} から有効な数値の連番を抽出できませんでした。スキップします。"
    136                 )
    137             except Exception as e:
    138                 print(f"エラー処理ファイル {feedback_file_path}: {e}")