ここまで
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種類あって、
- 辞書型
- なんやら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
ってクラスで保持する情報の型定義します。
で、肝はField
とBaseModel
-
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)
name='hogehoge' age=20 dt=datetime.datetime(2024, 11, 12, 12, 42, 26, 817299)
一つ一つ取り出すにはこんな感じ
print(user_info.name)
'hogehoge'
<<おまけ>>model_dump
関数を使えば辞書化
print(user_info.model_dump())
{'name': 'hogehoge', 'age': 20, 'dt': datetime.datetime(2024, 11, 12, 12, 42, 26, 817299)}
今はBaseModel
を使っているので、Jsonチックに取り扱いできますが、model_dump()
関数を使うと辞書型に変換されます。
ちょっと意地悪する
user_info2 = UserInfo(name="1243", age='20', dt='2024-11-10')
user_info2
UserInfo(name='1243', age=20, dt=datetime.datetime(2024, 11, 10, 0, 0))
変換された。すげぇ
UserInfo(name=1243, age=20, dt=datetime.datetime(2024, 11, 10, 0, 0))
ValidationError: 1 validation error for UserInfo
name
Input should be a valid string [type=string_type, input_value=1243, input_type=int]
エラー吐いた。よくわからん。(気にしない)
ま、サービス作るなら例外処理をしろって事かな。
input = UserInfo(name='ikedachin')
input
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を以前のバージョンでも使えるようにしたライブラリらしい。これはありがたい。
class UserInfoDict(TypedDict):
name: str = Field(..., description='name')
age: int = Field(default=0, description='age')
dt: datetime = Field(default=datetime.now(), description='input date')
user_info_dict = UserInfoDict(name='ikedachin_dict')
user_info_dict
{'name': 'ikedachin_dict'}
あり?デフォルト値は効かないみたい。
print(user_info_dict.keys())
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)
State1(name='ageage', age=8, info=FieldInfo(annotation=NoneType, required=False, default='', description='情報'))
なんや色々と増えとる。初期設定の情報も取得できそうね。
print(user_info.name)
print(user_info.name.description)
'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
こうすると決まった形式で取り出せるし、情報を追記できる。これが狙いっす。
user_info2 = test_basemodel(user_info)
print(user_info2)
name='ageage' age=8 info='ageage_8'
なんとなくわかってきましたよね
state
ってやつに情報を持たせたい
そしてそのstate
の情報を受け取って、新しい情報に更新したり、追加したり。
そしてまたstate
を返り値として返す事で情報をつないでいくんですね。
そのstate
には情報を適当に持たせるのではなくて、情報ごとに整理したい。
そのための型定義なんでしょう。このstate
は小さなデータベースだなって感じました。
さて、次はこのstate
を引継ぎながら動かすLangGraphをやってみましょう。
まとめ
Pydanticというかっこいいライブラリの使い方を勉強しました。
上手に使えばちっちゃなデータベースの様なものができる。僕はそう理解しました。
ではでは