跳到主要內容
Open In ColabOpen on GitHub

如何建立自訂輸出解析器

在某些情況下,您可能想要實作自訂解析器,以將模型輸出結構化為自訂格式。

有兩種方法可以實作自訂解析器

  1. LCEL 中使用 RunnableLambdaRunnableGenerator -- 我們強烈建議在大多數使用案例中使用此方法
  2. 透過繼承輸出解析的其中一個基底類別 -- 這是比較困難的方法

這兩種方法之間的主要差異在於表面,主要在於觸發的回呼(例如,on_chain_starton_parser_start),以及可執行 lambda 與解析器在 LangSmith 等追蹤平台中的視覺化方式。

可執行 Lambda 和產生器

建議的解析方法是使用可執行 lambda可執行產生器

在這裡,我們將建立一個簡單的解析器,反轉模型輸出的案例。

例如,如果模型輸出:「Meow」,解析器將產生「mEOW」。

from typing import Iterable

from langchain_anthropic.chat_models import ChatAnthropic
from langchain_core.messages import AIMessage, AIMessageChunk

model = ChatAnthropic(model_name="claude-2.1")


def parse(ai_message: AIMessage) -> str:
"""Parse the AI message."""
return ai_message.content.swapcase()


chain = model | parse
chain.invoke("hello")
'hELLO!'
提示

當使用 | 語法組合時,LCEL 會自動將函式 parse 升級為 RunnableLambda(parse)

如果您不喜歡這樣,您可以手動匯入 RunnableLambda,然後執行 parse = RunnableLambda(parse)

串流是否有效?

for chunk in chain.stream("tell me about yourself in one sentence"):
print(chunk, end="|", flush=True)
i'M cLAUDE, AN ai ASSISTANT CREATED BY aNTHROPIC TO BE HELPFUL, HARMLESS, AND HONEST.|

否,它無效,因為解析器會在解析輸出之前彙總輸入。

如果我們要實作串流解析器,我們可以讓解析器接受輸入的可迭代物件,並在結果可用時產生結果。

from langchain_core.runnables import RunnableGenerator


def streaming_parse(chunks: Iterable[AIMessageChunk]) -> Iterable[str]:
for chunk in chunks:
yield chunk.content.swapcase()


streaming_parse = RunnableGenerator(streaming_parse)
API 參考:RunnableGenerator
重要

請將串流解析器包裝在 RunnableGenerator 中,因為我們可能會停止使用 | 語法自動升級它。

chain = model | streaming_parse
chain.invoke("hello")
'hELLO!'

讓我們確認串流是否有效!

for chunk in chain.stream("tell me about yourself in one sentence"):
print(chunk, end="|", flush=True)
i|'M| cLAUDE|,| AN| ai| ASSISTANT| CREATED| BY| aN|THROP|IC| TO| BE| HELPFUL|,| HARMLESS|,| AND| HONEST|.|

從解析基底類別繼承

實作解析器的另一種方法是繼承 BaseOutputParserBaseGenerationOutputParser 或其他基底解析器之一,具體取決於您需要執行的操作。

一般而言,我們建議在大多數使用案例中使用此方法,因為它會導致更多程式碼需要編寫,而沒有顯著的好處。

最簡單的輸出解析器類型擴展了 BaseOutputParser 類別,並且必須實作以下方法

  • parse:取得模型的字串輸出並解析它
  • (選用)_type:識別解析器的名稱。

當聊天模型或 LLM 的輸出格式錯誤時,可以拋出 OutputParserException 以指示解析由於錯誤的輸入而失敗。 使用此例外狀況可讓利用解析器的程式碼以一致的方式處理例外狀況。

解析器是可執行物件! 🏃

由於 BaseOutputParser 實作了 Runnable 介面,因此您以此方式建立的任何自訂解析器都將成為有效的 LangChain 可執行物件,並受益於自動非同步支援、批次介面、記錄支援等。

簡單解析器

