GeminiとGoogle Apps Scriptでクイズを作ってフォームを作成【GAS】

セミナー講師や学校の先生など、授業の最後に確認テストを作るといった作業があると思います。しかし、フォームで授業でやったネタを元にテストを作るというのは、なかなか頭に負荷の係る仕事です。そこで、今回、Geminiを使ってこれを自動化して時間の削減ができないか?と考え挑戦。

GeminiのDeep Researchで資料を作成し、その資料を元にクイズを作成させて、結果を元にGoogleフォームを自動作成するというものを作ってみました。

今回利用するツール等

Gemini APIに対して、Deep Researchで作成しエクスポートしておいたGoogleドキュメントの資料を加えてリクエスト。クイズ用のデータが返ってくるので、それを元にテスト形式のGoogleフォームを自動作成して指定のディレクトリに配置するというプログラムです。

今回はGemini 2.5 Proを利用しています。

Googleフォーム生成は過去にスプレッドシートのデータを元に作成・再構築するものを作っています。今回は短答式なので個別のクイズはラジオボタンによる選択式になっています。

Google Apps ScriptでFormのGridをスプレッドシートより再構築する【GAS】

事前準備

Gemini APIキーを取得する

GeminiのAPIキーが必要です。以下のエントリーに独立してまとめています。課金されますので利用のしすぎには要注意。また課金されていない場合、学習に利用されてしまう恐れがあるので、きちんとGoogle Cloud上で課金アカウントとの紐付けなどをしておきましょう。

GeminiのAPIキーの取得と学習の可否

スクリプトプロパティに値を格納

前述までに取得しておいたGeminiのAPIキーについて、GASのスクリプトプロパティに値をセットします。

  • apikey : Gemini APIのAPIキー

図:スクリプトプロパティに格納する

コードと設定

クイズデータの作成条件

今回、Deep Researchのデータを元にクイズを作成する条件をプロンプトで指定しています。結果はJSON形式で返ってくるので、このデータを元にフォームを生成することになります。

  • Googleドキュメントの内容から確認クイズを作成する
  • 作成するクイズは10問
  • 4つの選択肢から正解を選ぶ方式で、正しい回答にはtrue、間違ってる回答にはfalseの値をつける
  • 10問合計で100点になるように配点。内容の難易度に応じてお任せで配点数値を加える
  • このクイズ全体のタイトルをつけること

このGemini問い合わせの結果のJSONの形式は以下のようなレスポンスになります。

{
  "formtitle": "氷河期世代と少子高齢化社会に関する理解度チェック",
  "quiz": [
    {
      "title": "「就職氷河期世代」とは、主にいつ頃に生まれた人々を指しますか?",
      "haiten": 5,
      "choice": [
        { "text": "1960年~1969年頃", "isCorrect": false },
        { "text": "1970年~1984年頃", "isCorrect": true },
        { "text": "1985年~1994年頃", "isCorrect": false },
        { "text": "1995年~2004年頃", "isCorrect": false }
      ]
    },
    {
      "title": "就職氷河期が生まれた直接的な経済的背景として最も適切なものはどれですか?",
      "haiten": 10,
      "choice": [
        { "text": "高度経済成長", "isCorrect": false },
        { "text": "オイルショック", "isCorrect": false },
        { "text": "バブル経済の崩壊", "isCorrect": true },
        { "text": "プラザ合意による円安", "isCorrect": false }
      ]
    },
  ]
}

Gemini問い合わせ用コード

基本的なスタイルはこれまでのGemini API問い合わせ系のプログラムとほとんど同じです。しかし、PDFではないので今回はドキュメントの内容は一度テキストで取得しておき、これをプロンプトの中に含める必要があります。

回答はJSON形式で返ってくるので、次のフォーム生成に渡してあげる。変数docsidにDeep Researchで生成したドキュメントのファイルのIDを入れるのを忘れずに。

//モデル
const model = "gemini-2.5-pro";

//エンドポイントURL
const endpoint = `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=`;

