LangChain 教學:自訂 Chain 與組合
簡介
在 LLM(大型語言模型)應用開發中,Chain 是將多個模型、工具或提示(Prompt)串連起來,形成一條完整工作流程的核心概念。單一模型往往只能完成「問答」或「文字生成」等簡單任務;而真實商業需求常常需要 前置資料處理 → 多步推理 → 後置結果格式化 等多階段操作。
本單元聚焦於 自訂 Chain(自己寫的流程)以及 Chain 的組合(把已有的 Chain 拼湊成更複雜的管線)。掌握這些技巧,你就能把 LangChain 變成「可程式化的 AI 工作流引擎」,快速構建客服機器人、文件摘要系統、資料清洗管線等實務應用。
核心概念
1. Chain 的基本結構
在 LangChain 中,Chain 本質上是一個 invoke(input) 方法的物件,接受輸入、呼叫一或多個 LLM 或工具,最後回傳結果。最簡單的 Chain 只包住一個 LLM:
import { LLMChain } from "langchain/chains";
import { OpenAI } from "langchain/llms/openai";
const llm = new OpenAI({ temperature: 0 });
const chain = new LLMChain({ llm, prompt: "請把以下文字翻成英文:\n{input}" });
const res = await chain.invoke({ input: "今天天氣很好" });
console.log(res); // "The weather is nice today."
LLMChain只負責 單一次呼叫 LLM,而prompt中的{input}會在執行時自動被替換。invoke的回傳值通常是 字串,但也可以自訂返回結構(如 JSON)。
2. 自訂 Chain:繼承 Chain 類別
若需要在 LLM 前後加入額外邏輯(例如資料庫查詢、正則表達式清理),可以自行 繼承 Chain,實作 _call 方法:
import { Chain } from "langchain/chains";
class CleanAndTranslateChain extends Chain {
constructor({ llm }) {
super();
this.llm = llm;
}
/** 取得此 Chain 所需的輸入欄位名稱 */
get inputKeys() {
return ["rawText"];
}
/** 取得此 Chain 輸出的欄位名稱 */
get outputKeys() {
return ["translated"];
}
/** 真正的執行邏輯 */
async _call(values) {
// 1️⃣ 前置清理:移除多餘空白、換行
const cleaned = values.rawText.replace(/\s+/g, " ").trim();
// 2️⃣ 呼叫 LLM 產生翻譯
const prompt = `請把以下中文翻成英文:\n${cleaned}`;
const response = await this.llm.call(prompt);
// 3️⃣ 後置處理:確保回傳為單行文字
const translated = response.trim().replace(/\n/g, " ");
return { translated };
}
}
// 使用範例
import { OpenAI } from "langchain/llms/openai";
const llm = new OpenAI({ temperature: 0 });
const myChain = new CleanAndTranslateChain({ llm });
const out = await myChain.invoke({ rawText: " 今 天 \n\n 很 好 " });
console.log(out.translated); // "Today is great."
重點:自訂 Chain 必須明確宣告
inputKeys與outputKeys,讓 LangChain 在組合時能自動對齊資料流。
3. 組合 Chain:SequentialChain & RouterChain
LangChain 提供兩種常見的組合方式:
| 組合方式 | 說明 | 典型使用情境 |
|---|---|---|
SequentialChain |
依序執行多個 Chain,前一個的輸出會自動成為下一個的輸入 | 多步問答、先摘要再翻譯 |
RouterChain |
根據條件(如關鍵字)選擇不同的子 Chain 執行 | 客服路由、不同領域的專家模型 |
3.1 SequentialChain 範例
把「摘要 → 翻譯」兩個步驟串成一條流程:
import { SequentialChain } from "langchain/chains";
import { PromptTemplate } from "langchain/prompts";
import { OpenAI } from "langchain/llms/openai";
const llm = new OpenAI({ temperature: 0 });
// Step 1: 摘要
const summaryPrompt = new PromptTemplate({
template: "請幫我將以下內容做 3 點重點摘要:\n{input}",
inputVariables: ["input"],
});
const summaryChain = new LLMChain({ llm, prompt: summaryPrompt });
// Step 2: 翻譯
const translatePrompt = new PromptTemplate({
template: "請把以下中文摘要翻成英文:\n{summary}",
inputVariables: ["summary"],
});
const translateChain = new LLMChain({ llm, prompt: translatePrompt });
// 串接
const workflow = new SequentialChain({
chains: [summaryChain, translateChain],
inputVariables: ["input"],
// 最後的輸出鍵會自動合併成 { summary, translated }
outputVariables: ["summary", "translated"],
});
const result = await workflow.invoke({
input: "LangChain 是一個用於建構 LLM 應用的框架,提供 Chain、Agent、Tool 等概念。..."
});
console.log(result.translated);
3.2 RouterChain 範例
根據使用者輸入的主題自動切換不同的專家模型:
import { RouterChain } from "langchain/chains";
import { OpenAI } from "langchain/llms/openai";
const llm = new OpenAI({ temperature: 0 });
const financeChain = new LLMChain({
llm,
prompt: "你是金融專家,請回答以下問題:\n{question}",
});
const techChain = new LLMChain({
llm,
prompt: "你是科技專家,請回答以下問題:\n{question}",
});
// 路由條件函式:回傳欲使用的 chain 名稱
function routeFn(values) {
const q = values.question.toLowerCase();
if (q.includes("股票") || q.includes("投資")) return "finance";
if (q.includes("程式") || q.includes("AI")) return "tech";
return "default";
}
const router = new RouterChain({
routes: {
finance: financeChain,
tech: techChain,
default: new LLMChain({
llm,
prompt: "請一般性回答以下問題:\n{question}",
}),
},
defaultRoute: "default",
routeFn,
});
const ans = await router.invoke({
question: "請問近期 AI 產業的趨勢如何?",
});
console.log(ans.text);
4. 讓 Chain 可重用:Runnable 與 Serializable
LangChain 2.x 引入 Runnable 概念,使 Chain 更像「函式」可直接 pipe、map、batch。以下示範如何把自訂 Chain 包裝成 Runnable,並在批次任務中使用:
import { RunnableSequence } from "langchain/schema/runnable";
import { OpenAI } from "langchain/llms/openai";
const llm = new OpenAI({ temperature: 0 });
// 先建立兩個 Runnable:清理 + LLM
const clean = RunnableSequence.from(async (input) => {
return input.replace(/\s+/g, " ").trim();
});
const translate = RunnableSequence.from(async (text) => {
const prompt = `請把以下中文翻成英文:\n${text}`;
return await llm.call(prompt);
});
// 組合成完整流水線
const pipeline = clean.pipe(translate);
// 批次執行
const inputs = [
" 今 天 \n很好 ",
" 明天 \n會下雨 ",
];
const outputs = await Promise.all(inputs.map(pipeline.invoke));
console.log(outputs);
常見陷阱與最佳實踐
| 陷阱 | 為什麼會發生 | 解決方式 |
|---|---|---|
忘記宣告 inputKeys / outputKeys |
自訂 Chain 無法與其他 Chain 自動對接 | 必寫 兩個 getter,或直接繼承 BaseChain 使用 BaseChain.inputKeys |
| LLM 呼叫過於頻繁 | 在 SequentialChain 中每一步都呼叫 LLM,成本飆升 |
把可以在本地完成的前置處理(正則、分詞)搬到 Python/Node 前端 |
| 回傳結構不一致 | 後續 Chain 期待 {summary} 卻收到 {text} |
統一使用 JSON Schema,或在自訂 Chain 中 return { key: value } |
| Prompt 複雜度過高 | 長 Prompt 會導致 token 超限或回應不穩定 | 使用 Few‑Shot 範例、PromptTemplate 變數化,必要時分段呼叫 |
未設定 timeout |
LLM 服務偶爾卡住導致整條流程卡死 | 為每個 llm.call 加上 timeout、重試機制(retry) |
最佳實踐
- 最小化 LLM 次數:先在程式內完成清理、抽取,再交給 LLM。
- 使用
PromptTemplate:把變數抽離,讓 Prompt 可重用、易維護。 - 加入日誌(logging):在每個 Chain 前後打印
input、output,方便除錯。 - 測試每個子 Chain:利用 Jest / Mocha 為自訂 Chain 撰寫單元測試,確保資料流正確。
- 設定
maxTokens與temperature:根據需求調整,避免產生過長或過於隨機的回應。
實際應用場景
| 場景 | 需要的 Chain | 為什麼適合自訂/組合 |
|---|---|---|
| 客服自動回覆 | RouterChain(根據問題類別切換) + SequentialChain(先搜尋 FAQ → 再生成回覆) |
可快速擴充新領域,只要新增對應子 Chain 即可 |
| 法律文件摘要與關鍵條款抽取 | SequentialChain(摘要 → 關鍵詞抽取 → JSON 格式化) |
多步驟必須保持上下文,Chain 保證資料流不遺失 |
| 多語言商品說明產生 | CleanAndTranslateChain(清理) → LLMChain(生成) → RouterChain(依語言路由) |
前置清理提升翻譯品質,路由讓不同語言使用不同模型或 prompt |
| 資料科學報告自動化 | SequentialChain(資料前處理 → 圖表生成工具 → LLM 報告撰寫) |
結合外部工具(如 matplotlib)與 LLM,Chain 把非 AI 步驟包起來 |
總結
自訂 Chain 與 Chain 組合是 LangChain 中最具彈性、最能發揮 LLM 潛力的功能。透過 繼承 Chain、明確宣告輸入/輸出鍵,我們可以把任何前置或後置邏輯封裝成可重用的模組;再利用 SequentialChain、RouterChain、Runnable 等組合工具,將這些模組拼湊成完整的 AI 工作流。
在實務開發時,記得 減少不必要的 LLM 呼叫、保持 Prompt 可維護、加入日誌與測試,即可建立高效、可靠且易於擴充的 AI 服務。未來隨著模型與工具的演進,這套「Chain 思維」將成為打造智慧應用的基礎建築,值得每位開發者深入掌握。祝你玩得開心,打造出令人驚豔的 LangChain 應用!