這是一個簡單的解析器,可以解析布林值的字串表示形式(例如,YESNO),並將其轉換為對應的 boolean 類型。

from langchain_core.exceptions import OutputParserException
from langchain_core.output_parsers import BaseOutputParser


# The [bool] desribes a parameterization of a generic.
# It's basically indicating what the return type of parse is
# in this case the return type is either True or False
class BooleanOutputParser(BaseOutputParser[bool]):
"""Custom boolean parser."""

true_val: str = "YES"
false_val: str = "NO"

def parse(self, text: str) -> bool:
cleaned_text = text.strip().upper()
if cleaned_text not in (self.true_val.upper(), self.false_val.upper()):
raise OutputParserException(
f"BooleanOutputParser expected output value to either be "
f"{self.true_val} or {self.false_val} (case-insensitive). "
f"Received {cleaned_text}."
)
return cleaned_text == self.true_val.upper()

@property
def _type(self) -> str:
return "boolean_output_parser"
parser = BooleanOutputParser()
parser.invoke("YES")
True
try:
parser.invoke("MEOW")
except Exception as e:
print(f"Triggered an exception of type: {type(e)}")
Triggered an exception of type: <class 'langchain_core.exceptions.OutputParserException'>

讓我們測試變更參數化

parser = BooleanOutputParser(true_val="OKAY")
parser.invoke("OKAY")
True

讓我們確認其他 LCEL 方法是否存在

parser.batch(["OKAY", "NO"])
[True, False]
await parser.abatch(["OKAY", "NO"])
[True, False]
from langchain_anthropic.chat_models import ChatAnthropic

anthropic = ChatAnthropic(model_name="claude-2.1")
anthropic.invoke("say OKAY or NO")
API 參考:ChatAnthropic
AIMessage(content='OKAY')

讓我們測試我們的解析器是否有效!

chain = anthropic | parser
chain.invoke("say OKAY or NO")
True
注意

解析器將適用於來自 LLM 的輸出(字串)或來自聊天模型的輸出(AIMessage)!

解析原始模型輸出

有時,模型輸出上除了原始文字之外,還有其他重要的中繼資料。 其中一個範例是工具呼叫,其中預期傳遞給呼叫函式的引數會以單獨的屬性傳回。 如果您需要更精細的控制,您可以改為子類別化 BaseGenerationOutputParser 類別。

此類別需要單一方法 parse_result。 此方法會取得原始模型輸出(例如,GenerationChatGeneration 列表),並傳回已解析的輸出。

同時支援 GenerationChatGeneration 可讓解析器與常規 LLM 以及聊天模型一起運作。

from typing import List

from langchain_core.exceptions import OutputParserException
from langchain_core.messages import AIMessage
from langchain_core.output_parsers import BaseGenerationOutputParser
from langchain_core.outputs import ChatGeneration, Generation


class StrInvertCase(BaseGenerationOutputParser[str]):
"""An example parser that inverts the case of the characters in the message.

This is an example parse shown just for demonstration purposes and to keep
the example as simple as possible.
"""

def parse_result(self, result: List[Generation], *, partial: bool = False) -> str:
"""Parse a list of model Generations into a specific format.

Args:
result: A list of Generations to be parsed. The Generations are assumed
to be different candidate outputs for a single model input.
Many parsers assume that only a single generation is passed it in.
We will assert for that
partial: Whether to allow partial results. This is used for parsers
that support streaming
"""
if len(result) != 1:
raise NotImplementedError(
"This output parser can only be used with a single generation."
)
generation = result[0]
if not isinstance(generation, ChatGeneration):
# Say that this one only works with chat generations
raise OutputParserException(
"This output parser can only be used with a chat generation."
)
return generation.message.content.swapcase()


chain = anthropic | StrInvertCase()

讓我們使用新的解析器! 它應該反轉模型的輸出。

chain.invoke("Tell me a short sentence about yourself")
'hELLO! mY NAME IS cLAUDE.'

此頁面是否對您有幫助?