iT邦幫忙

2026 iThome 鐵人賽

DAY 12
0
Build on Google AI

30 天玩轉 Google AI 全家桶:初學者的隨身 Coding 助理養成記系列 第 12 篇

Day 12:讓 AI 連接外部世界:Gemini Function Calling 極簡體驗

  • 分享至 

  • xImage
  •  

前幾天,我們一路把 Gemini 的回答變得越來越「像程式」。

Day 10,我們讓 Gemini 不再隨便輸出一大段文字,而是要求它回傳固定格式的 JSON。

Day 11,再往前一步,用 Zod 驗證這份資料,確保欄位與型別真的符合我們的預期。

做到這裡,DevPulse 已經可以比較可靠地「讀懂 Gemini 回傳的資料」。

但還有一個問題。

如果我問 Gemini:

台灣現在幾點?

Gemini 就算知道怎麼回答,也不代表它真的能直接取得我電腦目前的時間。

這時候,就需要今天的主角:

Function Calling。


一、Function Calling 到底是什麼?

先不要被名字嚇到。

Function Calling 可以先理解成:

讓 Gemini 判斷「現在需不需要使用某個工具」。

例如我們在程式裡準備一個函式:

getLocalTime("Asia/Taipei")

這個函式真的可以取得目前時間。

但 Gemini 並不會直接執行這段 JavaScript。

它真正做的事情比較像是:

使用者
  ↓
「台灣現在幾點?」
  ↓
Gemini
  ↓
我要呼叫 get_local_time
  ↓
Node.js 執行函式
  ↓
取得目前時間
  ↓
把結果交回 Gemini
  ↓
Gemini 整理成自然語言回答

Google 官方目前的 Function Calling 流程也是這樣設計:模型先產生 function_call,由應用程式執行實際函式,再把 function_result 傳回模型。

所以今天其實不是在教 Gemini「怎麼執行程式」。

而是在教 Gemini:

什麼時候該請我的程式幫它做事情。


二、今天的實驗:讓 Gemini 查詢台灣時間

這次我們故意不用天氣 API、股票 API 或其他第三方服務。

只做一個非常簡單的工具:

get_local_time

輸入:

Asia/Taipei

輸出:

目前的台灣時間

原因很簡單。

今天真正想學的不是「怎麼串第三方 API」,而是:

Gemini 怎麼提出工具呼叫,以及我們的 Node.js 怎麼接住它。

所以先把實驗縮到最小。


三、建立 Day 12 專案

如果延續前幾天的 Node.js 專案,可以直接建立一個新的資料夾:

mkdir day12-function-calling
cd day12-function-calling

npm init -y
npm install @google/genai dotenv
npm pkg set type=module
npm pkg set scripts.start="node index.js"

完成後,建立:

day12-function-calling/
├── .env
├── .gitignore
├── index.js
└── package.json

.env

GEMINI_API_KEY=你的_Gemini_API_Key

.gitignore

node_modules/
.env

這裡延續 Day 2~Day 8 的安全觀念:API Key 不直接寫進程式碼,也不要提交到 GitHub。


四、先宣告一個「工具」

接下來建立 index.js:

import "dotenv/config";
import { GoogleGenAI } from "@google/genai";

const MODEL = "gemini-3.8-flash";

const ai = new GoogleGenAI({});

接著告訴 Gemini:

「我有一個叫做 get_local_time 的工具。」

const getLocalTimeTool = {
  type: "function",
  name: "get_local_time",
  description: "取得指定 IANA 時區目前的日期與時間。",
  parameters: {
    type: "object",
    properties: {
      timezone: {
        type: "string",
        description: "IANA 時區,例如 Asia/Taipei。",
      },
    },
    required: ["timezone"],
  },
};

這裡會看到幾個熟悉的欄位:

name
description
parameters

是不是有點像昨天的 Schema?

沒錯。

因為我們必須先告訴 Gemini:

工具名稱:
get_local_time

它能做什麼?
取得指定時區的目前時間

需要什麼參數?
timezone,而且必須是字串

Google 官方目前的 Function Calling 寫法,也是透過 type: "function"、name、description 和 parameters 描述工具介面。


五、真的把 JavaScript 函式寫出來

接下來,才是我們自己真正負責的部分。

function getLocalTime(timezone) {
  try {
    const formatter = new Intl.DateTimeFormat("zh-TW", {
      timeZone: timezone,
      dateStyle: "full",
      timeStyle: "medium",
      hour12: false,
    });

    return {
      success: true,
      timezone,
      datetime: formatter.format(new Date()),
    };
  } catch (error) {
    return {
      success: false,
      timezone,
      error: `無效的時區:${timezone}`,
    };
  }
}

