OpenAI Agents SDKの7つのインターフェイスをコード分析で理解する
はじめに
株式会社ナレッジワークのAIエンジニアの zawakin です。
OpenAI が満を持して公開した AI Agent フレームワーク OpenAI Agents SDK について、その内部構造を理解するためにコードを分析しました。
その内容を自分だけにとどめておくのはもったいないので、この記事にまとめてみました。
OpenAI Agents SDK とは
OpenAI Agents SDK は、2025年3月12日に公開された、AIエージェントの開発を支援するための Python ライブラリです。
実際にサービス運用実績もたくさんあるOpenAIが出してきたフレームワークなので、期待値も高まっているかと思います。
今後のデファクトスタンダードになるか、は分かりませんが、これからのAIエージェント開発を見据え、このSDKがどのような考え方や概念に基づいて設計されているのかを理解しておくことは重要でしょう。
この記事では、OpenAI Agents SDK の実際のソースコードを詳細に分析し、その中でも特に興味深いポイントについて深掘りして解説していきます。
このように、単にドキュメントを読むだけでなく、SDKの内部構造を具体的に把握することで、本質的な設計思想や概念への理解が深まるので、"Hello World" を試している時や試す前に、SDKのコードの中身を読んでみることをおすすめします。
GitHubリポジトリ
ドキュメント
対象読者
- AIエージェントのフレームワークに興味があるエンジニア
OpenAI Agents SDKの7つのインターフェイス
概要をざっくり
OpenAI Agents SDK のコードを分析すると、以下の7つのインターフェイスが中心的な役割を果たしていることがわかりました。
- エージェント(Agent)インターフェイス
- ツール(Tool)インターフェイス
- ガードレール(Guardrail)インターフェイス
- ハンドオフ(Handoff)インターフェイス
- モデル(Model)インターフェイス
- ランナー(Runner)インターフェイス
- トレース(Trace)インターフェイス
このうち、1, 2, 5, 6 は聞き慣れた概念かもしれませんが、3, 4, 7 は少し特殊な概念かもしれません。
ハンドオフ(Handoff) は、エージェントがタスクを別のエージェントに委譲するための仕組みで、処理に応じて他のエージェントにバトンタッチすることができます。
つまり、あるエージェントがある処理を途中まで行い、その続きは別のエージェントに任せる、といった使い方ができるわけです。
また、ガードレール(Guardrail) は、入力や出力を検証・制御する仕組みで、エージェントの出力が意図した範囲内に収まるようにします。場合によっては、処理を中断することもあります。
トレース(Trace) は、エージェントの実行中に発生したイベントを記録し、デバッグや分析に役立てるための仕組みです。
1. エージェント(Agent)インターフェイス
エージェントは、割とイメージ通りの概念です。
システムプロンプトを受け取り、処理を行いつつ、
- ツールを使って処理を行う
- ハンドオフを使って他のエージェントに処理を委譲する
- ガードレールを使って入力や出力を検証・制御する
といったことができます。
エージェント(Agent)インターフェイスの詳細
-
定義クラス
-
Agent[TContext]
(src/agents/agent.py)
-
-
主な属性・設定
-
name
(str)- エージェント名。
-
instructions
(str または関数)- システムプロンプトとして使用され、エージェントの行動指針を定義。
- 関数の場合:引数は
RunContextWrapper
と自身のAgent
で、戻り値は文字列。
-
handoff_description
(str | None)- ハンドオフ時に使用されるエージェントの説明。
-
handoffs
(list[Agent | Handoff])- 他エージェントへの委譲(ハンドオフ)の候補。
- 入力/出力: ハンドオフの場合は後述の Handoff インターフェイスで定義される。
-
model
(str | Model | None)- 利用するLLMモデル。文字列の場合はモデルプロバイダーが解決する。
-
model_settings
(ModelSettings
)- 温度や top_p など、モデル固有のパラメータ設定。
-
tools
(list[Tool])- エージェントが利用できるツールのリスト。
-
input_guardrails
/output_guardrails
- 入力/出力の検証・制御用。各ガードレールは後述のガードレールインターフェイスで定義される。
-
output_type
(type | None)- エージェントの最終出力の型。
None
またはstr
ならプレーンテキスト。その他の型なら自動生成された JSON スキーマ(AgentOutputSchema
)による検証対象となる。
- エージェントの最終出力の型。
-
-
入力/出力の形式
-
入力:
- 単一のユーザーメッセージ(文字列)または
- 複数の入力アイテム(一般に
{"role": "user", "content": "..."}
のような辞書のリスト)
-
出力:
- 基本は文字列(プレーンテキスト)または、
output_type
に応じた JSON オブジェクト(AgentOutputSchema
により検証・整形)。
- 基本は文字列(プレーンテキスト)または、
-
入力:
-
補助機能
-
as_tool
メソッド:エージェント自体をツールとしてラップし、他のエージェントから呼び出せるように変換可能。
-
ref. https://openai.github.io/openai-agents-python/agents/
2. ツール(Tool)インターフェイス
ツールも、よくあるツールと同じです。
エージェントから呼び出され、処理を行い、結果を返します。
いわば、「AIエージェントの機能実行用のモジュール」 といった表現がわかりやすいと思います。
あくまでエージェントから呼び出されて、エージェントに処理を返すことになるので、後ほどでる別のエージェントそのものに処理を委譲するハンドオフとは異なります。
ツール(Tool)インターフェイスの詳細
A. FunctionTool
-
定義クラス
-
FunctionTool
(src/agents/tool.py)
-
-
主な特徴
- Python の関数をラップするツール。
- 作成はデコレーター
@function_tool
により行われ、関数のシグネチャから Pydantic モデルを生成し JSON スキーマを自動生成します。
-
入力/出力の形式
-
入力:
- LLM から渡される JSON 文字列。
- この JSON は、関数パラメータに対応するスキーマ(例:
{"param1": value1, "param2": value2}
)に基づいてパース・検証される。
-
出力:
- 関数の戻り値を文字列に変換したもの。
-
入力:
B. その他のツール
-
FileSearchTool
- 概要: ファイル検索(ベクトルストア検索)用。
-
入力:
- 検索対象の
vector_store_ids
や、必要に応じたフィルター、検索件数など。
- 検索対象の
-
出力:
- 検索結果(通常は JSON 形式のデータ、LLM に返すメッセージとして利用)。
-
WebSearchTool
- 概要: Web 検索用。
-
入力:
- オプションで
user_location
(検索の地域情報)や、検索コンテキストのサイズ("low", "medium", "high")など。
- オプションで
-
出力:
- Web 検索結果。
-
ComputerTool
- 概要: コンピュータ制御用。
-
入力:
- クリック、ダブルクリック、ドラッグ、キープレス、タイプ、スクリーンショット要求など、操作に必要なパラメータ(例:座標、ボタン名、テキストなど)。
-
出力:
- 操作結果としてのスクリーンショット画像を Base64 エンコードした data URL(文字列)。
ref. https://openai.github.io/openai-agents-python/tools/
3. ガードレール(Guardrail)インターフェイス
エージェントの入力や出力が意図した範囲内に収まるよう、入力や出力を検証・制御する仕組みです。
ガードレールインターフェイス自体は、
- 入力ガードレール(InputGuardrail)
- 出力ガードレール(OutputGuardrail)
の2つがあります。
その名の通り、入力ガードレールはエージェントに入力されるデータを検証し、出力ガードレールはエージェントの出力を検証します。
ガードレール(Guardrail)インターフェイスの詳細
A. InputGuardrail
-
定義クラス
-
InputGuardrail[TContext]
(src/agents/guardrail.py)
-
-
入力:
-
(RunContextWrapper, Agent, input)
- ここで
input
はユーザーメッセージの文字列または入力アイテムのリスト。
- ここで
-
-
出力:
-
GuardrailFunctionOutput
-
output_info
: 任意の追加情報 -
tripwire_triggered
: 真偽値(True の場合、入力が不適切と判断され、処理が中断される)
-
-
B. OutputGuardrail
-
定義クラス
-
OutputGuardrail[TContext]
(src/agents/guardrail.py)
-
-
入力:
-
(RunContextWrapper, Agent, agent_output)
-
agent_output
はエージェントが生成した最終出力。
-
-
-
出力:
- 同様に
GuardrailFunctionOutput
(出力の検証結果とトリップワイヤーの有無を返す)
- 同様に
ref. https://openai.github.io/openai-agents-python/guardrails/
4. ハンドオフ(Handoff)インターフェイス
エージェントがタスクを別のエージェントに委譲するための仕組みです。
ハンドオフ(Handoff)インターフェイスの詳細
-
定義クラス
-
Handoff[TContext]
(src/agents/handoffs.py)
-
-
主な属性
-
tool_name
(str)- ハンドオフ呼び出し用のツール名(例:"transfer_to_agentX")
-
tool_description
(str)- ツールの説明。
-
input_json_schema
(dict)- LLM から渡されるハンドオフ用入力の JSON スキーマ。
- これにより、ハンドオフに渡すデータの形式が明確になる。
-
on_invoke_handoff
(非同期関数)-
入力:
(RunContextWrapper, input: str)
- ここでの
input
は JSON 文字列として渡され、上記のinput_json_schema
に基づき検証される。
- ここでの
-
出力: 新たなエージェント(
Agent[TContext]
)を返す。
-
入力:
-
agent_name
(str)- ハンドオフ先のエージェント名。
-
input_filter
(任意)- ハンドオフ前に入力履歴などをフィルタリングする関数。
-
ツールとハンドオフの違い
一見、ツールとハンドオフは似たような機能を持っているように見えます。
確かにツールとハンドオフはどちらもエージェントの実行中に外部の処理を呼び出すための仕組みですが、役割や使われ方、入出力の扱いに明確な違いがあります。
ポイントは、ツールはエージェント内の「機能実行用のモジュール」とみなせて、ハンドオフは「エージェント間の処理の切り替え」を実現するインターフェイスという点です。
さらに具体的には、
- ツールはエージェント内の「機能実行用モジュール」であり、LLMの指示に応じて即座に処理を実行し結果を返す
- ハンドオフはエージェント間で処理の権限や役割を切り替えるための仕組みで、入力履歴のフィルタリングを行ったうえで別のエージェントに処理を委譲する
という違いがあります。
あるエージェントAから別のエージェントBに処理を委譲する際、ハンドオフの内部処理はどのようになっているのか
あるエージェントAから別のエージェントBに処理を委譲する際、ハンドオフの内部処理はどのようになっているのでしょうか?
ハンドオフが発生すると、これまでの対話履歴は「HandoffInputData」というオブジェクトにまとめられ、その内容が新しいエージェントへ引き継がれます。
具体的には、以下の3つの要素が含まれています:
-
input_history
- エージェント実行前の元のユーザー入力(文字列または入力アイテムのタプル)。
-
pre_handoff_items
- ハンドオフが発生する前までに生成された出力アイテム(メッセージやツール呼び出しなど)。
-
new_items
- ハンドオフのターン中に新たに生成されたアイテム。
さらに、ハンドオフには「input_filter」というオプションのフィルタ関数が設定でき、これを使って以下のような処理が行われます:
-
フィルタリング処理
- HandoffInputData が input_filter に渡され、不要な情報(例えばツール呼び出し関連のアイテムなど)を除去または変換した新しい入力履歴に変換されます。
- フィルタ関数の結果(再構成された HandoffInputData)が新エージェントへの入力として使われるため、新しいエージェントはクリーンな状態で対話を再開できます。
もしフィルタ関数が指定されなければ、これらすべての履歴がそのまま新しいエージェントに渡されます。
この仕組みにより、エージェント間のハンドオフ時に、不要な内部処理の詳細が削除され、次のエージェントに必要な情報だけが引き継がれるようになっています。
ref. https://openai.github.io/openai-agents-python/handoffs/
5. モデル(Model)インターフェイス
LLM との通信部分の抽象化を行います。イメージ通りかと思います。
モデル(Model)インターフェイスの詳細
-
定義クラス
- 抽象クラス
Model
(src/agents/models/interface.py)
- 抽象クラス
-
主なメソッド
-
get_response
-
入力:
-
system_instructions
: システムプロンプト(str | None) -
input
: ユーザー入力。文字列または、入力アイテムのリスト(各アイテムは通常{"role": "user", "content": "..."}
の形式) -
model_settings
: モデルのパラメータ設定(ModelSettings
) -
tools
: 利用可能なツールのリスト -
output_schema
: エージェント出力用の JSON スキーマ(AgentOutputSchema
)
※プレーンテキストの場合は不要 -
handoffs
: ハンドオフのリスト -
tracing
: トレーシング設定(ModelTracing
)
-
-
出力:
-
ModelResponse
オブジェクト-
output
: LLM の返す出力アイテムのリスト(例えば、メッセージやツール呼び出しの結果) -
usage
: 使用状況(トークン数等) -
referenceable_id
: (任意)後続で参照可能なID
-
-
-
入力:
-
stream_response
- 入力: 上記と同じ
-
出力: 非同期ストリーム(
AsyncIterator[TResponseStreamEvent]
)で、部分的な出力イベント(生成中のデルタ情報など)を順次返す
-
ref. https://openai.github.io/openai-agents-python/models/
6. ランナー(Runner)インターフェイス
エージェント実行のワークフロー全体を制御するコンポーネントです。
エントリポイントとなる starting_agent
を持ち、エージェントの実行を管理・監視するような役割を持ちます。
ランナー(Runner)インターフェイスの詳細
-
定義クラス
-
Runner
(src/agents/run.py)
-
-
主なメソッド
-
run (非同期実行)
-
入力:
-
starting_agent
: 実行開始エージェント(Agent[TContext]
) -
input
: 初期入力(文字列または入力アイテムのリスト) -
context
: 任意の実行コンテキスト -
max_turns
: エージェント呼び出しの最大ターン数(整数) -
hooks
: ライフサイクルフック(RunHooks[TContext]
) -
run_config
: グローバルな実行設定(RunConfig
)- ここにはグローバルなモデル設定、ハンドオフ用入力フィルター、トレーシングオプションなどが含まれる。
-
-
出力:
-
RunResult
- フィールド例:
-
input
: 最終的に使用された入力履歴(場合によりハンドオフフィルタで変形される) -
new_items
: エージェント実行中に生成された各種アイテム(メッセージ、ツール呼び出し、ツール出力など) -
raw_responses
: モデルから返された生のレスポンスのリスト -
final_output
: 最終的なエージェント出力(エージェントのoutput_type
に合わせた形式) -
input_guardrail_results
/output_guardrail_results
: ガードレールチェックの結果
-
- フィールド例:
-
-
入力:
-
run_sync
- 非同期版
run
の同期ラッパー(ただし、イベントループが存在しない環境向け)
- 非同期版
-
run_streamed
- ストリーミング実行を行い、逐次生成イベント(
RunResultStreaming
)を提供
- ストリーミング実行を行い、逐次生成イベント(
-
ref. https://openai.github.io/openai-agents-python/running_agents/
7. トレース(Trace)インターフェイス
エージェントの実行中に、各種情報をトレースするためのインターフェイスです。
まだちゃんと使えてないですが、どうやら結構便利そうという話を目にしています。
トレース(Trace)インターフェイスの詳細
全体の流れは以下の通りです。
-
トレースの開始
-
trace()
関数(src/agents/tracing/create.py)を呼び出して新たな Trace を作成・開始し、ワークフロー全体を記録開始。
-
-
エージェントやツールの実行ごとにスパンを作成
-
agent_span()
,function_span()
,generation_span()
などを使い、各処理単位ごとにスパンを作成して開始/終了を管理。
-
-
各スパンのエクスポート
- 各スパンは
export()
により辞書形式で出力され、トレーシングプロセッサを通じてバックエンドに送信またはコンソール出力される。
- 各スパンは
1. Trace(トレース)インターフェイス
-
Trace の役割
- 1つのワークフロー(例:エージェント実行の一連の流れ)全体をひとまとめにして記録するルートオブジェクトです。
-
主なメソッドと入出力
-
start(mark_as_current: bool)
- トレースを開始し、必要に応じて現在のトレースとして設定します。
- 入力:
mark_as_current
(真偽値) - 出力:内部状態が更新され、トレースID(例えば
"trace_<UUID>"
)が生成されます。
-
finish(reset_current: bool)
- トレースを終了し、終了時刻を記録。
- 入力:
reset_current
(真偽値)
-
export() → dict[str, Any]
- トレース情報を辞書形式に変換してエクスポート。
- 出力例:
{ "object": "trace", "id": "trace_XXXXXXXX", "workflow_name": "Agent workflow", "group_id": "chat_thread_123", "metadata": { ... } }
-
属性
-
trace_id
: ユニークなトレースID -
name
: ワークフロー名
-
-
start(mark_as_current: bool)
-
実装例
-
TraceImpl
(src/agents/tracing/traces.py) - また、トレースが無効な場合は
NoOpTrace
が返されます。
-
2. Span(スパン)インターフェイス
-
Span の役割
- トレース内の個々の処理単位(例:エージェント実行、ツール呼び出し、ハンドオフなど)を表すオブジェクトです。
- 各スパンは、開始時刻、終了時刻、エラー情報などを記録し、後から処理の詳細を分析するためにエクスポートされます。
-
主なメソッドと入出力
-
start(mark_as_current: bool)
- スパンの開始時刻を記録し、必要に応じて現在のスパンとして設定します。
-
finish(reset_current: bool)
- 終了時刻を記録し、エクスポート処理(トレーシングプロセッサへの通知)を行います。
-
set_error(error: SpanError)
- スパンにエラー情報を付加します。
-
SpanError
は、{"message": "エラーメッセージ", "data": { ... }}
の形式。
-
export() → dict[str, Any]
- スパンの情報を辞書形式に変換。
- 出力例:
{ "object": "trace.span", "id": "span_XXXXXXXX", "trace_id": "trace_YYYYYYYY", "parent_id": "span_ZZZZZZZZ", "started_at": "2025-03-13T12:34:56Z", "ended_at": "2025-03-13T12:35:56Z", "span_data": { "type": "function", "name": "my_tool_function", "input": "{...}", "output": "{...}" }, "error": null }
-
属性
-
trace_id
、span_id
、parent_id
(ある場合)、span_data
(スパンの種類に応じたデータ)
-
-
start(mark_as_current: bool)
-
実装例
-
SpanImpl
(src/agents/tracing/spans.py) - 無効な場合は
NoOpSpan
が利用されます。
-
3. SpanData(スパンデータ)
-
役割
- 各スパンに紐づく追加データを表現するための抽象クラスです。
- 例えば、関数呼び出しなら
FunctionSpanData
、エージェント実行ならAgentSpanData
、ハンドオフならHandoffSpanData
、生成処理ならGenerationSpanData
、ガードレールならGuardrailSpanData
など、用途に応じたサブクラスが存在します。
-
出力例
- 例えば、
AgentSpanData
は以下のような辞書をエクスポート:{ "type": "agent", "name": "MyAgent", "handoffs": ["AgentB"], "tools": ["tool_x", "tool_y"], "output_type": "MyOutputType" }
- 例えば、
4. トレーシングプロセッサ(TracingProcessor)インターフェイス
-
目的
- 作成されたトレースやスパンを収集し、適切にエクスポート(例:バックエンド送信、コンソール出力など)するための仕組みです。
-
主なメソッド
-
on_trace_start(trace: Trace)
- トレース開始時に呼び出される。
-
on_trace_end(trace: Trace)
- トレース終了時に呼び出される。
-
on_span_start(span: Span[Any])
- スパン開始時に呼び出される。
-
on_span_end(span: Span[Any])
- スパン終了時に呼び出される。
-
shutdown()
とforce_flush()
- エクスポートバッファのフラッシュやクローズ処理を行います。
-
-
実装例
- デフォルトでは、
SynchronousMultiTracingProcessor
やBatchTraceProcessor
(src/agents/tracing/processors.py)を利用しており、バックエンド(例:OpenAI のトレースエンドポイント)へのエクスポートが可能です。
- デフォルトでは、
5. ユーティリティ
-
ID 生成
-
gen_trace_id()
とgen_span_id()
(src/agents/tracing/util.py)- UUID を元にユニークなトレースID/スパンIDを生成します。
-
-
現在のトレース/スパンの管理
-
Scope
クラス(src/agents/tracing/scope.py)- 現在のトレースやスパンを
contextvars
を使って管理します。
- 現在のトレースやスパンを
-
ref. https://openai.github.io/openai-agents-python/tracing/
まとめ
OpenAI Agents SDK の7つのインターフェイスについて、実際にコードを分析した内容を共有してみました。
ここまで分析してみると、自信を持って Hello World しやすいのではないかと思います!
良かったらいいね&リポストしてもらえると嬉しいです!
余談
GitHub Repo全体をコード分析を行うために、完全自分用の自作ツールの
を使って、GitHub Repoの src/agents
以下のディレクトリ構造を一つのMarkdownに変換し、ChatGPT で分析を行いました。
ただ、もっと遥かに普及しているrepomixがオススメで、これからやる人はrepomixを使うのが良いと思います。
Discussion