//利用するドキュメントのID
const docsid = "ここに送信するGoogleドキュメントのファイルのIDを入れる";

//班分けをGeminiに依頼
function createGeminiQuiz(){
  //プロパティの値を取得する
  let prop = PropertiesService.getScriptProperties()
  let apikey = prop.getProperty("apikey");

  //スプシのUIを取得
  let ui = SpreadsheetApp.getUi();

  //リクエストURLを構築する
  let url = endpoint + apikey;

  //ドキュメントのテキストを取得する
  const documentText = DocumentApp.openById(docsid).getBody().getText();

  //プロンプトを構築する
  const prompt = `
    以下のテキストに基づいて、確認用のクイズを10問作成してください。

    # 制約条件
    - クイズは全部で10問作成してください。
    - 各問題には4つの選択肢を用意してください。
    - 4つの選択肢のうち、正解は1つだけにしてください。正しい回答はisCorrectでtrueとし、誤った回答はfalseとしてください。
    - 結果は指定されたJSON形式で出力してください。
    - JSON以外の余計な文字列(例: \`\`\`json ... \`\`\` など)は含めないでください。
    - 10問で合計100点となるように配点をつけてください。配点は問題の難易度に応じて割り振ってみてください。値は整数で、haitenの中に記述。
    - この問題集全体のタイトルを出力結果の中のformtitleに適切なものを生成してください。

    # 出力形式
    {
      "formtitle" : "このクイズの問題タイトル",
      "quiz":[
        {
          "title": "クイズのタイトル1",
          "haiten" : 10,
          "choice": [
            { "text": "回答1", "isCorrect": true },
            { "text": "回答2", "isCorrect": false },
            { "text": "回答3", "isCorrect": false },
            { "text": "回答4", "isCorrect": false }
          ]
        },
        {
          "title": "クイズのタイトル2",
          "haiten" : 25,
          "choice": [
            { "text": "回答1", "isCorrect": true },
            { "text": "回答2", "isCorrect": false },
            { "text": "回答3", "isCorrect": false },
            { "text": "回答4", "isCorrect": false }
          ]
        }
      ]
    }

    # テキスト
    ---
    ${documentText}
    ---
  `;

  //payloadを構築する
  let payload = {
    'contents': {
      'parts': [
        {
          'text': prompt
        }
      ]
    },
    'generation_config': {
      'temperature': 0,
      'topP': 0,
      'maxOutputTokens': 12000
    }
  };

  //リクエストオプション
  let options = {
    'method': 'POST',
    'contentType': 'application/json',
    'payload': JSON.stringify(payload)
  };

  //Geminiにリクエスト
  let response = UrlFetchApp.fetch(url, options);
  let json = JSON.parse(response.getContentText());

  //結果を受け取る
  if (json && json.candidates && json.candidates.length > 0) {
    let result = json.candidates[0].content.parts[0].text;
    
    //余計な文字を除外する
    let ret = result.replace("```json","");
    ret = ret.replace("```","");
    ret = ret.replace("\n","");

    //JSONパースする
    let jsondata = JSON.parse(ret);

    //フォームを作成する
    const end = createQuizFormInFolder(jsondata);
   
    //結果を表示
    ui.alert(end[1]);
    return true;
  } else {
    ui.alert("Geminiリクエストに失敗しました")
    return false;
  }
}

フォーム作成用コード

フォームを作成し、返り値を元にラジオボタンの選択肢を次々と生成していきます。同時に配点や回答のフィードバックを追加し、どれが正解項目なのか?など一連のテスト用としてのフォームの全体設定も行います。

最後に作成されたフォームを指定のフォルダに移動して完了となります。

//作成先フォルダ
const folderId = 'ここにフォームファイル生成先フォルダのIDを入れる'; 

