エージェント フック

エージェント フックは、エージェントの実行で明確に定義されたポイントでガバナンスとランタイムコントロールを適用するための、一流の Agent Framework 機能です。 フレームワークに依存しない AGENT-HOOKS-0.1 コントラクトを実装するため、ポリシー エンジン、承認ゲートウェイ、予算ガード、コンテンツ フィルター、エグレスコントロールは、1 つの共通コントロール サーフェスをターゲットにすることができます。

Important

エージェント フックは、テレメトリ プレーンではなくコントロール プレーンです。 すべてのインターセプターは、判定を返します。 enforce モードでは、フレームワークはその判定に対して動作します。evaluate_only モードでは、実行を変更せずに判定を記録します。 パッシブ トレース、メトリック、ログに 可観測性 を使用します。

エージェント フックは、.NETではまだ使用できません。 エージェント ミドルウェアツールの承認エージェントの安全性を使用して、.NET エージェントにランタイム コントロールを追加します。

エージェント フックは、Pythonで試験的です。 ファクトリは最初に使用されたときに ExperimentalWarning を出力し、その API は一般公開前に変更される可能性があります。

Agent Hooks を使用する場合

個別に開発されたコントロールで、エージェント入力、モデル呼び出し、ツール呼び出し、および最終的な出力全体で 1 つの共有の強制可能なコントラクトが必要な場合は、エージェント フックを使用します。

能力 これを次の目的に使用します。
エージェント フック エージェントのライフサイクル全体にわたる標準化されたポリシーの決定、変換、承認、予算、エグレス制御。
エージェント ミドルウェア エージェント フック コントラクトまたはそのコア ランタイムの保証を必要としないアプリケーション固有のクロスカット動作。
FIDES を使用したエージェント セキュリティ 信頼されていないコンテンツまたは機密コンテンツの決定論的な情報フロー ラベルとポリシー。
ツールの承認 個々の関数ツール呼び出しの人間による確認。
可観測性 実行を制御しないパッシブ トレース、メトリック、ログ。

エージェント フレームワークで適用される内容

エージェントにエージェント フックを追加すると、エージェントの実行、モデル呼び出し、およびツール呼び出しの間で、エージェント フレームワークによって調整された適用境界が適用されます。 ランタイムは、次のことを保証します。

  • フェイルクローズ: 拒否すると、保護された操作はブロックされます。 無効なコンテキスト、無効な判定、インターセプターの失敗、または強制適用の失敗があっても、制御機構が気付かれないままバイパスされることはありません。
  • 書き戻しの変換: 変換によって、実行で実際に使用されるネイティブ メッセージ、ツール引数、ツールの結果、または最終的な応答が変更されます。 変換を適用できない場合、実行はフェイルクローズになります。
  • バッファリングされたストリーミング: 完全なモデル応答と最終的な出力がインターセプト ポイントを通過するまで、応答の更新が呼び出し元に到達しません。
  • 判定連動型の永続化: 永続化は、それに適用される判定が出るまで待機します。 標準の実行後永続化は outputを待機し、サービスごとの呼び出し履歴永続化は各 post_model_callを待機します。
  • バンドルのインストールを完了します。 エージェント、チャット、および関数パーツは 1 つのユニットとしてインストールされるため、不完全な強制境界を誤って構成することはできません。

コントラクトは、プロセス分離境界ではなく協調的です。 インターセプターはホスト プロセスで実行され、決定に必要なコンテンツを受け取ります。 信頼できるインターセプターのみを登録します。

エージェント フックのインストール

コアパッケージ用のオプションの追加機能 agent-hooks をインストールします。

pip install "agent-framework-core[agent-hooks]"

uvを使用する場合:

uv add "agent-framework-core[agent-hooks]"

agent-hooks-sdk 依存関係は遅延読み込みされます。 エージェント フック ミドルウェア バンドルを作成しない限り、 agent_framework をインポートしても SDK は読み込まれません。

Note

余分な agent-hooks は意図的に agent-framework-core[all]に含まれません。 この実験用コントロール サーフェイスを有効にする場合は、明示的にインストールします。

インターセプターを追加する

