chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
|
||||
from agents import (
|
||||
Agent,
|
||||
GuardrailFunctionOutput,
|
||||
OutputGuardrail,
|
||||
OutputGuardrailTripwireTriggered,
|
||||
RunContextWrapper,
|
||||
)
|
||||
from agents.run_internal.guardrails import run_output_guardrails
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_run_output_guardrails_awaits_cancelled_tasks():
|
||||
"""When one output guardrail trips, sibling guardrails must be awaited after cancel.
|
||||
|
||||
Regression test: ``run_output_guardrails`` previously cancelled sibling tasks but
|
||||
did not await them, which can leak pending tasks and emit
|
||||
``Task was destroyed but it is pending!`` warnings. The input-guardrail variants
|
||||
already await on cancel; the output variant should match.
|
||||
"""
|
||||
|
||||
slow_started = asyncio.Event()
|
||||
cancelled_observed = asyncio.Event()
|
||||
|
||||
async def slow_then_observe_cancel(
|
||||
context: RunContextWrapper[Any], agent: Agent[Any], agent_output: Any
|
||||
) -> GuardrailFunctionOutput:
|
||||
slow_started.set()
|
||||
try:
|
||||
await asyncio.sleep(5)
|
||||
except asyncio.CancelledError:
|
||||
cancelled_observed.set()
|
||||
raise
|
||||
return GuardrailFunctionOutput(output_info=None, tripwire_triggered=False)
|
||||
|
||||
async def fast_tripwire(
|
||||
context: RunContextWrapper[Any], agent: Agent[Any], agent_output: Any
|
||||
) -> GuardrailFunctionOutput:
|
||||
# Wait until the slow guardrail is actually parked on its sleep so the
|
||||
# subsequent cancel hits the installed CancelledError handler. Without
|
||||
# this, the slow task could be cancelled before reaching the try block
|
||||
# and ``cancelled_observed`` would stay unset even with the production
|
||||
# fix in place.
|
||||
await slow_started.wait()
|
||||
return GuardrailFunctionOutput(output_info=None, tripwire_triggered=True)
|
||||
|
||||
guardrails = [
|
||||
OutputGuardrail(guardrail_function=slow_then_observe_cancel),
|
||||
OutputGuardrail(guardrail_function=fast_tripwire),
|
||||
]
|
||||
agent: Agent[Any] = Agent(name="test")
|
||||
context: RunContextWrapper[Any] = RunContextWrapper(context=None)
|
||||
|
||||
with pytest.raises(OutputGuardrailTripwireTriggered):
|
||||
await run_output_guardrails(guardrails, agent, "agent output", context)
|
||||
|
||||
# The slow guardrail must have observed cancellation and finished before
|
||||
# ``run_output_guardrails`` returned. If it had not been awaited, this event
|
||||
# would still be unset at this point.
|
||||
assert cancelled_observed.is_set()
|
||||
Reference in New Issue
Block a user