Skip to content

Commit 5e5487b

Browse files
authored
fix(node): Prevent duplicate LangChain spans from double module patching (#19684)
The LangChain instrumentation registers both a module-level and a file-level hook for each provider package (e.g. `@langchain/openai`). Both hooks call _patch, which wraps the same prototype methods (invoke, stream, batch) with a new proxy and callback handler. This results in every LangChain call producing duplicate gen_ai.chat spans. The fix adds a `__sentry_patched__` guard on the prototype to skip patching if it's already been done. Closes #19685 (added automatically)
1 parent f820401 commit 5e5487b

File tree

2 files changed

+23
-0
lines changed
  • dev-packages/node-integration-tests/suites/tracing/langchain
  • packages/node/src/integrations/tracing/langchain

2 files changed

+23
-0
lines changed

dev-packages/node-integration-tests/suites/tracing/langchain/test.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,23 @@ describe('LangChain integration', () => {
169169
.start()
170170
.completed();
171171
});
172+
173+
test('does not create duplicate spans from double module patching', async () => {
174+
await createRunner()
175+
.ignore('event')
176+
.expect({
177+
transaction: event => {
178+
const spans = event.spans || [];
179+
const genAiChatSpans = spans.filter(span => span.op === 'gen_ai.chat');
180+
// The scenario makes 3 LangChain calls (2 successful + 1 error).
181+
// Without the dedup guard, the file-level and module-level hooks
182+
// both patch the same prototype, producing 6 spans instead of 3.
183+
expect(genAiChatSpans).toHaveLength(3);
184+
},
185+
})
186+
.start()
187+
.completed();
188+
});
172189
});
173190

174191
createEsmAndCjsTests(__dirname, 'scenario.mjs', 'instrument-with-pii.mjs', (createRunner, test) => {

packages/node/src/integrations/tracing/langchain/instrumentation.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,12 @@ export class SentryLangChainInstrumentation extends InstrumentationBase<LangChai
228228
// Patch directly on chatModelClass.prototype
229229
const targetProto = chatModelClass.prototype as Record<string, unknown>;
230230

231+
// Skip if already patched (both file-level and module-level hooks resolve to the same prototype)
232+
if (targetProto.__sentry_patched__) {
233+
return;
234+
}
235+
targetProto.__sentry_patched__ = true;
236+
231237
// Patch the methods (invoke, stream, batch)
232238
// All chat model instances will inherit these patched methods
233239
const methodsToPatch = ['invoke', 'stream', 'batch'] as const;

0 commit comments

Comments
 (0)