Streamed run keeps calling the model and tools after its only stream_events() consumer stops outside the queue wait
I maintainer di solito rispondono entro 1 giorno
Nessuno ha ancora preso questa issue.
Valutazione
- Difficoltà
- 4/5
- Tempo stimato
- 3-5 giorni
- Idoneità per principianti
- 52/100
- Tipo di issue
- Bug
- Chiarezza
- Specificata chiaramente
- Stato di attività
- Attiva
- Stack tecnologico
- python
- Ambito
- ai-infra-agents
Direzione di ricerca
Start by reading RunResultStreaming.stream_events() and its cancellation behavior, especially the related cases in #2222 and #5213; run the supplied reproduction to observe the background tool calls. Done means the run is cancelled when its last consumer exits early by raising, cancellation, or aclose(), without aclose() waiting for the run to finish.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
Please read this first
- Have you read the docs? Yes, including the streaming cancellation section.
- Have you searched for related issues? Yes. #2222 made
stream_events()cancel the run when the consumer is cancelled while it waits for the next event. #5213 covers the waits on the input guardrail and run-loop tasks. This report is about a consumer that stops while it is handling an event, outside that wait.
Describe the bug
When the only consumer of RunResultStreaming.stream_events() stops while handling an event, the streamed run keeps going in the background. It keeps calling the model and tools, with nobody reading its events. This happens if the consumer raises from the loop body, or if its task is cancelled while it is awaiting something of its own (for example writing the event somewhere). Only a cancellation that lands on the internal await self._event_queue.get() stops the run (#2222).
A common case is a consumer that forwards each event elsewhere under an outer asyncio.timeout(). If the timeout expires during the forward, or the forward fails, the run carries on, and every model call it makes after that is still billed.
Closing the abandoned generator doesn't help either. With cancelled == False, the finally in stream_events() awaits run_loop_task, so aclosing(result.stream_events()) waits for the run to finish rather than stopping it.
The only way out today is for every caller to wrap its loop and call result.cancel() on any early exit.
Debug information
- Agents SDK version: v0.22.3, v0.24.0 and
mainat 9abc9182 - Python version: 3.12
- Operating system: macOS
- Model and model provider: none; reproduced with
agents.testing.ScriptedModel - Does the issue reproduce with the latest Agents SDK release? Yes
- Does the issue occur consistently or intermittently? Consistently
Repro steps
import asyncio
from agents import Agent, Runner, function_tool
from agents.testing import ScriptedModel, assistant_message, function_call
calls = 0
@function_tool
async def work() -> str:
"""Do one unit of work."""
global calls
calls += 1
await asyncio.sleep(0.05)
return "ok"
def agent() -> Agent:
steps = [[function_call("work", {}, call_id=f"c{i}")] for i in range(20)]
return Agent(
name="a", model=ScriptedModel([*steps, [assistant_message("done")]]), tools=[work]
)
async def consumer_raises_mid_body() -> None:
"""The consumer fails while handling an event (e.g. its publish fails)."""
global calls
calls = 0
result = Runner.run_streamed(agent(), "go", max_turns=50)
try:
async for _ in result.stream_events():
if calls:
raise ConnectionError("downstream publish failed")
except ConnectionError:
pass
at_exit = calls
await asyncio.sleep(1.0)
print(f"raise in loop body: tool calls at exit={at_exit}, 1s later={calls}, "
f"run done={result.run_loop_task.done() if result.run_loop_task else None}")
async def consumer_cancelled_mid_body() -> None:
"""The consumer's task is cancelled while it is handling an event, not awaiting the queue."""
global calls
calls = 0
result = Runner.run_streamed(agent(), "go", max_turns=50)
handling = asyncio.Event()
async def consume() -> None:
async for _ in result.stream_events():
if calls:
handling.set()
await asyncio.sleep(10) # e.g. a slow publish
task = asyncio.create_task(consume())
await handling.wait()
task.cancel()
await asyncio.gather(task, return_exceptions=True)
at_exit = calls
await asyncio.sleep(1.0)
print(f"cancel in loop body: tool calls at exit={at_exit}, 1s later={calls}, "
f"run done={result.run_loop_task.done() if result.run_loop_task else None}")
async def main() -> None:
await consumer_raises_mid_body()
await consumer_cancelled_mid_body()
asyncio.run(main())
Output on v0.24.0:
raise in loop body: tool calls at exit=1, 1s later=20, run done=False
cancel in loop body: tool calls at exit=1, 1s later=19, run done=False
The consumer stopped after the first tool call, but the run went on to make all the others.
Expected behavior
When the last registered consumer stops early, whether through an exception from the loop body, cancellation at any point, or aclose() on the generator, the run should be cancelled the same way a cancellation during the queue wait already cancels it (self.cancel()). If keeping the run alive is intended (for example, so another consumer can attach later), it would help to document that the code calling run_streamed() must call result.cancel() on every early exit, and for aclose() not to wait for the whole run to finish.
- Lingua principale
- Python
- Stelle
- 30k
- Fork
- 4.9k
- Merge medio
- 9h 53m
- PR unite (30g)
- 197
Preparare l'ambiente
- Nessun Dockerfile né file Docker Compose
- Ha un modello di pull request
- Leggi la guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di openai/openai-agents-python
-
fix(typing): explicitly specialize ToolContext[Any] in ToolFunctionWithToolContextForse già presa @AbarnaaSree l’ha presa 1 giorno fa. Aperta
Difficoltà 1/5 Meno di un'ora Idoneità per principianti 85/100
openai/openai-agents-python#5387 · 3 commenti ·
I maintainer di solito rispondono entro 1 giorno
-
ShellTool: empty ShellResult(output=[]) fabricates stdout "(no output)" in the model-visible shell_call_output and persisted shell_outputForse già presa @AbarnaaSree l’ha presa 1 giorno fa. Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 77/100
openai/openai-agents-python#5348 · 7 commenti ·
I maintainer di solito rispondono entro 1 giorno
-
question
Difficoltà 2/5 1-3 ore Idoneità per principianti 35/100
openai/openai-agents-python#5396 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
Invalid handoff arguments end the run, while invalid function tool arguments go back to the modelAperta
Difficoltà 3/5 1-2 giorni Idoneità per principianti 62/100
openai/openai-agents-python#5392 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
enhancement
Difficoltà 4/5 3-5 giorni Idoneità per principianti 35/100
openai/openai-agents-python#5383 ·
I maintainer di solito rispondono entro 1 giorno
Tutte le issue di openai/openai-agents-python
Issue simili
-
get_page_thumbnail modifies cached page images, affecting later thumbnails and picture exportsAperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 68/100
docling-project/docling-mcp#147 ·
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 80/100
I maintainer di solito rispondono entro 1 giorno
-
enhancement
Difficoltà 2/5 1-3 ore Idoneità per principianti 76/100
autowarefoundation/autoware_carla_scenario#405 ·
I maintainer di solito rispondono entro 1 giorno
-
bug
Difficoltà 2/5 1-3 ore Idoneità per principianti 79/100
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
mohitmishra786/readprism#322 ·
I maintainer di solito rispondono entro 1 giorno