本文 AI 產出,尚未審核

LangChain 教學:LCEL(LangChain Expression Language)完整介紹


簡介

在使用 LangChain 建構 Chain(流程串接)時,我們常會遇到「如何把多個 LLM 呼叫、工具、條件判斷與資料前處理」以簡潔、可讀的方式寫在同一段程式碼中。
傳統的做法是手寫大量的 awaitif/elsemapreduce 等程式邏輯,既冗長又容易出錯。

LCEL(LangChain Expression Language) 正是為了解決這個痛點而誕生的。它是一套 宣告式 的 DSL(Domain Specific Language),讓開發者可以在一行或幾行文字裡,描述完整的模型流程、資料流向與條件分支。
透過 LCEL,我們不僅能 提升程式可讀性減少樣板程式碼,還能讓 Chain 的組合更具彈性,快速在開發與測試階段切換不同的 LLM 或工具。

本篇文章將從概念說明、實作範例、常見陷阱與最佳實踐,帶你一步步掌握 LCEL,並了解它在真實專案中的應用價值。


核心概念

1. LCEL 的基本語法

LCEL 使用 JSON‑like 的結構描述每個「節點」(node) 與「資料流」(edge)。最常見的屬性包括:

屬性 說明 範例
type 節點類型:llm, tool, prompt, condition "type": "llm"
name 節點名稱,供後續引用 "name": "summarize"
input 輸入來源,可以是前一節點的輸出或外部變數 "input": "$.question"
output 輸出別名,供後續節點使用 "output": "summary"
condition 條件判斷,支援 ==, !=, >, < "condition": "$.score > 0.8"

範例(最小化的 LCEL)

{
  "type": "llm",
  "name": "answer",
  "input": "$.question",
  "output": "reply"
}

在 JavaScript 中,我們會把這段 JSON 直接傳給 LCELChain.fromExpression(),LangChain 會自動把它編譯成可執行的 Chain。

2. 連接多個節點:pipelinebranch

LCEL 支援兩種主要的流程組合方式:

  • pipeline:線性串接,前一節點的 output 成為下一節點的 input
  • branch:根據條件分支執行不同的子流程。
{
  "pipeline": [
    { "type": "prompt", "name": "genPrompt", "input": "$.question", "output": "promptText" },
    { "type": "llm", "name": "gpt4", "input": "$.promptText", "output": "answer" },
    {
      "type": "condition",
      "condition": "$.answer.includes('抱歉')",
      "true": [{ "type": "tool", "name": "search", "input": "$.question", "output": "searchResult" }],
      "false": [{ "type": "prompt", "name": "refine", "input": "$.answer", "output": "finalAnswer" }]
    }
  ]
}

上例先產生 Prompt → 呼叫 LLM → 若回覆包含「抱歉」則改走搜尋工具,否則直接精煉答案。

3. 變數與參照 ($)

LCEL 使用 $. 前綴來存取當前執行環境的變數或先前節點的輸出。例如:

  • $.question → 入口參數 question
  • $.searchResult → 先前 search 節點的 output

變數可以在 context 中預先定義,也可以在流程中動態加入。

4. 內建工具與自訂工具

LangChain 內建了常見的工具(如 SerpAPI, Wikipedia, PythonREPL),在 LCEL 中只要指定 type: "tool" 並提供 name 即可使用。
若要 自訂工具,先在程式碼中註冊一個 Tool 實例,然後在 LCEL 表達式裡使用相同的 name


程式碼範例

以下範例皆使用 Node.jslangchain v0.2+)與 JavaScript,請先安裝相依套件:

npm install langchain openai @langchain/community

範例 1:最簡單的 LLM 呼叫

import { ChatOpenAI } from "@langchain/openai";
import { LCELChain } from "@langchain/core/chains/lcel";

// 建立 LLM 實例
const llm = new ChatOpenAI({ modelName: "gpt-4o-mini", temperature: 0 });

// LCEL 表達式:直接把問題傳給 LLM,輸出叫 answer
const expr = {
  type: "llm",
  name: "answer",
  input: "$.question",
  output: "answer"
};

// 建立 Chain
const chain = await LCELChain.fromExpression(expr, { llm });

// 執行
const result = await chain.run({ question: "什麼是 LCEL?" });
console.log(result.answer);

