LangChain 教學:Agents – Tool Calling Agent(支援 OpenAI、Anthropic 等)
簡介
在「大型語言模型」(LLM) 越來越普及的今天,單純的文字產出已不夠,讓模型能夠主動呼叫外部工具(如搜尋 API、資料庫、計算程式)成為提升應用價值的關鍵。LangChain 透過 Agents 機制,將 LLM 變成「自主智能體」:模型根據使用者指令自行決策、選擇合適的 Tool 並執行,最後把結果回饋給使用者。
本篇文章聚焦於 Tool Calling Agent,說明它如何在 LangChain(支援 OpenAI、Anthropic、Claude 等)中配置與使用,並提供完整的程式碼範例、常見陷阱與最佳實踐,讓你能快速在自己的專案裡加入「可呼叫工具」的智能體。
核心概念
1. Agent 與 Tool 的關係
- Agent:負責解析使用者輸入、規劃行動、呼叫 Tool,並把結果回傳。
- Tool:實際執行特定功能的程式單元,例如
search_web,calculate,fetch_from_db。 - Tool Calling:LLM 產生的「呼叫指令」會被 Agent 解析,轉換成對應 Tool 的函式呼叫。
重點:在 LangChain 中,Tool 必須遵守「
name、description、func」的介面,Agent 才能正確辨識與執行。
2. 支援的模型
- OpenAI:
gpt-3.5-turbo-0613、gpt-4-0613等支援function calling的模型。 - Anthropic:Claude 3 系列透過
tool_use方式呼叫工具。 - 其他:如 Azure OpenAI、Google Gemini 亦可透過類似的
function calling介面整合。
3. LangChainJS 與 LangChain Python 的差異
本教學採用 LangChainJS(JavaScript/TypeScript)示範,因為它在前端與 Node.js 環境都能直接使用,且語法較為直觀。若你習慣 Python,只需要把 @langchain/core、@langchain/openai 換成相對應的 Python 套件即可。
程式碼範例
以下範例會一步步建立一個 Tool Calling Agent,具備三個常見工具:WebSearch、Calculator、TodoList。程式碼使用 JavaScript,搭配 LangChainJS v0.2+(請先 npm i @langchain/core @langchain/openai @langchain/agents)。
1. 基本設定與工具定義
// 1️⃣ 引入必要套件
import { ChatOpenAI } from "@langchain/openai";
import { StructuredTool } from "@langchain/tools";
import { initializeAgentExecutorWithOptions } from "@langchain/agents";
// 2️⃣ 建立 OpenAI LLM(支援 function calling)
const llm = new ChatOpenAI({
modelName: "gpt-4-0613", // 必須是支援 function calling 的模型
temperature: 0,
});
// 3️⃣ 定義三個工具 ----------------------------------------------------
// 3-1. 網路搜尋工具
class WebSearchTool extends StructuredTool {
name = "web_search";
description = "使用 Bing 或 Google 搜尋關鍵字,回傳前 3 個結果的標題與連結。";
async _call({ query }) {
// 這裡僅示意,實際可呼叫真實搜尋 API
const fakeResults = [
{ title: "LangChain 官方文件", link: "https://langchain.com/docs" },
{ title: "LLM Tool Calling 教學", link: "https://example.com/tool-calling" },
{ title: "OpenAI Function Calling 範例", link: "https://openai.com/function-calling" },
];
return JSON.stringify(fakeResults, null, 2);
}
// 設定參數 schema,讓 LLM 知道需要什麼欄位
static schema = {
type: "object",
properties: {
query: { type: "string", description: "使用者想要搜尋的文字" },
},
required: ["query"],
};
}
// 3-2. 計算機工具
class CalculatorTool extends StructuredTool {
name = "calculator";
description = "執行簡單的數學運算,支援加減乘除與括號。";
async _call({ expression }) {
// 使用內建 eval 前先做安全檢查
if (!/^[0-9+\-*/().\s]+$/.test(expression)) {
throw new Error("不允許的字元");
}
// eslint-disable-next-line no-eval
const result = eval(expression);
return `${expression} = ${result}`;
}
static schema = {
type: "object",
properties: {
expression: {
type: "string",
description: "需要計算的數學式,例如 12 * (3 + 4)",
},
},
required: ["expression"],
};
}
// 3-3. 待辦清單工具(示範 stateful tool)
class TodoListTool extends StructuredTool {
name = "todo_list";
description = "管理簡易待辦清單,支援新增、列出與刪除項目。";
// 內部狀態(簡易模擬資料庫)
static todos = [];
async _call({ action, item }) {
switch (action) {
case "add":
TodoListTool.todos.push(item);
return `已新增 "${item}" 到待辦清單。`;
case "list":
return TodoListTool.todos.length
? TodoListTool.todos.map((t, i) => `${i + 1}. ${t}`).join("\n")
: "待辦清單目前是空的。";
case "remove":
const idx = TodoListTool.todos.indexOf(item);
if (idx > -1) {
TodoListTool.todos.splice(idx, 1);
return `已移除 "${item}"。`;
}
return `找不到 "${item}",無法移除。`;
default:
return "未知的 action,請使用 add / list / remove。";
}
}
static schema = {
type: "object",
properties: {
action: {
type: "string",
enum: ["add", "list", "remove"],
description: "要執行的動作:add、list、remove",
},
item: {
type: "string",
description: "待辦項目文字(list 時可留空)",
},
},
required: ["action"],
};
}
// 把工具集合起來
const tools = [new WebSearchTool(), new CalculatorTool(), new TodoListTool()];
2. 建立 Agent 並啟動對話
// 4️⃣ 初始化 Agent(使用 ZeroShotAgent,讓 LLM 自行決策呼叫哪個工具)
const agentExecutor = await initializeAgentExecutorWithOptions(tools, llm, {
agentType: "openai-functions", // 針對 OpenAI function calling 的預設類型
verbose: true, // 顯示執行過程,方便除錯
});
// 5️⃣ 測試對話流程
async function runDemo() {
const userQueries = [
"請幫我搜尋 LangChain 的官方文件。",
"把剛剛搜尋結果的第一個連結的網址長度算一下。",
"幫我把 '寫技術部落格' 加入待辦清單,然後列出所有待辦事項。",
];
for (const q of userQueries) {
console.log("\n🗣️ 使用者:", q);
const result = await agentExecutor.invoke({ input: q });
console.log("🤖 Agent 回覆:", result.output);
}
}
runDemo();
執行結果說明
- 第一次詢問觸發
web_search,Agent 把搜尋結果 (JSON) 回傳。 - 第二次詢問 LLM 會解析「第一個連結的網址長度」這個需求,決定先呼叫
web_search取得結果,再呼叫calculator計算字串長度。 - 第三次則使用
todo_list的add與list兩個動作,展示 stateful tool(工具內部保持待辦清單)。
3. 使用 Anthropic Claude 的 Tool Calling
Anthropic 的工具呼叫方式稍有不同,需要把工具資訊包在 tool_use 事件中。以下示範如何把同樣的三個工具搬到 Anthropic:
import { ChatAnthropic } from "@langchain/anthropic";
const anthropicLLM = new ChatAnthropic({
modelName: "claude-3-opus-20240229",
temperature: 0,
});
const anthropicAgent = await initializeAgentExecutorWithOptions(tools, anthropicLLM, {
agentType: "anthropic-tools",
verbose: true,
});
async function demoAnthropic() {
const query = "請幫我計算 (12+8) * 3,然後把結果加入待辦清單。";
const result = await anthropicAgent.invoke({ input: query });
console.log("🤖 Claude 回覆:", result.output);
}
demoAnthropic();
小技巧:Anthropic 目前不支援「同時呼叫多個工具」的鏈結,若需要串接多步驟,請在
tool_use回傳後手動將結果傳回 LLM,形成「迭代」的對話。
4. 加入自訂的 HTTP API Tool
在實務上,常見需求是呼叫公司內部的 RESTful API。下面示範一個簡易的 http_get 工具,利用 node-fetch 完成。
import fetch from "node-fetch";
class HttpGetTool extends StructuredTool {
name = "http_get";
description = "對指定的 URL 進行 GET 請求,回傳 JSON 結果(若非 JSON,回傳文字)。";
async _call({ url }) {
const res = await fetch(url);
const contentType = res.headers.get("content-type") || "";
if (contentType.includes("application/json")) {
return await res.json();
}
return await res.text();
}
static schema = {
type: "object",
properties: {
url: {
type: "string",
format: "uri",
description: "要請求的完整 URL,例如 https://api.example.com/users",
},
},
required: ["url"],
};
}
// 把新工具加入 Agent
const extendedTools = [...tools, new HttpGetTool()];
const extendedAgent = await initializeAgentExecutorWithOptions(extendedTools, llm, {
agentType: "openai-functions",
verbose: true,
});
// 測試呼叫
async function testHttp() {
const q = "請幫我取得 https://jsonplaceholder.typicode.com/todos/1 的內容。";
const result = await extendedAgent.invoke({ input: q });
console.log("📦 HTTP 回傳:", result.output);
}
testHttp();
常見陷阱與最佳實踐
| 陷阱 | 說明 | 解決方式 |
|---|---|---|
| 工具參數 schema 不完整 | LLM 無法正確產生呼叫指令,導致錯誤或缺少參數。 | 務必為每個 StructuredTool 定義完整的 JSON Schema,包含 type、description、required。 |
使用 eval 直接計算 |
會產生安全風險,尤其在公開服務中。 | 使用安全的算術解析器(如 mathjs)或自行實作 whitelist。 |
| Stateful tool 並發衝突 | 多個使用者同時使用同一 Tool 時,內部狀態會互相干擾。 | 將工具實例化為 class instance per session,或使用外部資料庫(Redis、PostgreSQL)儲存狀態。 |
| 模型不支援 function calling | 仍使用舊版 gpt-3.5-turbo 會無法觸發工具呼叫。 |
確認模型版本(-0613 或更新)或改用 Anthropic 的 tool_use。 |
| 工具回傳過大 | LLM 有 token 限制,過長的 JSON 會被截斷。 | 在工具內部 限制回傳長度(只返回前 N 筆、或摘要),或在 Agent 中加入「分段」策略。 |
| 錯誤處理未傳遞給 LLM | 工具拋出例外時,Agent 直接失敗,使用者得不到回饋。 | 捕捉例外並回傳 易讀的錯誤訊息,讓 LLM 能夠說明問題並建議重新輸入。 |
最佳實踐
- 先定義工具再設計對話:先把所有可能需要的 Tool 列出,寫好 Schema,最後再思考使用者可能的需求。
- 使用
verbose: true觀察思考過程:開發階段開啟詳細日誌,可看到 LLM 如何決策呼叫哪個工具,便於調整 Prompt。 - 把工具的副作用最小化:例如寫入 DB 前先檢查權限、做 idempotent 設計,避免因 Agent 重試造成重複寫入。
- 分層測試:單元測試每個 Tool(確保
_call正確),再做整合測試 Agent 的決策流程。 - 使用環境變數管理 API 金鑰:不要把 OpenAI、Anthropic 金鑰硬寫在程式碼裡,使用
.env或雲端 secret 管理服務。
實際應用場景
| 場景 | 需求 | 可能的 Tool 組合 |
|---|---|---|
| 客服聊天機器人 | 為使用者即時查詢訂單狀態、退貨流程、FAQ。 | order_lookup_api、knowledge_base_search、ticket_create |
| 程式開發輔助 | 讓 LLM 幫忙執行單元測試、搜尋 StackOverflow、產生程式碼片段。 | code_runner、web_search、git_repo_browser |
| 財務分析儀表板 | 使用者問「今年第一季的營收成長多少?」 → 需要查 DB、計算、繪圖。 | sql_query_tool、calculator、chart_generator |
| 智慧辦公自動化 | 會議紀要自動整理、行事曆新增、郵件發送。 | meeting_transcript, calendar_add, email_sender |
| 教育平台 | 依照學生提問自動搜尋教材、計算數學題、提供互動練習。 | web_search、calculator、quiz_generator |
實務提示:在設計產品時,先把「使用者最常見的 3-5 個需求」對應到具體的 Tool,避免一次性加入過多功能,導致 LLM 判斷困難或系統維護負擔過重。
總結
Tool Calling Agent 是 LangChain 讓 LLM 從「文字生成」跨越到「主動執行」的關鍵橋樑。透過 StructuredTool 定義明確的介面、JSON Schema 描述參數,配合支援 function calling(OpenAI)或 tool_use(Anthropic)的模型,我們可以快速打造 可搜尋、可計算、可存取內部資源 的智慧型代理人。
本文示範了:
- 基本工具的建立(搜尋、計算、待辦清單)與 Agent 初始化。
- 跨模型使用(OpenAI 與 Anthropic)。
- 自訂 HTTP API 工具,展示如何把公司內部服務納入 Agent。
- 常見陷阱與最佳實踐,協助開發者避免安全、效能與狀態管理問題。
- 實務應用場景,讓你快速定位自己產品的切入點。
掌握這套流程後,你就能在 聊天機器人、智慧客服、資料分析平台 等領域,利用 LLM 的自然語言理解能力與工具的即時執行力,提供使用者真正「能解決問題」的 AI 服務。祝你開發順利,玩得開心! 🚀