2
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?

LangChain v0.3 その5 ~LangGraphの前に`Pydantic`~

Last updated at Posted at 2024-11-12

ここまで

LangChainを使って、色々と機能マシマシにしたい人、たくさんいますよね。
僕ものその一人。目的はここには書かないけど、LangChainって目的達成と合わせてプログラミングの理解にもいいと思います。
あ、今日のところは、ちゃんとclassについては理解しておいた方がいいと思う。

classの理解はこの講座がわかりやすかった。マジおすすめ。

なぜここでPydantic?

ちょっと遠回りしましょう。というのも、僕も行き詰まったから。
急がば回れ

このPydanticが理解できていれば次にやろうとしているLangGraphがわかりやすい。

ほら、ここ↑にもPydanticが必須って書いてあるでしょ!
だからやるしかないのよ!

というのは雑なので、少し説明

LLMの入出力ステートレスじゃないですか。
そう、使って終わったらその情報は出力先に行ってしまって、過去の内容は保持していないっていう。
でもさ、チャットとか、これから使いたいLangChainって状態を保持しておかないと会話がちゃらんぽらん。
つまり、会話履歴だったり、何かの判断結果だったり、前提条件だったり、保持しておかなければならない。そのために決まった書式で残しておくと後で使いやすい。この保持するべき情報をLangChainではstateって呼んでます。
このstateには何でもかんでも入れちゃったら意味わかんないので、

  • どの変数にはどんな型で
  • デフォルト値は何で
  • 上書きするのか、情報を追加するのか

そんなことをつらつらと定義します。そのために、Pydanticを使ったら便利だよってこと。

参考ページ

参考にしたのはこちらのページ

基本的にはこのページをベースに理解を進めます。ありがたや

そしてこちらの書籍を買ったんですが、おすすめです。

245ページ目から250ページが次にやるLangGraph
これが目標なんですが、わかりやすかった!

バージョン関連

Python 3.10.8
langchain==0.3.7
python-dotenv
langchain-openai==0.2.5
langgraph>0.2.27
langchain-core

※LLMのAPIはAzureOpenAIのgpt-4o-miniを使いました

Pydanticを使いながら理解

知らなかったんだけど、Pydanticって標準ライブラリなんですね。

ライブラリのインポート
from datetime import datetime
from pydantic import BaseModel, Field
from zoneinfo import ZoneInfo

型定義の方法は2種類あって、

  1. 辞書型
  2. なんやらJsonっぽいやつ

です。まずはJsonっぽいBaseModelを継承してつかいます。代わりにTypedDictでもできますが、入力した後の処理が変わります。BaseModelの場合はJsonの様に取り出しができますが、TypedDictだと辞書のように取り出します。それは後ほど。

BaseModelを使った場合(Jsonっぽいやつ)

型を宣言
class UserInfo(BaseModel):
    name: str = Field(..., description='name')
    age: int = Field(default=0, description='age')
    dt: datetime = Field(default=datetime.now(), description='input date')

何をやってるかっていうとUserInfoってクラスで保持する情報の型定義します。
で、肝はFieldBaseModel

  • nameは文字型(str)
  • ageは整数型(int)
  • dtはデータタイム型(datetime)

で指定しています。そして、Fieldでは初期値を設定できます。ここのnameのところは...になってるのは「必須だけど、初期値は指定しないよ」ってことです。

わかると思うけど、初期値はdefault=***で設定します。
Fieldの設定をしない場合は空になるのですが、入力したらそこに値が入っていきますが、他の値は存在しません。
Fieldで設定した場合は初期値として何かしら入るのでより決まった形式になります。

ちなみに、次回、多分「その6」でやるLangGraphを使った処理ではField設定しなかった場合とした場合では結果が変わります。
試してみると面白いのでぜひお試しください。(今日はやめとく)

情報を渡したインスタンスを作る
dt = datetime.now()

user_info = UserInfo(name="hogehoge", age=20, dt=dt)

print(user_info)
output
name='hogehoge' age=20 dt=datetime.datetime(2024, 11, 12, 12, 42, 26, 817299)

一つ一つ取り出すにはこんな感じ

print(user_info.name)
output
'hogehoge'