インターセプターは、 agent_hooks.AgentContext (エージェント ミドルウェアで使用される agent_framework.AgentContext ではなく、仕様のコンテキスト マッピング) を受け取り、判定を返します。 次のインターセプターは、 secretという単語を含む最終的な出力をブロックします。 この例では、 client が既に構成されている Agent Framework チャット クライアントであることを前提としています。

from agent_framework import Agent, create_agent_hooks_middleware
from agent_hooks import ALLOW, AgentContext, InterceptionBlocked, Verdict


class SecretEgressGuard:
    def intercept(self, context: AgentContext) -> Verdict:
        if (
            context["interception_point"] == "output"
            and "secret" in str(context["target"]).lower()
        ):
            return Verdict.deny(
                reason="secret_in_output",
                message="The final response contains restricted content.",
            )
        return ALLOW


hooks = create_agent_hooks_middleware(
    {"secret-egress": SecretEgressGuard()},
)

agent = Agent(
    client=client,
    instructions="You are a helpful assistant.",
    middleware=[hooks],
)

try:
    response = await agent.run("Summarize the account details.")
except InterceptionBlocked as exc:
    print(f"Blocked: {exc.result.verdict.reason}")

エージェントの middleware リストの 1 つの要素としてバンドルを渡します。 各エージェントにエージェント フック バンドルを 1 つだけインストールします。

インターセプト ポイント

Agent Framework は、適用可能なインターセプト ポイントを自動的に出力します。

インターセプト ポイント 出力されたとき 変換対象
agent_startup Agent Hooks セッションで最初の入力を行う前 変換不可
input 外部からのリクエストがエージェントに入ると 入力コンテンツとロール
pre_model_call 各モデルへのリクエストの前に モデルに送信されたメッセージ
post_model_call 各モデル応答が完了するたびに 応答コンテンツ、フレームワークで実行されるツール呼び出し、終了理由
pre_tool_call フレームワークで実行される各ツール呼び出しの前 ツールの引数
post_tool_call ツールが成功または失敗した後 ツールの結果
output 最後の応答が呼び出し元に到達する前 最終的な応答コンテンツ
agent_shutdown エージェント フック セッションが完了、失敗、または取り消されたとき 変換不可

ツールを呼び出す実行は、通常、次を出力します。

agent_startupinputpre_model_callpost_model_callpre_tool_callpost_tool_callpre_model_callpost_model_calloutputagent_shutdown

判定

契約には、 allowdenytransformの 3 つの決定があります。 Python SDK には、警告とリフト可能な拒否のヘルパーも用意されています。

Result Python API Behavior
許可する ALLOW または Verdict(decision=Decision.ALLOW) ターゲットを変更せずに続行します。
警告で許可する Verdict.warn(...) 続行し、警告をインターセプト記録に含めます。
拒否 Verdict.deny(...) ガードされたアクションをブロックします。
保留中の承認を拒否する Verdict.escalate(...) 構成された承認リゾルバーが許可の判定を返さない限り、ブロックします。
変換 Verdict(decision=Decision.TRANSFORM, transform=Transform(...)) $target 配下の値を書き換えてから、書き換え後の値で続行します。

実行レベルおよびモデルレベルでの拒否判定により、InterceptionBlocked が発生し、ガードされた結果が呼び出し元または次のステージに到達するのを防ぎます。 ツールの継ぎ目では、ポリシーの拒否によってツールアクションが防止されるか、結果が破棄され、拒否されたターゲット ペイロードを含まないポリシー理由を含む制御エラーがモデルに返されます。 これにより、エージェント ループを続行できます。 ホストまたは強制の失敗により、実行が停止します。

変換を適用する

変換パスは、 $targetから開始する必要があります。 たとえば、インターセプターは最終的な応答コンテンツを置き換えることができます。

from agent_hooks import ALLOW, AgentContext, Decision, Transform, Verdict


class OutputRedactor:
    def intercept(self, context: AgentContext) -> Verdict:
        if context["interception_point"] != "output":
            return ALLOW

        return Verdict(
            decision=Decision.TRANSFORM,
            reason="redacted_output",
            transform=Transform(
                path="$target.content",
                value="[Response removed by policy]",
            ),
        )