說明

  • $.question 代表外部傳入的 question 變數。
  • 執行結果會在 result.answer 中取得。

範例 2:結合 Prompt 與 LLM

import { PromptTemplate } from "@langchain/core/prompts";

const prompt = new PromptTemplate({
  template: "請用繁體中文說明以下概念:{concept}",
  inputVariables: ["concept"]
});

const expr = {
  pipeline: [
    {
      type: "prompt",
      name: "genPrompt",
      input: "$.concept",
      output: "promptText",
      prompt: prompt // 直接傳入 PromptTemplate 物件
    },
    {
      type: "llm",
      name: "gpt",
      input: "$.promptText",
      output: "explanation"
    }
  ]
};

const chain = await LCELChain.fromExpression(expr, { llm });
const out = await chain.run({ concept: "LangChain Expression Language" });
console.log(out.explanation);

重點:在 prompt 節點可以直接放入 PromptTemplate,LCEL 會自動把 input 填入模板中。

範例 3:條件分支 + 搜尋工具

import { SerpAPI } from "@langchain/community/tools/serpapi";

const searchTool = new SerpAPI(process.env.SERPAPI_API_KEY);

const expr = {
  pipeline: [
    {
      type: "llm",
      name: "answer",
      input: "$.question",
      output: "rawAnswer"
    },
    {
      type: "condition",
      condition: "$.rawAnswer.includes('不知道')",
      true: [
        {
          type: "tool",
          name: "search",
          input: "$.question",
          output: "searchResult",
          tool: searchTool
        },
        {
          type: "prompt",
          name: "refine",
          input: "$.searchResult",
          output: "finalAnswer",
          prompt: new PromptTemplate({
            template: "請根據以下搜尋結果,簡潔回答使用者的問題:\n{searchResult}",
            inputVariables: ["searchResult"]
          })
        }
      ],
      false: [
        {
          type: "prompt",
          name: "final",
          input: "$.rawAnswer",
          output: "finalAnswer",
          prompt: new PromptTemplate({
            template: "以下是 AI 的直接回覆,請簡化語句:\n{rawAnswer}",
            inputVariables: ["rawAnswer"]
          })
        }
      ]
    }
  ]
};

const chain = await LCELChain.fromExpression(expr, { llm });
const res = await chain.run({ question: "台北 2024 年的天氣趨勢是什麼?" });
console.log(res.finalAnswer);

說明

  • condition 節點會檢查 LLM 是否回傳「不知道」的字樣。
  • 若為 true,會觸發 search 工具,再把結果交給 refine Prompt。
  • 若為 false,直接走 final Prompt 進行文字精煉。

範例 4:使用 branch 產生多條平行路徑

const expr = {
  pipeline: [
    {
      type: "llm",
      name: "classify",
      input: "$.question",
      output: "topic"
    },
    {
      type: "branch",
      selector: "$.topic",
      cases: {
        "程式設計": [
          { type: "tool", name: "codeSearch", input: "$.question", output: "codeResult" }
        ],
        "旅遊": [
          { type: "tool", name: "travelInfo", input: "$.question", output: "travelResult" }
        ],
        "default": [
          { type: "prompt", name: "fallback", input: "$.question", output: "fallbackAnswer",
            prompt: new PromptTemplate({template: "抱歉,我無法辨識此類別。", inputVariables: []}) }
        ]
      }
    }
  ]
};

const chain = await LCELChain.fromExpression(expr, { llm, tools: { codeSearch, travelInfo } });
const ans = await chain.run({ question: "請告訴我 Python 的 list comprehension 用法" });
console.log(ans);

關鍵branch 允許根據前一步的分類結果,選擇不同的子流程,實現 動態路由

範例 5:將前置資料清理與後置格式化結合

import { JsonOutputParser } from "@langchain/core/output_parsers";

const expr = {
  pipeline: [
    {
      type: "prompt",
      name: "clean",
      input: "$.rawText",
      output: "cleaned",
      prompt: new PromptTemplate({
        template: "請把以下文字整理成純文字,去除所有 Markdown 標記:\n{rawText}",
        inputVariables: ["rawText"]
      })
    },
    {
      type: "llm",
      name: "extract",
      input: "$.cleaned",
      output: "jsonString",
      llmOptions: { stop: ["\n"] } // 防止過長回覆
    },
    {
      type: "parser",
      name: "jsonParse",
      input: "$.jsonString",
      output: "structuredData",
      parser: new JsonOutputParser()
    }
  ]
};

