Skip to content

Commit bb86bdd

Browse files
Chris Kinzelcopybara-github
authored andcommitted
fix: prevent contextvars leak across async generators
Merge #5725 Fixes #5722 PiperOrigin-RevId: 967359464
1 parent 8d2f277 commit bb86bdd

3 files changed

Lines changed: 547 additions & 196 deletions

File tree

‎src/google/adk/agents/base_agent.py‎

Lines changed: 60 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
from typing import Union
3333

3434
from google.genai import types
35+
from opentelemetry import context
3536
from pydantic import BaseModel
3637
from pydantic import ConfigDict
3738
from pydantic import Field
@@ -87,6 +88,21 @@ class BaseAgentState(BaseModel):
8788

8889

8990
AgentState = TypeVar('AgentState', bound=BaseAgentState)
91+
_T = TypeVar('_T')
92+
93+
94+
async def _with_caller_context(
95+
agen: AsyncGenerator[_T, None],
96+
caller_ctx: context.Context,
97+
) -> AsyncGenerator[_T, None]:
98+
"""Wraps an async generator to attach caller_ctx around each yield."""
99+
async with Aclosing(agen) as a:
100+
async for item in a:
101+
token = context.attach(caller_ctx)
102+
try:
103+
yield item
104+
finally:
105+
context.detach(token)
90106

91107

92108
# TODO: drop the explicit abc.ABC base once BaseNode surfaces ABCMeta to
@@ -295,26 +311,33 @@ async def run_async(
295311
Event: the events generated by the agent.
296312
"""
297313

298-
ctx = self._create_invocation_context(parent_context)
299-
async with _instrumentation.record_agent_invocation(ctx, self):
300-
try:
301-
if event := await self._handle_before_agent_callback(ctx):
302-
yield event
303-
if ctx.end_invocation:
304-
return
314+
caller_ctx = context.get_current()
305315

306-
async with Aclosing(self._run_async_impl(ctx)) as agen:
307-
async for event in agen:
316+
async def _run() -> AsyncGenerator[Event, None]:
317+
ctx = self._create_invocation_context(parent_context)
318+
async with _instrumentation.record_agent_invocation(ctx, self):
319+
try:
320+
if event := await self._handle_before_agent_callback(ctx):
308321
yield event
322+
if ctx.end_invocation:
323+
return
324+
325+
async with Aclosing(self._run_async_impl(ctx)) as agen:
326+
async for event in agen:
327+
yield event
309328

310-
if ctx.end_invocation:
311-
return
329+
if ctx.end_invocation:
330+
return
312331

313-
if event := await self._handle_after_agent_callback(ctx):
314-
yield event
315-
except Exception as e:
316-
await self._handle_agent_error_callback(ctx, e)
317-
raise
332+
if event := await self._handle_after_agent_callback(ctx):
333+
yield event
334+
except Exception as e:
335+
await self._handle_agent_error_callback(ctx, e)
336+
raise
337+
338+
async with Aclosing(_with_caller_context(_run(), caller_ctx)) as agen:
339+
async for event in agen:
340+
yield event
318341

319342
@override
320343
async def _run_impl(
@@ -350,23 +373,30 @@ async def run_live(
350373
Event: the events generated by the agent.
351374
"""
352375

353-
ctx = self._create_invocation_context(parent_context)
354-
async with _instrumentation.record_agent_invocation(ctx, self):
355-
try:
356-
if event := await self._handle_before_agent_callback(ctx):
357-
yield event
358-
if ctx.end_invocation:
359-
return
376+
caller_ctx = context.get_current()
377+
378+
async def _run() -> AsyncGenerator[Event, None]:
379+
ctx = self._create_invocation_context(parent_context)
380+
async with _instrumentation.record_agent_invocation(ctx, self):
381+
try:
382+
if event := await self._handle_before_agent_callback(ctx):
383+
yield event
384+
if ctx.end_invocation:
385+
return
386+
387+
async with Aclosing(self._run_live_impl(ctx)) as agen:
388+
async for event in agen:
389+
yield event
360390

361-
async with Aclosing(self._run_live_impl(ctx)) as agen:
362-
async for event in agen:
391+
if event := await self._handle_after_agent_callback(ctx):
363392
yield event
393+
except Exception as e:
394+
await self._handle_agent_error_callback(ctx, e)
395+
raise
364396

365-
if event := await self._handle_after_agent_callback(ctx):
366-
yield event
367-
except Exception as e:
368-
await self._handle_agent_error_callback(ctx, e)
369-
raise
397+
async with Aclosing(_with_caller_context(_run(), caller_ctx)) as agen:
398+
async for event in agen:
399+
yield event
370400

371401
async def _run_async_impl(
372402
self, ctx: InvocationContext

0 commit comments

Comments
 (0)