Repository navigation
fix(instrumentation): preserve context attributes in async coroutine decorators (#3583) - #3662
Merged
caroger merged 6 commits intoSep 4, 2026
Conversation
Contributor
|
CLA Assistant Lite bot All contributors have signed the CLA ✍️ ✅ |
Contributor
Author
|
I have read the CLA Document and I hereby sign the CLA |
Contributor
|
@dlowzzxx thanks for the PR. I'll take a look soon |
Contributor
|
@caroger - I did a full audit. Could use a python review here. |
…decorators (Arize-ai#3583) - Wrap async coroutines with reconstructed context manager to preserve span attributes across awaits - Add NoneType guards for streaming token usage calculations (Arize-ai#3499) - Add comprehensive unit tests covering async decorator execution, concurrent isolation, and exception handling
…ntext attribute decorators - Own __call__ on _UsingAttributesContextManager instead of overriding the private ContextDecorator._recreate_cm hook; each decorated call gets its own context token so concurrent and re-entrant calls never clobber it. - Cover coroutine functions, async generator functions and sync generator functions; generator bodies run in a copied contextvars context so their own spans survive across yield and nothing leaks into the consumer. - Restore first-party import grouping flagged by ruff. - Add a runnable Phoenix example and README section on decorator usage. - Drop the unrelated Anthropic input_tokens guard; it is covered by Arize-ai#3504 and Arize-ai#3556 for Arize-ai#3499. Fixes Arize-ai#3583
…ribute wrappers and document decorator behavior Extract _drive_in_contextvars_context so the sync generator wrapper and the awaitable used by the async generator wrapper run steps through one implementation. Expand docstrings on the using_* helpers, fix the swapped version/variables keywords in the using_prompt_template example, parametrize the per-attribute async decorator tests, and pass order ids through in the example.
Restore ContextDecorator for synchronous callables and remove generator-specific context isolation. Keep one local OpenTelemetry context token per coroutine invocation.
Replace contextvars.copy_context isolation with per-invocation rolling OpenTelemetry context so generator bodies see live caller ContextVars while attributes and spans still do not leak to consumers.
dlowzzxx
force-pushed
the
fix/context-attributes-async-coroutine-decorator
branch
from
September 4, 2026 16:40
4d2f94c to
6570db0
Compare
…rration Keep the public using_* contract notes; the wrappers and tests already show the behavior.
caroger
approved these changes
Sep 4, 2026
caroger
enabled auto-merge (squash)
September 4, 2026 17:20
caroger
disabled auto-merge
September 4, 2026 17:21
caroger
enabled auto-merge (squash)
September 4, 2026 17:22
Merged
3 tasks done
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
contextlib.ContextDecoratorruns its__exit__as soon as the decorated call returns. For anasync deffunction or a generator function, that call only builds the coroutine or generator object, so OpenInference context attributes detached before the body ran. Issue #3583 reports the coroutine case._UsingAttributesContextManagernow owns the decorator protocol and handles each kind of callable. A coroutine attaches when the body starts. A generator body runs each step under a rolling OpenTelemetry context.Scope
contextlib.ContextDecoratoras the base class.__call__routes async generator functions, generator functions, coroutine functions, and plain functions._RollingOtelContext,_drive_sync_generator_in_rolling_otel, and_AwaitRollingOtelStep. Each generator invocation gets its own rolling context. After every successful body step, the saved context becomesget_current(), so a span the body holds acrossyieldstays current when the body resumes.contextvars.copy_context()snapshot per generator, which froze every applicationContextVarfor the life of the generator. That snapshot is gone.selfwould break concurrent and re-entrant calls. Onlywithandasync withstill storeself._token._context_with_attributes, the expandedusing_*docstrings, and the correctedversion/variableskeywords in theusing_prompt_templateexample.examples/async_context_attribute_decorators.py, with the matching mypy exclude inpyproject.toml.The PR does not include the unrelated Anthropic token-count change from the original branch.
Tradeoffs
The generator drivers forward
send,throw,close,asend,athrow, andacloseby hand, which is more code thanyield from. The gain is that a generator body keeps its attributes and its open span acrossyieldwhile applicationContextVarvalues stay on the caller's live map.Blast Radius
The change affects Python callers that use a
using_*helper as a decorator. Context-manager behavior is unchanged. Sync decorators keep their existing semantics and additionally survive re-entrant calls. Generator decorators gain attribute and span scoping that the stdlib decorator never provided.Verification
{'session.id': 'async-session'}. Main returned{}.py310-ci-instrumentationontests/test_context_managers.pypassed Ruff formatting, Ruff lint, strict mypy, and 39 tests.ContextVarupdates acrossyieldfor sync and async generators, a body-ownedContextVarsurviving resume, a consumer span between yields not becoming the parent of body spans, and an abandoned generator running itsfinallywith attributes attached.yield, bidirectionalsend/throw/asend/athrow/aclose, re-entrant sync decorators, concurrent coroutine calls, exception detach, andusing_sessionstacked abovetracer.agent.