const chain = await LCELChain.fromExpression(expr, { llm });
const result = await chain.run({ rawText: "# 標題\n\n內容 **粗體**" });
console.log(result.structuredData);

技巧:LCEL 支援 parser 節點,讓你直接把 LLM 的文字結果轉成結構化資料,省去手寫 JSON.parse 的步驟。


常見陷阱與最佳實踐

陷阱 說明 解決方式
變數命名衝突 若兩個節點使用相同 output 名稱,後者會覆蓋前者。 為每個 output 使用具體且唯一的名稱,如 answer_v1answer_v2
條件判斷的字串比較 condition 只支援簡易的比較,無法直接使用正則。 先在 llmtool 中產生布林值,再於 condition 直接判斷 $..isRelevant
工具未註冊 在 LCEL 中引用的 tool 必須先於 LCELChain.fromExpression 注入。 使用 LCELChain.fromExpression(expr, { llm, tools: { myTool } }),或在 expr 中的 tool 節點直接提供實例。
大量分支導致表達式過長 多層 branch 會讓 JSON 變得難以閱讀。 把子流程抽成 子表達式(sub‑expression),使用 $ref 引用。
LLM 回傳非預期格式 若 LLM 回傳的文字不符合 parser 需求,會拋出例外。 llm 節點加入 stoptemperature 等參數,或在 parser 前加上 prompt 讓 LLM 明確說「請回傳 JSON」。

最佳實踐

  1. 保持單一職責:每個節點只做一件事(產生 Prompt、呼叫 LLM、執行工具、或做條件判斷)。
  2. 使用 output 別名:即使是同一個工具,也盡量使用不同的別名,以免在後續分支中混淆。
  3. 加入 metadata:在 run() 時傳入 metadata,可在條件或工具中參考,例如 $.metadata.userId
  4. 測試子表達式:將複雜的子流程抽成獨立的 LCEL JSON,先單獨測試再組合。
  5. 記錄 LLM 設定:在 llmOptions 中明確寫出 temperaturemaxTokensstop 等,確保每次執行行為一致。

實際應用場景

場景 需求 LCEL 怎麼幫助
客服聊天機器人 需要根據使用者問題決定是直接回覆、搜尋資料或升級給人工客服。 使用 condition + branch 判斷信心分數,動態切換 LLM、搜尋工具或 humanHandOff 節點。
文件摘要與結構化 從 PDF、Word 等文件抽取關鍵資訊,並輸出 JSON 給後端。 prompt 先清理文字 → llm 產生摘要 → parser 轉成 JSON → tool 寫入資料庫。
程式碼生成輔助 依使用者需求生成程式碼,並即時測試。 prompt 產生程式碼 → tool (PythonREPL) 執行 → condition 檢查執行結果 → 若失敗走 refine Prompt 再產生。
多語言翻譯工作流 先偵測語言,再選擇適合的翻譯模型或工具。 llm 判斷語言 → branch 根據語言呼叫不同的翻譯 API → prompt 統一格式化輸出。
資料探索與可視化 使用者以自然語言詢問資料庫統計,系統自動產生 SQL 並回傳圖表。 prompt 產生 SQL → tool 執行資料庫查詢 → condition 檢查結果是否為空 → tool 產生圖表 → 最後 prompt 整理回覆。

總結

LCEL 為 LangChain 的 Chains 提供了一層 宣告式、可組合、易除錯 的語法,使得模型流程的設計不再是繁瑣的程式碼堆砌,而是像寫配置檔一樣直觀。
透過本文的 概念說明實作範例、以及 最佳實踐,你應該已經能夠:

  • 使用 JSON‑style 表達式快速串接 Prompt、LLM、工具與條件分支。
  • 在同一個 Chain 中同時處理文字前處理、模型呼叫、結果解析與後置格式化。
  • 針對不同業務需求(客服、文件處理、程式碼輔助等)設計彈性且可維護的工作流。

未來隨著 LangChain 社群持續擴充工具與模型支援,LCEL 也會加入更多功能(如迴圈、錯誤重試策略),讓開發者能以更少的程式碼,完成更複雜的 AI 應用。現在就把 LCEL 帶入你的專案,體驗 「寫一次、跑多次」 的開發快感吧!