変換は Agent Framework Content 値に適用され、すべての値をプレーン テキストに減らすのではなく、サポートされているリッチ コンテンツを保持します。 正しくないパスまたは互換性のない置換は、元の値を続行する代わりに閉じて失敗します。

ツールの承認と引数の変換

Agent Framework ツールの承認とエージェント フック承認の継ぎ目は別のメカニズムです。 approval_mode="always_require"を使用する関数ツールの場合、Agent Framework は関数ミドルウェアを実行する前に人間の承認要求を作成します。 したがって、 pre_tool_call 変換では、ユーザーが元の値を承認した後に引数を変更できます。

Warning

approval_mode="always_require"を使用するツールのpre_tool_callで引数を変換しないでください。 フレームワーク承認要求に変換された値が含まれるように、post_model_callでツール呼び出しを変換するか、pre_tool_callVerdict.escalate(...)を返し、エージェント フック resolverを使用して承認を解決します。

ストリーミングと永続化

エージェント フックはストリーミング API を保持しますが、バッファー出力セマンティクスを使用します。 Agent Framework は、完全なモデル応答をアセンブルし、 post_model_callを出力し、最終的なエージェント応答をアセンブルして、更新プログラムをリリースする前に output を出力します。 いずれかのポイントが応答を拒否した場合、呼び出し元は部分的な更新を受信しません。

この動作は、トークン単位のレイテンシを犠牲にして、フェイルクローズな出力の強制を実現します。 出力変換は、最終的に呼び出し元にリリースされた更新にも反映されます。

永続化は、永続化操作を対象とするインターセプトポイントによって制御されます。

  • 既定では、履歴やその他の実行後のプロバイダー処理は、output の判定結果を待機します。 拒否された出力は永続化されず、出力変換は変換後に永続化されます。
  • Agentコンストラクターまたはclient.as_agent(...)require_per_service_call_history_persistence=Trueを設定すると、各モデル交換は、post_model_callの判定によって許可された後も保持されます。 後で output 拒否しても、既に許可されている履歴はロールバックされません。
  • 既定の実行後永続化の場合、再試行は最終的な output の決定の背後に残ります。 代わりに、サービスごとの呼び出しモードでは、 post_model_callを渡す各モデル応答が保持されます。

Important

モデル コンテンツを永続的にしてはならない場合は、require_per_service_call_history_persistence=True時にpost_model_callでそのポリシーを適用します。 出力専用エグレス ポリシーは、呼び出し元に到達するものを保護しますが、 post_model_callで既に許可および永続化されているモデル交換をさかのぼって削除することはありません。

セッションと監査レコード

既定では、エージェントの実行ごとに 1 つのエージェント フック セッションが作成されます。 agent_startupagent_shutdown は実行の前後に配置され、レコードには 1 つのセッション ID と単調増加するシーケンス番号が付与されます。

record_sinkを使用して各InterceptionRecordを受信します。

records = []

hooks = create_agent_hooks_middleware(
    {"secret-egress": SecretEgressGuard()},
    record_sink=records.append,
)

インターセプト レコードは、インターセプトされたペイロードを監査レコードにコピーせずに、決定、理由、インターセプターの概要、モード、ID、およびシーケンスをキャプチャします。 インターセプター自体は引き続き完全なコンテキストを受け取ります。

1つのセッションで複数の実行をまたいで処理する

アプリケーションが有効期間の長いエージェント フック セッション (1 つの承認台帳との会話など) を所有している場合は、 create_agent_hooks_middleware_from_emitter() を使用します。

from agent_framework import Agent, create_agent_hooks_middleware_from_emitter
from agent_hooks import AgentContextBuilder, InterceptionEmitter


emitter = InterceptionEmitter().register(SecretEgressGuard())
builder = AgentContextBuilder(
    agent_id="support-agent",
    framework="agent-framework",
    session_id="conversation-42",
)

hooks = create_agent_hooks_middleware_from_emitter(emitter, builder)
agent = Agent(client=client, middleware=[hooks])

await emitter.emit(builder.agent_startup(tools_registered=[]))
await agent.run("First turn")
await agent.run("Second turn")
await emitter.emit(builder.agent_shutdown(reason="completed"))

この形式では、アプリケーションはエミッタを構成し、スタートアップ、シャットダウン、エラーのクリーンアップを所有します。 ミドルウェアは、 input から outputまでの実行ごとのポイントを出力します。

