開発者ガイド

OpenAI Agents SDK エージェントに事前行為チェックを追加する

ツールラッパーが提案された行為を先にチェックしなければ、エージェントの関数ツールはその処理を実行できます。

チェックの動作

チェックは permit、 deny、または escalate を、判断を決めた条項および署名付きレシートとともに返します。これは助言的であり、実行を拒否するのは関数ツールのラッパーです。ホスト型チェックはリクエストをメモリ内で評価し、リクエスト内容を保持しません。

コード

これは、次の場所で公開されている監査済みの例と完全に同じものです: public/examples/frameworks/.

"""OpenAI Agents SDK tool wrapper that checks HANRIA before execution."""

from __future__ import annotations

import json
from collections.abc import Callable
from typing import Any
from urllib.error import URLError
from urllib.request import Request, urlopen

from agents import Agent, function_tool

CHECK_URL = "https://check.hanria.ai/v1/check"
MANDATE = {
    "schema_version": "0.2-draft",
    "mandate_id": "read-text-files",
    "purpose": "Permit reads of text files under the example directory.",
    "default": "deny",
    "clauses": [
        {
            "id": "permit-example-read",
            "effect": "permit",
            "match": {
                "kind": ["file"],
                "verb": ["read"],
                "target_prefix": ["/tmp/example/"],
            },
        }
    ],
}


def check_action(action: dict[str, Any], check_url: str = CHECK_URL) -> dict[str, Any]:
    """Return a HANRIA decision, mapping every transport or response failure to error."""
    body = json.dumps({"mandate": MANDATE, "action": action}).encode()
    request = Request(check_url, data=body, headers={"content-type": "application/json"})
    try:
        with urlopen(request, timeout=10) as response:
            decision = json.load(response)
    except (OSError, URLError, ValueError, json.JSONDecodeError) as error:
        return {"outcome": "error", "reason": f"HANRIA check failed: {error}"}
    if not isinstance(decision, dict) or decision.get("outcome") not in {
        "permit", "deny", "escalate", "error"
    }:
        return {"outcome": "error", "reason": "HANRIA returned an invalid decision"}
    return decision


def guarded_tool_call(
    tool_name: str,
    operation: dict[str, Any],
    execute: Callable[[], Any],
    check_url: str = CHECK_URL,
) -> Any:
    """Call execute only after HANRIA returns permit for the proposed tool call."""
    action = {
        "schema_version": "0.1-draft",
        "requested_by": {"agent": "openai-agents-example"},
        "operation": operation,
        "justification": f"Run {tool_name} through the OpenAI Agents SDK.",
    }
    decision = check_action(action, check_url)
    if decision.get("outcome") != "permit":
        return (
            f"Tool refused. HANRIA outcome={decision.get('outcome', 'error')}; "
            f"reason={decision.get('reason', 'no reason returned')}. Do not retry blindly."
        )
    return execute()


def build_agent(check_url: str = CHECK_URL) -> Agent:
    """Construct an agent whose file tool is wrapped by the HANRIA check."""

    @function_tool
    def read_text_file(path: str) -> str:
        """Read one UTF-8 text file after the mandate permits it."""
        return guarded_tool_call(
            "read_text_file",
            {"kind": "file", "verb": "read", "target": path},
            lambda: open(path, encoding="utf-8").read(),
            check_url,
        )

    return Agent(
        name="HANRIA guarded reader",
        instructions="Use read_text_file only when it is needed.",
        tools=[read_text_file],
    )


if __name__ == "__main__":
    agent = build_agent()
    print(f"Constructed {agent.name} with {len(agent.tools)} guarded tool.")

実行する

openai_agents.py を保存し、そのフォルダーで次のコマンドを実行します。

uv run --with openai-agents==0.23.1 python openai_agents.py

このコマンドは、保護された関数ツールを 1 つ持つエージェントを構築します。チェックが判断するのは記述された操作だけなので、各ツールが実際に行うことからそのツールの操作を組み立ててください。 guarded_tool_call の呼び出しをマンデートのチェックが必要なすべてのツール内に置き、処理を行う前にその操作を渡します。

すべての結果を処理する

転送障害、不正な形式の応答、または未知の結果は errorとなり、正確な permit だけが execute() に到達します。

参照

チェックを試す · 署名付きレシートを検証する · OpenAPI 文書 · エージェント向け要約