這個函式完全不知道 Gemini 是誰。

它只有一個工作:

輸入時區
↓
取得目前時間
↓
回傳結果

例如:

getLocalTime("Asia/Taipei");

就可能得到:

{
  "success": true,
  "timezone": "Asia/Taipei",
  "datetime": "2026/9/25 星期五 20:10:30"
}

這裡可以先記住一個非常重要的觀念:

Gemini 負責「決定要不要使用工具」。

真正執行工具的工作,還是在我們自己的程式裡。


六、完整跑一次 Function Calling

前面的程式各自看起來都很簡單。

現在把它們真的串起來。

先讓 Gemini 收到:

台灣現在幾點?

然後提供 get_local_time 這個工具。

const interaction = await ai.interactions.create({
  model: MODEL,
  input: "台灣現在幾點?",
  tools: [getLocalTimeTool],
  generation_config: {
    tool_choice: {
      allowed_tools: {
        mode: "any",
        tools: ["get_local_time"],
      },
    },
  },
});

這裡的 tool_choice 是這次 Demo 的一個小技巧。

在實際應用裡,可以讓模型自己判斷要不要呼叫工具。

但我們今天是在做教學實驗,因此希望每次執行都能穩定看到 Function Calling 流程,所以直接要求這次使用 get_local_time。Google 官方目前也提供 auto、any、none 等工具選擇模式。


七、看看 Gemini 到底想呼叫什麼

Gemini 不會直接幫我們執行 JavaScript。

它會回傳一個 function_call。

const functionCall = interaction.steps.find(
  (step) => step.type === "function_call"
);

if (!functionCall) {
  throw new Error("Gemini 沒有產生 function_call");
}

console.log("Gemini 要呼叫:", functionCall.name);
console.log("參數:", functionCall.arguments);

執行後可能看到:

Gemini 要呼叫: get_local_time
參數: { timezone: 'Asia/Taipei' }

這就是 Function Calling 最重要的瞬間。

Gemini 並沒有直接去執行:

getLocalTime("Asia/Taipei");

它只是告訴我們:

「我判斷這個問題需要 get_local_time,而且我要傳入 Asia/Taipei。」

接下來怎麼做?

由我們的 Node.js 決定。


八、Node.js 真的執行 Function

接收到 function_call 之後:

let toolResult;

if (functionCall.name === "get_local_time") {
  const timezone =
    functionCall.arguments?.timezone || "Asia/Taipei";

  toolResult = getLocalTime(timezone);
} else {
  throw new Error(
    `未知的 function:${functionCall.name}`
  );
}

console.log("工具執行結果:");
console.log(JSON.stringify(toolResult, null, 2));

現在才真正執行:

getLocalTime("Asia/Taipei");

也就是:

Gemini
  ↓
「我要 get_local_time」
  ↓
Node.js
  ↓
真的執行 getLocalTime()

這個差異非常重要。

Function Calling 並不是把你的 JavaScript 函式丟給 Gemini 執行。

而是:

Gemini 提出工具請求
        ↓
你的程式接收
        ↓
你的程式決定是否執行
        ↓
你的程式取得結果

這也是為什麼 Function Calling 可以拿來連接資料庫、內部 API、檔案系統,甚至是你自己寫的各種工具。


九、最後一步:把結果交回 Gemini

現在時間已經查到了。

但事情還沒完成。

因為 Gemini 還不知道:

{
  "success": true,
  "timezone": "Asia/Taipei",
  "datetime": "2026/9/25 星期五 20:10:30"
}

所以我們再把工具結果交回 Gemini。

const finalInteraction = await ai.interactions.create({
  model: MODEL,
  previous_interaction_id: interaction.id,
  tools: [getLocalTimeTool],
  input: [
    {
      type: "function_result",
      name: functionCall.name,
      call_id: functionCall.id,
      result: [
        {
          type: "text",
          text: JSON.stringify(toolResult),
        },
      ],
    },
  ],
});

最後:

console.log("\nGemini 最終回答:");
console.log(finalInteraction.output_text);

Google 官方目前的 Interactions API 範例,也是透過 previous_interaction_id 延續前一次互動,並將 function_result 傳回模型。


十、執行看看

完成 .env 與 index.js 之後:

npm start

可以看到類似:

================================
 Day 12 - Function Calling
================================

使用者:台灣現在幾點?