適用を設定する

create_agent_hooks_middleware() は、次のコントロールを受け入れます。

Parameter Purpose
interceptors インターセプターのシーケンスまたは名前とインターセプターのマッピング。 少なくとも 1 つが必要です。
resolver 承認チャネルを通じて、解除可能な拒否決定を解決します。 リゾルバーがないと、拒否は有効なままです。
mode "enforce" は、判定を適用します。 "evaluate_only" は、何が起こるかを記録しますが、すべてのアクションを許可します。
composition 複数のインターセプターの判定を組み合わせる方法を選択します。
identity_provider コンテンツ バインド コンテキスト ID を生成します。 既定値は "jcs-sha256" です。
timeout await 可能な呼び出しに対する、インターセプターごとおよびリゾルバーごとのタイムアウト。 既定値は 5 秒です。 イベント ループをブロックする同期インターセプターまたはリゾルバーは、このタイムアウトによって割り込まれません。
record_sink ペイロードを含まない各インターセプトレコードを受け取ります。

既定の構成はシーケンシャル first_deny で、承認によりフォールドが停止するよう設定されています。 したがって、インターセプターの順序は重要です。承認を要求できるコントロールの前に常に実行する必要があるコントロールを配置します。 別のコンポジション プロファイルを選択する前に、エージェント フック の運用チェックリスト を参照してください。

評価専用モードでロールアウトする

evaluate_onlyを使用して、適用前のポリシー動作を測定します。

hooks = create_agent_hooks_middleware(
    {"secret-egress": SecretEgressGuard()},
    mode="evaluate_only",
    record_sink=records.append,
)

このモードでは、インターセプターが実行され、レコードに判定が含まれますが、ブロックまたは変換されるアクションはありません。 evaluate_only のデプロイを、強制されたガバナンスとして説明しないでください。

コンポジション ルール

最も外側の強制境界を形成するように、最初にエージェントのミドルウェア リストにバンドルを配置します。

agent = Agent(
    client=client,
    middleware=[
        create_agent_hooks_middleware([SecretEgressGuard()]),
        application_middleware,
    ],
)

次の規則に従います。

  • エージェントごとに 1 つのエージェント フック バンドルをインストールします。 積み重ねられたバンドルは受け付けられません。
  • バンドルはそのままにします。 エージェント、チャット、および関数ミドルウェアを個別にインストールすることはできません。
  • チャット クライアントやコンテキスト プロバイダーを介して直接ではなく、 Agentにバンドルをインストールします。
  • バンドルの前に配置されたミドルウェアは、強制境界の外にあります。 外側の位置を外部信頼として扱います。
  • 内部モデルとツール アクティビティにもインターセプトが必要な場合は、入れ子になった各エージェントに独自のバンドルを付与します。

現在の制限

  • Pythonのみ: エージェント フックは、.NET SDK または Go SDK にまだ実装されていません。
  • 試験段階の API: ファクトリのシグネチャと動作は、一般公開前に変更される可能性があります。
  • バッファ型ストリーミング: フェイルクローズ判定の前に出力を完全な状態にしておく必要があるため、更新はトークン単位では配信されません。
  • ホストされるツール: モデル プロバイダーによって実行されるツールは、Agent Framework の関数呼び出しの継ぎ目を通過しません。 呼び出しと出力は post_model_callに表示されますが、 pre_tool_callpost_tool_call はプロバイダーのサーバー側の実行をブロックできません。
  • 協調境界: エージェント フックは、サンドボックス インターセプターや、敵対的なホストからの保護を行いません。 保護されたエージェント パイプラインをバイパスするコード パスについては説明しません。
  • インターセプターの可用性は、エージェントの可用性に影響します。 強制モードでは、インターセプターの障害またはタイムアウトによって、保護されたアクションが設計によってブロックされます。

本番展開、障害の原因、およびアラートに関するガイダンスについては、Agent Hooks の運用ランブックを参照してください。

エージェント フックは Go ではまだ使用できません。 エージェント ミドルウェアツールの承認エージェントの安全性を使用して、Go エージェントにランタイム コントロールを追加します。

次のステップ