iT邦幫忙

2025 iThome 鐵人賽

DAY 12
0
生成式 AI

用 Node.js 打造生成式 AI 應用:從 Prompt 到 Agent 開發實戰系列 第 12

Day 12 - 工具調用設計:使用 LangChain Tool Calling 整合外部功能

  • 分享至 

  • xImage
  •  

經過前幾篇內容,我們已經學會如何利用 LangChain 串接模型、設計提示模板、控制輸出格式,甚至能透過 LCEL 將多個步驟組合成流程。不過,僅依靠 LLM 本身依然存在限制,例如它無法直接存取即時的網路資料、查詢資料庫,或執行程式邏輯。

為了突破這些限制,我們可以在 LangChain 中設計 工具(Tool),並透過 Tool Calling 讓模型能夠動態決定何時使用這些工具。這樣一來,模型就不只是單純的文字生成器,而能真正與外部世界互動,這也是邁向 AI Agent 的關鍵一步。

認識 LangChain 的 Tool

在 LangChain 裡,Tool 就是一個封裝好的功能單元,本質上可以想成是一個帶有說明文件的函式。它負責把外部世界的功能抽象成模型可以理解並調用的形式,讓 LLM 不只是文字生成器,而是具備行動能力的智慧助理。

一個 Tool 通常包含兩個核心元素:

  1. 執行邏輯:一段實際要執行的程式,例如查詢資料庫、呼叫 API、或進行數學運算。
  2. 描述資訊:包含工具的名稱(name)、描述(description)與參數結構(schema)。用於提供給模型理解,讓它能判斷何時需要使用該工具。

以下是一個最簡單的 Tool 定義範例:

import { tool } from '@langchain/core/tools';
import { z } from 'zod';

const echoTool = tool(
  async ({ text }) => {
    // 工具邏輯:回傳輸入內容
    return `你輸入了:${text}`;
  },
  {
    name: 'echo_tool', // 工具名稱(模型會用這個來呼叫)
    description: '回傳輸入的文字內容', // 工具描述(幫助模型判斷何時使用)
    schema: z.object({
      text: z.string().describe('要回傳的文字內容'),
    }),
  }
);

在實際運作中,模型會先閱讀這些描述資訊,並在對話過程中依需求判斷是否需要使用某個 Tool。如果需要,它就會輸出一個「工具呼叫請求」(tool call),其中包含要使用的工具名稱與對應參數。應用程式接手後會執行對應邏輯,最後再把結果回傳給模型,讓模型整理後以自然語言回覆給使用者。

https://ithelp.ithome.com.tw/upload/images/20250912/20150150iBOQBPOVh3.png

如何使用 Tool Calling?

Tool Calling 指的是語言模型在回答問題時,能夠「自主判斷」是否需要呼叫外部工具(Tool),並自動填入所需的輸入參數,以完成特定任務。這讓 LLM 不再只是單純的回答器,而是具備實際執行力的智慧體。

OpenAI API 中,這個功能被稱為 Function Calling;而在 LangChain 中,則進一步做了抽象與封裝,提供統一的介面與多模型支援。換句話說,開發者不必再去處理不同 LLM 的細節差異,就能輕鬆整合多個工具,並快速擴充成更完整的 AI 系統。

要讓 Tool Calling 運作,通常需要經過以下幾個步驟:

  1. 工具建立:使用 tool 函式或裝飾器建立一個 Tool,將功能邏輯與輸入參數結構(schema)封裝起來。
  2. 模型綁定:將 Tool 清單綁定到支援 Tool Calling 的 LLM,讓模型能清楚知道有哪些工具可以使用,以及輸入格式為何。
  3. 模型呼叫:當模型判斷需要額外資訊或必須執行某項任務時,會主動輸出一個工具呼叫請求(tool call),並生成符合 schema 的輸入參數。
  4. 工具執行:應用程式端接收請求後,執行對應的函式,並將結果回傳給模型,由模型整合後再輸出自然語言回覆。

透過這套流程,Tool Calling 成為語言模型連接外部世界的關鍵橋樑,也是建構 AI Agent 時不可或缺的基礎設計。

實作 Tool Calling:如何讓 LLM 使用 Tool

LangChain 提供一套標準化的 Tool Calling 操作流程,讓語言模型可以透過自然語言指令,自主決定是否呼叫外部功能模組。整體流程分為四個步驟:工具建立 → 模型綁定 → 模型呼叫 → 工具執行

工具建立

設計一個 Tool 的推薦做法是使用 tool() 函式,這可以協助你將任意邏輯函式,轉換為符合 LangChain Tool 規範的結構。

以下是一個簡單的 加法工具 範例:

import { z } from 'zod';
import { tool } from '@langchain/core/tools';

export const add = tool(
  ({ a, b }: { a: number; b: number }): number => {
    return a + b;
  },
  {
    name: 'add',
    description: '將兩個數字相加,回傳它們的和。',
    schema: z.object({
      a: z.number().describe('第一個加數'),
      b: z.number().describe('第二個加數'),
    }),
  }
);

