iT邦幫忙

2025 iThome 鐵人賽

DAY 10
0
生成式 AI

用 Node.js 打造生成式 AI 應用:從 Prompt 到 Agent 開發實戰系列 第 10

Day 10 - 輸出格式解析:運用 LangChain Output Parsers 控制回應內容

  • 分享至 

  • xImage
  •  

在前一篇文章中,我們介紹了 提示模板(Prompt Templates),透過模組化與參數化設計,讓提示語更具彈性與可讀性。不過,提示模板解決的只是「輸入」問題;若要打造真正穩定、可控的應用,還必須進一步處理並驗證模型的「輸出」。

為此,LangChain 提供了 輸出解析器(Output Parsers),能將模型生成的自然語言結果,轉換為結構化且可程式化處理的資料格式,讓我們更容易將 AI 回應整合進應用邏輯之中。

為什麼需要輸出解析器?

在多數情境下,LLM 生成的內容都是自然語言的自由文字,例如:

這是一個範例回應。

這樣的輸出對人類閱讀沒問題,但在工程應用裡,往往需要的是可被程式直接處理的結構化資料,例如 JSON、清單或固定格式的物件。舉例來說,如果我們希望模型輸出一個帶有標題與關鍵字的結果,就可能需要長這樣:

{
  "title": "範例回應",
  "keywords": ["範例", "回應"]
}

問題是,模型不一定會嚴格遵守我們的要求:有時會多加描述性文字,有時 JSON 格式不完整,甚至可能產生語法錯誤。

這時候就需要 輸出解析器(Output Parsers) 來協助控管。它的作用主要包含:

  1. 定義格式:事先告訴模型應該如何組織回應,並生成清楚的「格式指令」。
  2. 驗證結果:在模型回應後,檢查輸出是否符合預期的結構,若有錯誤可以拋出例外或重新請求。
  3. 轉換資料:將文字輸出安全地轉換為結構化資料(例如物件或陣列),方便後續程式直接使用。

透過這種方式,我們能大幅降低「模型輸出不可控」所帶來的風險,讓 LLM 真正能成為工程流程的一部分。

LangChain 輸出解析器的運作流程

在 LangChain 裡,輸出解析器不只是單純的「後處理」工具,它同時也是「提示設計」的一部分。因為解析器能自動生成格式指令,讓我們插入到提示中,引導模型輸出更可控、更符合需求的結果。它的運作流程大致可以分為以下步驟:

  1. 生成格式指令:每個解析器都能提供一段「格式說明」,例如要求輸出為 JSON 或逗號分隔的清單。這段指令可以被插入到 Prompt 中,讓模型在生成內容時有明確的格式規範。
  2. 模型產生回應:模型依照提示內容與格式指令產生回應。雖然 LLM 不一定百分之百遵守,但通常能大幅提升輸出正確率。
  3. 解析與驗證:輸出結果會交給解析器處理:若符合定義,就轉換成結構化資料(例如物件、陣列);若不符合,則會回報錯誤,或透過額外的修正機制(例如 OutputFixingParser)讓模型重新生成。

整體流程可以用下圖來概括:

https://ithelp.ithome.com.tw/upload/images/20250910/20150150VY1amxqn39.png

有了這層機制,開發者不必再額外撰寫繁瑣的字串處理程式,就能直接獲得結構化且可靠的結果,讓模型輸出更容易整合進實際應用。

LangChain 內建了多種常用的輸出解析器,開發者可以依照不同的需求選擇合適的工具,讓模型輸出更符合應用場景。常見的解析器包括:

  • StringOutputParser:直接回傳模型的原始文字輸出,不做任何解析。
  • JsonOutputParser:將模型回應解析為 JSON 格式的物件或陣列,適合資料驅動的應用。
  • CommaSeparatedListOutputParser:將逗號分隔的字串轉為字串陣列,常用於列舉清單。
  • StructuredOutputParser:搭配 zod schema 定義結構化欄位,並自動解析與驗證。

透過這些解析器,我們能讓 LLM 的輸出更有結構、更可控,避免不必要的格式錯誤,也能更順暢地整合進應用程式流程中。

Notezod 是一個用於型別安全的 TypeScript/JavaScript 資料驗證與解析套件。它允許開發者以宣告式的方式定義資料結構(Schema),並在程式運行時對輸入或輸出的資料進行驗證。

StringOutputParser:最基礎的文字輸出

在所有輸出解析器中,StringOutputParser 是最基礎也最單純的一種。它不會對模型的回應進行任何額外處理,而是直接回傳原始文字。

以下是一個簡單範例,我們請模型用一句話介紹 Node.js,並透過 StringOutputParser 直接回傳結果:

