Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ Check out a variety of sample implementations of the SDK in the examples section
- Dynamic system prompts
- Basic tool usage (`examples/basic/tools.py`)
- Tool input/output guardrails (`examples/basic/tool_guardrails.py`)
- Tool input guardrails for pre-execution policy checks (`examples/basic/tool_policy_guardrail.py`)
- Image tool output (`examples/basic/image_tool_output.py`)
- Streaming outputs (text, items, function call args)
- Responses websocket transport with a shared session helper across turns (`examples/basic/stream_ws.py`)
Expand Down
67 changes: 67 additions & 0 deletions docs/guardrails.md
Original file line number Diff line number Diff line change
Expand Up @@ -227,3 +227,70 @@ agent = Agent(name="Classifier", tools=[classify_text])
result = Runner.run_sync(agent, "hello world")
print(result.final_output)
```

## Using tool input guardrails for pre-execution policy checks

Tool input guardrails run immediately before a local function tool is invoked. This makes
them a good place to enforce deterministic checks on the exact tool payload that the model
requested, such as:

- tool allowlists
- argument constraints
- caller or agent context
- destination or recipient allowlists
- budget or amount limits
- freshness, nonce, or replay checks that your application stores in context

For example, a payment tool can reject a charge before execution if the amount or merchant
is outside the current policy:

```python
import json
from dataclasses import dataclass

from agents import (
ToolGuardrailFunctionOutput,
ToolInputGuardrailData,
tool_input_guardrail,
)


@dataclass
class PaymentPolicy:
max_amount_usd: float
allowed_merchants: set[str]


@tool_input_guardrail
def enforce_payment_policy(data: ToolInputGuardrailData) -> ToolGuardrailFunctionOutput:
policy = data.context.context
args = json.loads(data.context.tool_arguments or "{}")

if data.context.tool_name != "charge_card":
return ToolGuardrailFunctionOutput.reject_content("This tool is not allowed.")

merchant = args.get("merchant")
amount = float(args.get("amount_usd", 0))

if merchant not in policy.allowed_merchants:
return ToolGuardrailFunctionOutput.reject_content(
f"Payment blocked: merchant {merchant!r} is not approved."
)

if amount > policy.max_amount_usd:
return ToolGuardrailFunctionOutput.reject_content(
f"Payment blocked: ${amount:.2f} exceeds the ${policy.max_amount_usd:.2f} limit."
)

return ToolGuardrailFunctionOutput.allow(
{"merchant": merchant, "amount_usd": amount, "agent": data.agent.name}
)
```

Attach the guardrail to the function tool with `tool_input_guardrails=[...]`. If the
guardrail returns `reject_content`, the function tool is not called and the rejection message
is returned to the model so it can choose a different action or ask for approval.

Tool input guardrails apply to local function tools executed by the SDK process. Hosted tools
are executed server-side by OpenAI, so Python-side tool input guardrails cannot intercept them
before execution.
80 changes: 80 additions & 0 deletions examples/basic/tool_policy_guardrail.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import asyncio
import json
from dataclasses import dataclass

from agents import (
Agent,
Runner,
ToolGuardrailFunctionOutput,
ToolInputGuardrailData,
function_tool,
tool_input_guardrail,
)


@dataclass
class PaymentPolicy:
max_amount_usd: float
allowed_merchants: set[str]


@tool_input_guardrail
def enforce_payment_policy(data: ToolInputGuardrailData) -> ToolGuardrailFunctionOutput:
"""Check the exact payment payload before the tool is invoked."""
policy = data.context.context
args = json.loads(data.context.tool_arguments or "{}")

if data.context.tool_name != "charge_card":
return ToolGuardrailFunctionOutput.reject_content("This tool is not allowed.")

merchant = args.get("merchant")
amount = float(args.get("amount_usd", 0))

if merchant not in policy.allowed_merchants:
return ToolGuardrailFunctionOutput.reject_content(
f"Payment blocked: merchant {merchant!r} is not approved."
)

if amount > policy.max_amount_usd:
return ToolGuardrailFunctionOutput.reject_content(
f"Payment blocked: ${amount:.2f} exceeds the ${policy.max_amount_usd:.2f} limit."
)

return ToolGuardrailFunctionOutput.allow(
{"merchant": merchant, "amount_usd": amount, "agent": data.agent.name}
)


@function_tool(tool_input_guardrails=[enforce_payment_policy])
def charge_card(customer_id: str, merchant: str, amount_usd: float) -> str:
"""Charge a customer's saved card."""
return f"Charged ${amount_usd:.2f} to {customer_id} for {merchant}."


agent = Agent(
name="Payment assistant",
instructions="Help with payments, but respect the payment policy.",
tools=[charge_card],
)


async def main() -> None:
policy = PaymentPolicy(max_amount_usd=100.0, allowed_merchants={"example.com"})

result = await Runner.run(
agent,
"Charge customer cus_123 $25 for example.com.",
context=policy,
)
print(result.final_output)

blocked = await Runner.run(
agent,
"Charge customer cus_123 $250 for unknown.example.",
context=policy,
)
print(blocked.final_output)


if __name__ == "__main__":
asyncio.run(main())