前回は Prompty から LLM を呼び出す基本的な話をしましたので、今回はもう少し実践的に Python のコードから Prompty を活用する方法を書いていきます。
通常のアプリや FastAPI や Azure Functions などから Prompty を活用する際に、前回書いてないことで考慮したい最低限の基礎知識は以下くらいと思っています。
- Prompty に必要に応じてチャット履歴を含める。
- Prompty で使っていた LLM への接続情報を、アプリで使っている環境変数へ上書きする
- Prompty で使っていた LLM へアクセスする際のパラメーター (
max_tokens
など) を上書きする
型定義をきっちりやっていきたいところですが、コード量が一気に増えるので今回はやめておきました。
これらを加味した実装をしてみましょう。
フォルダ構成
今回実装するコードのフォルダ構成です。といっても以下は個人的趣味な構成なので説明するほどでもないのですが一応説明しておきます。
- agents フォルダ: この下に、目的に応じた複数のエージェントとなるやつらを並べて使う想定です。
- agents\chat フォルダ: ここに一人目のエージェントとして chat さんの prompty を置いています。フォルダ構造をそのままパッケージとして扱いたいので直下に
__init__.py
を置きます。エージェントひとりを構成するのは基本的にこの2つのファイルが中心になります。 - main.py: ここからエージェントを呼び出すイメージです。これは FastAPI や Azure Functions でも同様の実装を使えます。
|--agents | |--chat | |-- __init__.py | |-- chat.prompty |-- .env |-- main.py |-- requirements.txt
実装編
前述のフォルダ構成で説明したファイルを実装していきます。
モジュールのインストール: requirements.txt
Prompty をシンプルに Python のコードから使うのに、2024年7月の頭くらいまでは promptflow-core を使うのが主流でした (主流と言われるほど使われてなさそうではある)。
そのほかに langchain-prompty や C# だと Semantic Kernel の SDK からとかですかね。Python で Semantic Kernel 使いたいモチベが未だ出たことないからそっちは知らないですが。
そして2024年7月上旬に、より軽量なモジュールである prompty が爆誕したので今回はこちらを使います。
すぐにでも本番環境で使いたい場合は、できたばかりの prompty よりもリリース履歴的に安定していると思われる promptflow-core を使った方がいいかもしれませんね。prompty は実装もシンプルで大したことやってるわけではないので個人的にはどっちでもいいと思ってますが。コードから使う際の実装方法はあんまり変わりません。どちらを使うかは自己責任ってやつですね。
ということで今回の requirements.txt には以下の2つを書いておきます。
prompty python-dotenv
環境変数のセットアップ: .env
コードで使う環境変数を .env に書いておきます。今回のサンプルコードで利用するキーは以下の4つなので、(一部適当な値入ってますが) この4つに自身で利用できる値をセットしておきます。
AOAI_ENDPOINT= AOAI_API_KEY= AOAI_API_VERSION=2024-06-01 AOAI_DEPLOYMENT_GPT=gpt-4o-mini
今回の Prompty: agents\chat\chat.prompty
agents\chat
直下に今回使う chat.prompty をおき、その中のコードは以下です。チャット履歴を含める想定なので以下を考慮して書いています。
sample
>chat_history
で、23行目に書いてあるようなチャットの履歴 (role
とcontent
の list) を渡す。- システムメッセージ (
system:
) と一番下のユーザーメッセージ (user:
) の間に Jinja2 の for (34 - 37行目) で動的にチャット履歴を挿入する。 - 私のどうでもよい好みの話になりますが、model の config はアプリで統制されることが多いので引数で受けとり、LLM へ渡す parameters はエージェントの特性次第なのでこのファイルで管理しています。
agents\chat\__init__.py
agents\chat
の直下に __init__.py
を置いて chat をパッケージとしています。 __init__.py
の実装は以下です。ざっくり解説は...
- Prompty のファイルからコードにするのを 28 - 32行目でやってます。
- 実際の LLM とのやり取りは15 - 24行目です。17行目の
execute()
でキーワード引数で渡しておりその名称のままですが...prompt
: 今回利用する prompt を渡します。configuration
: Prompty の中で定義している configuration を書き換えたいときにdict[str, any]
型で渡します。parameters
: Prompty の中で定義している parameters を書き換えたいときにdict[str, any]
型で渡します。inputs
: Prompty への inputs をdict[str, any]
型で渡します。
これだけなので非常に簡単です。つまりより一層 Prompty を使うモチベーションになるわけです。
main.py
あとは main.py から呼び出すだけです。
- 1行目で import して26行目で呼び出すだけのシンプルな実装です。
- inputs になる
context
やchat_history
は、実践的にはデータストアから取得する想定なので関数から呼び出しています。
これでターミナルから pip install -r requirements.txt
して python main.py
とかたたいて実行するとこんな感じ。
FastAPI や Azure Functions からも同様に呼び出せるのと、__init__.py
の実装はほとんど変わらず、実践的に使うにしても引数と戻り値の型をちゃんと定義しておくくらいなので、非常に汎用性が高く使いやすいです。
終わりに
今回はプリミティブな方法だけど汎用的なコード書きました。
LangChain や Semantic Kernel から利用する際も実装方法も雰囲気は一緒なので (Prompty のファイルを指定してインスタンス化するだけ) 、今回の雰囲気さえつかめば簡単です。