はじめに
OpenAI Agents SDK がリリースされました(2025年3月11日)。公式ドキュメントの Quickstartを参考にして動作を確認しながら、気になる部分を深堀りしてみます。
検証に使用したライブラリのバージョンは openai-agents==0.0.6 です。
OpenAI Agents SDK の紹介
公式ドキュメントの Introから引用します。
OpenAI Agents SDK を使用すると、抽象化をほとんど行わずに、軽量で使いやすいパッケージでエージェント AI アプリを構築できます。これは、エージェント向けの以前の実験である Swarm の製品版アップグレードです。Agents SDK には、非常に小さなプリミティブ セットが含まれています。
- エージェントは指示とツールを備えたLLMです
- ハンドオフにより、エージェントは特定のタスクを他のエージェントに委任できます
- ガードレールはエージェントへの入力を検証できるようにする
Python と組み合わせることで、これらのプリミティブはツールとエージェント間の複雑な関係を表現するのに十分なほど強力になり、急な学習曲線なしで現実世界のアプリケーションを構築できます。さらに、SDK には組み込みのトレースが付属しており、エージェント フローを視覚化してデバッグできるだけでなく、評価したり、アプリケーション用にモデルを微調整したりすることもできます。
エージェントの構築には、モデル、ツール、知識と記憶、音声、ガードレール、オーケストレーションなど、複数のドメインにわたるコンポーネントの組み立てが必要です。OpenAI は、各ドメインに対して、組み合わせ可能なプリミティブを提供しています。
ドメイン | 説明 | OpenAI の提供要素 |
---|---|---|
Model | 推論、意思決定、さまざまなモダリティの処理が可能な、中核となる知能 | o1, o3-mini, GPT-4.5, GPT-4o, GPT-4o-mini |
Tool | 外部とのインターフェース。環境との対話、関数呼び出し、組み込みツールなど | 関数呼び出し、Web検索、ファイル検索、コンピュータ利用 |
Knowledge and memory | 外部の永続的な知識でエージェントを強化 | ベクターストア、ファイル検索、埋め込み |
Audio and speech | 音声を理解し、自然言語で応答できるエージェントを作成 | 音声生成、リアルタイム音声、音声エージェント |
Guardrails | 不適切、有害、または望ましくない動作を防止 | モデレーション、指示階層 |
Orchestration | エージェントの開発、デプロイ、監視、改善 | Agents SDK、トレース、評価、ファインチューニング |
まずは Agents、Handoff、Guardrails を調べると良さそうです。
この記事では、Tool、Knowledge、Audio は扱いません。
環境構築
早速やってみましょう。pip コマンドなどで PyPI からインストールできます。
$ pip install openai-agents
OpenAI プラットフォーム で OpenAI API の API KEY を取得します。前払い制なので OpenAI プラットフォームに軽く課金しておきます。とりあえず $1 ぐらいでも遊べます。
以下のコマンドで環境変数を設定します。(bash 等の場合)
$ export OPENAI_API_KEY=your_openai_api_key
Windows の場合は、スタートメニューで「環境変数」と入力してユーザーの環境変数を設定します。
コードの内部で python-dotenv を使って OPENAI_API_KEY を設定する場合は、agents パッケージを読み込む前に load_dotenv() する必要があります。
Hello World
公式ドキュメントを参考にしてコードを書きます。名前、命令、依頼文は日本語にしてみました。
from agents import Agent, Runner
agent = Agent(
name="アシスタント", instructions="あなたはとても親切なアシスタントです"
)
result = Runner.run_sync(
agent, "今夜の夕飯のメニューを考えて。豚肉と野菜があります。"
)
print(result.final_output)
実行します。python コマンドに -i オプションをつけて、実行後にインタラクティブモードへ移行させます。
$ python -i src/hello.py
いいですね!ヘルシーで美味しいメニューを考えてみましょう。
### 豚肉と野菜のメニュー
1. **豚肉と野菜の生姜焼き**
- **材料**: 豚肉、玉ねぎ、人参、ピーマン
- **調味料**: 醤油、みりん、酒、生姜
- **作り方**:
1. 豚肉を薄切りにし、生姜、醤油、みりん、酒で下味をつけます。
2. 玉ねぎ、人参、ピーマンを薄切りにします。
3. フライパンに油を熱し、豚肉を焼きます。
4. 野菜を加え、全体が混ざるように炒めます。
2. **豚肉と野菜の蒸し物**
- **材料**: 豚肉、キャベツ、しいたけ、ブロッコリー
- **調味料**: 塩、胡椒、ポン酢
- **作り方**:
1. キャベツをざく切りにし、しいたけとブロッコリーは適当なサイズに切ります。
2. 豚肉と野菜を蒸し器に並べ、塩と胡椒を振ります。
3. 約15分蒸します。
4. ポン酢をかけていただきます。
どちらのメニューも簡単に作れますし、バランスが良いのでおすすめです。
>>>
色々調べてみます。
agent インスタンスについて
agent インスタンスの概観を眺めてみます。
>>> from pprint import pprint
>>> pprint(agent)
Agent(name='アシスタント',
instructions='あなたはとても親切なアシスタントです',
handoff_description=None,
handoffs=[],
model=None,
model_settings=ModelSettings(temperature=None,
top_p=None,
frequency_penalty=None,
presence_penalty=None,
tool_choice=None,
parallel_tool_calls=False,
truncation=None,
max_tokens=None),
tools=[],
input_guardrails=[],
output_guardrails=[],
output_type=None,
hooks=None,
tool_use_behavior='run_llm_again')
コンストラクタの引数が表示されている感じです。ソースで指定したのは、name と instructions のみです。それ以外の引数はデフォルト値が入ってるのでしょう。model_settings とか model はモデルの指定っぽいです。その他の引数は先に進んだら考えてみます。
dir() でメソッドとかインスタンス変数を見てみます。_(アンダースコア) で始まるものは内部使用と考えられるので除外します。
pprint([x for x in dir(agent) if not x.startswith("_") ])
['as_tool',
'clone',
'get_system_prompt',
'handoff_description',
'handoffs',
'hooks',
'input_guardrails',
'instructions',
'model',
'model_settings',
'name',
'output_guardrails',
'output_type',
'tool_use_behavior',
'tools']
コンストラクタ引数と一致するものは後から差し替えるためのインタフェースなのかもしれません。
ここでAPIリファレンスを見てみると、dataclass になってました。clone() などはメソッドだと思いますが、基本的にインスタンス変数で構成されてると理解して良さそうです。
result について
Runner.run_sync() の結果オブジェクトを見てみます。
>>> type(result)
<class 'agents.result.RunResult'>
RunResult Class のインスタンスのようです。
>>> pprint(result)
RunResult(input='今夜の夕飯のメニューを考えて。豚肉と野菜があります。',
new_items=[MessageOutputItem(agent=Agent(name='アシスタント',
instructions='あなたはとても親切なアシスタントです', # 以下 Agent クラスのデフォルト値なので省略
),
raw_item=ResponseOutputMessage(id='msg_xxxxxxxxxxxxxxx', content=[ResponseOutputText(annotations=[], text='いいですね!ヘルシーで美味しいメニューを考えてみましょう。\n\n### 豚肉と・・・(応答文を省略)', type='output_text')], role='assistant', status='completed', type='message'),
type='message_output_item')],
raw_responses=[ModelResponse(output=[ResponseOutputMessage(id='msg_xxxxxxxxxxxxxxx', content=[ResponseOutputText(annotations=[], text='いいですね!ヘルシーで美味しいメニューを考えてみましょう。\n\n### 豚肉と・・・(応答文を省略)', type='output_text')], role='assistant', status='completed', type='message')],
usage=Usage(requests=1,
input_tokens=61,
output_tokens=372,
total_tokens=433),
referenceable_id='resp_xxxxxxxxxxxxxxx')],
final_output='いいですね!ヘルシーで美味しいメニューを考えてみましょう。\n'
'\n'
'### 豚肉と野菜のメニュー\n'
'\n'
'1. **豚肉と野菜の生姜焼き**\n'
' - **材料**: 豚肉、玉ねぎ、人参、ピーマン\n'
' - **調味料**: 醤油、みりん、酒、生姜\n'
' - **作り方**: \n'
' 1. 豚肉を薄切りにし、生姜、醤油、みりん、酒で下味をつけます。\n'
' 2. 玉ねぎ、人参、ピーマンを薄切りにします。\n'
' 3. フライパンに油を熱し、豚肉を焼きます。\n'
' 4. 野菜を加え、全体が混ざるように炒めます。\n'
'\n'
'2. **豚肉と野菜の蒸し物**\n'
' - **材料**: 豚肉、キャベツ、しいたけ、ブロッコリー\n'
' - **調味料**: 塩、胡椒、ポン酢\n'
' - **作り方**:\n'
' 1. キャベツをざく切りにし、しいたけとブロッコリーは適当なサイズに切ります。\n'
' 2. 豚肉と野菜を蒸し器に並べ、塩と胡椒を振ります。\n'
' 3. 約15分蒸します。\n'
' 4. ポン酢をかけていただきます。\n'
'\n'
'どちらのメニューも簡単に作れますし、バランスが良いのでおすすめです。괴',
input_guardrail_results=[],
output_guardrail_results=[],
_last_agent=Agent(name='アシスタント',
instructions='あなたはとても親切なアシスタントです',
handoff_description=None,
handoffs=[],
model=None,
model_settings=ModelSettings(temperature=None,
top_p=None,
frequency_penalty=None,
presence_penalty=None,
tool_choice=None,
parallel_tool_calls=False,
truncation=None,
max_tokens=None),
tools=[],
input_guardrails=[],
output_guardrails=[],
output_type=None,
hooks=None,
tool_use_behavior='run_llm_again'))
ちょっと長いので応答文を省略しました。LLM の応答と会話の履歴を保持している雰囲気です。
公式ドキュメントの RunResultを見ると dataclass でした。
インスタンス変数をリストアップします。
>>> pprint([x for x in dir(result) if not x.startswith("_") ])
['final_output',
'final_output_as',
'input',
'input_guardrail_results',
'last_agent',
'new_items',
'output_guardrail_results',
'raw_responses',
'to_input_list']
サンプルコードでは、final_output を使っていますが、他にも役立ちそうな情報が入っています。
Trace
OpenAI プラットフォームの Trace 機能で動作を確認します。
デフォルトでは Trace 機能が無効になっているかもしれません。有効にする際は、設定 >> Data Control 設定画面で Logs の設定を「Hidden」から「Visible to organization owners」等に変更します。
先程の動作結果を表示します。
サマリー情報が取得できます。
Handoffs 機能
次のサンプルコードを見てみます。
公式ドキュメントの次のサンプルを実行してみます。プロンプトや名前は適当に日本語にしています。
import asyncio
from agents import Agent, GuardrailFunctionOutput, InputGuardrail, Runner
from pydantic import BaseModel
class HomeworkOutput(BaseModel):
is_homework: bool
reasoning: str
guardrail_agent = Agent(
name="ガードレールチェック",
instructions="ユーザーが宿題について質問しているのかチェックして。",
output_type=HomeworkOutput,
)
math_tutor_agent = Agent(
name="数学の家庭教師",
handoff_description="数学の専門家のエージェント",
instructions="数学の問題について回答を与えて。回答の理由を例を交えてステップバイステップで示して",
)
history_tutor_agent = Agent(
name="歴史の家庭教師",
handoff_description="歴史の専門家エージェント",
instructions="歴史の問題に回答して。重要なイベントとコンテキストを明らかにして。",
)
async def homework_guardrail(ctx, agent, input_data):
result = await Runner.run(guardrail_agent, input_data, context=ctx.context)
final_output = result.final_output_as(HomeworkOutput)
return GuardrailFunctionOutput(
output_info=final_output,
tripwire_triggered=not final_output.is_homework,
)
triage_agent = Agent(
name="トリアージエージェント",
instructions="ユーザーの質問が宿題に関するものなのかを判定して",
handoffs=[history_tutor_agent, math_tutor_agent],
input_guardrails=[
InputGuardrail(guardrail_function=homework_guardrail),
],
)
async def main():
result = await Runner.run(
triage_agent,
"これは宿題ですが二人の擲弾兵という曲について教えて。",
)
print(result.final_output)
if __name__ == "__main__":
asyncio.run(main())
コードでは、4 個の Agent と、1 個のガードレールを定義する関数や pydantic のデータ表現クラス(HomewordOutput)が定義されています。以下の構成に見えます。
- 登場人物(Agent)
- ガードレールチェック
- 数学の家庭教師
- 歴史の家庭教師
- トリアージエージェント
- ガードレール判定関数
- ガードレールチェック Agent の呼び出す
- データ表現クラスで値を取り出す(HomeworkOutput)
- 結果を GuardrailFunctionOutput で返す
実行時はトリアージエージェントを呼び出しています。ガードレールの参照や家庭教師の登録をしているので、全体の流れをコントロールしている感じがします。Agent のコンストラクタに出てきた handoff が使われています。
Hello のサンプルコードは同期モードで実行していましたが、このサンプルコードは非同期モードで実行しています。
$ python src/handoffs.py
「二人の擲弾兵」という曲は、フランスの作曲家ロベルト・シューマンが1800年代に作曲したリート(歌曲)です。ナポレオンの兵士が主人公で、パウル・エールリント作の詩に基づいています。兵士たちが祖国フランスに帰還し、ナポレオンに忠誠を誓う内容です。
### 曲の概要:
1. **作曲者**: ロベルト・シューマン
2. **詩**: ハインリヒ・ハイネによるもの
3. **内容**: 兵士たちの忠誠心と愛国心がテーマ
4. **音楽スタイル**: ロマン派音楽特有の情感豊かな旋律
この曲は歴史的背景や感情の深さが特長で、シューマンの特徴的な音楽スタイルを反映しています。
triage_agent
デバッグ表示させます。情報量が増えたので一度 Dict に変換してから表示しています。
{'handoff_description': None,
'handoffs': [{'handoff_description': '歴史の専門家エージェント',
'handoffs': [],
'hooks': None,
'input_guardrails': [],
'instructions': '歴史の問題に回答して。重要なイベントとコンテキストを明らかにして。',
'model': None,
'model_settings': {'frequency_penalty': None,
'max_tokens': None,
'parallel_tool_calls': False,
'presence_penalty': None,
'temperature': None,
'tool_choice': None,
'top_p': None,
'truncation': None},
'name': '歴史の家庭教師',
'output_guardrails': [],
'output_type': None,
'tool_use_behavior': 'run_llm_again',
'tools': []},
{'handoff_description': '数学の専門家のエージェント',
'handoffs': [],
'hooks': None,
'input_guardrails': [],
'instructions': '数学の問題について回答を与えて。回答の理由を例を交えてステップバイステップで示して',
'model': None,
'model_settings': {'frequency_penalty': None,
'max_tokens': None,
'parallel_tool_calls': False,
'presence_penalty': None,
'temperature': None,
'tool_choice': None,
'top_p': None,
'truncation': None},
'name': '数学の家庭教師',
'output_guardrails': [],
'output_type': None,
'tool_use_behavior': 'run_llm_again',
'tools': []}],
'hooks': None,
'input_guardrails': [{'guardrail_function': <function homework_guardrail at 0x73c1672edf80>,
'name': None}],
'instructions': 'ユーザーの質問が宿題に関するものなのかを判定して',
'model': None,
'model_settings': {'frequency_penalty': None,
'max_tokens': None,
'parallel_tool_calls': False,
'presence_penalty': None,
'temperature': None,
'tool_choice': None,
'top_p': None,
'truncation': None},
'name': 'トリアージエージェント',
'output_guardrails': [],
'output_type': None,
'tool_use_behavior': 'run_llm_again',
'tools': []}
トリアージエージェントから、数学の家庭教師、歴史の家庭教師、ガードレールエージェントの情報が確認できました。ガードレールの実装関数も見えています。handoff は処理の移譲先の候補のように見えます。input_guardrails で定義される規制は、トリアージエージェントにとっての Input を対象にしている様子です。
result
result も見てみます。Dict 形式になってるので表記はありませんが RunResult クラスのインスタンスです。
{'_last_agent': {'handoff_description': '数学の専門家のエージェント',
'handoffs': [],
'hooks': None,
'input_guardrails': [],
'instructions': '数学の問題について回答を与えて。回答の理由を例を交えてステップバイステップで示して',
'model': None,
'model_settings': {'frequency_penalty': None,
'max_tokens': None,
'parallel_tool_calls': False,
'presence_penalty': None,
'temperature': None,
'tool_choice': None,
'top_p': None,
'truncation': None},
'name': '数学の家庭教師',
'output_guardrails': [],
'output_type': None,
'tool_use_behavior': 'run_llm_again',
'tools': []},
'final_output': '「二人の擲弾兵」という曲は、フランスの作曲家ロベルト・シューマンが1800年代に作曲したリート(歌曲)です。ナポレオンの兵士が主人公で、パウル・エールリント作の詩に基づいています。兵士たちが祖国フランスに帰還し、ナポレオンに忠誠を誓う内容です。\n'
'\n'
'### 曲の概要:\n'
'1. **作曲者**: ロベルト・シューマン\n'
'2. **詩**: ハインリヒ・ハイネによるもの\n'
'3. **内容**: 兵士たちの忠誠心と愛国心がテーマ\n'
'4. **音楽スタイル**: ロマン派音楽特有の情感豊かな旋律\n'
'\n'
'この曲は歴史的背景や感情の深さが特長で、シューマンの特徴的な音楽スタイルを反映しています。',
'input': 'これは宿題ですが二人の擲弾兵という曲について教えて。',
'input_guardrail_results': [{'guardrail': {'guardrail_function': <function homework_guardrail at 0x73c1672edf80>,
'name': None},
'output': {'output_info': HomeworkOutput(is_homework=True, reasoning="The user explicitly mentions 'これは宿題ですが' which directly translates to 'this is homework,' indicating that they are seeking information for a homework assignment."),
'tripwire_triggered': False}}],
'new_items': [{'agent': {'handoff_description': None,
'handoffs': [{'handoff_description': '歴史の専門家エージェント',
'handoffs': [],
'hooks': None,
'input_guardrails': [],
'instructions': '歴史の問題に回答して。重要なイベントとコンテキストを明らかにして。',
'model': None,
'model_settings': {'frequency_penalty': None,
'max_tokens': None,
'parallel_tool_calls': False,
'presence_penalty': None,
'temperature': None,
'tool_choice': None,
'top_p': None,
'truncation': None},
'name': '歴史の家庭教師',
'output_guardrails': [],
'output_type': None,
'tool_use_behavior': 'run_llm_again',
'tools': []},
{'handoff_description': '数学の専門家のエージェント',
'handoffs': [],
'hooks': None,
'input_guardrails': [],
'instructions': '数学の問題について回答を与えて。回答の理由を例を交えてステップバイステップで示して',
'model': None,
'model_settings': {'frequency_penalty': None,
'max_tokens': None,
'parallel_tool_calls': False,
'presence_penalty': None,
'temperature': None,
'tool_choice': None,
'top_p': None,
'truncation': None},
'name': '数学の家庭教師',
'output_guardrails': [],
'output_type': None,
'tool_use_behavior': 'run_llm_again',
'tools': []}],
'hooks': None,
'input_guardrails': [{'guardrail_function': <function homework_guardrail at 0x73c1672edf80>,
'name': None}],
'instructions': 'ユーザーの質問が宿題に関するものなのかを判定して',
'model': None,
'model_settings': {'frequency_penalty': None,
'max_tokens': None,
'parallel_tool_calls': False,
'presence_penalty': None,
'temperature': None,
'tool_choice': None,
'top_p': None,
'truncation': None},
'name': 'トリアージエージェント',
'output_guardrails': [],
'output_type': None,
'tool_use_behavior': 'run_llm_again',
'tools': []},
'raw_item': ResponseFunctionToolCall(arguments='{}', call_id='call_xxxxxxxxxxxx', name='transfer_to________', type='function_call', id='fc_xxxxxxxxxxxxxx', status='completed'),
'type': 'handoff_call_item'},
{'agent': {'handoff_description': None,
'handoffs': [{'handoff_description': '歴史の専門家エージェント',
'handoffs': [],
'hooks': None,
'input_guardrails': [],
'instructions': '歴史の問題に回答して。重要なイベントとコンテキストを明らかにして。',
'model': None,
'model_settings': {'frequency_penalty': None,
'max_tokens': None,
'parallel_tool_calls': False,
'presence_penalty': None,
'temperature': None,
'tool_choice': None,
'top_p': None,
'truncation': None},
'name': '歴史の家庭教師',
'output_guardrails': [],
'output_type': None,
'tool_use_behavior': 'run_llm_again',
'tools': []},
{'handoff_description': '数学の専門家のエージェント',
'handoffs': [],
'hooks': None,
'input_guardrails': [],
'instructions': '数学の問題について回答を与えて。回答の理由を例を交えてステップバイステップで示して',
'model': None,
'model_settings': {'frequency_penalty': None,
'max_tokens': None,
'parallel_tool_calls': False,
'presence_penalty': None,
'temperature': None,
'tool_choice': None,
'top_p': None,
'truncation': None},
'name': '数学の家庭教師',
'output_guardrails': [],
'output_type': None,
'tool_use_behavior': 'run_llm_again',
'tools': []}],
'hooks': None,
'input_guardrails': [{'guardrail_function': <function homework_guardrail at 0x73c1672edf80>,
'name': None}],
'instructions': 'ユーザーの質問が宿題に関するものなのかを判定して',
'model': None,
'model_settings': {'frequency_penalty': None,
'max_tokens': None,
'parallel_tool_calls': False,
'presence_penalty': None,
'temperature': None,
'tool_choice': None,
'top_p': None,
'truncation': None},
'name': 'トリアージエージェント',
'output_guardrails': [],
'output_type': None,
'tool_use_behavior': 'run_llm_again',
'tools': []},
'raw_item': {'call_id': 'call_xxxxxxxxxxxxx',
'output': "{'assistant': '数学の家庭教師'}",
'type': 'function_call_output'},
'source_agent': {'handoff_description': None,
'handoffs': [{'handoff_description': '歴史の専門家エージェント',
'handoffs': [],
'hooks': None,
'input_guardrails': [],
'instructions': '歴史の問題に回答して。重要なイベントとコンテキストを明らかにして。',
'model': None,
'model_settings': {'frequency_penalty': None,
'max_tokens': None,
'parallel_tool_calls': False,
'presence_penalty': None,
'temperature': None,
'tool_choice': None,
'top_p': None,
'truncation': None},
'name': '歴史の家庭教師',
'output_guardrails': [],
'output_type': None,
'tool_use_behavior': 'run_llm_again',
'tools': []},
{'handoff_description': '数学の専門家のエージェント',
'handoffs': [],
'hooks': None,
'input_guardrails': [],
'instructions': '数学の問題について回答を与えて。回答の理由を例を交えてステップバイステップで示して',
'model': None,
'model_settings': {'frequency_penalty': None,
'max_tokens': None,
'parallel_tool_calls': False,
'presence_penalty': None,
'temperature': None,
'tool_choice': None,
'top_p': None,
'truncation': None},
'name': '数学の家庭教師',
'output_guardrails': [],
'output_type': None,
'tool_use_behavior': 'run_llm_again',
'tools': []}],
'hooks': None,
'input_guardrails': [{'guardrail_function': <function homework_guardrail at 0x73c1672edf80>,
'name': None}],
'instructions': 'ユーザーの質問が宿題に関するものなのかを判定して',
'model': None,
'model_settings': {'frequency_penalty': None,
'max_tokens': None,
'parallel_tool_calls': False,
'presence_penalty': None,
'temperature': None,
'tool_choice': None,
'top_p': None,
'truncation': None},
'name': 'トリアージエージェント',
'output_guardrails': [],
'output_type': None,
'tool_use_behavior': 'run_llm_again',
'tools': []},
'target_agent': {'handoff_description': '数学の専門家のエージェント',
'handoffs': [],
'hooks': None,
'input_guardrails': [],
'instructions': '数学の問題について回答を与えて。回答の理由を例を交えてステップバイステップで示して',
'model': None,
'model_settings': {'frequency_penalty': None,
'max_tokens': None,
'parallel_tool_calls': False,
'presence_penalty': None,
'temperature': None,
'tool_choice': None,
'top_p': None,
'truncation': None},
'name': '数学の家庭教師',
'output_guardrails': [],
'output_type': None,
'tool_use_behavior': 'run_llm_again',
'tools': []},
'type': 'handoff_output_item'},
{'agent': {'handoff_description': '数学の専門家のエージェント',
'handoffs': [],
'hooks': None,
'input_guardrails': [],
'instructions': '数学の問題について回答を与えて。回答の理由を例を交えてステップバイステップで示して',
'model': None,
'model_settings': {'frequency_penalty': None,
'max_tokens': None,
'parallel_tool_calls': False,
'presence_penalty': None,
'temperature': None,
'tool_choice': None,
'top_p': None,
'truncation': None},
'name': '数学の家庭教師',
'output_guardrails': [],
'output_type': None,
'tool_use_behavior': 'run_llm_again',
'tools': []},
'raw_item': ResponseOutputMessage(id='msg_xxxxxxxxxxxxxxxxxxxxxxxxx', content=[ResponseOutputText(annotations=[], text='「二人の擲弾兵」という曲は、フランスの作曲家ロベルト・シューマンが(以下略)', type='output_text')], role='assistant', status='completed', type='message'),
'type': 'message_output_item'}],
'output_guardrail_results': [],
'raw_responses': [{'output': [ResponseFunctionToolCall(arguments='{}', call_id='call_xxxxxxxxxxxxxxxxxxxx', name='transfer_to________', type='function_call', id='fc_xxxxxxxxxxxxxxxxxxxxxxxx', status='completed')],
'referenceable_id': 'resp_xxxxxxxxxxxxxxxxxxxxxxxxxx',
'usage': {'input_tokens': 343,
'output_tokens': 12,
'requests': 1,
'total_tokens': 355}},
{'output': [ResponseOutputMessage(id='msg_xxxxxxxxxxxxxxxxxxxxx', content=[ResponseOutputText(annotations=[], text='「二人の擲弾兵」という曲は、フランスの作曲家ロベルト・シューマンが(以下略)', type='output_text')], role='assistant', status='completed', type='message')],
'referenceable_id': 'resp_xxxxxxxxxxxxxxxxxxx',
'usage': {'input_tokens': 103,
'output_tokens': 224,
'requests': 1,
'total_tokens': 327}}]}
長いですね。生成されたテキストを省略しているのと ID を伏せ字としています。
_last_agent によると「数学の家庭教師」エージェントが回答を生成しています(笑)
input_guardrail_results でガードレールの結果を見ると「The user explicitly mentions 'これは宿題ですが' which directly translates to 'this is homework,' indicating that they are seeking information for a homework assignment.」ということで、宿題であると認定している様子です。tripwire_triggered が False になっています。このコードでは、宿題じゃないと判定されると、例外を raise して動作が停止します。
Trace
OpenAI プラットフォームで Trace も見てみます。
Trace 一覧でフローの概要がわかります。
個別の Trace を見ると処理の階層構造がみえます。
ガードレールの処理結果です。コードで見るよりわかりやすいです。
「数学の家庭教師」エージェントによる回答生成結果です。音楽に詳しい数学の先生ですね。
まとめ
今回は OpenAI Agents SDK の基本的な使い方を見て、データ構造を追いかける形で深堀りしてみました。
分かったこと
- Agent クラスは、エージェントの名前、指示、モデル設定などを保持するデータクラス
- Runner.run_sync() や Runner.run() でエージェントを実行し、RunResult オブジェクトで結果を取得できる
- RunResult オブジェクトは、入力、出力、会話履歴、トークン使用量などの情報を持つ
- Handoffs 機能を使うと、複数のエージェント間で処理を移譲できる
- InputGuardrail を使うと、エージェントへの入力に対してガードレール(制約)を設定できる
- OpenAI プラットフォームの Trace 機能で、エージェントの実行フローや詳細な情報を確認できる
- 公式ドキュメントが良い感じで助かる
もう少し知りたいこと
今回は試していない機能があるので引き続き調べようと思います。公式ドキュメントの API リファレンスが役立ちそうだと思いました。
- Agent クラスの tools、output_guardrails、hooks、tool_use_behavior などの引数の役割
- そもそも高機能っぽいし今後も機能が追加される気がします
- Web アクセス等の Tool を使ってこそ「AIエージェント」って気がします
- ガードレール機能の詳細な挙動とカスタマイズ方法
- 非同期処理(Runner.run())と並列処理の具体的な使い分けと効果
- アプリケーション要件が同期で行けるのは同期にしたい気もします
- より複雑なエージェントの連携方法(エージェント間での情報の受け渡しなど)
- エラーハンドリングや例外処理の方法
- ローカルの Trace サーバが立てられるか?
最後に
OpenAI Agents SDK は、2025年3月11日にアナウンスされた新しい機能です。「AI エージェント」っぽいアプリケーションの開発において一定の地位を持つかもしれないと感じました。しばらく注目しようと思います。
最後までお読みいただきありがとうございました!