Output Parser (出力パーサー)

LLM言語モデルはテキスト形式でコンテンツを出力しますが、AIアプリケーションを開発する際には、結果を対象オブジェクト、配列などに変換するなど、整形されたコンテンツを受け取りたいと考えています。これには、LangChainが提供する出力パーサーが必要です。

出力パーサーの機能は、言語モデルによって返される結果をフォーマットすることです。出力パーサーには、次の2つの必須メソッドを実装する必要があります。

  • "get_format_instructions": 言語モデルがどのようなフォーマットで返すかについての命令を含む文字列を返します。
  • "parse": モデルによって返されたコンテンツを対象の形式にパースします。

それではLangChainで組み込まれた出力パーサーを見てみましょう。

Pydantic Parser(Pydanticパーサー)

以下はLangChainによってカプセル化されたコアの出力パーサー「PydanticOutputParser」です。このパーサーはPythonのpydanticライブラリをベースにしており、モデルの出力結果をPythonオブジェクトに変換するために使用されます。

from langchain.output_parsers import PydanticOutputParser
from langchain.prompts import PromptTemplate
from langchain_core.pydantic_v1 import BaseModel, Field, validator
from langchain_openai import OpenAI

model = OpenAI(model_name="gpt-3.5-turbo-instruct", temperature=0.0)

class Joke(BaseModel):
    setup: str = Field(description="ジョークの設定を表す質問")
    punchline: str = Field(description="ジョークの答え")

    @validator("setup")
    def question_ends_with_question_mark(cls, field):
        if field[-1] != "?":
            raise ValueError("質問の形式が正しくありません!")
        return field

parser = PydanticOutputParser(pydantic_object=Joke)

prompt = PromptTemplate(
    template="ユーザーのクエリに回答してください。\n{format_instructions}\n{query}\n",
    input_variables=["query"],
    partial_variables={"format_instructions": parser.get_format_instructions()},
)

prompt_and_model = prompt | model
output = prompt_and_model.invoke({"query": "ジョークを教えて。"})
parser.invoke(output)

サンプルの返り値:

Joke(setup='Why did the chicken cross the road?', punchline='To get to the other side!')

LCEL インタフェース

Runnable インタフェース

出力パーサーはRunnableインタフェースを実装しており、これはLangChain Expression Language(LCEL)の基本的な構成要素の1つです。invokeainvokestreamastreambatchabatchastream_logなどの呼び出しメソッドをサポートしています。

LCELでの出力パーサーの適用

出力パーサーは文字列またはBaseMessageを入力として受け取り、任意のタイプの構造化されたデータを返します。出力パーサーをRunnableシーケンスに追加してパーサーチェーンを構築し、呼び出すことができます。

chain = prompt | model | parser
chain.invoke({"query": "ジョークを教えて。"})

返り値

Joke(setup='Why did the chicken cross the road?', punchline='To get to the other side!')

一部のパーサーはSimpleJsonOutputParserのように部分的なパースオブジェクトをストリーミングできますが、他のパーサーはストリーミングをサポートしていません。最終的な出力は、パーサーが部分的なパースオブジェクトを構築できるかどうかに依存します。

from langchain.output_parsers.json import SimpleJsonOutputParser

json_prompt = PromptTemplate.from_template(
    "質問:{question}に答える`answer`キーを持つJSONオブジェクトを返す"
)
json_parser = SimpleJsonOutputParser()
json_chain = json_prompt | model | json_parser
list(json_chain.stream({"question": "顕微鏡は誰が発明しましたか?"}))
[{},
 {'answer': ''},
 {'answer': 'Ant'},
 {'answer': 'Anton'},
 {'answer': 'Antonie'},
 {'answer': 'Antonie van'},
 {'answer': 'Antonie van Lee'},
 {'answer': 'Antonie van Leeu'},
 {'answer': 'Antonie van Leeuwen'},
 {'answer': 'Antonie van Leeuwenho'},
 {'answer': 'Antonie van Leeuwenhoek'}]

LCELでは、さまざまな要件を満たすために異なるパーサーを組み合わせることで、複雑なデータ処理フローを構築することができます。