本文 AI 產出,尚未審核
LangChain 教學:自訂工具 Class 與回呼(Callbacks)
簡介
在 LangChain 的 Tools(工具)系統 中,工具是讓 LLM(大型語言模型)執行外部動作的橋樑,例如查詢資料庫、呼叫 API、或是觸發自訂程式邏輯。預設提供的工具雖然已經相當好用,但在真實專案裡,我們常常需要 自訂工具,以符合業務需求或整合既有系統。
此外,回呼(Callback) 機制讓開發者可以在工具執行的前後插入自訂程式碼,實作日誌、計時、錯誤處理或是動態調整參數。掌握工具與回呼的設計,等於掌握了將 LLM 與外部資源安全、彈性地結合的關鍵。
本篇文章將從概念說明、實作範例到最佳實踐,完整帶你打造 自訂工具 Class 並配合 回呼 使用,適合剛入門 LangChain 的新手,也能為中階開發者提供可直接上線的參考範例。
核心概念
1. LangChain 中的「工具」是什麼?
- 工具 (Tool):一段可被 LLM 呼叫的程式碼,通常遵守
Tool介面,至少包含name、description與run(input: string): Promise<string>三個屬性/方法。 - 作用:把自然語言指令轉換成具體的程式行為,讓模型不必直接寫程式碼,而是透過「工具」完成任務。
舉例:
SearchTool讓模型可以在網路上搜尋關鍵字;CalculatorTool讓模型執行算術運算。
2. 為什麼需要自訂工具?
- 業務需求:例如公司內部的 ERP 系統、CRM API、或是特殊的資料清理流程,這些都不是 LangChain 預設的工具。
- 安全與授權:自訂工具可以內建驗證、權限檢查,避免模型直接執行危險指令。
- 彈性擴充:透過自訂工具,我們可以把任何 Node.js / TypeScript 函式包裝成 LLM 可呼叫的介面。
3. 回呼(Callback)機制
LangChain 提供 CallbackManager,允許開發者在工具的 onStart、onSuccess、onError 等階段掛鉤自訂函式。常見用途包括:
- 記錄執行時間與參數
- 監控錯誤並自動重試
- 動態改變工具的行為(例如根據使用者身份切換資料庫)
核心概念:回呼不會改變工具本身的實作,只是「觀察」或「增強」它的執行流程。
程式碼範例
以下範例使用 LangChainJS(Node.js)編寫,示範如何:
- 建立自訂工具 Class
- 為工具掛上回呼
- 在 Chain 中使用自訂工具
註:在實務專案中,請先安裝
langchain套件npm install langchain
1. 基本自訂工具範例:呼叫公司內部 API
// file: MyApiTool.js
import { Tool } from "langchain/tools";
/**
* MyApiTool - 讓 LLM 能呼叫公司內部的 /users/:id API
*/
export class MyApiTool extends Tool {
constructor() {
super();
this.name = "MyApiTool";
this.description = "取得指定使用者的詳細資訊,參數為使用者 ID。";
}
/**
* @param {string} input - 需傳入的使用者 ID
* @returns {Promise<string>}
*/
async _call(input) {
// 假設有一個已封裝好的 HTTP client
const response = await fetch(`https://api.mycompany.com/users/${input}`, {
headers: { Authorization: `Bearer ${process.env.API_TOKEN}` },
});
const data = await response.json();
// 回傳簡化的文字描述,方便 LLM 處理
return `使用者 ${data.name}(${data.email}),職稱 ${data.title}`;
}
}
說明
- 只需要繼承
Tool並實作_call方法。name與description會被 LLM 用來理解工具的用途。- 使用環境變數保護 API 金鑰,提升安全性。
2. 加入回呼:記錄執行時間與錯誤
// file: callbackExample.js
import { CallbackManager } from "langchain/callbacks";
import { MyApiTool } from "./MyApiTool";
// 建立 CallbackManager
const callbackManager = new CallbackManager();
// onStart:工具執行前
callbackManager.addHandler({
async onToolStart(tool, input) {
console.log(`[${new Date().toISOString()}] Tool "${tool.name}" start with input: ${input}`);
// 可放入計時器
tool._startTime = Date.now();
},
// onSuccess:工具成功返回結果
async onToolEnd(tool, output) {
const duration = Date.now() - tool._startTime;
console.log(`[${new Date().toISOString()}] Tool "${tool.name}" succeeded in ${duration}ms`);
console.log(`Output: ${output}`);
},
// onError:工具拋出例外
async onToolError(tool, error) {
console.error(`[${new Date().toISOString()}] Tool "${tool.name}" error:`, error);
},
});
// 把回呼注入工具
const apiTool = new MyApiTool();
apiTool.callbackManager = callbackManager;
// 測試呼叫
(async () => {
const result = await apiTool.run("12345");
console.log("最終結果:", result);
})();
關鍵點
callbackManager可以同時管理多個回呼,且每個工具只要指派callbackManager即可。- 透過
tool._startTime暫存資料,示範如何在不同階段共享狀態。
3. 結合 LLM 與自訂工具:簡易 QA Chain
// file: qaChain.js
import { OpenAI } from "langchain/llms/openai";
import { initializeAgentExecutorWithOptions } from "langchain/agents";
import { MyApiTool } from "./MyApiTool";
import { CallbackManager } from "langchain/callbacks";
const llm = new OpenAI({ temperature: 0 });
const apiTool = new MyApiTool();
// 共享同一個 CallbackManager(可同時監控 LLM 與工具)
const callbackManager = new CallbackManager();
callbackManager.addHandler({
async onLLMStart(llm, prompts) {
console.log("LLM 開始生成回應");
},
async onLLMEnd(llm, response) {
console.log("LLM 完成回應");
},
});
apiTool.callbackManager = callbackManager;
// 建立 Agent(工具驅動的對話代理)
const executor = await initializeAgentExecutorWithOptions(
[apiTool],
llm,
{
agentType: "zero-shot-react-description",
callbackManager,
}
);
// 使用者提問
const userQuestion = "請告訴我 John Doe 的職稱與電子郵件。";
(async () => {
const result = await executor.run(userQuestion);
console.log("最終答案:", result);
})();
說明
initializeAgentExecutorWithOptions會自動根據工具的description生成 React 風格的思考過程(思考 → 呼叫工具 → 回答)。- 透過同一個
callbackManager,我們可以同時觀察 LLM 與工具的執行,達到完整的除錯與監控。
4. 動態參數:根據使用者角色切換不同 API
// file: RoleBasedTool.js
import { Tool } from "langchain/tools";
export class RoleBasedTool extends Tool {
constructor() {
super();
this.name = "RoleBasedTool";
this.description = "根據使用者角色取得對應的資料。輸入格式:<role>::<id>";
}
async _call(input) {
const [role, id] = input.split("::");
// 依角色切換不同的 endpoint
const endpoint = role === "admin"
? `/admin/users/${id}`
: `/users/${id}`;
const response = await fetch(`https://api.mycompany.com${endpoint}`, {
headers: { Authorization: `Bearer ${process.env.API_TOKEN}` },
});
const data = await response.json();
return JSON.stringify(data);
}
}
應用:在回呼的
onToolStart中,我們甚至可以檢查使用者的權限,若不符合則直接拋出錯誤,避免工具被濫用。
5. 重試機制:利用回呼自動重試失敗的 API 呼叫
// file: retryCallback.js
import { CallbackManager } from "langchain/callbacks";
const MAX_RETRY = 3;
const retryHandler = {
async onToolError(tool, error) {
if (!tool._retryCount) tool._retryCount = 0;
if (tool._retryCount < MAX_RETRY) {
tool._retryCount += 1;
console.warn(`工具 ${tool.name} 發生錯誤,嘗試第 ${tool._retryCount} 次重試...`);
// 重新呼叫工具
const lastInput = tool._lastInput;
try {
const output = await tool.run(lastInput);
console.log(`第 ${tool._retryCount} 次重試成功`);
// 觸發成功的回呼
await tool.callbackManager?.handleToolEnd?.(tool, output);
} catch (e) {
await tool.callbackManager?.handleToolError?.(tool, e);
}
} else {
console.error(`工具 ${tool.name} 重試次數已達上限,拋出錯誤`);
}
},
async onToolStart(tool, input) {
tool._lastInput = input; // 記錄最後一次的輸入供重試使用
},
};
export const retryCallbackManager = new CallbackManager();
retryCallbackManager.addHandler(retryHandler);
使用方式
const apiTool = new MyApiTool(); apiTool.callbackManager = retryCallbackManager; // 接下來的呼叫若失敗會自動重試最多 3 次
常見陷阱與最佳實踐
| 常見問題 | 可能原因 | 解決方案 |
|---|---|---|
工具回傳空字串或 null |
_call 沒有正確 return,或 API 回傳非 200 狀態 |
確認 await response.ok,必要時拋出錯誤讓回呼捕捉 |
| 回呼無法觸發 | 工具未設定 callbackManager,或回呼名稱寫錯 (onToolStart vs onToolStart) |
在工具實例化後必須設定 tool.callbackManager = yourManager |
| 回呼中拋出例外導致整個 Chain 中斷 | 回呼本身的程式碼錯誤 | 在回呼內部使用 try/catch 包裹,或在 CallbackManager 中加入 onError 處理 |
| 工具執行時間過長 | 後端 API 延遲、缺少 Timeout 設定 | 使用 AbortController 設定請求超時,或在 onToolStart 記錄開始時間、在 onToolEnd 計算耗時 |
| 安全風險:工具被濫用 | run 直接接受使用者原始字串 |
在 _call 前先 驗證、正規化 輸入,並在回呼中檢查使用者權限 |
最佳實踐
- 明確的
description:LLM 依賴描述來決定何時呼叫工具,描述越精確,誤呼叫的機率越低。 - 分層錯誤處理:在工具內部先捕捉 API 錯誤,然後拋出自訂的
ToolError,讓回呼負責統一記錄與重試。 - 回呼最小化副作用:回呼的職責應該是觀察或增強,避免在回呼裡直接改變工具的業務邏輯,保持程式碼可讀性。
- 統一的
CallbackManager:在大型應用中,將 LLM、工具、Chain 都掛上同一個CallbackManager,方便集中監控與除錯。 - 環境變數與密鑰管理:絕對不要把 API 金鑰硬寫在程式碼,使用
.env或雲端密鑰管理服務(如 AWS Secrets Manager)載入。
實際應用場景
| 場景 | 為什麼需要自訂工具 & 回呼 |
|---|---|
| 客服機器人 | 需要呼叫 CRM 系統查詢客戶訂單,並在每次查詢後記錄操作日誌供稽核。 |
| 內部資料分析平台 | LLM 產生的分析指令必須透過自訂 SQL 執行工具,回呼負責計算查詢耗時並自動寫入監控系統。 |
| 自動化報表產出 | 工具負責從 ERP 抓取月度銷售資料,回呼在成功取得資料後觸發 Slack 通知與檔案上傳。 |
| 安全稽核 | 在金融系統中,工具必須先驗證使用者角色,回呼可即時將異常呼叫寫入 SIEM 系統。 |
| 多語言翻譯服務 | LLM 產生翻譯需求,工具呼叫外部翻譯 API,回呼負責統計每日翻譯字數,避免超額。 |
總結
- 自訂工具 Class 讓 LangChain 能夠無縫整合公司內部或第三方服務,只要遵守
Tool介面即可。 - 回呼(Callback) 是觀察、增強與保護工具執行的核心機制,透過
CallbackManager我們可以在工具前後加入日誌、計時、重試與安全檢查。 - 實作時,務必 提供完整且精確的
description、做好輸入驗證,以及 在回呼中處理錯誤與資源釋放,才能讓 LLM 與外部系統的互動既安全又可靠。 - 透過本文的範例,你已經能夠在自己的專案中 快速打造、監控、重試 並 安全地使用 自訂工具,為 LLM 應用開啟更多可能性。
祝你在 LangChain 的開發旅程中玩得開心,打造出更智慧、更可靠的 AI 服務! 🚀