//指定したフォルダにクイズ形式のGoogleフォームを自動生成する関数
function createQuizFormInFolder(jsondata) {
  //JSONデータからデータを取り出す
  let question = jsondata.quiz;
  let formtitle = jsondata.formtitle;

  // 新しいフォームを作成し、タイトルと説明を設定
  const form = FormApp.create(formtitle);

  //説明文とテスト形式に変更
  form.setIsQuiz(true) //テスト形式のフォームにする
      .setDescription(formtitle) //フォームの説明文
      .setConfirmationMessage('お疲れ様でした')                    //フォーム回答後メッセージ文
      .setCollectEmail(false)                                    //メアド収集オフ
      .setLimitOneResponsePerUser(true)                         //1回のみ回答
      .setPublishingSummary(true);                              //サマリー表示

  //questionからフォームへ問題を生成する
  for(let i = 0;i<question.length;i++){
    //レコードを一個取り出す
    let rec = question[i];

    //問題文作成開始
    let item = form.addMultipleChoiceItem();

    //問題のタイトルを追加
    item.setTitle(rec.title);

    //回答データの追加  
    let tempkai = rec.choice;
    let kaiarr = [];
    let trueman = "";

    for(let j = 0;j<tempkai.length;j++){
      //回答を取得してセット
      let answer = tempkai[j].text;
      let result = tempkai[j].isCorrect;

      //正解のものはtruemanに格納
      if(result == true){
        trueman = answer;
      }

      //配列に追加
      kaiarr.push(item.createChoice(answer, result));
    }

    //問題文に固有の設定を追加
    item.setChoices(kaiarr);   //問題文を設定
    item.setPoints(rec.haiten);         //配点を設定
    item.setRequired(true);     //回答必須とする

    //回答結果のフィードバック設定
    item.setFeedbackForCorrect(FormApp.createFeedback().setText('その通り!正解です。').build());
    item.setFeedbackForIncorrect(FormApp.createFeedback().setText('残念!正解は' + trueman + 'です。').build());
  }

  //指定フォルダにFormファイルを移動する
  try {
    // フォームのファイルオブジェクトを取得
    const formFile = DriveApp.getFileById(form.getId());

    // 指定したIDのフォルダオブジェクトを取得
    const folder = DriveApp.getFolderById(folderId);

    // ファイルをフォルダに移動
    formFile.moveTo(folder);

    //処理終了
    return [true,"フォームが指定のフォルダに作成されました。"];

  } catch (e) {
    return [false,"エラー: " + e.message];
  }
}

作成されたフォーム

生成後フォームに追加設定

Google Apps Scriptでは設定できない項目があり、それらについては手動で生成されたフォームに追加が必要になります。以下は自分が好みの設定として追加しています。

  • 成績の発表は「送信直後」に変更する
  • 回答の設定として、不正解だった質問・正解・点数についてはオンにしておく

これをしておかないと、クイズに回答後にユーザーが何が正解なのか?どのくらいの点数なのか?といったことがわからないままになってしまいます。

図:フォームに手動で設定追加

一問一答形式にしたい

この結果作成されるフォームは、一問一答式ではなく全問が表示されていて送信するタイプのフォームです。よって、個別に送信しては回答が出るというタイプではありません。しかし、各設問をセクション単位で区切っておくことで、一問一答式に変更することはできます(ただし送信されていないので、都度回答が出るという仕組みは作れない)。

セクション区切りもGASでやろうと思えば可能ですが今回は省略しています。

セクションの各アクションは「次のセクションに進む」というアクションをセットしてるだけです。

図:設問ごとにセクションをセットしています

図:一問一答式になりました

使ってみた様子

実際に生成されたフォームを開いてみました。きちんと各設問には、設問タイトル、回答、配点、フィードバックがセットされており、何が正解で何を間違えたのか?ということが最後の送信後にわかるようになっています。

できればこのあと、「form.getPublishedUrl()」でフォーム公開URLを取得し、対象者をまとめたグループアドレスを用意しておき、そのメアドに対してこのURLを一括で通知するコードを用意しておけば、作成後にスパっと通知が出来るので便利です。

ユーザーは送信後にスコアを表示というボタンをクリックすると、獲得点数、設問の回答と正誤状況がわかるようになっています。

図:個別の設問の設定状況

図:スコアの表示

関連リンク

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)