import { ChatOpenAI } from '@langchain/openai';
import { StringOutputParser } from '@langchain/core/output_parsers';

const model = new ChatOpenAI({
  model: 'gpt-4o-mini',
});

const parser = new StringOutputParser();
const response = await model.invoke("請用一句話介紹 Node.js。");
const parsed = await parser.parse(response.content);

console.log(parsed);

執行後,輸出可能會是:

Node.js 是一個基於 Chrome V8 引擎的 JavaScript 執行環境,適合開發高效能伺服器應用。

如同範例所示,StringOutputParser 只是單純把模型輸出轉為字串並回傳,不會進行格式檢查或驗證。這種方式非常適合那些只需要直接取用模型文字內容的場景,例如對話回覆、文章摘要或自由生成的內容。

JsonOutputParser:將模型回應轉成結構化物件

在需要讓模型輸出結構化資料的情境下,JsonOutputParser 是非常實用的工具。它能將模型回應中的 JSON 直接轉換為 JavaScript 物件或陣列,讓程式能安全地存取欄位,而不需要再手動處理字串。

以下是一個範例,我們要求模型輸出一部電影的基本資訊,並以 JSON 格式回傳,再透過解析器將其轉換成 JavaScript 物件:

import { ChatOpenAI } from '@langchain/openai';
import { JsonOutputParser } from '@langchain/core/output_parsers';
import { PromptTemplate } from '@langchain/core/prompts';

const model = new ChatOpenAI({
  model: 'gpt-4o-mini',
});

const parser = new JsonOutputParser();
const response = await model.invoke("請提供一部電影的基本資訊,並以 JSON 格式輸出,包含 title, genre, year。");
const parsed = await parser.parse(response.content);

console.log(parsed);

執行後的輸出結果會是一個結構化物件,例如:

{ title: 'Inception', genre: 'Science Fiction', year: 2010 }

透過這樣的設計,我們可以不用自己處理 JSON 格式字串,直接得到程式可操作的物件。若要進一步加上格式驗證或結構限制,也可以考慮使用 StructuredOutputParser,搭配 schema 定義,確保模型輸出的格式與內容完全符合規範。

CommaSeparatedListOutputParser:將文字清單轉為陣列

當我們希望模型輸出一份清單時,CommaSeparatedListOutputParser 就派上用場了。它能將模型回應中的「逗號分隔字串」自動轉換成陣列,讓輸出結果更方便操作,例如產生關鍵字列表、熱門選項或代辦事項。

以下是一個範例,我們透過 getFormatInstructions() 要求模型輸出三種熱門程式語言,並使用解析器自動轉換成字串陣列:

import { ChatOpenAI } from '@langchain/openai';
import { CommaSeparatedListOutputParser } from '@langchain/core/output_parsers';
import { PromptTemplate } from '@langchain/core/prompts';

const model = new ChatOpenAI({
  model: 'gpt-4o-mini',
});

const parser = new CommaSeparatedListOutputParser();

const prompt = new PromptTemplate({
  template: `列出三種熱門程式語言。\n{format_instructions}`,
  inputVariables: [],
  partialVariables: {
    format_instructions: parser.getFormatInstructions(),
  },
});

const input = await prompt.format({});
const response = await model.invoke(input);
const parsed = await parser.parse(response.content);

console.log(parsed);

TipPromptTemplate 通常需要我們在 inputVariables 中傳入使用者輸入,但有些變數(像 format_instructions)是固定的,不需要每次重新指定。這時就能透過 partialVariables 先行綁定,讓模板在建立時就包含固定內容。

執行後的輸出結果會是一個陣列,例如:

[ 'JavaScript', 'Python', 'Java' ]

這樣的方式能讓模型輸出更貼近我們需要的資料結構,非常適合處理需要列舉清單或選項的場景。

StructuredOutputParser:嚴謹結構化輸出的利器

如果你需要模型輸出複雜的結構化資料,並且希望對欄位型別和格式進行嚴格驗證,那麼 StructuredOutputParser 就是最佳選擇。它能搭配 zod schema 來定義每個欄位的名稱、型別與描述,並在解析時自動檢查輸出是否符合規範。

以下範例中,我們定義一個 zod schema,描述電影資訊的格式,並透過 getFormatInstructions() 要求模型輸出符合結構的內容:

import { ChatOpenAI } from '@langchain/openai';
import { StructuredOutputParser } from '@langchain/core/output_parsers';
import { PromptTemplate } from '@langchain/core/prompts';
import { z } from 'zod';

const model = new ChatOpenAI({
  model: 'gpt-4o-mini',
});

