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:
```python
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:
```python
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
```python
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.
<!-- START COPILOT CODING AGENT TIPS -->
---
💬 Share your feedback on Copilot coding agent for the chance to win a
$200 gift card! Click
[here](https://survey.alchemer.com/s3/8343779/Copilot-Coding-agent) to
start the survey.
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: mdrxy <61371264+mdrxy@users.noreply.github.com>
Co-authored-by: Mason Daugherty <github@mdrxy.com>
Co-authored-by: Mason Daugherty <mason@langchain.dev>
Packages
Important
This repository is structured as a monorepo, with various packages located in this libs/ directory. Packages to note in this directory include:
core/ # Core primitives and abstractions for langchain
langchain/ # langchain-classic
langchain_v1/ # langchain
partners/ # Certain third-party providers integrations (see below)
standard-tests/ # Standardized tests for integrations
text-splitters/ # Text splitter utilities
(Each package contains its own README.md file with specific details about that package.)
Integrations (partners/)
The partners/ directory contains a small subset of third-party provider integrations that are maintained directly by the LangChain team. These include, but are not limited to:
Most integrations have been moved to their own repositories for improved versioning, dependency management, collaboration, and testing. This includes packages from popular providers such as Google and AWS. Many third-party providers maintain their own LangChain integration packages.
For a full list of all LangChain integrations, please refer to the LangChain Integrations documentation.