iT邦幫忙

2026 iThome 鐵人賽

DAY 27
0
Build on Google AI

用 Google AI 生態系 30 天從零打造一個全棧 AI SaaS 服務系列 第 27 篇

Day 27 -【Agentic AI 工具調用】Gemini 1.5 Function Calling 實戰:從內容提煉到自動發送 Email 與建立日曆行程

  • 分享至 

  • xImage
  •  

在昨天的 [Day 26] 中,我們利用 Qdrant 向量資料庫 + Gemini Embedding API,打造了 Multi-Modal Hybrid RAG 跨資產混合檢索系統,讓 OmniVibe AI 能夠精準跨檔案比對影音與文檔精華。

然而,至今為止我們的 AI 系統依然屬於「被動輸出型(Passive AI)」——它能給出完美的摘要、爆款貼文與短影音腳本,但後續的「執行動作」仍需使用者手動複製貼上。例如:

  • 提煉完會議記錄後,使用者要自己開 GMail 寄信給團隊。
  • 解析完影片中的活動/線上研討會時間後,使用者要手動開 Google Calendar 新增行程。

今天,我們將引進 Gemini 1.5 專為自主代理(Agentic Workflow)設計的核心能力 —— Function Calling(函式調用 / 工具使用)!我們將賦予 Gemini 1.5 自動識別意圖並調用外部 API 的能力,實現從「內容提煉」到「自動寄送摘要 Email」與「自動建立 Google 日曆行程」的完整自主 Agent 工作流!


🤖 Agentic AI 工作流架構設計 (Function Calling Loop)

在 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. 「已為您完成影片提煉!郵件已順利寄出,日曆行程也已自動建立完成!🎉」


🛠️ 第一步:宣告 Agent 可使用的工具 (Tool Declarations)

我們使用 @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'],
  },
};


⚡ 第二步:實作後端實際工具執行函數 (Tool Executors)

當 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}`,
  };
}


🧠 第三步:建構 Agentic Function Calling 迴圈 API (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 日曆中。」

🤖 後端 Terminal 日誌與執行結果:

[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 作最終回覆...

📱 前端 Dashboard 最終獲得的 Agent 回覆:

「已為您完成會議錄音的精華提煉!郵件已順利寄送給 clay@example.com。同時,我已為您在 Google 日曆中新增了【10/15 下午 2:00 - 3:00 需求對齊會議】的行程。您隨時可以點擊查看!」


🎯 總結與明日預告

今天我們成功讓 OmniVibe AI 從單純的「回答者」跨越為能採取行動的「自主 Agent」:

  1. 掌握了 Gemini 1.5 原生 Function Calling 的宣告規範與執行迴圈架構。
  2. 實作了 SendGrid Email 發送 與 Google Calendar 日曆行程建立 的實體執行工具。
  3. 實現了使用者只需下達一段複合自然語言指令,Agent 即可自主規劃並一次調用多個外部 API 完成複雜工作流!

截至今天,我們已經涵蓋了 Full-stack AI SaaS 的架構、介面、檔案管線、串流、資料庫、金流、護欄、評測、快取、跨平台外掛、非同步任務、向量 RAG 與 Function Calling Agent。

👉 明天(Day 28),我們將進入【系統整體架構回顧與效能防禦】:全棧架構總覽、API Rate Limiting 限流防禦與災難復原演練!看我們如何為營運做出最後的穩定性武裝!

我們明天見!🔥


上一篇
Day 26 -【進階檢索與向量檢索】Qdrant + Gemini Embedding 打造跨資產 Multi-Modal Hybrid RAG 混合檢索系統
系列文
用 Google AI 生態系 30 天從零打造一個全棧 AI SaaS 服務 共 27 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言