在前幾篇文章中,我們已經能讓聊天機器人具備多輪對話能力,並透過提示工程與參數控制回應風格。但到目前為止,它仍然只能依靠模型本身的知識來回答問題。一旦使用者提出需要即時或外部資訊的問題,模型往往就會顯得無能為力。
為了突破這個限制,我們可以善用 OpenAI 的 Function Calling 機制。藉由這項功能,模型在判斷有需要時,可以主動呼叫我們定義的外部函式來查詢資料或執行操作,從而讓 AI 從單純的對話角色進化為真正能夠採取行動的智慧助理。
大型語言模型(LLM)雖然能生成自然流暢的文字,並且在許多情境中提供相當有用的回答,但它並不是全知全能的智慧系統。從本質上來說,LLM 只是一個根據既有資料來預測文字的工具,並沒有真正連接到外部世界。在沒有外力介入的情況下,它回答問題時,完全依賴的是訓練過程中所學到的知識,而非即時查詢的結果。
舉個例子,如果我們下達這樣的指令:
你是一位氣象專家,請回答使用者輸入的城市目前天氣狀況。
模型並不會真的去查詢該城市的最新天氣數據,而是根據過去訓練資料中所見過的描述來推測。例如,它可能會回答「基隆經常下雨」或「新竹風很大」。這些描述或許符合印象,但與實際的即時天氣往往有落差。換句話說,模型給出的只是基於經驗的猜測,而不是可靠的即時資訊。
這樣的限制會帶來兩個主要問題:
在日常對話中,這些限制或許不會造成太大困擾。但若 AI 的輸出被直接引用在報告、決策或商業流程中,錯誤資訊就可能被放大,甚至造成嚴重的後果。
為了突破這些限制,OpenAI 提供了 Function Calling 機制。它讓語言模型不再只是單純的文字產生器,而能夠在需要時主動請求系統執行某個功能,例如查天氣、搜尋資料、呼叫外部 API,並根據回傳結果給出更完整且可靠的回答。
舉例來說,當使用者詢問天氣時,模型若判斷必須透過外部資訊才能正確回答,就會輸出一段結構化的 JSON,描述要呼叫的工具與對應參數,例如:
{
"tool_calls": [
{
"id": "call_abc123",
"type": "function",
"function": {
"name": "getWeather",
"arguments": "{ \"location\": \"Taipei\" }"
}
}
]
}
這段 JSON 表示模型希望呼叫名為 getWeather
的工具,並傳入參數 { "location": "Taipei" }
。應用程式收到這個請求後,就能執行對應的函式(例如實際查詢氣象 API),取得結果後再回傳給模型。最後,模型會根據真實資料整理並輸出自然語言回覆給使用者。
整個流程可以拆解為三個步驟:
透過這樣的機制,模型不再需要「假裝知道答案」,而是能主動查詢、取得正確資料,再進行整合後回覆。這不僅降低了資料過時與幻覺的問題,也讓 AI 助理在真實應用情境中更加實用與可信。
要讓模型能正確使用工具,我們必須先告訴它有哪些功能,以及該如何使用。這就像先準備好一份「工具清單」與「使用說明書」,模型才能在需要時主動挑選並呼叫合適的工具。
整體流程如下圖所示:
在 OpenAI API 中,工具清單透過 tools
參數傳入。每個工具需要包含三個核心資訊:
name
:模型依名稱呼叫對應的函式description
:描述在什麼情況下應使用這個工具parameters
:使用 JSON Schema 格式,定義所需欄位、型別與說明以下示範定義一個 getWeather
工具:
import OpenAI from "openai";
const client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
const tools = [
{
type: "function",
function: {
name: "getWeather", // 工具名稱
description: "取得指定城市的天氣資訊", // 工具用途
parameters: {
type: "object", // 接收參數型別
properties: {
location: {
type: "string",
description: "城市名稱,例如 Taipei",
},
},
required: ["location"], // 必填參數
},
},
},
];
這段程式碼定義了一個名為 getWeather
的工具,使用者只要輸入 location
,模型就能透過它查詢當地的天氣資訊。
定義好工具後,就能將 tools
傳進 API 請求:
const response = await client.chat.completions.create({
model: "gpt-4o-mini",
messages: [{ role: "user", content: "台北今天天氣如何?" }],
tools, // 告訴模型有哪些工具可用
});
console.log(JSON.stringify(response.choices[0].message, null, 2));
此時,模型可能會輸出一段 tool_calls
工具呼叫請求:
{
"tool_calls": [
{
"id": "call_abc123",
"type": "function",
"function": {
"name": "getWeather",
"arguments": "{ \"location\": \"Taipei\" }"
}
}
]
}
這表示模型希望呼叫 getWeather
工具,並傳入參數 location: "Taipei"
。
接下來,應用程式要攔截這段工具請求,執行對應邏輯,並將結果回傳給模型。
假設我們有一個模擬的 getWeather
函式:
// 模擬天氣查詢
async function getWeather(location) {
return `今天 ${location} 天氣晴朗,氣溫約 25°C`;
}
整合流程如下:
const message = response.choices[0].message;
if (message.tool_calls) {
const toolCall = message.tool_calls[0];
const args = JSON.parse(toolCall.function.arguments);
// 執行工具邏輯
const result = await getWeather(args.location);
// 回傳執行結果給模型
const secondResponse = await client.chat.completions.create({
model: "gpt-4o-mini",
messages: [
{ role: "user", content: "台北今天天氣如何?" },
message, // 模型輸出的 tool_call
{
role: "tool",
tool_call_id: toolCall.id, // 對應工具呼叫 ID
content: result, // 工具查詢結果
},
],
});
console.log("AI 回覆:", secondResponse.choices[0].message.content);
}
在這裡,我們把工具的輸出結果以 role: "tool"
的形式回傳給模型,並透過 tool_call_id
對應到原本的呼叫請求。接著,模型就會基於真實資料整理出最終的自然語言回覆。
最後,模型就會依據真實查詢結果,生成自然語言回應,例如:
AI 回覆:台北今天的天氣是晴朗,氣溫大約 25°C,適合外出活動。
透過這樣的流程,模型能完成 「判斷需求 → 呼叫工具 → 接收結果 → 整合回覆」 的閉環,不再只是「猜答案」,而是能真正查詢並提供可靠資訊。
在前面我們已經完成一個基礎的 CLI Chatbot,這次要替它加上 Function Calling 的能力,讓它能查詢「指定地點的目前時間」。這個例子雖然簡單,但能完整展示工具定義、模型調用工具、以及回傳結果給模型的流程。
在使用 Function Calling 時,第一步就是定義工具,告訴模型有哪些功能可用,以及應該如何使用。接著,還需要準備一個對應的實作函式,確保當模型真的發出工具呼叫時,應用程式能執行邏輯並回傳結果。
以下範例定義了一個 getCurrentTime
工具,用來查詢指定時區的當前時間:
// src/tools.ts
import type { ChatCompletionTool } from 'openai/resources';
export const tools: ChatCompletionTool[] = [
{
type: 'function',
function: {
name: 'getCurrentTime',
description: '取得指定時區的當前時間',
parameters: {
type: 'object',
properties: {
timeZone: {
type: 'string',
description: 'IANA 時區名稱,例如 Asia/Taipei, America/New_York',
},
},
required: ['timeZone'],
},
},
},
];
export const toolFunctions: Record<string, any> = {
getCurrentTime: async ({ timeZone }: { timeZone: string }) => {
const date = new Date();
return date.toLocaleString('zh-TW', { timeZone });
},
};
這裡有兩個關鍵部分:
tools
:定義工具的結構,包括名稱、用途描述與參數規格。這份清單會傳給 OpenAI API,讓模型知道有哪些工具可用。toolFunctions
:實際的函式實作。當模型輸出工具呼叫時,應用程式會根據工具名稱找到對應函式,執行並取得結果。在這個範例中,我們建立了一個名為 getCurrentTime
的工具,它需要一個必填參數 timeZone
,並透過 JavaScript 的 Date.toLocaleString
方法回傳對應時區的時間。
接著,我們需要修改 CLI Chatbot 的主程式,讓它具備處理工具呼叫與回傳結果的能力。
以下是完整的程式碼範例:
// src/index.ts
import 'dotenv/config';
import readline from 'readline';
import yargs, { Arguments } from 'yargs';
import { hideBin } from 'yargs/helpers';
import { OpenAI } from 'openai';
import { roles } from './prompts';
import { tools, toolFunctions } from './tools';
interface Argv {
role: keyof typeof roles;
temperature: number;
top_p: number;
}
const argv = yargs(hideBin(process.argv))
.option('role', {
alias: 'r',
type: 'string',
choices: Object.keys(roles) as (keyof typeof roles)[],
default: 'default',
description: '指定助理角色',
})
.option('temperature', {
alias: 't',
type: 'number',
description: '控制模型的創造力 (0 ~ 2)',
default: 1,
})
.option('top_p', {
alias: 'tp',
type: 'number',
description: '限制模型的選詞範圍 (0 ~ 1)',
default: 1,
})
.help()
.parseSync();
async function main(argv: Arguments<Argv>) {
const openai = new OpenAI();
const messages = [...roles[argv.role]];
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
console.log('GPT Chatbot 已啟動,輸入訊息開始對話(按 Ctrl+C 離開)。\n');
rl.setPrompt('> ');
rl.prompt();
rl.on('line', async (input) => {
messages.push({ role: 'user', content: input });
try {
const response = await openai.chat.completions.create({
model: 'gpt-4o-mini',
messages,
tools,
tool_choice: 'auto',
});
const message = response.choices[0].message;
if (message.tool_calls) {
const toolCall = message.tool_calls[0];
const fn = toolFunctions[toolCall.function.name];
const args = JSON.parse(toolCall.function.arguments);
const result = await fn(args);
messages.push(message);
messages.push({
role: 'tool',
tool_call_id: toolCall.id,
content: result,
});
}
const stream = await openai.chat.completions.create({
model: 'gpt-4o-mini',
stream: true,
temperature: argv.temperature,
top_p: argv.top_p,
messages,
});
let reply = '';
process.stdout.write('\n');
for await (const chunk of stream) {
const content = chunk.choices[0]?.delta?.content || '';
process.stdout.write(content);
reply += content;
}
process.stdout.write('\n\n');
messages.push({ role: 'assistant', content: reply });
} catch (err) {
console.error(err);
}
rl.prompt();
});
}
main(argv);
這段程式的核心流程可以拆解為以下步驟:
tool_calls
。tool_calls
,執行對應的函式並取得結果。role: "tool"
的訊息形式,把執行結果交回給模型。這樣一來,我們的 CLI Chatbot 不僅能回答問題,還具備主動呼叫工具、整合外部資訊的能力。
完成程式後,就可以啟動 CLI Chatbot:
npm run dev
在終端機中輸入:
現在台北時間?
模型會自動判斷這個問題需要呼叫工具,接著由程式執行 getCurrentTime
函式,並將結果回傳給模型,最後輸出自然語言回覆。
範例輸出可能會是:
現在台北時間是 2025/09/06 上午 08:00。
透過這樣的設計,Chatbot 不僅能單純回答問題,還能主動判斷需求、呼叫工具並整合外部資訊,功能上已經邁入初階 AI Agent 的範疇。
今天我們介紹了如何透過 OpenAI 提供的 Function Calling 機制,讓大型語言模型不只是回答問題,而是能真正「做事」:
tools
參數,用來定義可用工具的名稱、用途與 JSON Schema 格式的參數。tool_calls
,執行外部 API 或函式,並再將結果回饋給模型,以生成完整回應。有了這套機制,AI 助理就能不再局限於訓練時的知識,而是具備真正行動力,讓回覆更即時、更可靠,往智慧助理的方向更進一步。