LangChain 教學:Chains 中的 Transform Chain
簡介
在使用大型語言模型(LLM)時,單純把問題直接丟給模型往往會得到過於寬泛或不符合需求的回應。Chain 的概念正是為了把多個處理步驟串起來,讓每一步都能聚焦於特定任務,最終產出更精準、可控的結果。
在 LangChain 的眾多 Chain 類型中,Transform Chain 扮演「資料轉換」的角色。它不會直接呼叫模型,而是接受前一步的輸出,透過自訂的函式或工具將資料轉換成符合下一步需求的格式。這種「前處理/後處理」的機制在實務開發裡相當常見:例如把使用者的自然語言指令轉成 API 請求參數、把 JSON 結果整理成表格、或是將長篇文字切割成段落供模型逐段處理。
掌握 Transform Chain 的使用方式,能讓你的 LangChain 應用更具彈性、易於維護,也更容易在不同模型或工具之間切換。接下來,我們會一步步拆解概念、示範實作,並分享常見的坑與最佳實踐,幫助你快速上手。
核心概念
1. Transform Chain 的定位
| 步驟 | 功能 | 典型範例 |
|---|---|---|
| Input | 前一個 Chain(或使用者)提供原始資料 | 使用者問題、LLM 回答、外部 API 回傳 |
| Transform | 以程式碼/函式對資料做 轉換、清理、重組 | 正規表達式抽取、JSON 解析、文字切割 |
| Output | 轉換後的結果交給下一個 Chain | 產生 Prompt、呼叫工具、回傳給前端 |
重點:Transform Chain 本身不會呼叫 LLM,也不會執行 I/O(例如 HTTP),它只負責「純粹的資料變形」。
2. 建立 Transform Chain 的基本語法
在 LangChain(Node.js 版)中,最簡單的方式是使用 TransformChain 類別,傳入一個 async (input) => output 的函式:
import { TransformChain } from "langchain/chains";
const myTransform = new TransformChain({
// inputKey: "text", // 可自行指定輸入欄位名稱,預設為 "input"
// outputKey: "processed", // 可自行指定輸出欄位名稱,預設為 "output"
transform: async (input) => {
// 這裡寫你的轉換邏輯
return input.toUpperCase();
},
});
myTransform.run({ input: "hello world" }) 會得到 { output: "HELLO WORLD" }。
3. 多輸入/多輸出
有時候需要同時處理多個欄位,或是產生多個結果。只要在 transform 函式中接受與回傳 物件 即可:
const splitAndCount = new TransformChain({
transform: async ({ text }) => {
const words = text.split(/\s+/);
return {
wordList: words,
wordCount: words.length,
};
},
});
呼叫 splitAndCount.run({ text: "LangChain 很好用" }) 會得到:
{
"output": {
"wordList": ["LangChain", "很好用"],
"wordCount": 2
}
}
4. 與其他 Chain 結合
Transform Chain 常被放在 LLMChain 前後,例如:
User Input → TransformChain (清理) → PromptTemplate → LLMChain → TransformChain (後處理) → Response
這樣的流水線可以確保模型只收到乾淨、結構化的 Prompt,同時也把模型的回覆整理成前端或資料庫易於使用的格式。
程式碼範例
以下提供 5 個實用範例,涵蓋文字前處理、JSON 轉換、資料切割、API 參數產生與結果格式化。每個範例都附上說明註解,方便直接套用。
範例 1:移除雜訊 & 正規化文字
import { TransformChain } from "langchain/chains";
/**
* 目的:將使用者輸入的自然語言指令,移除多餘的空白、標點,
* 並將全形英數字轉成半形,以免影響後續 Prompt。
*/
const cleanTextChain = new TransformChain({
transform: async ({ text }) => {
// 1. 去除前後空白
let cleaned = text.trim();
// 2. 正規化全形英數字 (Full‑width → Half‑width)
cleaned = cleaned.replace(/[A-Za-z0-9]/g, (ch) =>
String.fromCharCode(ch.charCodeAt(0) - 0xfee0)
);
// 3. 移除多餘的標點 (只保留句號、問號、感嘆號)
cleaned = cleaned.replace(/[,、;:]/g, "");
return { cleaned };
},
});
await cleanTextChain.run({ text: " 請幫我查詢 2023 年 的營收! " });
// => { output: { cleaned: "請幫我查詢 2023 年的營收!" } }
範例 2:將 LLM 回答的 JSON 文字轉成物件
import { TransformChain } from "langchain/chains";
/**
* LLM 常會回傳「請把資料以 JSON 格式呈現」的文字。
* 此 Chain 把字串安全地解析成 JavaScript 物件,若解析失敗則回傳錯誤訊息。
*/
const parseJsonChain = new TransformChain({
transform: async ({ jsonString }) => {
try {
const data = JSON.parse(jsonString);
return { data };
} catch (e) {
// 回傳可供後續 Chain 使用的錯誤資訊
return { error: "JSON 解析失敗,請檢查模型回覆格式。" };
}
},
});
await parseJsonChain.run({
jsonString: '{"name":"Alice","age":30}',
});
// => { output: { data: { name: "Alice", age: 30 } } }
範例 3:將長篇文字切割成固定長度的段落
import { TransformChain } from "langchain/chains";
/**
* 某些模型(例如 token 限制較低的模型)一次只能處理 500 token。
* 這個 Chain 把長文字切成若干段,每段不超過 `maxTokens`。
*/
const chunkTextChain = new TransformChain({
transform: async ({ text, maxTokens = 500 }) => {
// 簡易的 token 估算:以空白分割的字數作為近似值
const words = text.split(/\s+/);
const chunks = [];
let current = [];
for (const w of words) {
if (current.length + 1 > maxTokens) {
chunks.push(current.join(" "));
current = [];
}
current.push(w);
}
if (current.length) chunks.push(current.join(" "));
return { chunks };
},
});
const longText = "Lorem ipsum ... (很長的文字) ...";
await chunkTextChain.run({ text: longText, maxTokens: 300 });
// => { output: { chunks: ["第一段...", "第二段...", "..."] } }
範例 4:產生外部 API 的查詢參數
import { TransformChain } from "langchain/chains";
/**
* 假設我們要根據使用者的自然語言指令,產生
* Weather API 所需的 query string。
* 例如:「請告訴我台北明天的天氣」 → { city: "Taipei", date: "2025-11-26" }
*/
const weatherParamChain = new TransformChain({
transform: async ({ command }) => {
// 簡易的關鍵字抽取,實務上可改用正規表達式或 LLM 產生
const cityMatch = command.match(/台北|新北|高雄|台中/);
const dateMatch = command.match(/今天|明天|後天/);
const city = cityMatch ? cityMatch[0] : "Taipei";
const offset = { 今天: 0, 明天: 1, 後天: 2 }[dateMatch?.[0] || "今天"];
const targetDate = new Date();
targetDate.setDate(targetDate.getDate() + offset);
const date = targetDate.toISOString().split("T")[0]; // YYYY‑MM‑DD
return { params: { city, date } };
},
});
await weatherParamChain.run({ command: "請告訴我台北明天的天氣" });
// => { output: { params: { city: "台北", date: "2025-11-26" } } }
範例 5:把模型回覆的 Markdown 轉成純文字
import { TransformChain } from "langchain/chains";
/**
* 有時候 LLM 會回傳 Markdown 格式的說明,我們的前端只能接受純文字。
* 使用此 Chain 把常見的 Markdown 標記去除。
*/
const markdownToPlainChain = new TransformChain({
transform: async ({ markdown }) => {
// 移除標題、粗體、斜體、程式碼區塊等
let plain = markdown
.replace(/^#{1,6}\s+/gm, "") // 標題
.replace(/\*\*(.*?)\*\*/g, "$1") // 粗體
.replace(/\*(.*?)\*/g, "$1") // 斜體
.replace(/`{3}[\s\S]*?`{3}/g, "") // 多行程式碼
.replace(/`([^`]*)`/g, "$1") // 行內程式碼
.replace(/\[([^\]]+)\]\([^\)]+\)/g, "$1"); // 連結文字
// 去除多餘空白
plain = plain.replace(/\n{2,}/g, "\n\n").trim();
return { plain };
},
});
await markdownToPlainChain.run({
markdown: "# 介紹\n\n**LangChain** 是一個 *強大* 的框架。",
});
// => { output: { plain: "介紹\n\nLangChain 是一個 強大 的框架。" } }
常見陷阱與最佳實踐
| 陷阱 | 說明 | 解決方式 / 最佳實踐 |
|---|---|---|
| 忘記回傳物件 | transform 必須回傳一個 物件,否則後續 Chain 會拿不到 output。 |
確保 return { key: value },即使只有一個欄位也要包在物件裡。 |
| 同步/非同步混用 | transform 支援 async,但若裡面寫了同步阻塞程式(如大量迴圈),會拖慢整條流水線。 |
使用 await 處理 I/O,對 CPU 密集任務考慮分批或 Web Worker。 |
| 過度依賴全域變數 | Chain 應保持 純函式,避免在 transform 中直接讀寫外部狀態。 |
若需要共享設定,使用建構子參數或 context 物件傳遞。 |
| 錯誤未捕捉 | 當 transform 拋出例外,整條 Chain 會中斷。 |
在 transform 內部使用 try/catch,並把錯誤資訊包到回傳物件,讓後續 Chain 決定如何處理。 |
| 輸入/輸出鍵名衝突 | 預設 inputKey = "input"、outputKey = "output",若上一個 Chain 也使用相同鍵名,會被覆蓋。 |
明確設定 inputKey / outputKey,或在 run 時傳入具體的欄位名稱。 |
最佳實踐總結
- 保持純粹:Transform Chain 只做「資料變形」,不做外部呼叫。
- 明確鍵名:使用
inputKey/outputKey讓 Chain 間的資料流更清晰。 - 錯誤友好:回傳
{ error: "..."}而不是直接拋例外,讓整體流程更健壯。 - 測試驅動:為每個
transform撰寫單元測試,確保邊界條件(空字串、非法 JSON 等)都能正確處理。 - 效能考量:若需要大量文字切割或正規表達式,先用簡易的字元計算或分批處理,避免一次性載入過大資料。
實際應用場景
客服機器人
- 使用者輸入 → CleanTextChain(去雜訊) → Prompt → LLM 回答 → MarkdownToPlainChain(前端顯示)
資料抽取與 API 呼叫
- 使用者問句 → WeatherParamChain(產生 API 參數) → HttpGetChain(呼叫天氣服務) → ParseJsonChain(解析回傳) → LLM 產出自然語言回覆
文件摘要
- 大段 PDF 文字 → ChunkTextChain(分段) → 多個 LLMChain(分段摘要) → TransformChain(合併摘要、去重) → 最終摘要輸出
自動化報表
- 資料庫查詢結果(JSON) → TransformChain(轉成 CSV) → 交給 EmailChain(寄送報表)
程式碼生成
- 使用者需求描述 → CleanTextChain → Prompt → LLM 產生程式碼 → TransformChain(移除不必要的註解、格式化) → IDE 插件顯示
總結
Transform Chain 是 LangChain 中負責 資料前處理與後處理 的關鍵組件。透過純粹的函式式轉換,我們能夠:
- 提升模型效能:只把乾淨、結構化的 Prompt 給 LLM。
- 降低錯誤率:在模型接收前就把格式、編碼等問題解決。
- 增強可維護性:每個轉換步驟都是獨立、可測試的模組。
本文從概念說明、基本語法、五個實務範例、常見陷阱與最佳實踐,最後列出多種應用情境,提供了從 入門 到 進階 的完整指引。只要熟練掌握 Transform Chain 的寫法,配合其他 Chain(如 LLMChain、ToolChain),就能打造彈性十足、可擴充的 AI 工作流程。祝你在 LangChain 的世界裡玩得開心,寫出更聰明、更可靠的 AI 應用!