本文 AI 產出,尚未審核

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 必須遵守「namedescriptionfunc」的介面,Agent 才能正確辨識與執行。

2. 支援的模型

  • OpenAIgpt-3.5-turbo-0613gpt-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,具備三個常見工具:WebSearchCalculatorTodoList。程式碼使用 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();

執行結果說明

  1. 第一次詢問觸發 web_search,Agent 把搜尋結果 (JSON) 回傳。
  2. 第二次詢問 LLM 會解析「第一個連結的網址長度」這個需求,決定先呼叫 web_search 取得結果,再呼叫 calculator 計算字串長度。
  3. 第三次則使用 todo_listaddlist 兩個動作,展示 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,包含 typedescriptionrequired
使用 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 能夠說明問題並建議重新輸入。

最佳實踐

  1. 先定義工具再設計對話:先把所有可能需要的 Tool 列出,寫好 Schema,最後再思考使用者可能的需求。
  2. 使用 verbose: true 觀察思考過程:開發階段開啟詳細日誌,可看到 LLM 如何決策呼叫哪個工具,便於調整 Prompt。
  3. 把工具的副作用最小化:例如寫入 DB 前先檢查權限、做 idempotent 設計,避免因 Agent 重試造成重複寫入。
  4. 分層測試:單元測試每個 Tool(確保 _call 正確),再做整合測試 Agent 的決策流程。
  5. 使用環境變數管理 API 金鑰:不要把 OpenAI、Anthropic 金鑰硬寫在程式碼裡,使用 .env 或雲端 secret 管理服務。

實際應用場景

場景 需求 可能的 Tool 組合
客服聊天機器人 為使用者即時查詢訂單狀態、退貨流程、FAQ。 order_lookup_apiknowledge_base_searchticket_create
程式開發輔助 讓 LLM 幫忙執行單元測試、搜尋 StackOverflow、產生程式碼片段。 code_runnerweb_searchgit_repo_browser
財務分析儀表板 使用者問「今年第一季的營收成長多少?」 → 需要查 DB、計算、繪圖。 sql_query_toolcalculatorchart_generator
智慧辦公自動化 會議紀要自動整理、行事曆新增、郵件發送。 meeting_transcript, calendar_add, email_sender
教育平台 依照學生提問自動搜尋教材、計算數學題、提供互動練習。 web_searchcalculatorquiz_generator

實務提示:在設計產品時,先把「使用者最常見的 3-5 個需求」對應到具體的 Tool,避免一次性加入過多功能,導致 LLM 判斷困難或系統維護負擔過重。


總結

Tool Calling Agent 是 LangChain 讓 LLM 從「文字生成」跨越到「主動執行」的關鍵橋樑。透過 StructuredTool 定義明確的介面、JSON Schema 描述參數,配合支援 function calling(OpenAI)或 tool_use(Anthropic)的模型,我們可以快速打造 可搜尋、可計算、可存取內部資源 的智慧型代理人。

本文示範了:

  1. 基本工具的建立(搜尋、計算、待辦清單)與 Agent 初始化
  2. 跨模型使用(OpenAI 與 Anthropic)。
  3. 自訂 HTTP API 工具,展示如何把公司內部服務納入 Agent。
  4. 常見陷阱與最佳實踐,協助開發者避免安全、效能與狀態管理問題。
  5. 實務應用場景,讓你快速定位自己產品的切入點。

掌握這套流程後,你就能在 聊天機器人、智慧客服、資料分析平台 等領域,利用 LLM 的自然語言理解能力與工具的即時執行力,提供使用者真正「能解決問題」的 AI 服務。祝你開發順利,玩得開心! 🚀