fix(node): Use context-scoped suppression for LangChain + AI provider span deduplication#22366
fix(node): Use context-scoped suppression for LangChain + AI provider span deduplication#22366nicohrubec wants to merge 2 commits into
Conversation
size-limit report 📦
|
isaacs
left a comment
There was a problem hiding this comment.
LGTM! Some tiny nits / suggestions for a doc comment, but an improvement for sure.
| /** | ||
| * Execute a callback with AI provider spans suppressed in the current scope. | ||
| * This is used by higher-level integrations (like LangChain) to prevent | ||
| * duplicate spans from underlying AI provider instrumentations. |
There was a problem hiding this comment.
low: I think this approach means that anything that calls getCurrentScope() will get this child scope instead of the original? It seems like it might have side effects if anything in the callback calls Sentry.setTag/setContext/setUser, since it'll be attached to a discarded scope.
I don't really see a way around it, tbh, but maybe a doc comment could be worthwhile here?
| * duplicate spans from underlying AI provider instrumentations. | |
| * duplicate spans from underlying AI provider instrumentations. | |
| * | |
| * Suppression rides on a forked scope (as `suppressTracing` does) because a | |
| * forked scope is the only Sentry-native carrier that both survives async | |
| * boundaries and stays isolated from concurrent, unrelated work. | |
| * | |
| * Trade-off: current-scope mutations made inside `callback` land on the fork | |
| * and do not propagate to the outer scope. |
| // EDGE CASE: Import and instantiate Anthropic client BEFORE LangChain is imported | ||
| // This simulates the timing issue where a user creates an Anthropic client in one file | ||
| // before importing LangChain in another file | ||
| // Direct Anthropic call made BEFORE LangChain is imported/used |
There was a problem hiding this comment.
nit: this file is named scenario-openai-before-langchain.mjs but it is using anthropic? Should it be renamed scenario-anthropic-before-langchain.mjs? It doesn't change it functionally, of course, but just seems a little weird 😅
| // EDGE CASE: Import and instantiate Anthropic client BEFORE LangChain is imported | ||
| // This simulates the timing issue where a user creates an Anthropic client in one file | ||
| // before importing LangChain in another file | ||
| // Direct Anthropic call made BEFORE LangChain is imported/used |
| } | ||
|
|
||
| /** @inheritDoc */ | ||
| protected _setupIntegrations(): void { |
There was a problem hiding this comment.
Nice. Removing this is a good cleanup.
|
I noticed that this solution doesn't work as is for the orchestrion path. I will fix that in a follow up but need to put some of the global skip logic for that one back in for now. |
f522c23 to
099efb6
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Want reviews to match your repository better? Bugbot Learning can learn team-specific rules from PR activity. A team admin can enable Learning in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 099efb6. Configure here.
… span deduplication Replaces the global AI-provider skip flag with context-scoped suppression for the OTel LangChain path: LangChain invoke/stream/batch (and embeddings) run inside `_INTERNAL_withSuppressedAiProviderSpans`, and the core provider instrumentations skip span creation when `_INTERNAL_isAiProviderSpanSuppressed()`. Direct provider SDK calls outside LangChain keep their spans, fixing globally-dropped provider spans. The orchestrion provider channels continue to use the global skip flag for now; a follow-up converts them to scoped suppression. Fixes #19687 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
099efb6 to
7eed185
Compare

When both LangChain and an AI provider integration (OpenAI, Anthropic, Google GenAI) are active, a single LangChain invoke() call produces duplicate gen_ai.chat spans — one from LangChain's callback handler and one from the underlying provider instrumentation.
The previous fix globally disabled provider instrumentations when LangChain was detected, but this broke direct provider SDK calls unrelated to LangChain.
This PR sets a suppression flag on the current scope during LangChain invoke/stream/batch execution. Provider instrumentations check this flag and skip span creation if set.
Closes #19687