【用意するもの】
Google Apps Script(GAS)
Google AI Studio(推論AI用APIキー)
Google Driveフォルダ
Googleスプレッドシート(PC使用)
Gemini
メモ帳
PCでGoogleスプレッドシートを開いて、上部のファイルや編集や表示の並びにある「拡張機能」を選択して「Apps Script」を表示させる
↓
画面最上部右上などに「Geminiに相談」があれば、GeminiにGASコードを書くように頼んで、GASコードをコピー&ペーストする
↓
丸見えにならないようにする為にGASの左にある設定マークからスクリプトプロパティに次を追加登録する
- スプレッドシートID
- DriveフォルダID
- 推論AI用APIキー
プロパティ
GASコードにある同じ文字列
スプレッドシートやDriveフォルダごとにリンクをコピーしてメモ帳にペーストすると、スプレッドシートIDやDriveフォルダIDがわかる
- スプレッドシートなら間の文字列がスプレッドシートID
- DriveフォルダならURLの後方の文字列がDriveフォルダID
- GASコードにある推論AIの文字列
値
IDやAPIキーの文字列
↓
GASの左にある設定から次の項目にチェックを入れる
「appsscript.json」マニフェスト ファイルをエディタで表示する
↓
GASコードの画面と左の設定マークの間にある+ファイルの項目にappsscript.jsonが表示されるので、appsscript.jsonをGeminiに書くように頼んでコピー&ペーストする
↓
GASのデプロイの横をonEditにする
↓
連動させる為にGASの左にある時計マークから次を選択する
トリガーを追加をタッチしてから
実行する関数
onEdit
実行するデプロイ
Head
イベントのソース
フォームから
イベントの種類
編集時、フォーム送信時
↓
スプレッドシートで入力と出力を行なう為に、登録したスプレッドシートを開く
A1のセルに質問
B1のセルにAI回答
と入力してA2に質問を入力して「改行」すると、A2にAIが回答するか確認する
- GASで実行してもエラーが表示される
- スプレッドシートの改行で実行する
↓
スプレッドシートの下にタブがあるので、タブ1とタブ2で関連付けることができる
↓
エージェント風(自立型)になる
例:スプレッドシートのタブ1をAI対話、タブ2を理論株価計算とした場合
タブ1 質問を入力して改行する
タブ2 独自理論株価の計算式や GOOGLEFINANCE 関数、各種指標・算出結果の一覧を配置。AIが参照するための「データハブ」として機能させる。
↓
質問を入力後に改行して、AI回答にエラーっぽく表示されたらAI回答も改行する
/**
* スプレッドシートが編集されたときに自動で発動する関数
*/
function onEdit(e) {
if (!e) return;
const sheet = e.source.getActiveSheet();
const range = e.range;
const row = range.getRow();
const col = range.getColumn();
// A列(1列目)の2行目以降に文字が入力された場合のみ実行
if (col === 1 && row >= 2 && range.getValue() !== "") {
const userQuery = range.getValue();
// 処理中の仮表示をB列(2列目)に出力
sheet.getRange(row, 2).setValue("AI思考中...");
try {
const scriptProperties = PropertiesService.getScriptProperties();
const apiKey = scriptProperties.getProperty('GEMINI_API_KEY');
const spreadsheetId = scriptProperties.getProperty('DATA_SPREADSHEET_ID') || e.source.getId();
const folderId = scriptProperties.getProperty('TARGET_DRIVE_FOLDER_ID');
if (!apiKey) {
throw new Error("スクリプトプロパティに 'GEMINI_API_KEY' が設定されていません。");
}
// メインのAIワークフローを実行
const finalAnswer = mainAIWorkflowCore(userQuery, apiKey, spreadsheetId, folderId);
// B列(2列目)に最終回答を書き込む
sheet.getRange(row, 2).setValue(finalAnswer);
} catch (error) {
sheet.getRange(row, 2).setValue("エラー: " + error.message);
}
}
}
/**
* AIの処理をまとめたコア関数
*/
function mainAIWorkflowCore(userQuery, apiKey, spreadsheetId, folderId) {
// ステップ1: セレクター&リスター(データベースとドライブから関連データを抽出)
const workingMemory = retrieveAndFilterData_(userQuery, spreadsheetId, folderId);
// ステップ2: ゼネラリストAIによる方向性決定
const generalPlan = callGeminiFlash_(
apiKey,
"あなたは全体を統括するゼネラリストです。以下の作業台(ワーキングメモリ)のデータに基づき、今回の質問に対するアプローチを整理してください。",
`質問: ${userQuery}\n\n作業台データ: ${workingMemory}`
);
// ステップ3: 並列・スペシャリストAIによる多角的検証
const expertDataResult = callGeminiFlash_(
apiKey,
"あなたはデータ分析のスペシャリストです。数字や事実関係に矛盾がないか検証してください。",
`計画: ${generalPlan}\nデータ: ${workingMemory}`
);
const expertActionResult = callGeminiFlash_(
apiKey,
"あなたは構成・実用化のスペシャリストです。実行手順や実用性の観点から検証してください。",
`計画: ${generalPlan}\nデータ: ${workingMemory}`
);
// ステップ4: 最終統合
const finalAnswer = callGeminiFlash_(
apiKey,
"あなたは最高責任者のAIです。これまでの専門家の意見を統合し、お世辞や前置きなしで、結論から分かりやすく箇条書きで回答してください。",
`質問: ${userQuery}\nスペシャリストAの意見: ${expertDataResult}\nスペシャリストBの意見: ${expertActionResult}`
);
return finalAnswer;
}
/**
* データ抽出・中間メモリ構築関数(リスター&セレクター)
*/
function retrieveAndFilterData_(userQuery, spreadsheetId, folderId) {
let matchedData = [];
try {
const sheet = SpreadsheetApp.openById(spreadsheetId).getSheets()[0];
const rows = sheet.getDataRange().getValues();
for (let i = 1; i < rows.length; i++) {
let rowText = rows[i].join(" ");
if (userQuery.split(" ").some(keyword => keyword !== "" && rowText.includes(keyword))) {
matchedData.push(rowText);
}
}
} catch (e) {
Logger.log('スプレッドシート読み込みエラー: ' + e.message);
}
if (folderId) {
try {
const folder = DriveApp.getFolderById(folderId);
const files = folder.getFiles();
while (files.hasNext()) {
let file = files.next();
if (file.getMimeType() === MimeType.GOOGLE_DOCS) {
let content = DocumentApp.openById(file.getId()).getBody().getText();
if (userQuery.split(" ").some(keyword => keyword !== "" && content.includes(keyword))) {
matchedData.push(`[Driveファイル: ${file.getName()}] ${content.substring(0, 500)}...`);
}
}
}
} catch (e) {
Logger.log('ドライブ読み込みエラー: ' + e.message);
}
}
if (matchedData.length === 0) {
matchedData.push("※直接一致する特記事項なし。一般知識ベースで推論します。");
}
return matchedData.join("\n---\n");
}
/**
* Gemini Flash API呼び出し共通関数
*/
function callGeminiFlash_(apiKey, systemPrompt, userContent) {
const url = `https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key=${apiKey}`;
const payload = {
contents: [
{
role: "user",
parts: [{ text: systemPrompt + "\n\n" + userContent }]
}
]
};
const options = {
method: "post",
contentType: "application/json",
payload: JSON.stringify(payload),
muteHttpExceptions: true
};
try {
const response = UrlFetchApp.fetch(url, options);
const json = JSON.parse(response.getContentText());
if (json.candidates && json.candidates[0].content) {
return json.candidates[0].content.parts[0].text;
} else {
return "AIからの応答を取得できませんでした。";
}
} catch (e) {
return "API通信エラー: " + e.message;
}
}