皆さんこんにちは、横浜で清掃業を営む49歳、ヤスです。
前回は論破王と口喧嘩!論理的思考を鍛える「AIディベートボット
のアプリを作りましたね。
同世代の皆さん、現場の機材マニュアルや業務用洗剤の成分表、分厚い専門書……読まなきゃいけないと分かっていても、活字ばかりで睡魔に襲われませんか?
私は思いつきました。「この退屈なPDFを、運転中に聴けるラジオ番組と、スマホで解けるクイズに全自動で変えてしまえばいいんだ!」と。 そこで今回は、DifyとGASを連携させ、**「PDFを投げ込むだけで、Googleドキュメント(ラジオ台本)とGoogleフォーム(自動採点クイズ)を同時に作り出す魔法のアプリ」**を開発しました。
実は今回、途中で「JSONの文字化け」というAPI連携最大の壁にぶち当たりました。しかし、荷造りの方法(form-data)を変えることで見事突破!今回はその「失敗と解決のリアルな軌跡」も含めて、中学生でも絶対に真似できるレベルで丁寧に解説します。49歳の等身大の挑戦、一緒にやってみましょう!
【結論】 このアプリの正体は、「Dify(脳)」と「GAS(手)」の最強タッグです。
DifyがPDFの文章を読み込み、「無知なDJと専門家の分かりやすい対話」と「巧妙なダミーを含んだ3択クイズ」を作成します。そして、そのデータをGASへ送信し、GASがGoogleドキュメントとGoogleフォームを自動で組み立てます。
開発の最大のキモは、DifyからGASへデータを送る際の**「form-data」**という通信方式です。AIが作る文章には「改行」や「記号」がたくさん含まれるため、通常のJSON形式で送るとエラー(箱の中身が空っぽ現象)が起きます。これをform-dataに切り替えることで、どんなに複雑な台本でも安全にGASへ届けることができるようになりました!
【手順:専門書変換マシンの作り方】
ステップ1:GASで「ドキュメント&フォーム自動作成工場」を作る
まずは受け皿となるGASの設定です。Googleドライブから「Google Apps Script」を開き、以下のコードを貼り付けます。前回のエラーを教訓に、form-dataで受け取るための最新仕様になっています!
JavaScript
// Difyからデータが送られてきた時に自動で実行されるメインの関数です
function doPost(e) {
// エラーが起きてもシステム全体が止まらないように安全装置(try-catch)をつけます
try {
// Difyの「form-data」から、送られてきたタイトル(theme)を受け取ります
const title = e.parameter.title;
// Difyの「form-data」から、AIが書いたラジオ台本を受け取ります
const script = e.parameter.script;
// クイズデータは文字の塊で届くので、プログラムで扱える配列(JSON)に変換します
const quizzes = JSON.parse(e.parameter.quizzes);
// 【1. Googleドキュメント(ラジオ台本)の作成】
// ファイル名に「【ラジオ台本】」と付けて、新しいドキュメントを作成します
const doc = DocumentApp.create("【ラジオ台本】" + title);
// 作成したドキュメントの中身(本文)を編集する準備をします
const body = doc.getBody();
// ドキュメントの一番上に、一番大きな文字(見出し1)でタイトルを書き込みます
body.insertParagraph(0, "■ " + title + " - 解説ラジオ台本").setHeading(DocumentApp.ParagraphHeading.HEADING1);
// その下に、AIが作ってくれたラジオ台本の文章をそのまま貼り付けます
body.appendParagraph(script);
// 完成したドキュメントのURL(リンク)を取得して、後でDifyに返せるように保存します
const docUrl = doc.getUrl();
// 【2. Googleフォーム(確認クイズ)の作成】
// ファイル名に「【確認クイズ】」と付けて、新しいGoogleフォームを作成します
const form = FormApp.create("【確認クイズ】" + title);
// フォームの説明文に、回答者を励ますメッセージを設定します
form.setDescription("ラジオ台本を読んだ後の確認テストです。全問正解を目指しましょう!");
// アンケートではなく、正解・不正解の採点ができる「テストモード」をONにします
form.setIsQuiz(true);
// 【3. クイズの問題を順番に作っていくループ処理】
// AIが作った問題の数(今回は3問)だけ、以下の作業を繰り返します
for (let i = 0; i < quizzes.length; i++) {
// 今から処理する1問分のデータを変数 q に取り出します
const q = quizzes[i];
// フォームに「ラジオボタン形式(1つだけ選ぶ形式)」の質問を新しく追加します
const item = form.addMultipleChoiceItem();
// 質問のタイトルとして、AIが考えた問題文を設定します
item.setTitle(q.question);
// この問題に正解した時にもらえる点数を「10点」に設定します
item.setPoints(10);
// 選択肢を入れるための空っぽのリスト(配列)を準備します
const choiceList = [];
// AIが考えた選択肢の数(今回は3つ)だけ、さらに作業を繰り返します
for (let j = 0; j < q.choices.length; j++) {
// 今処理している選択肢が、AIが指定した「正解」と同じかどうかを判定します(同じならtrue)
const isCorrect = (q.choices[j] === q.answer);
// 選択肢の文字と、それが正解かどうか(true/false)をセットにして、リストに追加します
choiceList.push(item.createChoice(q.choices[j], isCorrect));
}
// 完成した3つの選択肢のリストを、質問アイテムに一気に設定します
item.setChoices(choiceList);
}
// 完成したGoogleフォームの編集用URL(リンク)を取得して保存します
const formUrl = form.getEditUrl();
// 【4. Difyへ大成功の報告をする】
// ドキュメントとフォーム、2つのURLをセットにしてDifyに返信するデータを作ります
const result = { status: "success", docUrl: docUrl, formUrl: formUrl };
// 返信データをJSONという形式に変換して、Difyへ送り返します
return ContentService.createTextOutput(JSON.stringify(result)).setMimeType(ContentService.MimeType.JSON);
// エラーが起きた場合の処理です
} catch (error) {
// 万が一エラーが起きた場合は、エラーの詳しい原因をDifyに送り返します
const errorResult = { status: "error", message: "GASエラー: " + error.toString() };
// エラーデータも同じようにJSON形式にしてDifyへ送り返します
return ContentService.createTextOutput(JSON.stringify(errorResult)).setMimeType(ContentService.MimeType.JSON);
}
}
⚠️超重要ポイント(私がハマった罠) コードを貼り付けたら「デプロイ」を行いますが、コードを修正した際は必ず「デプロイを管理」から「新バージョン」を選んでデプロイしてください!これをやらないと、いつまでも古いコードが動いてエラー地獄から抜け出せません!
ステップ2:Difyで「AIの脳みそ」を組み立てる
Difyの「ワークフロー」で以下の順番にブロックを繋ぎます。
- 開始ブロック:
theme(文字)とdocument(ファイル)を入力。 - 文書抽出: アップロードしたファイルから文字を読み取る。
- LLM(ラジオ用): 抽出した文字を元に、DJと専門家の対話台本を作成。
- プロンプトのコツ: 「DJは無知な設定にする」「必ず例え話を強制する」ことで劇的に面白くなります。
- LLM(クイズ用): 重要なポイントを3択クイズ(JSON形式)にする。
- プロンプトのコツ: 「ダミーの選択肢はもっともらしい間違いにする」「余計な挨拶は一切出力させない」のがプロの技です。
ステップ3:最大の難関「HTTPリクエスト」の設定
ここが今回一番苦労したところです!⑤番目に「HTTPリクエスト」ブロックを置きますが、ボディの設定を間違えると undefined is not valid JSON という恐怖のエラーが出ます。
- ❌ ダメな例(JSON): AIが改行や記号をたくさん使うと、送信用の箱が内側から破裂してGASに届きません。
- ⭕️ 大正解(form-data): ボディを
form-dataに切り替えましょう!- キー:
title/ 値:{x}theme - キー:
script/ 値:{x}text(ラジオLLMの出力) - キー:
quizzes/ 値:{x}text(クイズLLMの出力)
- キー:
これで、どんなに長い台本でも頑丈な箱に入れて安全に届けられます!最後に終了ブロックを繋いで、GASから返ってきたURLを表示させれば完成です。

【まとめ】 テスト実行の画面で {"status":"success", "docUrl":"...", "formUrl":"..."} という文字が出た瞬間、思わず「ヨッシャ!!」とガッツポーズをしてしまいました。
「undefined(箱が空っぽだぞ!)」とGASに怒られ続け、何度も心が折れそうになりましたが、form-dataへの切り替えと、GASのバージョン更新という「プログラミングの基本ルール」を学べたことは、私にとって星5つ以上の大きな収穫です。

年齢を理由に諦める必要なんてありません。エラーはAIからの「ここを直せば動くよ」というただのラブレターです。自分で作った魔法のドキュメントとクイズで、今日も現場の知識をアップデートしてきます。今回作ったラジオ台本をラジオにするにはどうすればいいか
調べていたらノートブックLMで作れる事が分かりました。
その結果そもそもPDFをノートブックLMにアップすればこのアプリを使わなくても
ラジオが作れるし、クイズも作れる!
まあ、今回も練習と思って良しとします。
xもやっているので遊びに来てください。
次回のアプリも是非お楽しみに!


コメント