Hacktoberfest 2026: le issue che i maintainer hanno segnato per ottobre, aperte e adatte ai principianti. Sfoglia le issue Hacktoberfest

Streamed run keeps calling the model and tools after its only stream_events() consumer stops outside the queue wait

Aperta
#5,386 5 commenti 0 reazioni 0 assegnatari Vedi su GitHub

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

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 main at 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

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di openai/openai-agents-python

Tutte le issue di openai/openai-agents-python

Issue simili

Altre issue su Python

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.