この記事では、Googleフォームの回答を起点に、Google Apps Scriptで個別フィードバックの下書きを作る構成を、続編としてもう少し再現しやすい形に整理します。
前回の「フォーム回答から個別FB下書き」系の流れを、校務で扱いやすいように次の3段階に分けます。
- フォーム回答をスプレッドシートに集める
- GASで回答行を読み取り、AIに渡すプロンプトを組み立てる
- 生成結果を「下書き」列に書き戻し、最終確認しやすくする
AIのAPIキーはフロントエンドに置かず、GAS側のスクリプトプロパティに保存してサーバーサイドから呼び出します。コード内にも記事本文にも実キーは書きません。
想定するシート
フォーム回答先のスプレッドシートに、次の列がある想定です。
| 列 | 内容 |
|---|---|
| A | タイムスタンプ |
| B | 氏名または識別名 |
| C | 提出内容 |
| D | 自己評価 |
| E | フィードバック下書き |
| F | 生成日時 |
E列とF列は、あとから手動で追加しておきます。
事前準備
- Googleフォームを作成する
- 回答先をGoogleスプレッドシートにする
- スプレッドシートで
拡張機能 > Apps Scriptを開く - Apps Scriptのプロジェクト設定からスクリプトプロパティに
AI_API_KEYを保存する
APIキーをソースコードに直接書かないため、PropertiesService.getScriptProperties() で読み出します。
コード例
以下は、未生成の行だけを処理して、E列にフィードバック下書き、F列に生成日時を書き込む例です。
const SHEET_NAME = 'フォームの回答 1';
const COL_NAME = 2;
const COL_SUBMISSION = 3;
const COL_SELF_REVIEW = 4;
const COL_DRAFT = 5;
const COL_GENERATED_AT = 6;
function generateFeedbackDrafts() {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(SHEET_NAME);
if (!sheet) {
throw new Error(`Sheet not found: ${SHEET_NAME}`);
}
const lastRow = sheet.getLastRow();
if (lastRow < 2) return;
const values = sheet.getRange(2, 1, lastRow - 1, COL_GENERATED_AT).getValues();
values.forEach((row, index) => {
const rowNumber = index + 2;
const name = row[COL_NAME - 1];
const submission = row[COL_SUBMISSION - 1];
const selfReview = row[COL_SELF_REVIEW - 1];
const existingDraft = row[COL_DRAFT - 1];
if (!submission || existingDraft) return;
const prompt = buildFeedbackPrompt({
name,
submission,
selfReview,
});
const draft = callAiApi(prompt);
sheet.getRange(rowNumber, COL_DRAFT).setValue(draft);
sheet.getRange(rowNumber, COL_GENERATED_AT).setValue(new Date());
});
}
function buildFeedbackPrompt({ name, submission, selfReview }) {
return [
'あなたは学校の提出物に対して、学習者が次の改善に移りやすいフィードバック下書きを作る補助者です。',
'断定しすぎず、提出内容に基づいて具体的に書いてください。',
'出力は次の3項目です。',
'1. 良かった点',
'2. 次に伸ばせそうな点',
'3. 具体的な次の一歩',
'',
`氏名または識別名: ${name || '未入力'}`,
`提出内容: ${submission}`,
`自己評価: ${selfReview || '未入力'}`,
].join('\n');
}
function callAiApi(prompt) {
const apiKey = PropertiesService.getScriptProperties().getProperty('AI_API_KEY');
if (!apiKey) {
throw new Error('AI_API_KEY is not set in script properties.');
}
const response = UrlFetchApp.fetch('https://api.openai.com/v1/responses', {
method: 'post',
contentType: 'application/json',
headers: {
Authorization: `Bearer ${apiKey}`,
},
payload: JSON.stringify({
model: 'gpt-4.1-mini',
input: prompt,
}),
muteHttpExceptions: true,
});
const status = response.getResponseCode();
const body = response.getContentText();
if (status < 200 || status >= 300) {
throw new Error(`AI API request failed: ${status} ${body}`);
}
const json = JSON.parse(body);
return extractText(json);
}
function extractText(responseJson) {
const output = responseJson.output || [];
const texts = [];
output.forEach((item) => {
(item.content || []).forEach((content) => {
if (content.type === 'output_text' && content.text) {
texts.push(content.text);
}
});
});
return texts.join('\n').trim();
}
実行手順
- Apps Scriptに上のコードを貼る
-
SHEET_NAMEが実際の回答シート名と一致しているか確認する - スクリプトプロパティに
AI_API_KEYを保存する -
generateFeedbackDraftsを手動実行して権限を許可する - E列に下書き、F列に生成日時が入ることを確認する
初回は手動実行で動作を確認し、意図した下書きになってから時間主導トリガーやフォーム送信トリガーに進めると扱いやすくなります。
フォーム送信時に動かす場合
フォーム送信のたびに生成したい場合は、次の関数を追加します。
function onFormSubmit(e) {
const sheet = e.range.getSheet();
if (sheet.getName() !== SHEET_NAME) return;
const rowNumber = e.range.getRow();
const row = sheet.getRange(rowNumber, 1, 1, COL_GENERATED_AT).getValues()[0];
const name = row[COL_NAME - 1];
const submission = row[COL_SUBMISSION - 1];
const selfReview = row[COL_SELF_REVIEW - 1];
const existingDraft = row[COL_DRAFT - 1];
if (!submission || existingDraft) return;
const prompt = buildFeedbackPrompt({
name,
submission,
selfReview,
});
const draft = callAiApi(prompt);
sheet.getRange(rowNumber, COL_DRAFT).setValue(draft);
sheet.getRange(rowNumber, COL_GENERATED_AT).setValue(new Date());
}
Apps Scriptのトリガー画面で、onFormSubmit を「フォーム送信時」に設定します。
校務で使うときの調整ポイント
校務で使うAI活用tipsとしては、AIに任せる範囲を「最終コメント」ではなく「下書き」に限定しておくと運用しやすくなります。
プロンプトでは、次のように出力形式を固定しておくと、確認時の目線をそろえやすくなります。
出力は次の3項目です。
1. 良かった点
2. 次に伸ばせそうな点
3. 具体的な次の一歩
また、フォーム項目が増えた場合は、列番号の定数を増やして buildFeedbackPrompt() に渡す情報を追加します。
const COL_GOAL = 7;
// 例:
const goal = row[COL_GOAL - 1];
この形にしておくと、フォーム、スプレッドシート、GASの役割が分かれます。フォームは入力、スプレッドシートは確認場所、GASは下書き生成の処理として扱えます。
まとめ
GASでフォーム回答を読み取り、AIで個別フィードバック下書きを作り、スプレッドシートへ戻す流れを作ると、校務で使うAI活用の手順をコード込みで共有できます。
大事なのは、APIキーをコードに書かないこと、AIの出力をそのまま最終文にしないこと、そして再現できる小さな構成から始めることです。