0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【0から目指すAIエンジニア】Day6:OutputParserの基礎

Posted at

はじめに

こんにちは、慶應義塾大学理工学部機械工学科3年の金賢佑です!
私は東大発AIベンチャーの株式会社2WINSの元でインターンをしています!

これからの記事では自分がインターンを通して最強のAIエンジニアを目指していきます!
第六回はOutputParserの基礎を記録しました!

OutputParserとは?

OutputParserとはLangChainのコンポーネントの一つ!
言語モデルの出力を特定の形に変換してくれる!

いろんな種類があって、たとえば
StructuredOutputParserは出力をJSON形式にしてくれる!
CommaSeparatedListOutputParserはリストに変換してくれる!
PydanticOutputParserは型のついたデータにしてくれる!

今回はPydanticOutputParserを使ってみるよ!

PydanticOutputParserを使ってみよう

以下では急にPydanticライブラリが出てくるから先に説明しておくよ!

Pydanticって?

PydanticPythonでデータの型を決めて扱うためのライブラリ📦!

例えばPydanticライブラリの中のBaseModelというクラスを使ってみるよ!

from pydantic import BaseModel

class Recipe(BaseModel):  # ← BaseModel を継承!
    ingredients: list[str]
    steps: list[str]

だから以下のようにルール通りにデータを渡すとちゃんとうまくいきますね!

data = {
  "ingredients": ["たまねぎ", "にんじん"],
  "steps": ["切る", "炒める"]
}

recipe = Recipe(**data)

print(recipe.ingredients)  # → ['たまねぎ', 'にんじん']
print(recipe.steps)        # → ['切る', '炒める']

だけどルールと違う以下のような場合もエラーになる!

Recipe(
    ingredients="たまねぎ", 'にんじん'
    steps=["切る", 1000]
)

まずはRecipeクラスを作成しよう!

from pydantic import BaseModel, Field

class Recipe(BaseModel):
  ingredients: list[str] = Field(description='ingredients of the dish')
  steps: list[str] = Field(description='steps to make the dish')

class Recipe(BaseModel):Recipeという名前のクラスを定義してるよ!

ingredients: list[str]ingredientsには「文字列のリスト」しか加えられないことを意味している!
Fieldで説明を付け加えている。

次はこのコードを追加してください!

from langchain_core.output_parsers import PydanticOutputParser

output_parsers = PydanticOutputParser(pydantic_object=Recipe)

format_instructions = output_parsers.get_format_instructions()
print(format_instructions)

PydanticOutputParserは出力をRecipe型に変換するよって定義している感じ!

get_format_instructions()はLLMに「さっき定義したRecipe型で答えてね〜」と指定している感じ!

だから、print(format_instruction)での出力は「正しい出力」と「間違った出力」の例を出している!

The output should be formatted as a JSON instance that conforms to the JSON schema below.

As an example, for the schema {"properties": {"foo": {"title": "Foo", "description": "a list of strings", "type": "array", "items": {"type": "string"}}}, "required": ["foo"]}
the object {"foo": ["bar", "baz"]} is a well-formatted instance of the schema. The object {"properties": {"foo": ["bar", "baz"]}} is not well-formatted.

ではこのformat_instructionsを使ってChatPromptTemplateを作成します。

from langchain_core.prompts import ChatPromptTemplate
prompt = ChatPromptTemplate(
    [
        (
            'system',
            'ユーザーが入力した料理のレシピを教えてください。\n\n'
            '{format_instructions}',
        ),
        ('human', '{dish}'),

    ]
)

prompt_with_format_instructions = prompt.partial(
   format_instructions=format_instructions
)

systemformat_instructionsで答えてね、と伝えてる!
'human', '{dish}'でユーザーが聞きたい料理名を入力できる

format_instructionsの部分にはいつも同じものが入るはず!いちいち形式を指定するのは面倒くさいからprompt.partialで固定しちゃう!
ちなみに復習するとこれが入っているよ

format_instructions = output_parsers.get_format_instructions()

では次!

prompt_value = prompt_with_format_instructions.invoke({'dish':'カレー'})

from langchain_openai import ChatOpenAI
model = ChatOpenAI(model='gpt-4o', temperature=0)

ai_message = model.invoke(prompt_value)

GPT-4oを使ってプロンプトを送信した!
今回でいうと「カレーのレシピを指定したJSON形式で答えて〜」ってお願いしている!

recipe = output_parsers.invoke(ai_message)
print(type(recipe))
print(recipe)

最終的に返ってきたテキストをRecipeオブジェクトに変換しました!

