Skip to content

fix(core): context preservation in shielded async callbacks #32163

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: master
Choose a base branch
from

Conversation

Copilot
Copy link
Contributor

@Copilot Copilot AI commented Jul 22, 2025

The @shielded decorator in async callback managers was not preserving context variables, breaking OpenTelemetry instrumentation and other context-dependent functionality.

Problem

When using async callbacks with the @shielded decorator (applied to methods like on_llm_end, on_chain_end, etc.), context variables were not being preserved across the shield boundary. This caused issues with:

  • OpenTelemetry span context propagation
  • Other instrumentation that relies on context variables
  • Inconsistent context behavior between sync and async execution

The issue was reproducible with:

from contextvars import copy_context
import asyncio
from langgraph.graph import StateGraph

# Sync case: context remains consistent
print("SYNC")
print(copy_context())  # Same object
graph.invoke({"result": "init"})
print(copy_context())  # Same object

# Async case: context was inconsistent (before fix)
print("ASYNC") 
asyncio.run(graph.ainvoke({"result": "init"}))
print(copy_context())  # Different object than expected

Root Cause

The original shielded decorator implementation:

async def wrapped(*args: Any, **kwargs: Any) -> Any:
    return await asyncio.shield(func(*args, **kwargs))

Used asyncio.shield() directly without preserving the current execution context, causing context variables to be lost.

Solution

Modified the shielded decorator to:

  1. Capture the current context using copy_context()
  2. Create a task with explicit context using asyncio.create_task(coro, context=ctx) for Python 3.11+
  3. Shield the context-aware task
  4. Fallback to regular task creation for Python < 3.11
async def wrapped(*args: Any, **kwargs: Any) -> Any:
    # Capture the current context to preserve context variables
    ctx = copy_context()
    coro = func(*args, **kwargs)
    
    try:
        # Create a task with the captured context to preserve context variables
        task = asyncio.create_task(coro, context=ctx)
        return await asyncio.shield(task)
    except TypeError:
        # Python < 3.11 fallback
        task = asyncio.create_task(coro)
        return await asyncio.shield(task)

Testing

  • Added comprehensive test test_shielded_callback_context_preservation() that validates context variables are preserved across shielded callback boundaries
  • Verified the fix resolves the original LangGraph context consistency issue
  • Confirmed all existing callback manager tests still pass
  • Validated OpenTelemetry-like instrumentation scenarios work correctly

The fix is minimal, maintains backward compatibility, and ensures proper context preservation for both modern Python versions and older ones.

Fixes #31398.


💬 Share your feedback on Copilot coding agent for the chance to win a $200 gift card! Click here to start the survey.

@Copilot Copilot AI assigned Copilot and mdrxy Jul 22, 2025
Copy link

vercel bot commented Jul 22, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

1 Skipped Deployment
Name Status Preview Comments Updated (UTC)
langchain ⬜️ Ignored (Inspect) Visit Preview Jul 22, 2025 2:33pm

@Copilot Copilot AI changed the title [WIP] core/callbacks - context is not maintained when async callbacks get shielded core/callbacks - fix context preservation in shielded async callbacks Jul 22, 2025
@Copilot Copilot AI requested a review from mdrxy July 22, 2025 01:14
Copilot finished work on behalf of mdrxy July 22, 2025 01:14
Copy link

codspeed-hq bot commented Jul 22, 2025

CodSpeed WallTime Performance Report

Merging #32163 will not alter performance

Comparing copilot/fix-31398 (a5cecf7) with master (c14bd1f)

⚠️ Unknown Walltime execution environment detected

Using the Walltime instrument on standard Hosted Runners will lead to inconsistent data.

For the most accurate results, we recommend using CodSpeed Macro Runners: bare-metal machines fine-tuned for performance measurement consistency.

Summary

✅ 13 untouched benchmarks

@mdrxy mdrxy changed the title core/callbacks - fix context preservation in shielded async callbacks fix(core): context preservation in shielded async callbacks Jul 22, 2025
Copy link

codspeed-hq bot commented Jul 22, 2025

CodSpeed Instrumentation Performance Report

Merging #32163 will not alter performance

Comparing copilot/fix-31398 (a5cecf7) with master (c14bd1f)

Summary

✅ 14 untouched benchmarks

@mdrxy mdrxy unassigned Copilot and mdrxy Jul 22, 2025
@mdrxy mdrxy requested a review from sydney-runkle July 22, 2025 14:33
@mdrxy mdrxy marked this pull request as ready for review July 22, 2025 15:32
@mdrxy mdrxy requested a review from eyurtsev as a code owner July 22, 2025 15:32
@mdrxy mdrxy added the core Related to the package `langchain-core` label Jul 28, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
core Related to the package `langchain-core`
Projects
None yet
Development

Successfully merging this pull request may close these issues.

core/callbacks - context is not maintained when async callbacks get shielded
2 participants