<<おまけ>>model_dump関数を使えば辞書化

print(user_info.model_dump())
output
{'name': 'hogehoge', 'age': 20, 'dt': datetime.datetime(2024, 11, 12, 12, 42, 26, 817299)}

今はBaseModelを使っているので、Jsonチックに取り扱いできますが、model_dump()関数を使うと辞書型に変換されます。

ちょっと意地悪する

年齢にstrを入れてみる
user_info2 = UserInfo(name="1243", age='20', dt='2024-11-10')
user_info2
output
UserInfo(name='1243', age=20, dt=datetime.datetime(2024, 11, 10, 0, 0))

変換された。すげぇ

nameにintを入れてみた
UserInfo(name=1243, age=20, dt=datetime.datetime(2024, 11, 10, 0, 0))
output
ValidationError: 1 validation error for UserInfo
name
  Input should be a valid string [type=string_type, input_value=1243, input_type=int]

エラー吐いた。よくわからん。(気にしない)
ま、サービス作るなら例外処理をしろって事かな。

nameだけ指定
input = UserInfo(name='ikedachin')
input
output
UserInfo(name='ikedachin', age=0, dt=datetime.datetime(2024, 11, 11, 20, 55, 42, 616638))

ageとdtにはデフォルト値

TypedDictを継承した場合(辞書型)

from datetime import datetime
from pydantic import BaseModel, Field
from typing_extensions import TypedDict

typing_extentionsはPython3.11以降使えるようになったPydanticを以前のバージョンでも使えるようにしたライブラリらしい。これはありがたい。

継承するクラスはTypedDict
class UserInfoDict(TypedDict):
    name: str = Field(..., description='name')
    age: int = Field(default=0, description='age')
    dt: datetime = Field(default=datetime.now(), description='input date')
nameだけ指定してみる
user_info_dict = UserInfoDict(name='ikedachin_dict')
user_info_dict
output
{'name': 'ikedachin_dict'}

あり?デフォルト値は効かないみたい。

キーを見てみる
print(user_info_dict.keys())
output
dict_keys(['name'])

確かに、初期設定したキーが存在しない。

そして、langchian_coreのPydanticを使ってみる

いよいよです。はい
行ってみよう

ライブラリインポート
from datetime import datetime
from langchain_core.pydantic.v1 import BaseModel
型宣言
class State1(BaseModel):
    name : str = Field(..., description='名前')
    age: int = Field(default=0, description='年齢')
    info: str = Field(default='', description='情報')
使ってみる
user_info = State1(name='ageage')
print(user_info)
output
State1(name='ageage', age=8, info=FieldInfo(annotation=NoneType, required=False, default='', description='情報'))

なんや色々と増えとる。初期設定の情報も取得できそうね。

print(user_info.name)
print(user_info.name.description)
output
'ageage'
'名前'

LangGraphに向けて練習

LangGraphは関数をノードとして、ノードとノードをエッヂで繋ぐ。
そのノードの練習です。

def test_basemodel(state: State1): # BaseModelを継承したクラスで定義したstateを使う
    name_info = state.name
    age_info = state.age
    state.info = f'{name_info}_{str(age_info)}' # name_ageの形にしてinfoに入力
    return state

こうすると決まった形式で取り出せるし、情報を追記できる。これが狙いっす。

test_basemodelにuser_infoを渡してみる
user_info2 = test_basemodel(user_info)
print(user_info2)
output
name='ageage' age=8 info='ageage_8'

なんとなくわかってきましたよね
stateってやつに情報を持たせたい
そしてそのstateの情報を受け取って、新しい情報に更新したり、追加したり。
そしてまたstateを返り値として返す事で情報をつないでいくんですね。

そのstateには情報を適当に持たせるのではなくて、情報ごとに整理したい。
そのための型定義なんでしょう。このstateは小さなデータベースだなって感じました。

さて、次はこのstateを引継ぎながら動かすLangGraphをやってみましょう。

まとめ

Pydanticというかっこいいライブラリの使い方を勉強しました。
上手に使えばちっちゃなデータベースの様なものができる。僕はそう理解しました。

ではでは

2
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
2
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?