#出力結果
<class '__main__.Recipe'>
ingredients=['鶏肉 300g', '玉ねぎ 2', 'にんじん 1', 'じゃがいも 2', 'カレールー 1', ' 600ml', 'サラダ油 大さじ1', ' 少々', 'こしょう 少々'] steps=['鶏肉玉ねぎにんじんじゃがいもを一口大に切る', '鍋にサラダ油を熱し鶏肉を炒める', '鶏肉の色が変わったら玉ねぎにんじんじゃがいもを加えて炒める', '全体に油が回ったら水を加えて煮立たせる', 'アクを取り除き弱火で野菜が柔らかくなるまで煮る', '火を止めてカレールーを加え溶かす', '再び弱火で煮込み塩とこしょうで味を調える', 'お好みのとろみになるまで煮込んで完成']

これでPythonでデータとして扱うことができます!

format_instructionsがないとどうなるのか

なんだかPydanticOutputParserで「Recipe型で返してね」って定義しているのにformat_instructionでもまた、「Recipe型で返してね〜」って指示を出してるの、なんだか気持ち悪くありませんか?

そこで試しにformat_instructionsを消してみました。
実行例は以下の通りです。

from pydantic import BaseModel, Field

class Recipe(BaseModel):
  ingredients: list[str] = Field(description='ingredients of the dish')
  steps: list[str] = Field(description='steps to make the dish')

from langchain_core.output_parsers import PydanticOutputParser

output_parsers = PydanticOutputParser(pydantic_object=Recipe)

# format_instructions = output_parsers.get_format_instructions()
# print(format_instructions) <- 消しました

from langchain_core.prompts import ChatPromptTemplate
prompt = ChatPromptTemplate.from_messages(
    [
        (
            'system',
            'ユーザーが入力した料理のレシピを教えてください。' # \n\n''{format_instructions}', <- も消しています
        ),
        ('human', '{dish}'),

    ]
)

# prompt_with_format_instructions = prompt.partial(
#    format_instructions=format_instructions
# ) <- もちろん消す

from langchain_openai import ChatOpenAI
model = ChatOpenAI(model='gpt-4o', temperature=0)

prompt_value = prompt.invoke({'dish': 'カレー'})
ai_message = model.invoke(prompt_value)

recipe = output_parsers.parse(ai_message.content)
print(recipe)

実行結果は何やらエラーが起きています。LangSmithで詳細を確認してみましょう。
左が前回まで、右が今回です。

alt text alt text

まず一枚目のシステムプロンプトについてですが、右だと、ただLLMに質問を投げかけているだけですね。
一方左はこういう型で返してねーとちゃんと伝えています。

二枚目の返答についても、ぱっと見二つとも返せているように見えますが、左とは違って右はただ文章を生成しているだけになっていますね。

(筆者はここで、また頭がこんがらがったので一応メモ程度に記しておきます。左の方、もう"ingredients""steps"にまとまっていてこれ以上何をパースするんだっけ?ってなりましたが、この"ingredients"はまだ文字列ですよね。だからこの後レシピ型にパースすることでしっかりingredientsと言うリストに入るんですね。)

これらのことからOutputParserは形の定義そのものであり、format_instructionsで指示をして初めて正常に動くことがわかりました!
いろいろ複雑ですね。。。

おさらい

from pydantic import BaseModel, Field

class Recipe(BaseModel):
    ingredients: list[str] = Field(description="ingredients of the dish")
    steps: list[str] = Field(description="steps to make the dish")

レシピの型を定義した!

from langchain_core.output_parsers import PydanticOutputParser
output_parsers = PydanticOutputParser(pydantic_object=Recipe)

出力をこの型にしてくれるParserを作成!

format_instructions = output_parsers.get_format_instructions()

LLMにこの形式で返答して!と指示書を作る!

prompt = ChatPromptTemplate([
    ('system', 'レシピを出力してください。\n\n{format_instructions}'),
    ('human', '{dish}')
])

prompt_with_format_instructions = prompt.partial(format_instructions=format_instructions)

プロンプトテンプレートを作成!

from langchain_openai import ChatOpenAI
model = ChatOpenAI(model='gpt-4o', temperature=0)

prompt_value = prompt_with_format_instructions.invoke({'dish': 'カレー'})
ai_message = model.invoke(prompt_value)

LLMに質問を投げる!

recipe = output_parsers.parse(ai_message.content)
print(recipe)

最後に返ってきたテキストをRecipe型に変換しておしまい!

まとめ

今回はLangChainコンポーネントの一つのOutputparserを使ってみました!

お疲れ様でした!

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?