在這段程式碼中,我們完成了幾件事:

  • 封裝邏輯函式:把「加法運算」封裝成符合 LangChain 的工具介面。
  • 指定工具名稱與用途namedescription 是模型判斷該工具是否適用的依據。
  • 定義輸入參數結構:透過 zod 定義輸入 schema,LangChain 會自動轉換成 JSON Schema,讓模型知道必須填入哪些參數以及型別限制。

有了這些資訊,模型才能在需要進行數字相加時,正確選擇並呼叫這個 add 工具。

模型綁定

工具設計完成後,下一步就是讓模型「知道它可以使用哪些工具」。這個動作可以透過 bindTools() 方法來完成,它會回傳一個新的模型實例,並附加上工具使用的能力。

以下示範將我們剛才定義的 add 工具綁定到模型:

import { ChatOpenAI } from '@langchain/openai';
import { add } from './tools/add';

const llm = new ChatOpenAI({
  model: 'gpt-4o-mini',
});

const llmWithTools = llm.bindTools([add]);

在這段程式碼中:

  • llm 可以是 OpenAI 的 ChatOpenAI,或任何支援 Tool Calling 的模型。
  • bindTools([add]) 會把我們定義好的 add 工具掛載到模型,讓它在需要時能主動選擇呼叫。

這段程式碼中,model 可以是 OpenAI 的 ChatOpenAI 實例,或任何支援 Tool Calling 的模型。經過綁定後,模型就能自動選擇使用這些工具。

注意:並非所有模型都支援 Tool Calling。以 OpenAI 為例,必須使用 gpt-3.5-turbo-1106 或更新的版本,才能正確啟用並執行這項功能。

模型呼叫

工具綁定後,模型的使用方式與一般情境相同,你依然可以透過 .invoke() 傳入自然語言輸入。不過,當輸入內容與某個工具的用途 語意高度相關 時,模型就會主動產生 Tool 呼叫,並依據工具的描述與參數結構,自動填入正確的參數。

模型是否會觸發 Tool Calling,取決於輸入是否符合工具的用途。例如:

  • 如果輸入明確涉及數字運算,而你提供了 add 這類計算工具,模型就可能選擇使用它。
  • 如果輸入只是閒聊或問候,模型則會直接生成自然語言回應,不會啟動工具呼叫。

不會觸發 Tool 的情境

const result = await llmWithTools.invoke('哈囉,你好嗎?');
console.log(result.content); 
// => '你好!很高興見到你。'

這類單純對話不涉及數字計算,因此模型會直接回覆文字,不會呼叫工具。

觸發 Tool 的情境

const result = await llmWithTools.invoke('請幫我計算 2 加 3 等於多少?');

這類輸入與 add 工具的描述高度吻合,因此模型會決定呼叫工具,並自動填入參數。回傳內容中會包含 tool_calls 欄位,清楚顯示它選擇了哪個工具與對應參數:

console.log(result.tool_calls);
/*
[
  {
    name: 'add',
    args: { a: 2, b: 3 },
    type: 'tool_call',
    id: 'call_xxxxxxxxxxxxxxxxxxxxxxxx'
  }
]
*/

工具是否會被呼叫,完全取決於輸入語意是否與工具的描述與參數格式「對得上」。因此,建議在設計工具時,務必讓名稱簡潔、用途描述明確,才能提升模型正確選擇並使用工具的成功率。

工具執行

在 LangChain 中,每個 Tool 本質上都是一個符合 Runnable 介面的物件,因此可以直接用 .invoke() 來執行,就像呼叫一般函式一樣。

以下範例示範如何手動執行我們定義好的加法工具:

const toolResult = await add.invoke({ a: 2, b: 3 });
console.log(toolResult); 
// => 5

在實際應用中,這個步驟通常會發生在模型產生了 tool_calls 之後。tool_calls 會告訴我們「模型打算呼叫哪個工具」以及「需要填入的參數」。工具的執行必須由應用程式來完成。流程大致如下:

  • 從模型回傳的 tool_calls 中解析工具名稱與參數。
  • 找到對應的工具物件(例如 add)。
  • 使用 .invoke() 執行工具並取得結果。
  • 將工具結果再交還給模型,或直接由系統組合成最終回覆提供給使用者。

換言之,工具執行代表模型「決定要用工具」之後,應用程式負責實際完成任務的階段。這樣的設計讓模型不需要真的會做運算或查資料,而是透過工具取得正確資訊,再以自然語言整合成回應提供給使用者。

實作範例:多工具 Tool Calling 流程

在實際應用中,我們常常希望 AI 不只具備單一能力,而是能同時使用多個工具,並根據使用者的提問,自主判斷該呼叫哪些功能模組。

以下示範一個多工具的範例,讓 OpenAI 模型能在一次對話中同時調用 加法乘法 兩個工具,並整合計算結果後生成最終回應:

import { z } from 'zod';
import { tool } from '@langchain/core/tools';
import { ChatOpenAI } from '@langchain/openai';
import { HumanMessage } from '@langchain/core/messages';

