在昨天的 [Day 26] 中,我們利用 Qdrant 向量資料庫 + Gemini Embedding API,打造了 Multi-Modal Hybrid RAG 跨資產混合檢索系統,讓 OmniVibe AI 能夠精準跨檔案比對影音與文檔精華。
然而,至今為止我們的 AI 系統依然屬於「被動輸出型(Passive AI)」——它能給出完美的摘要、爆款貼文與短影音腳本,但後續的「執行動作」仍需使用者手動複製貼上。例如:
今天,我們將引進 Gemini 1.5 專為自主代理(Agentic Workflow)設計的核心能力 —— Function Calling(函式調用 / 工具使用)!我們將賦予 Gemini 1.5 自動識別意圖並調用外部 API 的能力,實現從「內容提煉」到「自動寄送摘要 Email」與「自動建立 Google 日曆行程」的完整自主 Agent 工作流!
在 Gemini 的 Function Calling 機制中,模型並不會「直接」執行你的程式碼。Gemini 扮演的是 大腦與指揮官(Controller):它根據使用者輸入與我們提供的工具宣告(Tool Declarations),決定是否觸發工具、填入正確的 JSON 參數,並交由我們的後端執行實際的 API 動作,最後將執行結果回傳給 Gemini 匯報成果。
sequenceDiagram
autonumber
actor User as 使用者
participant Server as Next.js Agent Route
participant Gemini as Gemini 1.5 Agent 大腦
participant Tool as External Services (SendGrid / Google Calendar)
User->>Server: 1. 「請提煉這支影片,並把重點寄給 clay@example.com,且在明天下午 3 點建立開會行程。」
Server->>Gemini: 2. generateContent(prompt, { tools: [sendEmailDeclaration, createCalendarDeclaration] })
Note over Gemini: 辨識出需要調用 2 個工具,並提取參數
Gemini-->>Server: 3. 回傳 functionCall (指定工具名稱與 JSON 參數)
par 執行工具 (Tool Execution)
Server->>Tool: 4a. 呼叫 SendGrid API 寄送 Email
Tool-->>Server: 4b. 寄信成功 (200 OK)
Server->>Tool: 5a. 呼叫 Google Calendar API 新增行程
Tool-->>Server: 5b. 新增行程成功 (Event Created)
end
Server->>Gemini: 6. 將工具執行結果 (functionResponse) 送回 Gemini 進行最終合成
Gemini-->>Server: 7. 回傳最終自然語言確認訊息
Server-->>User: 8. 「已為您完成影片提煉!郵件已順利寄出,日曆行程也已自動建立完成!🎉」
我們使用 @google/generative-ai 提供的 FunctionDeclaration 語法,定義 Gemini 能夠使用的外部工具及其參數 Schema:
// src/lib/agent/tools.ts
import { FunctionDeclaration, SchemaType } from '@google/generative-ai';
// 1. 工具宣告:自動發送 Email 摘要
export const sendEmailToolDeclaration: FunctionDeclaration = {
name: 'sendEmailSummary',
description: '將提煉後的會議紀錄、影片摘要或重點發送 Email 給指定的收件者。',
parameters: {
type: SchemaType.OBJECT,
properties: {
toEmail: {
type: SchemaType.STRING,
description: '收件人的 Email 地址 (例如 clay@example.com)',
},
subject: {
type: SchemaType.STRING,
description: '郵件主旨',
},
contentHtml: {
type: SchemaType.STRING,
description: 'HTML 格式的郵件內文,包含核心洞見與 Action Items',
},
},
required: ['toEmail', 'subject', 'contentHtml'],
},
};
// 2. 工具宣告:自動建立 Google Calendar 日曆行程
export const createCalendarEventToolDeclaration: FunctionDeclaration = {
name: 'createCalendarEvent',
description: '根據提煉出的會議時間、活動或 Task,自動在 Google 日曆中新增行程。',
parameters: {
type: SchemaType.OBJECT,
properties: {
title: {
type: SchemaType.STRING,
description: '日曆行程標題',
},
startTime: {
type: SchemaType.STRING,
description: '行程開始時間 (ISO 8601 格式,例如 2026-10-12T15:00:00+08:00)',
},
endTime: {
type: SchemaType.STRING,
description: '行程結束時間 (ISO 8601 格式)',
},
description: {
type: SchemaType.STRING,
description: '行程詳細說明與備註',
},
},
required: ['title', 'startTime', 'endTime'],
},
};
當 Gemini 決定呼叫工具時,我們需要有真實的 TypeScript 函數去執行操作:
// src/lib/agent/executors.ts
/**
* 工具執行器:模擬/實際呼叫 SendGrid / Resend API 發送 Email
*/
export async function executeSendEmailSummary(args: {
toEmail: string;
subject: string;
contentHtml: string;
}) {
console.log(`[Agent Tool Executed] 正在寄送郵件給 ${args.toEmail}...`);
console.log(`[Subject]: ${args.subject}`);
// 實務上在此處呼叫 SendGrid 或 Resend API
// await resend.emails.send({ ... });
return {
status: 'SUCCESS',
message: `郵件已成功寄送至 ${args.toEmail}`,
timestamp: new Date().toISOString(),
};
}
/**
* 工具執行器:模擬/實際呼叫 Google Calendar API 新增行程
*/
export async function executeCreateCalendarEvent(args: {
title: string;
startTime: string;
endTime: string;
description?: string;
}) {
console.log(`[Agent Tool Executed] 正在建立日曆行程: ${args.title}...`);
console.log(`[Time]: ${args.startTime} ~ ${args.endTime}`);
// 實務上在此處呼叫 Google Calendar API
// await calendar.events.insert({ ... });
return {
status: 'SUCCESS',
eventId: `evt_${Date.now()}`,
htmlLink: 'https://calendar.google.com/calendar/event?eid=sample_event_id',
message: `已成功在 Google 日曆建立行程:${args.title}`,
};
}
src/app/api/ai/agent/route.ts)現在,我們將 Gemini 1.5 Pro 與 Function Calling 執行迴圈封裝至 Next.js API Route 中,實現自動化的「分析-呼叫-回覆」閉環:
// src/app/api/ai/agent/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { GoogleGenerativeAI } from '@google/generative-ai';
import { sendEmailToolDeclaration, createCalendarEventToolDeclaration } from '@/lib/agent/tools';
import { executeSendEmailSummary, executeCreateCalendarEvent } from '@/lib/agent/executors';
const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY || '');
export async function POST(req: NextRequest) {
try {
const { userPrompt, fileUri, mimeType } = await req.json();
// 1. 初始化模型並掛載 Tools 宣告
const model = genAI.getGenerativeModel({
model: 'gemini-1.5-pro',
tools: [{ functionDeclarations: [sendEmailToolDeclaration, createCalendarEventToolDeclaration] }],
});
const chat = model.startChat();
// 2. 構建包含多模態資產與使用者指令的 Prompt
const parts: any[] = [{ text: userPrompt }];
if (fileUri && mimeType) {
parts.unshift({ fileData: { fileUri, mimeType } });
}
console.log('[Agent Engine] 發送請求給 Gemini 1.5 Agent 大腦...');
let response = await chat.sendMessage(parts);
// 3. 檢查 Gemini 是否回傳了 functionCalls 請求
const functionCalls = response.response.functionCalls();
if (functionCalls && functionCalls.length > 0) {
console.log(`[Agent Engine] Gemini 決定調用 ${functionCalls.length} 個外部工具!`);
const functionResponses = [];
for (const call of functionCalls) {
const { name, args } = call;
console.log(`[Agent Call] 觸發工具: ${name}, 參數:`, args);
let executionResult: any;
// 4. 依據工具名稱配對執行器
if (name === 'sendEmailSummary') {
executionResult = await executeSendEmailSummary(args as any);
} else if (name === 'createCalendarEvent') {
executionResult = await executeCreateCalendarEvent(args as any);
} else {
executionResult = { error: `未知的工具名稱: ${name}` };
}
// 整理給 Gemini 的工具回應結構
functionResponses.push({
response: {
name: name,
content: executionResult,
},
});
}
// 5. 將工具執行結果 (functionResponses) 送回 Gemini 進行最終自然語言彙整
console.log('[Agent Engine] 將工具執行結果回傳給 Gemini 作最終回覆...');
response = await chat.sendMessage(
functionResponses.map((item) => ({
functionResponse: item.response,
}))
);
}
// 6. 回傳最終 Agent 回覆給前端
return NextResponse.json({
agentResponse: response.response.text(),
executedTools: functionCalls ? functionCalls.map((c) => c.name) : [],
});
} catch (error: any) {
console.error('[Agent API Error]:', error);
return NextResponse.json({ error: error.message }, { status: 500 });
}
}
我們在上傳一段 30 分鐘的團隊會議錄音後,發起一條複合型代理指令:
使用者 Prompt:
「請提煉這段會議錄音的重點,將摘要寄信給 clay@example.com,主旨設為『10月專案進度會議精華』;另外,會議中提到的『10/15 下午 2 點需求對齊會議』,請自動幫我加入 Google 日曆中。」
[Agent Engine] 發送請求給 Gemini 1.5 Agent 大腦...
[Agent Engine] Gemini 決定調用 2 個外部工具!
[Agent Call] 觸發工具: sendEmailSummary, 參數: {
toEmail: 'clay@example.com',
subject: '10月專案進度會議精華',
contentHtml: '<h3>核心洞見與決議事項</h3><ul><li>第一階段 UI 切版已完成...</li></ul>'
}
[Agent Tool Executed] 正在寄送郵件給 clay@example.com...
[Agent Call] 觸發工具: createCalendarEvent, 參數: {
title: '需求對齊會議',
startTime: '2026-10-15T14:00:00+08:00',
endTime: '2026-10-15T15:00:00+08:00',
description: '討論 10 月專案後續需求與介面串接細節。'
}
[Agent Tool Executed] 正在建立日曆行程: 需求對齊會議...
[Agent Engine] 將工具執行結果回傳給 Gemini 作最終回覆...
「已為您完成會議錄音的精華提煉!郵件已順利寄送給 clay@example.com。同時,我已為您在 Google 日曆中新增了【10/15 下午 2:00 - 3:00 需求對齊會議】的行程。您隨時可以點擊查看!」
今天我們成功讓 OmniVibe AI 從單純的「回答者」跨越為能採取行動的「自主 Agent」:
截至今天,我們已經涵蓋了 Full-stack AI SaaS 的架構、介面、檔案管線、串流、資料庫、金流、護欄、評測、快取、跨平台外掛、非同步任務、向量 RAG 與 Function Calling Agent。
👉 明天(Day 28),我們將進入【系統整體架構回顧與效能防禦】:全棧架構總覽、API Rate Limiting 限流防禦與災難復原演練!看我們如何為營運做出最後的穩定性武裝!
我們明天見!🔥