取り組みの背景 — なぜプレゼン生成を自動化すべきなのか
ビジネスの現場では、プレゼン資料の作成に多くの時間が費やされています。McKinseyの調査によれば、ナレッジワーカーは週の業務時間のうち約28%を資料作成や情報整理に充てているとされています。この負担を根本的に軽減できるのが、Gemini APIとGoogle Apps Scriptを組み合わせたAIプレゼン自動生成パイプライン です。
Google Slides の基本的な AI 活用法についてはGoogle Slides × Gemini AI 実践ガイドで紹介していますが、ここで扱うのはその先にあるプログラマティックなパイプライン構築 に踏み込みます。
この記事の対象読者は以下のとおりです。
Google Apps Script の基本文法を理解している方
Gemini API を使った開発経験がある方(または API キーを取得済みの方)
社内のプレゼン作成業務を効率化・自動化したいエンジニアやマネージャー
パイプラインの全体設計 — 5段階のアーキテクチャ
AIプレゼン自動生成パイプラインは、以下の5つのステージで構成されます。
Stage 1: テーマ入力と要件定義
ユーザーがテーマ・対象者・スライド枚数・トーンを指定します。Google Forms や Sheets のサイドバーから入力する方式が実用的です。
Stage 2: アウトライン生成(Gemini API)
Gemini API の Structured Output を使い、スライドごとのタイトル・要点・レイアウトタイプを JSON 形式で生成します。
Stage 3: コンテンツ生成(Gemini API)
アウトラインの各スライドに対して、本文・箇条書き・図表の説明テキストを生成します。
Stage 4: スライド構築(Google Slides API)
Apps Script の SlidesApp を使い、テンプレートに基づいてスライドを自動生成します。
Stage 5: 後処理と配信
PDF エクスポート、多言語版の生成、Google Drive への保存、Slack/メール通知を行います。
この設計の利点は、各ステージが独立しているため、途中でアウトラインを人間がレビュー・修正してから次に進めるヒューマン・イン・ザ・ループ のワークフローが自然に組めることです。
Stage 1: 入力インターフェースの実装
まず、スプレッドシートをベースにした入力システムを構築します。
// config.gs — パイプライン設定
const CONFIG = {
GEMINI_API_KEY: PropertiesService. getScriptProperties (). getProperty ( 'GEMINI_API_KEY' ),
GEMINI_MODEL: 'gemini-2.5-pro' ,
GEMINI_ENDPOINT: 'https://generativelanguage.googleapis.com/v1beta/models/' ,
TEMPLATE_FOLDER_ID: 'YOUR_TEMPLATE_FOLDER_ID' ,
OUTPUT_FOLDER_ID: 'YOUR_OUTPUT_FOLDER_ID' ,
MAX_SLIDES: 20 ,
DEFAULT_SLIDES: 10 ,
};
// input.gs — サイドバーから入力を受け取る
function showInputSidebar () {
const html = HtmlService. createHtmlOutputFromFile ( 'Sidebar' )
. setTitle ( 'AI Presentation Generator' )
. setWidth ( 350 );
SpreadsheetApp. getUi (). showSidebar (html);
}
// サイドバーからの呼び出し用
function generatePresentation ( params ) {
const { theme , audience , slideCount , tone , language } = params;
// バリデーション
if ( ! theme || theme. trim (). length < 5 ) {
throw new Error ( 'テーマは5文字以上で入力してください' );
}
const count = Math. min (
Math. max ( parseInt (slideCount) || CONFIG . DEFAULT_SLIDES , 3 ),
CONFIG . MAX_SLIDES
);
// パイプライン実行
const outline = generateOutline (theme, audience, count, tone, language);
const contents = generateSlideContents (outline, tone, language);
const presentation = buildPresentation (contents, language);
const result = postProcess (presentation, language);
return {
presentationUrl: result.url,
pdfUrl: result.pdfUrl,
slideCount: contents.slides. length ,
};
}
ここでのポイントは、PropertiesService を使って API キーをスクリプトプロパティに安全に格納していることです。コード内にキーを直接記載することは絶対に避けてください。
Stage 2: Gemini APIでアウトラインをJSON生成する
パイプラインの中核となるのが、Gemini API の Structured Output を活用したアウトライン生成です。
// outline.gs — アウトライン生成
function generateOutline ( theme , audience , slideCount , tone , language ) {
const systemPrompt = `あなたはプレゼンテーション構成の専門家です。
与えられたテーマに基づき、プロフェッショナルなスライドデッキのアウトラインを設計してください。
制約:
- スライド枚数: ${ slideCount }枚(タイトルスライドと最終スライドを含む)
- 対象者: ${ audience }
- トーン: ${ tone }
- 各スライドに適切なレイアウトタイプを指定すること
- レイアウトタイプ: title_slide, section_header, content_with_bullets,
content_with_image, two_column, chart_suggestion, quote, closing` ;
const userPrompt = `テーマ: ${ theme }
以下のJSON形式で出力してください:
{
"title": "プレゼンテーションのメインタイトル",
"subtitle": "サブタイトル",
"slides": [
{
"slideNumber": 1,
"layout": "title_slide",
"title": "スライドタイトル",
"keyPoints": ["要点1", "要点2"],
"speakerNotes": "発表者ノートの概要"
}
]
}` ;
const response = callGeminiAPI (systemPrompt, userPrompt, {
responseMimeType: 'application/json' ,
});
const outline = JSON . parse (response);
// バリデーション: スライド数チェック
if ( ! outline.slides || outline.slides. length < 3 ) {
throw new Error ( 'アウトライン生成に失敗しました。再試行してください。' );
}
// ログに保存(デバッグ・監査用)
logToSheet ( 'Outline' , JSON . stringify (outline, null , 2 ));
return outline;
}
// gemini-api.gs — Gemini API呼び出しの共通関数
function callGeminiAPI ( systemPrompt , userPrompt , options = {}) {
const url = `${ CONFIG . GEMINI_ENDPOINT }${ CONFIG . GEMINI_MODEL }:generateContent?key=${ CONFIG . GEMINI_API_KEY }` ;
const payload = {
system_instruction: {
parts: [{ text: systemPrompt }],
},
contents: [
{
role: 'user' ,
parts: [{ text: userPrompt }],
},
],
generationConfig: {
temperature: options.temperature || 0.7 ,
topP: 0.95 ,
maxOutputTokens: options.maxTokens || 8192 ,
},
};
// Structured Output(JSON Mode)の指定
if (options.responseMimeType) {
payload.generationConfig.responseMimeType = options.responseMimeType;
}
const fetchOptions = {
method: 'post' ,
contentType: 'application/json' ,
payload: JSON . stringify (payload),
muteHttpExceptions: true ,
};
// リトライロジック(指数バックオフ)
let lastError;
for ( let attempt = 0 ; attempt < 3 ; attempt ++ ) {
try {
const res = UrlFetchApp. fetch (url, fetchOptions);
const status = res. getResponseCode ();
if (status === 200 ) {
const json = JSON . parse (res. getContentText ());
return json.candidates[ 0 ].content.parts[ 0 ].text;
}
if (status === 429 ) {
// レート制限: 指数バックオフで待機
const waitMs = Math. pow ( 2 , attempt) * 1000 + Math. random () * 1000 ;
Utilities. sleep (waitMs);
continue ;
}
throw new Error ( `Gemini API error: ${ status } - ${ res . getContentText () }` );
} catch (e) {
lastError = e;
if (attempt < 2 ) {
Utilities. sleep ( 2000 * (attempt + 1 ));
}
}
}
throw lastError;
}
Structured Output で responseMimeType: 'application/json' を指定することで、Gemini API は必ず有効な JSON を返します。プロンプトで JSON スキーマを明示する方式と比べて、パースエラーのリスクが大幅に低減される点が本番環境では重要です。
Gemini API の Structured Output についてさらに詳しく知りたい方は、Gemini API 構造化出力の完全ガイドも併せてご参照ください。
Stage 3: スライドごとのコンテンツ生成
アウトラインが確定したら、各スライドの詳細コンテンツを生成します。ここではバッチ処理パターン を使い、API呼び出し回数を最適化します。
// content-gen.gs — スライドコンテンツ生成
function generateSlideContents ( outline , tone , language ) {
const batchSize = 3 ; // 3スライドずつバッチ処理
const allSlides = [];
for ( let i = 0 ; i < outline.slides. length ; i += batchSize) {
const batch = outline.slides. slice (i, i + batchSize);
const batchContents = generateBatchContents (batch, outline.title, tone, language);
allSlides. push ( ... batchContents);
// レート制限対策: バッチ間に短い待機
if (i + batchSize < outline.slides. length ) {
Utilities. sleep ( 500 );
}
}
return {
title: outline.title,
subtitle: outline.subtitle,
slides: allSlides,
};
}
function generateBatchContents ( slides , presentationTitle , tone , language ) {
const systemPrompt = `あなたはプレゼンテーション資料のコンテンツライターです。
プレゼンのタイトル「${ presentationTitle }」の一部として、各スライドの詳細コンテンツを生成してください。
ルール:
- トーン: ${ tone }
- 箇条書きは1スライドあたり3〜5項目
- 各項目は簡潔かつ具体的に(1行20〜40文字程度)
- 発表者ノートは200〜400文字で詳しく
- chart_suggestion レイアウトの場合はダミーデータを含むグラフの仕様を記述` ;
const slidesInfo = slides. map ( s =>
`スライド${ s . slideNumber }: レイアウト=${ s . layout }, タイトル="${ s . title }", 要点=${ JSON . stringify ( s . keyPoints ) }`
). join ( ' \n ' );
const userPrompt = `以下のスライドの詳細コンテンツをJSON配列で生成してください:
${ slidesInfo }
出力形式:
[
{
"slideNumber": 1,
"title": "スライドタイトル",
"body": "本文テキスト(content_with_image等で使用)",
"bullets": ["箇条書き1", "箇条書き2"],
"speakerNotes": "発表者ノート(詳細な説明)",
"chartSpec": null
}
]` ;
const response = callGeminiAPI (systemPrompt, userPrompt, {
responseMimeType: 'application/json' ,
temperature: 0.6 , // コンテンツ生成は少し低めの温度で安定化
});
return JSON . parse (response);
}
バッチ処理で3スライドずつまとめて生成することで、10枚のスライドでも API 呼び出しは4回で済みます。1スライドずつ生成する方式と比べて、レイテンシが約60%短縮されるうえ、スライド間の文脈の一貫性も保たれやすくなります。
Stage 4: Google Slides APIでスライドを構築する
生成されたコンテンツを実際の Google Slides に変換するステージです。テンプレート駆動のアプローチを採用し、デザインの品質を担保します。
// slide-builder.gs — スライド構築
function buildPresentation ( contents , language ) {
// テンプレートをコピーして新規プレゼンテーションを作成
const templateId = getTemplateId (language);
const template = DriveApp. getFileById (templateId);
const newFile = template. makeCopy (
`${ contents . title } — ${ new Date (). toISOString (). slice ( 0 , 10 ) }` ,
DriveApp. getFolderById ( CONFIG . OUTPUT_FOLDER_ID )
);
const presentation = SlidesApp. openById (newFile. getId ());
const masterSlides = presentation. getMasters ()[ 0 ]. getLayouts ();
// テンプレートの既存スライドを全て削除(レイアウトマスターは保持)
const existingSlides = presentation. getSlides ();
existingSlides. forEach ( slide => slide. remove ());
// コンテンツからスライドを生成
contents.slides. forEach (( slideData , index ) => {
const layout = findLayout (masterSlides, slideData.layout);
const slide = presentation. appendSlide (layout);
populateSlide (slide, slideData);
// 発表者ノートを設定
if (slideData.speakerNotes) {
slide. getNotesPage ()
. getSpeakerNotesShape ()
. getText ()
. setText (slideData.speakerNotes);
}
});
presentation. saveAndClose ();
return {
id: newFile. getId (),
url: newFile. getUrl (),
name: newFile. getName (),
};
}
// レイアウト名とGoogleスライドレイアウトのマッピング
function findLayout ( masterLayouts , layoutType ) {
const layoutMap = {
'title_slide' : 'TITLE' ,
'section_header' : 'SECTION_HEADER' ,
'content_with_bullets' : 'TITLE_AND_BODY' ,
'content_with_image' : 'TITLE_AND_BODY' ,
'two_column' : 'TITLE_AND_TWO_COLUMNS' ,
'chart_suggestion' : 'TITLE_AND_BODY' ,
'quote' : 'TITLE_ONLY' ,
'closing' : 'TITLE' ,
};
const targetName = layoutMap[layoutType] || 'TITLE_AND_BODY' ;
for ( const layout of masterLayouts) {
if (layout. getLayoutName () === targetName) {
return layout;
}
}
// フォールバック: 最初のレイアウトを使用
return masterLayouts[ 0 ];
}
// スライドにコンテンツを配置
function populateSlide ( slide , data ) {
const placeholders = slide. getPlaceholders ();
placeholders. forEach ( placeholder => {
const type = placeholder. getPlaceholderType ();
switch (type) {
case SlidesApp.PlaceholderType. TITLE :
case SlidesApp.PlaceholderType. CENTER_TITLE :
placeholder. asShape (). getText (). setText (data.title);
break ;
case SlidesApp.PlaceholderType. BODY :
case SlidesApp.PlaceholderType. SUBTITLE :
if (data.bullets && data.bullets. length > 0 ) {
const textRange = placeholder. asShape (). getText ();
textRange. clear ();
data.bullets. forEach (( bullet , i ) => {
const paragraph = i === 0
? textRange. setText (bullet)
: textRange. appendParagraph (bullet);
});
} else if (data.body) {
placeholder. asShape (). getText (). setText (data.body);
}
break ;
}
});
}
テンプレート駆動のアプローチでは、デザイナーが作成したマスタースライドのフォント・配色・レイアウトがそのまま適用されます。AIが生成するのはコンテンツだけなので、ブランドガイドラインに沿った統一感のある資料が常に出力されます。
Stage 5: 後処理 — PDF出力・多言語化・通知
最終ステージでは、生成されたプレゼンテーションの後処理を行います。
// post-process.gs — 後処理パイプライン
function postProcess ( presentation , language ) {
const result = {
url: presentation.url,
pdfUrl: null ,
translations: [],
};
// 1. PDF エクスポート
result.pdfUrl = exportToPDF (presentation.id, presentation.name);
// 2. メタデータをスプレッドシートに記録
logPresentationMetadata (presentation);
// 3. 完了通知(オプション)
sendNotification (presentation, result.pdfUrl);
return result;
}
function exportToPDF ( fileId , fileName ) {
const url = `https://docs.google.com/presentation/d/${ fileId }/export/pdf` ;
const token = ScriptApp. getOAuthToken ();
const response = UrlFetchApp. fetch (url, {
headers: { Authorization: `Bearer ${ token }` },
muteHttpExceptions: true ,
});
if (response. getResponseCode () !== 200 ) {
Logger. log ( `PDF export failed: ${ response . getResponseCode () }` );
return null ;
}
const pdfBlob = response. getBlob (). setName ( `${ fileName }.pdf` );
const outputFolder = DriveApp. getFolderById ( CONFIG . OUTPUT_FOLDER_ID );
const pdfFile = outputFolder. createFile (pdfBlob);
return pdfFile. getUrl ();
}
// 多言語版の自動翻訳生成
function generateTranslatedVersion ( presentationId , targetLanguage ) {
const original = SlidesApp. openById (presentationId);
const slides = original. getSlides ();
// 全スライドのテキストを抽出
const texts = slides. map ( slide => {
const elements = slide. getPageElements ();
return elements
. filter ( e => e. getPageElementType () === SlidesApp.PageElementType. SHAPE )
. map ( e => e. asShape (). getText (). asString ())
. filter ( t => t. trim (). length > 0 );
});
// Gemini APIで一括翻訳
const systemPrompt = `あなたはプロフェッショナルな翻訳者です。
プレゼンテーション資料のテキストを${ targetLanguage }に翻訳してください。
- 箇条書きの構造は維持すること
- 技術用語は原語のまま保持すること
- 自然な${ targetLanguage }の表現にすること` ;
const userPrompt = `以下のスライドテキストを翻訳してください。
各スライドの構造を維持してJSON配列で出力してください:
${ JSON . stringify ( texts , null , 2 ) }` ;
const response = callGeminiAPI (systemPrompt, userPrompt, {
responseMimeType: 'application/json' ,
maxTokens: 16384 ,
});
const translations = JSON . parse (response);
// コピーを作成して翻訳テキストを適用
const file = DriveApp. getFileById (presentationId);
const copy = file. makeCopy (
file. getName (). replace ( /( . + )/ , `$1 (${ targetLanguage })` ),
DriveApp. getFolderById ( CONFIG . OUTPUT_FOLDER_ID )
);
const translatedPres = SlidesApp. openById (copy. getId ());
const translatedSlides = translatedPres. getSlides ();
translations. forEach (( slideTexts , slideIndex ) => {
if (slideIndex >= translatedSlides. length ) return ;
const elements = translatedSlides[slideIndex]. getPageElements ()
. filter ( e => e. getPageElementType () === SlidesApp.PageElementType. SHAPE );
let textIndex = 0 ;
elements. forEach ( element => {
const shape = element. asShape ();
const currentText = shape. getText (). asString (). trim ();
if (currentText. length > 0 && textIndex < slideTexts. length ) {
shape. getText (). setText (slideTexts[textIndex]);
textIndex ++ ;
}
});
});
translatedPres. saveAndClose ();
return copy. getUrl ();
}
多言語対応では、スライドの構造(箇条書きのインデント、番号付きリスト)を維持したまま翻訳する点が肝心です。Gemini API に JSON 形式で出力させることで、テキストの差し替えが正確に行えます。
テンプレート管理システムの設計
本番環境では、複数のテンプレートを用途別に管理する仕組みが必要です。
// template-manager.gs — テンプレート管理
const TEMPLATES = {
corporate: {
ja: 'TEMPLATE_ID_JA_CORPORATE' ,
en: 'TEMPLATE_ID_EN_CORPORATE' ,
description: '社内ミーティング・役員報告用' ,
},
pitch: {
ja: 'TEMPLATE_ID_JA_PITCH' ,
en: 'TEMPLATE_ID_EN_PITCH' ,
description: '投資家向けピッチデッキ' ,
},
workshop: {
ja: 'TEMPLATE_ID_JA_WORKSHOP' ,
en: 'TEMPLATE_ID_EN_WORKSHOP' ,
description: 'ワークショップ・勉強会用' ,
},
minimal: {
ja: 'TEMPLATE_ID_JA_MINIMAL' ,
en: 'TEMPLATE_ID_EN_MINIMAL' ,
description: 'ミニマルデザイン(汎用)' ,
},
};
function getTemplateId ( language , templateType = 'corporate' ) {
const template = TEMPLATES [templateType];
if ( ! template) {
throw new Error ( `Unknown template type: ${ templateType }` );
}
const lang = language === 'en' ? 'en' : 'ja' ;
return template[lang];
}
// テンプレートのプレビュー情報を取得
function getTemplateList () {
return Object. entries ( TEMPLATES ). map (([ key , value ]) => ({
id: key,
description: value.description,
thumbnailUrl: getThumbnail (value.ja),
}));
}
function getThumbnail ( presentationId ) {
try {
const presentation = SlidesApp. openById (presentationId);
const firstSlide = presentation. getSlides ()[ 0 ];
const thumbnail = firstSlide. getThumbnail ();
return thumbnail. getUrl ();
} catch (e) {
return null ;
}
}
テンプレートを言語ごとに分離し、フォントや文字サイズの最適化を個別に行えるようにしています。日本語は Noto Sans JP、英語は Inter など、言語に合ったフォントをマスタースライドに事前設定しておくことで、出力品質が大きく向上します。
エラーハンドリングとリトライ戦略
本番パイプラインでは、API エラーやタイムアウトに対する堅牢なハンドリングが不可欠です。
// error-handler.gs — エラーハンドリング
class PipelineError extends Error {
constructor ( stage , message , retryable = false ) {
super ( `[Stage ${ stage }] ${ message }` );
this .stage = stage;
this .retryable = retryable;
}
}
function executePipelineWithRetry ( params , maxRetries = 2 ) {
let lastError;
for ( let attempt = 0 ; attempt <= maxRetries; attempt ++ ) {
try {
if (attempt > 0 ) {
Logger. log ( `Retry attempt ${ attempt }/${ maxRetries }` );
Utilities. sleep ( 3000 * attempt); // 指数バックオフ
}
return generatePresentation (params);
} catch (error) {
lastError = error;
// リトライ不可能なエラーは即座に中断
if (error instanceof PipelineError && ! error.retryable) {
break ;
}
// Apps Script の実行時間制限(6分)に近い場合は中断
const elapsed = ( new Date () - scriptStartTime) / 1000 ;
if (elapsed > 300 ) { // 5分経過
Logger. log ( 'Approaching execution time limit. Aborting retry.' );
break ;
}
logError (error, attempt);
}
}
// 全リトライ失敗時: 途中結果を保存して通知
savePartialResult (lastError);
sendErrorNotification (lastError);
throw lastError;
}
function logError ( error , attempt ) {
const sheet = SpreadsheetApp. getActiveSpreadsheet ()
. getSheetByName ( 'ErrorLog' ) ||
SpreadsheetApp. getActiveSpreadsheet (). insertSheet ( 'ErrorLog' );
sheet. appendRow ([
new Date (),
error.stage || 'unknown' ,
error.message,
attempt,
JSON . stringify (error.stack || '' ),
]);
}
Apps Script には6分の実行時間制限があるため、処理の進行状況を常に監視し、タイムアウト前に中間結果を保存する設計が重要です。大規模なプレゼン(20枚超)の場合は、トリガーを使った非同期処理の分割も検討してください。
バッチ処理 — 定期的なレポートプレゼンの自動生成
週次・月次レポートなど、定期的に同じ形式のプレゼンを生成するユースケースでは、時間ベースのトリガーと組み合わせた完全自動化が可能です。
// batch.gs — バッチ処理
function setupWeeklyReportTrigger () {
// 既存のトリガーを削除
ScriptApp. getProjectTriggers (). forEach ( trigger => {
if (trigger. getHandlerFunction () === 'generateWeeklyReport' ) {
ScriptApp. deleteTrigger (trigger);
}
});
// 毎週月曜 9:00 に実行
ScriptApp. newTrigger ( 'generateWeeklyReport' )
. timeBased ()
. onWeekDay (ScriptApp.WeekDay. MONDAY )
. atHour ( 9 )
. create ();
}
function generateWeeklyReport () {
// スプレッドシートから最新データを取得
const dataSheet = SpreadsheetApp. getActiveSpreadsheet ()
. getSheetByName ( 'WeeklyData' );
const data = dataSheet. getDataRange (). getValues ();
// データをコンテキストとしてプレゼンを生成
const dataContext = formatDataForPrompt (data);
const params = {
theme: `週次業績レポート(${ getWeekRange () })` ,
audience: '経営チーム' ,
slideCount: 8 ,
tone: 'professional' ,
language: 'ja' ,
additionalContext: dataContext,
};
try {
const result = executePipelineWithRetry (params);
// 成功時: Slack に通知
sendSlackNotification ({
text: `✅ 週次レポートが自動生成されました \n 📊 ${ result . presentationUrl }` ,
});
} catch (error) {
sendSlackNotification ({
text: `❌ 週次レポート生成に失敗しました: ${ error . message }` ,
});
}
}
function formatDataForPrompt ( sheetData ) {
const headers = sheetData[ 0 ];
const rows = sheetData. slice ( 1 );
return rows. map ( row => {
const obj = {};
headers. forEach (( h , i ) => { obj[h] = row[i]; });
return obj;
});
}
function getWeekRange () {
const now = new Date ();
const weekAgo = new Date (now. getTime () - 7 * 24 * 60 * 60 * 1000 );
const fmt = d => `${ d . getMonth () + 1 }/${ d . getDate () }` ;
return `${ fmt ( weekAgo ) }〜${ fmt ( now ) }` ;
}
スプレッドシートのデータを自動的にグラフや表に変換してスライドに配置することで、手作業ゼロの完全自動レポーティングが実現します。Apps Scriptの機能をさらに活用するパターンについては、Gemini API × Apps Script で Gmail・Calendar・Drive を横断する全自動 AI ワークフロー構築ガイド で詳しく解説しています。
コスト最適化とパフォーマンスチューニング
本番運用では、API コストとパフォーマンスの最適化が収益性に直結します。
モデルの使い分け戦略 : アウトライン生成(Stage 2)には gemini-2.5-pro を使い、コンテンツ生成(Stage 3)には gemini-2.5-flash を使うことで、品質を維持しながらコストを約40%削減できます。
// cost-optimization.gs — モデル使い分け
function callGeminiAPIWithModel ( model , systemPrompt , userPrompt , options = {}) {
const url = `${ CONFIG . GEMINI_ENDPOINT }${ model }:generateContent?key=${ CONFIG . GEMINI_API_KEY }` ;
// ... (callGeminiAPIと同じ実装、URLのモデル部分のみ差し替え)
}
// Stage 2: 構造設計は Pro(高品質)
function generateOutlineOptimized ( theme , audience , slideCount , tone ) {
return callGeminiAPIWithModel ( 'gemini-2.5-pro' , /* ... */ );
}
// Stage 3: コンテンツ埋めは Flash(高速・低コスト)
function generateSlideContentsOptimized ( outline , tone ) {
return callGeminiAPIWithModel ( 'gemini-2.5-flash' , /* ... */ );
}
キャッシュ戦略 : 同じテーマの再生成リクエストに対して、アウトライン(Stage 2)の結果をスプレッドシートにキャッシュしておくことで、API呼び出しを50%削減できます。
Apps Script 実行時間の最適化 : 6分の制限内で完了させるため、非同期処理を活用します。
10枚以下のスライド: 同期実行(約2〜3分)
11〜20枚のスライド: Stage 2〜3 と Stage 4〜5 をトリガーで分割
個人開発者の視点から(実体験メモ)
まとめ
ここで扱うのはGemini API と Google Apps Script を組み合わせたプレゼン自動生成パイプラインの設計から実装までを解説しました。5段階のパイプラインアーキテクチャ、Structured Output を活用したアウトライン生成、テンプレート駆動のスライド構築、そして多言語対応とバッチ処理まで、本番環境で実際に運用できる水準の実装パターンを紹介しました。
このパイプラインを導入することで、プレゼン作成にかかる時間を90%以上削減しながら、ブランドガイドラインに準拠した品質を常に維持できます。まずは社内の定例レポートなど、テンプレートが決まっている資料から自動化を始めてみてください。