const parser = StructuredOutputParser.fromZodSchema(
  z.object({
    title: z.string().describe("電影名稱"),
    genre: z.string().describe("電影類型"),
    year: z.number().describe("上映年份"),
  })
);

const prompt = new PromptTemplate({
  template: `請提供一部知名電影的基本資訊。\n{format_instructions}`,
  inputVariables: [],
  partialVariables: {
    format_instructions: parser.getFormatInstructions(),
  },
});

const input = await prompt.format({});
const response = await model.invoke(input);
const parsed = await parser.parse(response.content);

console.log(parsed);

執行後,輸出會是一個完整且符合 schema 驗證的物件,例如:

{ title: 'Interstellar', genre: 'Science Fiction', year: 2014 }

JsonOutputParser 相比,StructuredOutputParser 提供更嚴格的驗證機制,它能透過 schema 驗證輸出的完整性與型別正確性。如果模型少了欄位或格式錯誤,解析器會立即拋出例外,避免下游應用因資料異常而出現問題。這種設計特別適合需要欄位完整性與型別一致性的應用場景,例如表單填寫、知識資料庫存取,或需嚴格遵循資料格式的 API 整合。

OutputFixingParser:自動修正錯誤輸出

即使有了格式指令與解析器,LLM 仍有可能產生格式錯誤的輸出,例如漏掉欄位、多了一些描述文字,或 JSON 語法不完整。這時候,我們就可以利用 OutputFixingParser 來自動修正。

它的運作方式是:先嘗試使用原本的解析器(例如 JsonOutputParserStructuredOutputParser)處理輸出,如果解析失敗,則會再次呼叫模型,要求它依照指定的格式重新生成合格結果。

以下範例示範如何搭配 StructuredOutputParserOutputFixingParser,即使模型輸出缺少欄位,也能透過修正得到完整物件:

import { ChatOpenAI } from '@langchain/openai';
import { StructuredOutputParser, OutputFixingParser } from '@langchain/core/output_parsers';
import { z } from 'zod';

const model = new ChatOpenAI({
  model: 'gpt-4o-mini',
});

const baseParser = StructuredOutputParser.fromZodSchema(
  z.object({
    title: z.string().describe("電影名稱"),
    genre: z.string().describe("電影類型"),
    year: z.number().describe("上映年份"),
  })
);

const parser = OutputFixingParser.fromLLM(model, baseParser);

const badOutput = `{
  "title": "Interstellar",
  "genre": "Science Fiction"
}`;

const parsed = await parser.parse(badOutput);

console.log(parsed);

注意OutputFixingParser 並非隸屬於 @langchain/core/output_parsers,而是提供在 langchain/output_parsers 模組中。使用時請確認已安裝 langchain 套件。

在這個例子中,雖然原始輸出缺少了 year 欄位,OutputFixingParser 會請模型自動補齊,最後輸出完整的物件,例如:

{ title: 'Interstellar', genre: 'Science Fiction', year: 2014 }

透過 OutputFixingParser,我們就能建立一個更穩健的輸出流程:先讓模型依照格式指令生成輸出,再由解析器驗證,若失敗則自動修正,直到產生正確的格式。這讓 LLM 的輸出更結構化、更可靠,適合應用在高可靠度需求的系統中。

小結

今天我們學會了如何運用 LangChain 的 輸出解析器(Output Parsers) 來控管 LLM 的回應格式,讓模型輸出更結構化、穩定且可靠,並能更順利地整合進應用程式流程:

  • 大型語言模型的原始輸出通常是自由文字,但實際應用中往往需要 JSON、清單或結構化物件。
  • 輸出解析器同時也是提示設計的一環,能自動生成格式指令,協助引導模型輸出符合規範的內容。
  • 它的核心功能包含:定義格式、驗證結果、轉換資料,確保輸出正確且可被程式直接使用。
  • LangChain 內建多種解析器,能依需求選擇合適的工具。
  • StringOutputParser:單純取回文字。
  • JsonOutputParser:將回應轉為 JSON。
  • CommaSeparatedListOutputParser:自動將逗號分隔字串轉成陣列。
  • StructuredOutputParser:搭配 schema 嚴格驗證欄位與型別。
  • OutputFixingParser:當格式錯誤時,可請模型自動修正並輸出合格結果。

透過這些解析工具,我們能大幅降低模型輸出不可控的風險,讓 AI 不只是聊天助手,更能成為能穩定融入程式邏輯的可靠元件。


上一篇
Day 09 - 提示模板設計:掌握 LangChain Prompt Templates 用法
下一篇
Day 11 - 流程鏈組合應用:使用 LCEL 打造靈活應用邏輯
系列文
用 Node.js 打造生成式 AI 應用:從 Prompt 到 Agent 開發實戰14
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言