OpenAI「Agents SDK」④コンテキスト管理
以下の続き。
今回は「コンテキスト管理」
コンテキスト管理
一口に「コンテキスト」と言っても色んな意味がある。ここでは主に以下の2つ。
- コードからローカルに利用できるコンテキスト
- ツール関数の実行時に、
on_handoff
のようなコールバック、ライフサイクルフックなどで必要になるデータや依存関係
- ツール関数の実行時に、
- LLMに利用できるコンテキスト
- 応答を生成する際にLLMが参照できるデータ
それぞれの観点で見ていく。
ローカルコンテキスト
RunContextWrapper
暮らさうとその中のcontext
プロパティで表される。以下のような動作となる。
- 任意のPythonオブジェクトを作成する。dataclassやPydanticオブジェクトなど
- そのオブジェクトを実行メソッド(
Runner.run(..., context=XXXX)
)に渡す。 - すべてのツール呼び出しやライフサイクルフックに、ラッパーオブジェクト
RunContextWrapper[T]
が渡され、wrapper.context
でアクセスできる(T
はコンテキストオブジェクトの型。)
重要なのは、特定のエージェント実行においては、全てのエージェント、ツール関数、ライフサイクルなどは、同じ方のコンテキストを使用しなければならない、という点。
コンテキストの用途は以下。
- 実行のための状況/状態データ(例: ユーザ名、uid、その他ユーザに関する情報など)
- 依存関係(例: loggerオブジェクト、データ取得コンポーネントなど)
- ヘルパー関数
なお、コンテキストオブジェクトはLLMには送信されず、ローカル内でのみ使用可能なオブジェクトであり、読み書き・メソッドでの呼び出しなどが行える。
サンプル
import asyncio
from dataclasses import dataclass
from agents import Agent, RunContextWrapper, Runner, function_tool
@dataclass
class UserInfo:
name: str
uid: int
@function_tool
async def fetch_user_age(wrapper: RunContextWrapper[UserInfo]) -> str:
"""ユーザの年齢を取得する。ユーザの年齢に関する情報を取得する場合はこのツールを呼び出す。"""
return f"ユーザ {wrapper.context.name} は、47歳です。"
async def main():
# ユーザのコンテキストを定義
user_info = UserInfo(name="太郎", uid=123)
# エージェントを定義
agent = Agent[UserInfo](
name="Assistant",
tools=[fetch_user_age],
)
# コンテキストを渡して、エージェントを実行
result = await Runner.run(
agent,
input="ユーザについて知っていることを全部教えて。",
context=user_info,
)
print(result.final_output)
if __name__ == "__main__":
asyncio.run(main())
ユーザは「太郎」さんで、47歳です。それ以外の情報は持っていません。
コンテキストでユーザ名とUIDを渡す→ツール内からコンテキストにアクセスして結果を返す→LLMが回答を返す、という感じ。
コンテキストオブジェクトは LLM に 送信されません。ローカル専用のオブジェクトであり、読み書きやメソッド呼び出しができます。
というところについても、例えば上記の例だとコンテキストとしてUIDが渡されているが、あくまでもこれにアクセスできるのはツールであって、エージェント(LLM)ではない。なので、何かしらそれを読み出すようなことがなければ、エージェントは知り得ない、ということになるのだと思う。
エージェント / LLM コンテキスト
LLMは基本的に会話履歴にしかアクセスできない。これ以外の新しいデータをLLMに参照させる場合には、その履歴を経由して提供する必要がある。例えば以下。
- エージェントの
instructions
- 「システムプロンプト」または「開発者メッセージ」
- スタティックな文字列、または、コンテキストを受け取って動的に文字列を返すような関数
- 常に有用な情報に最適(ユーザ名や現在の日付)
-
Runner.run()
実行時の入力-
instructions
に追加するのと似ている - ただし、モデルスペックにある "chain of command" の下位にあるメッセージをもたせる事が可能
- つまり「ユーザープロンプト」「ユーザメッセージ」
-
- 関数ツールで公開
- オンデマンドのコンテキスト向け
- LLMが必要と判断した際に必要な情報を取得する
- リトリーバルやWeb検索
- ファイルやデータベース、Webから関連するデータを取得する
- 関連情報を使って**応答の内容を「グラウンディング」**するのに有用
余談だが、このあたりの使い分け、みたいなのがやりやすい、ってのはフレームワークにも求められるところだと思う。先日、とあるフルスタックフレームワークをいじっていたのだけど、コンテキストをいろんなやり方で注入しようとしたら、仕様上難しかったことがある(そのフレームワーク的にはコンテキストをいろんなやり方で注入する、というよりはある程度一つのやり方でまるっと全部いれる、みたいな思想だった)
コンテキストについては、エージェントのところでやった「動的な指示」も参考に。
次はモデル