Post Hook Output
Validate agent responses for completeness, tone, safety, and length with post_hooks that raise OutputCheckError.
Example demonstrating output validation using post-hooks with Agno Agent.
"""
Post Hook Output
=============================
Example demonstrating output validation using post-hooks with Agno Agent.
"""
import asyncio
from agno.agent import Agent
from agno.exceptions import CheckTrigger, OutputCheckError
from agno.models.openai import OpenAIResponses
from agno.run.agent import RunOutput
from pydantic import BaseModel
# ---------------------------------------------------------------------------
# Create Agent
# ---------------------------------------------------------------------------
class OutputValidationResult(BaseModel):
is_complete: bool
is_professional: bool
is_safe: bool
concerns: list[str]
confidence_score: float
def validate_response_quality(run_output: RunOutput) -> None:
"""
Post-hook: Validate the agent's response for quality and safety.
This hook checks:
- Response completeness (not too short or vague)
- Professional tone and language
- Safety and appropriateness of content
Raises OutputCheckError if validation fails.
"""
# Skip validation for empty responses
if not run_output.content or len(run_output.content.strip()) < 10:
raise OutputCheckError(
"Response is too short or empty",
check_trigger=CheckTrigger.OUTPUT_NOT_ALLOWED,
)
# Create a validation agent
validator_agent = Agent(
name="Output Validator",
model=OpenAIResponses(id="gpt-5-mini"),
instructions=[
"You are an output quality validator. Analyze responses for:",
"1. COMPLETENESS: Response addresses the question thoroughly",
"2. PROFESSIONALISM: Language is professional and appropriate",
"3. SAFETY: Content is safe and doesn't contain harmful advice",
"",
"Provide a confidence score (0.0-1.0) for overall quality.",
"List any specific concerns found.",
"",
"Be reasonable - don't reject good responses for minor issues.",
],
output_schema=OutputValidationResult,
)
validation_result = validator_agent.run(
input=f"Validate this response: '{run_output.content}'"
)
result = validation_result.content
# Check validation results and raise errors for failures
if not result.is_complete:
raise OutputCheckError(
f"Response is incomplete. Concerns: {', '.join(result.concerns)}",
check_trigger=CheckTrigger.OUTPUT_NOT_ALLOWED,
)
if not result.is_professional:
raise OutputCheckError(
f"Response lacks professional tone. Concerns: {', '.join(result.concerns)}",
check_trigger=CheckTrigger.OUTPUT_NOT_ALLOWED,
)
if not result.is_safe:
raise OutputCheckError(
f"Response contains potentially unsafe content. Concerns: {', '.join(result.concerns)}",
check_trigger=CheckTrigger.OUTPUT_NOT_ALLOWED,
)
if result.confidence_score < 0.6:
raise OutputCheckError(
f"Response quality score too low ({result.confidence_score:.2f}). Concerns: {', '.join(result.concerns)}",
check_trigger=CheckTrigger.OUTPUT_NOT_ALLOWED,
)
def simple_length_validation(run_output: RunOutput) -> None:
"""
Simple post-hook: Basic validation for response length.
Ensures responses are neither too short nor excessively long.
"""
content = run_output.content.strip()
if len(content) < 20:
raise OutputCheckError(
"Response is too brief to be helpful",
check_trigger=CheckTrigger.OUTPUT_NOT_ALLOWED,
)
if len(content) > 5000:
raise OutputCheckError(
"Response is too lengthy and may overwhelm the user",
check_trigger=CheckTrigger.OUTPUT_NOT_ALLOWED,
)
async def main():
"""Demonstrate output validation post-hooks."""
print("Output Validation Post-Hook Example")
print("=" * 60)
# Agent with comprehensive output validation
agent_with_validation = Agent(
name="Customer Support Agent",
model=OpenAIResponses(id="gpt-5-mini"),
post_hooks=[validate_response_quality],
instructions=[
"You are a helpful customer support agent.",
"Provide clear, professional responses to customer inquiries.",
"Be concise but thorough in your explanations.",
],
)
# Agent with simple validation only
agent_simple = Agent(
name="Simple Agent",
model=OpenAIResponses(id="gpt-5-mini"),
post_hooks=[simple_length_validation],
instructions=[
"You are a helpful assistant. Keep responses focused and appropriate length."
],
)
# Test 1: Good response (should pass validation)
print("\n[TEST 1] Well-formed response")
print("-" * 40)
try:
await agent_with_validation.aprint_response(
input="How do I reset my password on my Microsoft account?"
)
print("[OK] Response passed validation")
except OutputCheckError as e:
print(f"[ERROR] Validation failed: {e}")
print(f" Trigger: {e.check_trigger}")
# Test 2: Force a short response (should fail simple validation)
print("\n[TEST 2] Too brief response")
print("-" * 40)
try:
# Use a more constrained instruction to get a brief response
brief_agent = Agent(
name="Brief Agent",
model=OpenAIResponses(id="gpt-5-mini"),
post_hooks=[simple_length_validation],
instructions=["Answer in 1-2 words only."],
)
await brief_agent.aprint_response(input="What is the capital of France?")
except OutputCheckError as e:
print(f"[ERROR] Validation failed: {e}")
print(f" Trigger: {e.check_trigger}")
# Test 3: Normal response with simple validation
print("\n[TEST 3] Normal response with simple validation")
print("-" * 40)
try:
await agent_simple.aprint_response(
input="Explain what a database is in simple terms."
)
print("[OK] Response passed simple validation")
except OutputCheckError as e:
print(f"[ERROR] Validation failed: {e}")
print(f" Trigger: {e.check_trigger}")
# ---------------------------------------------------------------------------
# Run Agent
# ---------------------------------------------------------------------------
if __name__ == "__main__":
asyncio.run(main())Guardrail InputCheckError and OutputCheckError become failed run outputs. They are not caught by the source's try/except around print_response() or aprint_response(). Use run() or arun() and check the returned status before displaying content or declaring success.
Add this helper after your imports:
from agno.run import RunStatus
def show_checked_response(response) -> None:
if response.status != RunStatus.completed:
print(f"Run rejected or failed ({response.status.value}).")
return
print(response.content)A generic failed status can also indicate a provider error. Nonstream run outputs do not expose a check_trigger field. Output rejected by a post-hook can remain in the run record; this helper withholds it from the display. Streaming content may already have been emitted before the post-hook runs.
Require a valid validator result
Immediately before result = validation_result.content, insert this check in the validation hook:
if validation_result.status != RunStatus.completed or not isinstance(validation_result.content, OutputValidationResult):
raise OutputCheckError(
"The validator did not return a valid result.",
check_trigger=CheckTrigger.OUTPUT_NOT_ALLOWED,
)Ordinary hook exceptions are logged and execution continues. Raising the appropriate check error makes a failed, refused, or unparsed validator response stop this run instead of bypassing the check.
Current runner
After adding the helper and typed validator check, replace main() with:
async def main():
agent_with_validation = Agent(name='Customer Support Agent', model=OpenAIResponses(id='gpt-5-mini'), post_hooks=[validate_response_quality], instructions=['You are a helpful customer support agent.', 'Provide clear, professional responses to customer inquiries.', 'Be concise but thorough in your explanations.'])
agent_simple = Agent(name='Simple Agent', model=OpenAIResponses(id='gpt-5-mini'), post_hooks=[simple_length_validation], instructions=['You are a helpful assistant. Keep responses focused and appropriate length.'])
brief_agent = Agent(name='Brief Agent', model=OpenAIResponses(id='gpt-5-mini'), post_hooks=[simple_length_validation], instructions=['Answer in 1-2 words only.'])
for target, prompt in (
(agent_with_validation, "How do I reset my password on my Microsoft account?"),
(brief_agent, "What is the capital of France?"),
(agent_simple, "Explain what a database is in simple terms."),
):
show_checked_response(await target.arun(input=prompt))Run the Example
Set up your virtual environment
uv venv --python 3.12
source .venv/bin/activateInstall dependencies
uv pip install -U agno openaiExport your OpenAI API key
export OPENAI_API_KEY="your_openai_api_key_here"Apply the current checks
Add the status helper and apply the replacements described above. Keep the original imports and agent definitions that the replacement uses.
Run the example
Save the adapted code as post_hook_output.py, then run:
python post_hook_output.pyFull source: cookbook/02_agents/09_hooks/post_hook_output.py