Guarding what comes out
The other half. An input guardrail decides what the agent is allowed to hear; an output guardrail decides what the customer is allowed to see.
@output_guardrail
async def no_promises(context, agent, agent_output):
"""Never let the agent promise a delivery date."""
promised = "guarantee" in str(agent_output).lower()
return GuardrailFunctionOutput(
output_info={"promised": promised},
tripwire_triggered=promised,
)Same shape as lesson 16, with one difference in the arguments: this one is handed the agent's answer instead of the user's question.
for agent in (careful, reckless):
try:
result = await Runner.run(agent, "when will it arrive?")
print(f"{agent.name:<9} -> {result.final_output}")
except OutputGuardrailTripwireTriggered:
print(f"{agent.name:<9} -> blocked, the customer never saw it")The reckless agent produced its answer, the guardrail read it, and the run raised OutputGuardrailTripwireTriggered before anything reached the customer.
Only on the last agent
The mirror of lesson 16, and the docs are equally explicit: an agent's output guardrails only run if that agent is the last one, the one that actually produced the final answer.
In a handoff chain that is the agent the work ended up with, not the one you started. If Triage hands to Billing, Billing's output guardrails run and Triage's do not.
Which means a rule you want applied to everything the customer ever sees has to go on every agent that can finish a run. There is no single place to put it.
The awkward part
The model call already happened. You paid for it, and the answer exists. An output guardrail cannot save you money, only save you from sending something.
So the two are for different jobs. Input guardrails are cheap and catch whole categories of request. Output guardrails are the last line, for the things you cannot describe in advance and can only recognise when you see them.
| Input guardrail | Output guardrail | |
|---|---|---|
| Runs | before the model | after the model |
| Costs | nothing | the model call you are about to discard |
| Catches | questions you will not answer | answers you will not send |
| Typical use | off topic, prompt injection, abuse | promises, private data, wrong tone |
- Ban a second word and try an answer containing it.
- Catch the exception and print a fixed apology instead.
- Read
output_infooff the exception and log which rule fired.
You understood something today that you didn't yesterday.