Gemini 要呼叫:
Function:get_local_time
Arguments:{"timezone":"Asia/Taipei"}

Node.js 正在執行工具...
timezone = Asia/Taipei

工具執行結果:
{
  "success": true,
  "timezone": "Asia/Taipei",
  "datetime": "2026/9/25 星期五 20:10:30"
}

Gemini 最終回答:
台灣目前是晚上 8 點 10 分左右。

時間會依照實際執行時間而不同。

這次最值得觀察的,其實不是最後那一句:

台灣目前是晚上 8 點 10 分左右。

真正重要的是前面的:

Gemini 要呼叫:
Function:get_local_time
Arguments:{"timezone":"Asia/Taipei"}

以及:

Node.js 正在執行工具...

因為這兩段終於讓我們看到:

AI 和程式之間真的開始「分工」了。


十一、一次看懂 Function Calling

把剛才的流程重新畫一次:

使用者
  │
  │ 台灣現在幾點?
  ▼
Gemini
  │
  │ function_call
  │ get_local_time
  │ timezone = Asia/Taipei
  ▼
Node.js
  │
  │ 執行 getLocalTime()
  ▼
取得目前時間
  │
  │ function_result
  ▼
Gemini
  │
  │ 整理結果
  ▼
自然語言回答

可以把今天的三個角色簡化成:

Gemini

負責:

理解使用者需求
↓
判斷要使用哪個工具
↓
提出 function_call

Node.js

負責:

接收 function_call
↓
真正執行 Function
↓
取得真實世界的資料

Gemini

最後負責:

理解 function_result
↓
整理成使用者看得懂的回答

也就是:

Gemini
  ↓
「我想做什麼?」

你的程式
  ↓
「真的去做。」

Gemini
  ↓
「把結果說清楚。」

這就是今天最重要的觀念。


十三、今天為什麼不用天氣 API?

其實今天完全可以直接做:

get_weather()

甚至可以讓 Gemini 幫我們查:

台北現在幾度?

但我刻意沒有這麼做。

因為一旦加入第三方天氣 API,就會多出:

  • API Key
  • 網路請求
  • 第三方服務
  • API 格式
  • 錯誤處理

這些東西很容易讓初學者把注意力放到:

「天氣 API 怎麼串?」

而不是:

「Function Calling 到底怎麼運作?」

所以今天故意選擇:

get_local_time()

它不需要任何第三方服務。

只靠 Node.js 本身,就可以把 Function Calling 的整個流程跑完。

先把觀念做小,再慢慢把功能做大。

這也是這個系列一直採用的方式。


十三、今天和昨天有什麼不同?

Day 11:

Gemini
  ↓
Structured Output
  ↓
Zod
  ↓
確認「資料長什麼樣子」

Day 12:

使用者需求
  ↓
Gemini
  ↓
Function Calling
  ↓
決定「我要做什麼」
  ↓
Node.js 執行
  ↓
回傳結果

所以可以把這兩天理解成:

Day 11 是讓 AI 的「資料」可靠。

Day 12 是讓 AI 的「行動」開始有出口。

這是一個很重要的進步。

昨天的 Gemini 比較像:

你問問題
↓
它產生資料
↓
你的程式驗證資料

今天開始變成:

你問問題
↓
Gemini 判斷需要什麼工具
↓
你的程式真的去做
↓
結果再交回 Gemini

DevPulse 也因此開始從單純的「AI 問答小工具」,慢慢變成一個可以與外部世界互動的程式。

而這件事情,也開始替後面要學的 Agent、AI Workflow 打基礎。


今日學習踩坑小記

今天最大的觀念轉變,是第一次發現:

AI 不一定要直接回答問題,它也可以先決定「要叫哪個工具來幫忙」。

而 Function Calling 最值得記住的,也不是某一個 API 參數。

而是這條流程:

想做什麼
↓
提出工具呼叫
↓
程式真的執行
↓
拿結果回來
↓
AI 再回答

昨天我們讓 AI 的輸出變得更可靠。

今天,終於讓 AI 開始有機會「做事情」。

原來 AI 應用程式真正強大的地方,不一定是模型自己知道多少,而是我們可以提供什麼工具給它。

下一篇,換個方向。

如果兩段程式碼內容完全不同,但其實是在解決非常接近的問題,Gemini 能不能看出它們的「語意相似度」?

Day 13:

Embedding。


上一篇
Day 11:讓格式更可靠:用 Zod 幫 Gemini 回應加上一道型別防線
系列文
30 天玩轉 Google AI 全家桶:初學者的隨身 Coding 助理養成記 共 12 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言