const addTool = tool(
  ({ a, b }: { a: number; b: number }): number => {
    return a + b;
  },  
  {
    name: 'add',
    schema: z.object({ a: z.number(), b: z.number() }),
    description: '計算兩個數字的加法',
  },
);

const multiplyTool = tool(
  ({ a, b }: { a: number; b: number }): number => {
    return a * b;
  },
  {
    name: 'multiply',
    schema: z.object({ a: z.number(), b: z.number() }),
    description: '計算兩個數字的乘法',
  },
);

const llmWithTools = new ChatOpenAI({
  model: 'gpt-4o-mini',
  temperature: 0,
}).bindTools([addTool, multiplyTool]);

const messages = [new HumanMessage('請幫我計算 3 乘以 12,還有 11 加 49。')];
const aiMessage = await llmWithTools.invoke(messages);
messages.push(aiMessage);

const toolsByName = { add: addTool, multiply: multiplyTool };
for (const toolCall of aiMessage.tool_calls) {
  const selectedTool = toolsByName[toolCall.name];
  const toolMessage = await selectedTool.invoke(toolCall);
  messages.push(toolMessage);
}

const response = await llmWithTools.invoke(messages);
console.log(response.content);

以下說明程式執行時的流程:

  • 第一次呼叫模型:使用者輸入問題時,模型根據工具描述與輸入格式,自主判斷需要呼叫 multiplyadd 兩個工具,並在回應中產生 tool_calls
  • 執行工具:應用程式解析 tool_calls,依序執行對應工具,得到 3660,並將結果包裝成訊息加入對話上下文。
  • 再次呼叫模型:將工具結果一併回傳給模型,模型再生成自然語言回覆,例如:
3 乘以 12 的結果是 36,11 加 49 的結果是 60。

透過這樣的流程,LLM 不再只是單純的對話引擎,而是能自主判斷、選擇並呼叫外部功能,讓整個系統具備真正的執行力與擴充性。

Tool Calling 實作的最佳實踐

要讓語言模型能穩定、準確地使用工具,設計方式至關重要。以下整理出幾項 LangChain 官方建議的最佳實踐,能有效提升模型的理解力與工具執行的正確率:

  • 選擇支援 Tool Calling 的模型:並非所有語言模型都具備 Tool Calling 能力。以 OpenAI 為例,需使用 gpt-3.5-turbo-1106 之後的版本,這些模型已針對工具描述與 JSON Schema 理解進行優化,能更精準判斷何時需要調用工具,以及如何填寫正確的參數。
  • 清楚命名與說明工具用途:工具名稱與描述是模型決策的關鍵依據。命名應簡潔且具描述性,避免太抽象或多義的詞彙。例如,multiplycalculate 更具體,而「查天氣」比「查資訊」更清楚。描述文字應直白說明工具的功能與限制,最好包含參數意圖與適用場景。
  • 讓工具保持單一職責:工具功能越單一、越專注,模型越容易理解其用途與格式。例如,「計算乘法」與「計算除法」應拆成兩個工具,而不是一個接收 operation 參數的通用計算器。這能避免模型混淆,也更容易自動產生正確參數格式。
  • 避免一次提供過多工具:若一次綁定的工具數量過多(例如超過 10 個),模型在有限的上下文中進行判斷時容易出現誤判,導致準確率下降。比較理想的做法是根據實際任務需求動態載入所需的工具集合,或者採用分層式的選擇策略:先由模型判斷任務類型(如「數學」、「查詢」、「翻譯」),再從對應的類別中挑選可用工具。

這些原則雖然直觀,但若能在設計初期就落實,將大幅提升 Tool Calling 的穩定性與可維護性,並讓整體系統更容易隨需求成長與演進。

小結

今天我們學會了如何在 LangChain 中使用 Tool Calling,讓模型能夠主動呼叫外部功能,突破單純文字生成的限制:

  • LangChain 的 Tool 是一個封裝好的功能單元,包含執行邏輯與描述資訊,讓模型能理解並決定何時使用。
  • LangChain 的 Tool Calling 流程:工具建立 → 模型綁定 → 模型呼叫 → 工具執行。
  • 工具設計重點:利用 tool() 封裝邏輯、定義清楚的名稱與用途、用 zod 建立輸入 schema。
  • 綁定工具後,模型就能在符合語意的情境下,自動產生 tool_calls 並填入正確參數。
  • 應用程式需解析 tool_calls,執行工具並回傳結果,最後再由模型生成自然語言回覆。
  • 在實作 Tool Calling 時,應選用支援 Tool Calling 的模型,並確保工具命名清楚、功能保持單一,同時避免一次綁定過多工具。

透過 Tool Calling,LLM 不再只是回答器,而能真正與外部世界互動,這正是邁向 AI Agent 的關鍵一步。


上一篇
Day 11 - 流程鏈組合應用:使用 LCEL 打造靈活應用邏輯
下一篇
Day 13 - LangChain 整合實戰:打造具網路搜尋能力的 AI 部落格寫手
系列文
用 Node.js 打造生成式 AI 應用:從 Prompt 到 Agent 開發實戰14
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言