Gemini Function Callingは、モデルが外部関数を直接実行する機能ではありません。モデルが関数名と引数を function_call として提案し、JavaScriptアプリが検証・認可・実行し、その結果を function_result としてモデルへ返す仕組みです。
たとえば「来週火曜に3人の打ち合わせを入れて」という文章から、日時、参加者、議題をJSON形式の引数へ変換できます。ただし、カレンダーへの登録やメール送信を行うのは自分のコードです。
この記事では、Gemini Interactions APIと公式JavaScript SDKを使い、会議予約関数を宣言し、呼び出しを受け取り、結果を返すところまで実装します。
情報確認日:2026年7月18日(日本時間)
結論:Function Callingは4段階で実装する
1. 関数名・説明・引数スキーマをモデルへ渡す
↓
2. モデルがfunction_callを返す
↓
3. アプリが引数検証・認可・実行を行う
↓
4. function_resultを返し、最終文章を生成する
安全に実装する原則
- 許可した関数名だけを実行する
- モデルが作った引数を信用せず検証する
- 利用者が対象データへアクセスできるか認可する
- 送信、削除、購入などは実行直前に承認する
- 関数結果を信頼済み命令として扱わない
- 重複実行を防ぐIDを使う
Function Callingのベンダー共通の考え方はFunction Callingとは何かでも解説しています。
プロジェクトを準備する
mkdir gemini-function-sample
cd gemini-function-sample
npm init -y
npm pkg set type=module
npm install @google/genai
export GEMINI_API_KEY="your_api_key_here"
export GEMINI_MODEL="gemini-3.5-flash"
公式ドキュメントの2026年7月18日時点の例では、Interactions APIと gemini-3.5-flash が使われています。本番コードはモデルIDを環境変数へ分離し、利用可能なモデルとSDKバージョンを実装時に確認します。
会議予約関数を宣言する
const scheduleMeetingTool = {
type: "function",
name: "schedule_meeting",
description:
"参加者、日付、開始時刻、議題を指定して会議候補を作成する",
parameters: {
type: "object",
properties: {
attendees: {
type: "array",
items: { type: "string" },
description: "参加者のメールアドレス",
},
date: {
type: "string",
description: "YYYY-MM-DD形式の日付",
},
time: {
type: "string",
description: "24時間表記HH:mmの開始時刻",
},
topic: {
type: "string",
description: "会議の議題",
},
},
required: ["attendees", "date", "time", "topic"],
additionalProperties: false,
},
};
説明には「何ができるか」だけでなく、値の形式と意味を書きます。メールアドレスや日時の妥当性をJSON Schemaだけで完全に保証できないため、実行前にJavaScriptでも検証します。
モデルからfunction_callを受け取る
import { GoogleGenAI } from "@google/genai";
const ai = new GoogleGenAI({});
const model = process.env.GEMINI_MODEL;
if (!model) {
throw new Error("GEMINI_MODEL is not set");
}
const interaction = await ai.interactions.create({
model,
input:
"2026年7月21日の15:00に、" +
"a@example.comとb@example.comを招待して、" +
"新機能レビューの会議候補を作ってください。",
tools: [scheduleMeetingTool],
});
const call = interaction.steps.find(
(step) => step.type === "function_call"
);
if (!call) {
console.log(interaction.output_text);
process.exit(0);
}
console.log(call.name);
console.log(call.arguments);
モデルがツールを使わず文章で答える場合もあるため、function_call が必ず存在するとは限りません。複数の関数呼び出しが返る場合もあるため、本番では filter() で全件を取得します。
引数を検証してアプリ側で実行する
function validateMeetingArgs(args) {
if (!Array.isArray(args.attendees)) {
throw new Error("attendees must be an array");
}
if (
args.attendees.length === 0 ||
args.attendees.length > 10
) {
throw new Error("attendees count is out of range");
}
const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!args.attendees.every((value) =>
emailPattern.test(value)
)) {
throw new Error("invalid email address");
}
if (!/^\d{4}-\d{2}-\d{2}$/.test(args.date)) {
throw new Error("date must be YYYY-MM-DD");
}
if (!/^\d{2}:\d{2}$/.test(args.time)) {
throw new Error("time must be HH:mm");
}
if (
typeof args.topic !== "string" ||
args.topic.length > 100
) {
throw new Error("invalid topic");
}
return args;
}
形式検証に加え、日付が実在するか、過去ではないか、参加者が同じ組織に属するか、利用者に招待権限があるかを確認します。モデルの引数へSQL、シェル、URLをそのまま連結しないでください。
async function scheduleMeeting(args, context) {
await assertCanInvite(context.userId, args.attendees);
return {
reservationId: `meeting_${crypto.randomUUID()}`,
status: "draft",
date: args.date,
time: args.time,
attendees: args.attendees,
topic: args.topic,
};
}
if (call.name !== "schedule_meeting") {
throw new Error(`unsupported function: ${call.name}`);
}
const args = validateMeetingArgs(call.arguments);
const result = await scheduleMeeting(args, {
userId: "current-user-id",
});
実行範囲:この例は予定の「下書き」を作るだけです。実際の招待メール送信は、対象と日時を画面へ表示して人が承認した後の別関数に分けると安全です。
function_resultを返して最終回答を作る
const finalInteraction = await ai.interactions.create({
model,
input: [
{
type: "function_result",
name: call.name,
call_id: call.id,
result: [
{
type: "text",
text: JSON.stringify(result),
},
],
},
],
tools: [scheduleMeetingTool],
previous_interaction_id: interaction.id,
});
console.log(finalInteraction.output_text);
call_id で、どの関数呼び出しへの結果かを対応付けます。会話をつなぐため previous_interaction_id を指定し、次のInteractionでもツール定義を再指定します。
会議予約の実行ログ例
| 段階 | 記録する内容 | この例の状態 |
|---|---|---|
| 要求 | 利用者、Interaction ID、関数名 | schedule_meeting |
| 検証 | スキーマ、件数、形式、認可 | passed |
| 実行 | 冪等性キー、対象、開始・終了時刻 | draft created |
| 結果返却 | call ID、結果の要約、最終Interaction ID | completed |
| 外部送信 | 承認者、差分、送信結果 | not executed |
プロンプト全文や関数結果に機密情報が含まれる場合、ログへ無制限に保存しません。調査に必要なIDとメタデータを中心にし、本文の保持期間と閲覧権限を別途定めます。
複数の関数呼び出しを扱う
Geminiは1ターンで複数関数を提案する並列呼び出しと、結果を受けて次の関数を選ぶ連続呼び出しをサポートします。ただし、すべてを Promise.all() で実行してよいわけではありません。
| 関数 | 並列実行 | 理由 |
|---|---|---|
| 複数都市の天気取得 | しやすい | 読み取り専用で互いに独立 |
| 在庫確認と価格取得 | 条件付き | 同じ時点の情報か確認が必要 |
| 注文作成と決済 | しない | 順序、承認、失敗時の補償が必要 |
| ファイル更新と削除 | しない | 競合と不可逆な影響がある |
読み取り関数でも、外部APIのレート制限とタイムアウトを設けます。書き込み関数では、順序、トランザクション、補償処理、承認、冪等性キーを設計します。
安全な関数ルーターを作る
const handlers = new Map([
[
"schedule_meeting",
{
validate: validateMeetingArgs,
execute: scheduleMeeting,
risk: "medium",
},
],
]);
async function executeFunctionCall(call, context) {
const handler = handlers.get(call.name);
if (!handler) {
throw new Error("function is not allowlisted");
}
const args = handler.validate(call.arguments);
if (handler.risk !== "low") {
await requireApproval({
callId: call.id,
name: call.name,
args,
userId: context.userId,
});
}
return handler.execute(args, context);
}
モデルが自由に指定した関数パスを動的importするような実装は避け、許可リストからハンドラーを選びます。MCPとFunction Callingの関係を整理したい場合はMCPの基本も参考になります。
よくある失敗
- 関数の説明が曖昧で、似たツールを誤選択する
- 引数を検証せず外部APIやSQLへ渡す
- モデルに認可判断まで任せる
- function_callがない場合を処理していない
- 複数function_callの1件目しか処理しない
- 結果を返す際にcall IDを対応付けていない
- 再送で同じ外部操作を二重実行する
- 書き込み関数を利用者の確認なしで実行する
よくある質問
GeminiがJavaScript関数を実行してくれますか?
いいえ。モデルは関数名と引数を提案します。実際に実行するか、何を返すかはアプリが決めます。
JSON Schemaがあれば引数検証は不要ですか?
必要です。型だけでなく、件数、範囲、日時、権限、業務ルールをアプリ側で検証します。スキーマに合っていても危険な値はあります。
関数結果をそのままモデルへ返してよいですか?
必要な項目だけへ絞り、秘密情報や不要な個人情報を除きます。外部APIの文章に命令が含まれていても、システム指示として扱わないよう境界を分けます。
まとめ
Gemini Function Callingでは、関数宣言を渡し、function_call を受け取り、アプリが実行し、function_result を返します。モデルは引数を提案するだけで、認可や副作用の責任はアプリ側にあります。
最初は読み取り専用の1関数から始め、許可リスト、引数検証、タイムアウト、ログを整えてください。書き込み関数を追加するときは、承認と二重実行防止をセットで実装します。