From cee1039057b01879d2afa3987177c5fdeabef8a8 Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Thu, 4 Dec 2025 11:55:09 +0100 Subject: [PATCH 01/20] Add failing test --- .../tracing/langgraph/agent-scenario.mjs | 73 +++++++++++++++++++ .../suites/tracing/langgraph/test.ts | 40 ++++++++++ 2 files changed, 113 insertions(+) create mode 100644 dev-packages/node-integration-tests/suites/tracing/langgraph/agent-scenario.mjs diff --git a/dev-packages/node-integration-tests/suites/tracing/langgraph/agent-scenario.mjs b/dev-packages/node-integration-tests/suites/tracing/langgraph/agent-scenario.mjs new file mode 100644 index 000000000000..a0db57c630dd --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/langgraph/agent-scenario.mjs @@ -0,0 +1,73 @@ +import { ChatAnthropic } from '@langchain/anthropic'; +import { createReactAgent } from '@langchain/langgraph/prebuilt'; +import { HumanMessage, SystemMessage } from '@langchain/core/messages'; +import * as Sentry from '@sentry/node'; +import express from 'express'; + +function startMockAnthropicServer() { + const app = express(); + app.use(express.json()); + + app.post('/v1/messages', (req, res) => { + const model = req.body.model; + + // Simulate basic response + res.json({ + id: 'msg_react_agent_123', + type: 'message', + role: 'assistant', + content: [ + { + type: 'text', + text: 'Mock response from Anthropic!', + }, + ], + model: model, + stop_reason: 'end_turn', + stop_sequence: null, + usage: { + input_tokens: 10, + output_tokens: 15, + }, + }); + }); + + return new Promise(resolve => { + const server = app.listen(0, () => { + resolve(server); + }); + }); +} + +async function run() { + const server = await startMockAnthropicServer(); + const baseUrl = `http://localhost:${server.address().port}`; + + await Sentry.startSpan({ op: 'function', name: 'main' }, async () => { + // Create mocked LLM instance + const llm = new ChatAnthropic({ + model: 'claude-3-5-sonnet-20241022', + apiKey: 'mock-api-key', + clientOptions: { + baseURL: baseUrl, + }, + }); + + // Create a simple react agent with no tools + const agent = createReactAgent({ llm, tools: [] }); + + // Test: basic invocation + await agent.invoke({ + messages: [ + new SystemMessage('You are a helpful assistant.'), + new HumanMessage('What is the weather today?'), + ], + }); + }); + + await Sentry.flush(2000); + + server.close(); +} + +run(); diff --git a/dev-packages/node-integration-tests/suites/tracing/langgraph/test.ts b/dev-packages/node-integration-tests/suites/tracing/langgraph/test.ts index 6a67b5cd1e86..bc1646db5468 100644 --- a/dev-packages/node-integration-tests/suites/tracing/langgraph/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/langgraph/test.ts @@ -205,4 +205,44 @@ describe('LangGraph integration', () => { await createRunner().ignore('event').expect({ transaction: EXPECTED_TRANSACTION_WITH_TOOLS }).start().completed(); }); }); + + const EXPECTED_TRANSACTION_REACT_AGENT = { + transaction: 'main', + spans: expect.arrayContaining([ + // create_agent span + expect.objectContaining({ + data: expect.objectContaining({ + 'gen_ai.operation.name': 'create_agent', + 'sentry.op': 'gen_ai.create_agent', + 'sentry.origin': 'auto.ai.langgraph', + }), + description: expect.stringContaining('create_agent'), + op: 'gen_ai.create_agent', + origin: 'auto.ai.langgraph', + status: 'ok', + }), + // invoke_agent span + expect.objectContaining({ + data: expect.objectContaining({ + 'gen_ai.operation.name': 'invoke_agent', + 'sentry.op': 'gen_ai.invoke_agent', + 'sentry.origin': 'auto.ai.langgraph', + }), + description: expect.stringContaining('invoke_agent'), + op: 'gen_ai.invoke_agent', + origin: 'auto.ai.langgraph', + status: 'ok', + }), + ]), + }; + + createEsmAndCjsTests(__dirname, 'agent-scenario.mjs', 'instrument.mjs', (createRunner, test) => { + test('should instrument LangGraph createReactAgent with default PII settings', async () => { + await createRunner() + .ignore('event') + .expect({ transaction: EXPECTED_TRANSACTION_REACT_AGENT }) + .start() + .completed(); + }); + }); }); From 877b10edfb81133c50952a87fb5786d5324a1c93 Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Thu, 4 Dec 2025 15:10:18 +0100 Subject: [PATCH 02/20] Try to instrument createReactAgent (doesn't work yet) --- packages/core/src/index.ts | 2 +- packages/core/src/tracing/langgraph/index.ts | 45 +++++++++++++++++++ .../tracing/langgraph/instrumentation.ts | 32 ++++++++++++- 3 files changed, 77 insertions(+), 2 deletions(-) diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 387ba0aba4a2..d0e355b0e55f 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -152,7 +152,7 @@ export type { GoogleGenAIResponse } from './tracing/google-genai/types'; export { createLangChainCallbackHandler } from './tracing/langchain'; export { LANGCHAIN_INTEGRATION_NAME } from './tracing/langchain/constants'; export type { LangChainOptions, LangChainIntegration } from './tracing/langchain/types'; -export { instrumentStateGraphCompile, instrumentLangGraph } from './tracing/langgraph'; +export { instrumentStateGraphCompile, instrumentCreateReactAgent, instrumentLangGraph } from './tracing/langgraph'; export { LANGGRAPH_INTEGRATION_NAME } from './tracing/langgraph/constants'; export type { LangGraphOptions, LangGraphIntegration, CompiledGraph } from './tracing/langgraph/types'; export type { OpenAiClient, OpenAiOptions, InstrumentedMethod } from './tracing/openai/types'; diff --git a/packages/core/src/tracing/langgraph/index.ts b/packages/core/src/tracing/langgraph/index.ts index 5601cddf458b..c323d2d0d701 100644 --- a/packages/core/src/tracing/langgraph/index.ts +++ b/packages/core/src/tracing/langgraph/index.ts @@ -156,6 +156,51 @@ function instrumentCompiledGraphInvoke( }) as (...args: unknown[]) => Promise; } +/** + * Instruments createReactAgent to create spans for agent creation and invocation + * + * Creates a `gen_ai.create_agent` span when createReactAgent() is called + */ +export function instrumentCreateReactAgent( + originalCreateReactAgent: (...args: unknown[]) => CompiledGraph, + options: LangGraphOptions, +): (...args: unknown[]) => CompiledGraph { + return new Proxy(originalCreateReactAgent, { + apply(target, thisArg, args: unknown[]): CompiledGraph { + return startSpan( + { + op: 'gen_ai.create_agent', + name: 'create_agent', + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: LANGGRAPH_ORIGIN, + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'gen_ai.create_agent', + [GEN_AI_OPERATION_NAME_ATTRIBUTE]: 'create_agent', + }, + }, + () => { + try { + const compiledGraph = Reflect.apply(target, thisArg, args); + const compiledOptions = args.length > 0 ? (args[0] as Record) : {}; + const originalInvoke = compiledGraph.invoke; + if (originalInvoke && typeof originalInvoke === 'function') { + compiledGraph.invoke = instrumentCompiledGraphInvoke( + originalInvoke.bind(compiledGraph) as (...args: unknown[]) => Promise, + compiledGraph, + compiledOptions, + options, + ) as typeof originalInvoke; + } + + return compiledGraph; + } catch (error) { + throw error; + } + }, + ); + }, + }) as (...args: unknown[]) => CompiledGraph; +} + /** * Directly instruments a StateGraph instance to add tracing spans * diff --git a/packages/node/src/integrations/tracing/langgraph/instrumentation.ts b/packages/node/src/integrations/tracing/langgraph/instrumentation.ts index d275e1b9d39b..03713f10a258 100644 --- a/packages/node/src/integrations/tracing/langgraph/instrumentation.ts +++ b/packages/node/src/integrations/tracing/langgraph/instrumentation.ts @@ -6,7 +6,7 @@ import { InstrumentationNodeModuleFile, } from '@opentelemetry/instrumentation'; import type { CompiledGraph, LangGraphOptions } from '@sentry/core'; -import { getClient, instrumentStateGraphCompile, SDK_VERSION } from '@sentry/core'; +import { getClient, instrumentStateGraphCompile, instrumentCreateReactAgent, SDK_VERSION } from '@sentry/core'; const supportedVersions = ['>=0.0.0 <2.0.0']; @@ -50,6 +50,16 @@ export class SentryLangGraphInstrumentation extends InstrumentationBase exports, ), + new InstrumentationNodeModuleFile( + /** + * Patch the prebuilt subpath exports for CJS. + * The @langchain/langgraph/prebuilt entry point re-exports from dist/prebuilt/index.cjs + */ + '@langchain/langgraph/dist/prebuilt/index.cjs', + supportedVersions, + this._patch.bind(this), + exports => exports, + ), ], ); return module; @@ -83,6 +93,26 @@ export class SentryLangGraphInstrumentation extends InstrumentationBase CompiledGraph, + options, + ); + /* + const originalCreateReactAgent = exports.createReactAgent; + Object.defineProperty(exports, 'createReactAgent', { + value: instrumentCreateReactAgent( + originalCreateReactAgent as (...args: unknown[]) => CompiledGraph, + options, + ), + writable: true, + enumerable: true, + configurable: true, + }); + */ + } + return exports; } } From 335ee5a382e0e6ce3d0626cbf47edd0617c204d0 Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Wed, 10 Dec 2025 17:34:24 +0100 Subject: [PATCH 03/20] Use two modules --- .../tracing/langgraph/instrumentation.ts | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/packages/node/src/integrations/tracing/langgraph/instrumentation.ts b/packages/node/src/integrations/tracing/langgraph/instrumentation.ts index 03713f10a258..7126a45321ff 100644 --- a/packages/node/src/integrations/tracing/langgraph/instrumentation.ts +++ b/packages/node/src/integrations/tracing/langgraph/instrumentation.ts @@ -31,8 +31,8 @@ export class SentryLangGraphInstrumentation extends InstrumentationBase exports, ), + ], + ); + + const prebuiltModule = new InstrumentationNodeModuleDefinition( + '@langchain/langgraph/prebuilt', + supportedVersions, + this._patch.bind(this), + exports => exports, + [ new InstrumentationNodeModuleFile( /** * Patch the prebuilt subpath exports for CJS. @@ -62,7 +71,8 @@ export class SentryLangGraphInstrumentation extends InstrumentationBase Date: Wed, 10 Dec 2025 17:38:26 +0100 Subject: [PATCH 04/20] clean comment --- .../tracing/langgraph/instrumentation.ts | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/packages/node/src/integrations/tracing/langgraph/instrumentation.ts b/packages/node/src/integrations/tracing/langgraph/instrumentation.ts index 7126a45321ff..4538ddcda7ee 100644 --- a/packages/node/src/integrations/tracing/langgraph/instrumentation.ts +++ b/packages/node/src/integrations/tracing/langgraph/instrumentation.ts @@ -109,18 +109,6 @@ export class SentryLangGraphInstrumentation extends InstrumentationBase CompiledGraph, options, ); - /* - const originalCreateReactAgent = exports.createReactAgent; - Object.defineProperty(exports, 'createReactAgent', { - value: instrumentCreateReactAgent( - originalCreateReactAgent as (...args: unknown[]) => CompiledGraph, - options, - ), - writable: true, - enumerable: true, - configurable: true, - }); - */ } return exports; From 819cf399962ea879cf5fb8f11ddca938d370cc64 Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Wed, 10 Dec 2025 17:55:49 +0100 Subject: [PATCH 05/20] Split patch for main and prebuilt langgraph modules --- .../tracing/langgraph/instrumentation.ts | 59 ++++++++++++------- 1 file changed, 37 insertions(+), 22 deletions(-) diff --git a/packages/node/src/integrations/tracing/langgraph/instrumentation.ts b/packages/node/src/integrations/tracing/langgraph/instrumentation.ts index 4538ddcda7ee..e8fe0ca2fbe0 100644 --- a/packages/node/src/integrations/tracing/langgraph/instrumentation.ts +++ b/packages/node/src/integrations/tracing/langgraph/instrumentation.ts @@ -35,7 +35,7 @@ export class SentryLangGraphInstrumentation extends InstrumentationBase exports, [ new InstrumentationNodeModuleFile( @@ -43,11 +43,11 @@ export class SentryLangGraphInstrumentation extends InstrumentationBase exports, ), ], @@ -56,17 +56,19 @@ export class SentryLangGraphInstrumentation extends InstrumentationBase exports, [ new InstrumentationNodeModuleFile( /** - * Patch the prebuilt subpath exports for CJS. - * The @langchain/langgraph/prebuilt entry point re-exports from dist/prebuilt/index.cjs - */ + * In CJS, LangGraph packages re-export from dist/prebuilt/index.cjs files. + * Patching only the root module sometimes misses the real implementation or + * gets overwritten when that file is loaded. We add a file-level patch so that + * _patchPrebuiltModule runs again on the concrete implementation + */ '@langchain/langgraph/dist/prebuilt/index.cjs', supportedVersions, - this._patch.bind(this), + this._patchPrebuiltModule.bind(this), exports => exports, ), ], @@ -76,20 +78,10 @@ export class SentryLangGraphInstrumentation extends InstrumentationBase Date: Thu, 11 Dec 2025 17:27:29 +0100 Subject: [PATCH 06/20] revert stuff --- .../tracing/langgraph/instrumentation.ts | 85 +++++++------------ 1 file changed, 33 insertions(+), 52 deletions(-) diff --git a/packages/node/src/integrations/tracing/langgraph/instrumentation.ts b/packages/node/src/integrations/tracing/langgraph/instrumentation.ts index e8fe0ca2fbe0..646460720721 100644 --- a/packages/node/src/integrations/tracing/langgraph/instrumentation.ts +++ b/packages/node/src/integrations/tracing/langgraph/instrumentation.ts @@ -6,7 +6,7 @@ import { InstrumentationNodeModuleFile, } from '@opentelemetry/instrumentation'; import type { CompiledGraph, LangGraphOptions } from '@sentry/core'; -import { getClient, instrumentStateGraphCompile, instrumentCreateReactAgent, SDK_VERSION } from '@sentry/core'; +import { getClient, instrumentCreateReactAgent, instrumentStateGraphCompile, SDK_VERSION } from '@sentry/core'; const supportedVersions = ['>=0.0.0 <2.0.0']; @@ -31,11 +31,11 @@ export class SentryLangGraphInstrumentation extends InstrumentationBase exports, [ new InstrumentationNodeModuleFile( @@ -43,45 +43,45 @@ export class SentryLangGraphInstrumentation extends InstrumentationBase exports, ), - ], - ); - - const prebuiltModule = new InstrumentationNodeModuleDefinition( - '@langchain/langgraph/prebuilt', - supportedVersions, - this._patchPrebuiltModule.bind(this), - exports => exports, - [ new InstrumentationNodeModuleFile( /** * In CJS, LangGraph packages re-export from dist/prebuilt/index.cjs files. * Patching only the root module sometimes misses the real implementation or * gets overwritten when that file is loaded. We add a file-level patch so that - * _patchPrebuiltModule runs again on the concrete implementation - */ + * _patch runs again on the concrete implementation + */ '@langchain/langgraph/dist/prebuilt/index.cjs', supportedVersions, - this._patchPrebuiltModule.bind(this), + this._patch.bind(this), exports => exports, ), ], ); - - return [mainModule, prebuiltModule]; + return module; } /** - * Patch logic applying instrumentation to the LangGraph main module. + * Core patch logic applying instrumentation to the LangGraph module. */ - private _patchMainModule(exports: PatchedModuleExports): PatchedModuleExports | void { - const options = this._getOptions(); + private _patch(exports: PatchedModuleExports): PatchedModuleExports | void { + const client = getClient(); + const defaultPii = Boolean(client?.getOptions().sendDefaultPii); + + const config = this.getConfig(); + const recordInputs = config.recordInputs ?? defaultPii; + const recordOutputs = config.recordOutputs ?? defaultPii; + + const options: LangGraphOptions = { + recordInputs, + recordOutputs, + }; // Patch StateGraph.compile to instrument both compile() and invoke() if (exports.StateGraph && typeof exports.StateGraph === 'function') { @@ -95,37 +95,18 @@ export class SentryLangGraphInstrumentation extends InstrumentationBase CompiledGraph, + const originalCreateReactAgent = exports.createReactAgent; + Object.defineProperty(exports, 'createReactAgent', { + value: instrumentCreateReactAgent( + originalCreateReactAgent as (...args: unknown[]) => CompiledGraph, options, - ); - } + ), + writable: true, + enumerable: true, + configurable: true, + }); return exports; } - - /** - * Helper to get instrumentation options - */ - private _getOptions(): LangGraphOptions { - const client = getClient(); - const defaultPii = Boolean(client?.getOptions().sendDefaultPii); - const config = this.getConfig(); - - return { - recordInputs: config.recordInputs ?? defaultPii, - recordOutputs: config.recordOutputs ?? defaultPii, - }; - } } From 0246624f4c8fa973cd8c65c2fafe795ac94aa1a2 Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Thu, 11 Dec 2025 17:45:33 +0100 Subject: [PATCH 07/20] now we get some spans for cjs --- .../tracing/langgraph/instrumentation.ts | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/packages/node/src/integrations/tracing/langgraph/instrumentation.ts b/packages/node/src/integrations/tracing/langgraph/instrumentation.ts index 646460720721..0aac8801fee4 100644 --- a/packages/node/src/integrations/tracing/langgraph/instrumentation.ts +++ b/packages/node/src/integrations/tracing/langgraph/instrumentation.ts @@ -71,6 +71,7 @@ export class SentryLangGraphInstrumentation extends InstrumentationBase CompiledGraph, - options, - ), - writable: true, - enumerable: true, - configurable: true, - }); + if (exports.createReactAgent && typeof exports.createReactAgent === 'function') { + const originalCreateReactAgent = exports.createReactAgent; + Object.defineProperty(exports, 'createReactAgent', { + value: instrumentCreateReactAgent( + originalCreateReactAgent as (...args: unknown[]) => CompiledGraph, + options, + ), + writable: true, + enumerable: true, + configurable: true, + }); + } return exports; } From e9862c0d141b5d892677031d7db34b3312ca3baa Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Thu, 11 Dec 2025 18:00:08 +0100 Subject: [PATCH 08/20] try to fix esm with explicit file wrap --- .../tracing/langgraph/instrumentation.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/node/src/integrations/tracing/langgraph/instrumentation.ts b/packages/node/src/integrations/tracing/langgraph/instrumentation.ts index 0aac8801fee4..c739472cd758 100644 --- a/packages/node/src/integrations/tracing/langgraph/instrumentation.ts +++ b/packages/node/src/integrations/tracing/langgraph/instrumentation.ts @@ -62,8 +62,19 @@ export class SentryLangGraphInstrumentation extends InstrumentationBase exports, ), + new InstrumentationNodeModuleFile( + /** + * ESM builds use dist/prebuilt/index.js (without .cjs extension) + * This catches ESM imports that resolve through the main package + */ + '@langchain/langgraph/dist/prebuilt/index.js', + supportedVersions, + this._patch.bind(this), + exports => exports, + ), ], ); + return module; } @@ -71,7 +82,6 @@ export class SentryLangGraphInstrumentation extends InstrumentationBase Date: Thu, 11 Dec 2025 19:03:24 +0100 Subject: [PATCH 09/20] Fix formatting --- .../suites/tracing/langgraph/agent-scenario.mjs | 7 ++----- packages/core/src/tracing/langgraph/index.ts | 9 ++++++++- .../integrations/tracing/langgraph/instrumentation.ts | 5 +---- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/dev-packages/node-integration-tests/suites/tracing/langgraph/agent-scenario.mjs b/dev-packages/node-integration-tests/suites/tracing/langgraph/agent-scenario.mjs index a0db57c630dd..cef786b8988f 100644 --- a/dev-packages/node-integration-tests/suites/tracing/langgraph/agent-scenario.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/langgraph/agent-scenario.mjs @@ -1,6 +1,6 @@ import { ChatAnthropic } from '@langchain/anthropic'; -import { createReactAgent } from '@langchain/langgraph/prebuilt'; import { HumanMessage, SystemMessage } from '@langchain/core/messages'; +import { createReactAgent } from '@langchain/langgraph/prebuilt'; import * as Sentry from '@sentry/node'; import express from 'express'; @@ -58,10 +58,7 @@ async function run() { // Test: basic invocation await agent.invoke({ - messages: [ - new SystemMessage('You are a helpful assistant.'), - new HumanMessage('What is the weather today?'), - ], + messages: [new SystemMessage('You are a helpful assistant.'), new HumanMessage('What is the weather today?')], }); }); diff --git a/packages/core/src/tracing/langgraph/index.ts b/packages/core/src/tracing/langgraph/index.ts index c323d2d0d701..d29b97262aa3 100644 --- a/packages/core/src/tracing/langgraph/index.ts +++ b/packages/core/src/tracing/langgraph/index.ts @@ -177,7 +177,7 @@ export function instrumentCreateReactAgent( [GEN_AI_OPERATION_NAME_ATTRIBUTE]: 'create_agent', }, }, - () => { + span => { try { const compiledGraph = Reflect.apply(target, thisArg, args); const compiledOptions = args.length > 0 ? (args[0] as Record) : {}; @@ -193,6 +193,13 @@ export function instrumentCreateReactAgent( return compiledGraph; } catch (error) { + span.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' }); + captureException(error, { + mechanism: { + handled: false, + type: 'auto.ai.langgraph.error', + }, + }); throw error; } }, diff --git a/packages/node/src/integrations/tracing/langgraph/instrumentation.ts b/packages/node/src/integrations/tracing/langgraph/instrumentation.ts index c739472cd758..87077d457909 100644 --- a/packages/node/src/integrations/tracing/langgraph/instrumentation.ts +++ b/packages/node/src/integrations/tracing/langgraph/instrumentation.ts @@ -110,10 +110,7 @@ export class SentryLangGraphInstrumentation extends InstrumentationBase CompiledGraph, - options, - ), + value: instrumentCreateReactAgent(originalCreateReactAgent as (...args: unknown[]) => CompiledGraph, options), writable: true, enumerable: true, configurable: true, From 481fc12164371a6e1206cad2f2e7aafb8275e410 Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Thu, 11 Dec 2025 19:30:26 +0100 Subject: [PATCH 10/20] try catch --- .../src/integrations/tracing/langgraph/instrumentation.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/node/src/integrations/tracing/langgraph/instrumentation.ts b/packages/node/src/integrations/tracing/langgraph/instrumentation.ts index 87077d457909..6bf50718b66b 100644 --- a/packages/node/src/integrations/tracing/langgraph/instrumentation.ts +++ b/packages/node/src/integrations/tracing/langgraph/instrumentation.ts @@ -107,10 +107,12 @@ export class SentryLangGraphInstrumentation extends InstrumentationBase CompiledGraph, options); + try { + exports.createReactAgent = wrappedCreateReactAgent; + } catch (error) { Object.defineProperty(exports, 'createReactAgent', { - value: instrumentCreateReactAgent(originalCreateReactAgent as (...args: unknown[]) => CompiledGraph, options), + value: wrappedCreateReactAgent, writable: true, enumerable: true, configurable: true, From 5ec44274aa59bdf2e8e4bab7f77cbec59a6acf14 Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Thu, 11 Dec 2025 19:48:13 +0100 Subject: [PATCH 11/20] Revert "try catch" This reverts commit 481fc12164371a6e1206cad2f2e7aafb8275e410. --- .../src/integrations/tracing/langgraph/instrumentation.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/node/src/integrations/tracing/langgraph/instrumentation.ts b/packages/node/src/integrations/tracing/langgraph/instrumentation.ts index 6bf50718b66b..87077d457909 100644 --- a/packages/node/src/integrations/tracing/langgraph/instrumentation.ts +++ b/packages/node/src/integrations/tracing/langgraph/instrumentation.ts @@ -107,12 +107,10 @@ export class SentryLangGraphInstrumentation extends InstrumentationBase CompiledGraph, options); - try { - exports.createReactAgent = wrappedCreateReactAgent; - } catch (error) { + if (exports.createReactAgent && typeof exports.createReactAgent === 'function') { + const originalCreateReactAgent = exports.createReactAgent; Object.defineProperty(exports, 'createReactAgent', { - value: wrappedCreateReactAgent, + value: instrumentCreateReactAgent(originalCreateReactAgent as (...args: unknown[]) => CompiledGraph, options), writable: true, enumerable: true, configurable: true, From cade4aa0ad8abee8b86f0bd87fd43d4c3fac91b7 Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Fri, 19 Dec 2025 10:05:34 +0100 Subject: [PATCH 12/20] bump iitm --- packages/node-core/package.json | 2 +- packages/node/package.json | 2 +- yarn.lock | 12 +++++++++++- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/packages/node-core/package.json b/packages/node-core/package.json index d9ddb61ed79f..b06941f69a71 100644 --- a/packages/node-core/package.json +++ b/packages/node-core/package.json @@ -69,7 +69,7 @@ "@apm-js-collab/tracing-hooks": "^0.3.1", "@sentry/core": "10.31.0", "@sentry/opentelemetry": "10.31.0", - "import-in-the-middle": "^2" + "import-in-the-middle": "^2.0.1" }, "devDependencies": { "@apm-js-collab/code-transformer": "^0.8.2", diff --git a/packages/node/package.json b/packages/node/package.json index 6809b5b56710..f88f27f3b36e 100644 --- a/packages/node/package.json +++ b/packages/node/package.json @@ -98,7 +98,7 @@ "@sentry/core": "10.31.0", "@sentry/node-core": "10.31.0", "@sentry/opentelemetry": "10.31.0", - "import-in-the-middle": "^2", + "import-in-the-middle": "^2.0.1", "minimatch": "^9.0.0" }, "devDependencies": { diff --git a/yarn.lock b/yarn.lock index db80dbff16ae..232266a262db 100644 --- a/yarn.lock +++ b/yarn.lock @@ -19238,7 +19238,7 @@ import-fresh@^3.2.1: parent-module "^1.0.0" resolve-from "^4.0.0" -import-in-the-middle@^2, import-in-the-middle@^2.0.0: +import-in-the-middle@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/import-in-the-middle/-/import-in-the-middle-2.0.0.tgz#295948cee94d0565314824c6bd75379d13e5b1a5" integrity sha512-yNZhyQYqXpkT0AKq3F3KLasUSK4fHvebNH5hOsKQw2dhGSALvQ4U0BqUc5suziKvydO5u5hgN2hy1RJaho8U5A== @@ -19248,6 +19248,16 @@ import-in-the-middle@^2, import-in-the-middle@^2.0.0: cjs-module-lexer "^1.2.2" module-details-from-path "^1.0.3" +import-in-the-middle@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/import-in-the-middle/-/import-in-the-middle-2.0.1.tgz#8d1aa2db18374f2c811de2aa4756ebd6e9859243" + integrity sha512-bruMpJ7xz+9jwGzrwEhWgvRrlKRYCRDBrfU+ur3FcasYXLJDxTruJ//8g2Noj+QFyRBeqbpj8Bhn4Fbw6HjvhA== + dependencies: + acorn "^8.14.0" + acorn-import-attributes "^1.9.5" + cjs-module-lexer "^1.2.2" + module-details-from-path "^1.0.3" + import-local@3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.1.0.tgz#b4479df8a5fd44f6cdce24070675676063c95cb4" From 5ac942fa781ed451160f1715188312fcc15265ae Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Fri, 19 Dec 2025 10:54:41 +0100 Subject: [PATCH 13/20] resolve iitm --- package.json | 3 ++- yarn.lock | 12 +----------- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/package.json b/package.json index b0f32aa93a50..25bfcbba603d 100644 --- a/package.json +++ b/package.json @@ -150,7 +150,8 @@ "gauge/strip-ansi": "6.0.1", "wide-align/string-width": "4.2.3", "cliui/wrap-ansi": "7.0.0", - "sucrase": "getsentry/sucrase#es2020-polyfills" + "sucrase": "getsentry/sucrase#es2020-polyfills", + "import-in-the-middle": "^2.0.1" }, "version": "0.0.0", "name": "sentry-javascript", diff --git a/yarn.lock b/yarn.lock index df79ec4ceba6..4e4f4e00b0ee 100644 --- a/yarn.lock +++ b/yarn.lock @@ -19238,17 +19238,7 @@ import-fresh@^3.2.1: parent-module "^1.0.0" resolve-from "^4.0.0" -import-in-the-middle@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/import-in-the-middle/-/import-in-the-middle-2.0.0.tgz#295948cee94d0565314824c6bd75379d13e5b1a5" - integrity sha512-yNZhyQYqXpkT0AKq3F3KLasUSK4fHvebNH5hOsKQw2dhGSALvQ4U0BqUc5suziKvydO5u5hgN2hy1RJaho8U5A== - dependencies: - acorn "^8.14.0" - acorn-import-attributes "^1.9.5" - cjs-module-lexer "^1.2.2" - module-details-from-path "^1.0.3" - -import-in-the-middle@^2.0.1: +import-in-the-middle@^2.0.0, import-in-the-middle@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/import-in-the-middle/-/import-in-the-middle-2.0.1.tgz#8d1aa2db18374f2c811de2aa4756ebd6e9859243" integrity sha512-bruMpJ7xz+9jwGzrwEhWgvRrlKRYCRDBrfU+ur3FcasYXLJDxTruJ//8g2Noj+QFyRBeqbpj8Bhn4Fbw6HjvhA== From 703c29a106bf80a55c092d5db96f946ca72959af Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Fri, 19 Dec 2025 17:29:56 +0100 Subject: [PATCH 14/20] remove file --- .../tracing/langgraph/instrumentation.ts | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/packages/node/src/integrations/tracing/langgraph/instrumentation.ts b/packages/node/src/integrations/tracing/langgraph/instrumentation.ts index 87077d457909..82591a12eb23 100644 --- a/packages/node/src/integrations/tracing/langgraph/instrumentation.ts +++ b/packages/node/src/integrations/tracing/langgraph/instrumentation.ts @@ -62,16 +62,6 @@ export class SentryLangGraphInstrumentation extends InstrumentationBase exports, ), - new InstrumentationNodeModuleFile( - /** - * ESM builds use dist/prebuilt/index.js (without .cjs extension) - * This catches ESM imports that resolve through the main package - */ - '@langchain/langgraph/dist/prebuilt/index.js', - supportedVersions, - this._patch.bind(this), - exports => exports, - ), ], ); @@ -82,6 +72,7 @@ export class SentryLangGraphInstrumentation extends InstrumentationBase; }; @@ -108,6 +100,7 @@ export class SentryLangGraphInstrumentation extends InstrumentationBase CompiledGraph, options), From 7e81978421493b03fbb0f818643f1b450d6beb0a Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Fri, 19 Dec 2025 18:18:59 +0100 Subject: [PATCH 15/20] try as separate module --- .../tracing/langgraph/instrumentation.ts | 73 ++++++++++--------- 1 file changed, 38 insertions(+), 35 deletions(-) diff --git a/packages/node/src/integrations/tracing/langgraph/instrumentation.ts b/packages/node/src/integrations/tracing/langgraph/instrumentation.ts index 82591a12eb23..9cfa98bb2932 100644 --- a/packages/node/src/integrations/tracing/langgraph/instrumentation.ts +++ b/packages/node/src/integrations/tracing/langgraph/instrumentation.ts @@ -18,6 +18,7 @@ type LangGraphInstrumentationOptions = InstrumentationConfig & LangGraphOptions; interface PatchedModuleExports { [key: string]: unknown; StateGraph?: abstract new (...args: unknown[]) => unknown; + createReactAgent?: (...args: unknown[]) => CompiledGraph; } /** @@ -31,41 +32,43 @@ export class SentryLangGraphInstrumentation extends InstrumentationBase exports, - [ - new InstrumentationNodeModuleFile( - /** - * In CJS, LangGraph packages re-export from dist/index.cjs files. - * Patching only the root module sometimes misses the real implementation or - * gets overwritten when that file is loaded. We add a file-level patch so that - * _patch runs again on the concrete implementation - */ - '@langchain/langgraph/dist/index.cjs', - supportedVersions, - this._patch.bind(this), - exports => exports, - ), - new InstrumentationNodeModuleFile( - /** - * In CJS, LangGraph packages re-export from dist/prebuilt/index.cjs files. - * Patching only the root module sometimes misses the real implementation or - * gets overwritten when that file is loaded. We add a file-level patch so that - * _patch runs again on the concrete implementation - */ - '@langchain/langgraph/dist/prebuilt/index.cjs', - supportedVersions, - this._patch.bind(this), - exports => exports, - ), - ], - ); - - return module; + public init(): InstrumentationModuleDefinition[] { + return [ + new InstrumentationNodeModuleDefinition( + '@langchain/langgraph', + supportedVersions, + this._patch.bind(this), + exports => exports, + [ + new InstrumentationNodeModuleFile( + /** + * In CJS, LangGraph packages re-export from dist/index.cjs files. + * Patching only the root module sometimes misses the real implementation or + * gets overwritten when that file is loaded. We add a file-level patch so that + * _patch runs again on the concrete implementation + */ + '@langchain/langgraph/dist/index.cjs', + supportedVersions, + this._patch.bind(this), + exports => exports, + ), + ], + ), + new InstrumentationNodeModuleDefinition( + '@langchain/langgraph/prebuilt', + supportedVersions, + this._patch.bind(this), + exports => exports, + [ + new InstrumentationNodeModuleFile( + '@langchain/langgraph/dist/prebuilt/index.js', + supportedVersions, + this._patch.bind(this), + exports => exports, + ), + ], + ), + ]; } /** From e22b7079708985a9bdb2b0983e26bdc5f1307d30 Mon Sep 17 00:00:00 2001 From: isaacs Date: Mon, 15 Dec 2025 15:58:06 -0800 Subject: [PATCH 16/20] fix(tracing): get llm request model in createReactAgent --- packages/core/src/tracing/langchain/types.ts | 8 + packages/core/src/tracing/langgraph/index.ts | 12 +- packages/core/src/tracing/langgraph/utils.ts | 19 +- .../test/lib/utils/langraph-utils.test.ts | 255 ++++++++++++++++++ 4 files changed, 291 insertions(+), 3 deletions(-) create mode 100644 packages/core/test/lib/utils/langraph-utils.test.ts diff --git a/packages/core/src/tracing/langchain/types.ts b/packages/core/src/tracing/langchain/types.ts index 7379de764817..e2aa08f4631c 100644 --- a/packages/core/src/tracing/langchain/types.ts +++ b/packages/core/src/tracing/langchain/types.ts @@ -30,6 +30,14 @@ export interface LangChainSerialized { kwargs?: Record; } +/** + * Subset of the 'llm' param passed to createReactAgent + */ +export interface BaseChatModel { + lc_namespace: string[]; + modelName: string; +} + /** * LangChain message structure * Supports both regular messages and LangChain serialized format diff --git a/packages/core/src/tracing/langgraph/index.ts b/packages/core/src/tracing/langgraph/index.ts index d29b97262aa3..67f90357d713 100644 --- a/packages/core/src/tracing/langgraph/index.ts +++ b/packages/core/src/tracing/langgraph/index.ts @@ -8,14 +8,15 @@ import { GEN_AI_PIPELINE_NAME_ATTRIBUTE, GEN_AI_REQUEST_AVAILABLE_TOOLS_ATTRIBUTE, GEN_AI_REQUEST_MESSAGES_ATTRIBUTE, + GEN_AI_REQUEST_MODEL_ATTRIBUTE, } from '../ai/gen-ai-attributes'; import { truncateGenAiMessages } from '../ai/messageTruncation'; -import type { LangChainMessage } from '../langchain/types'; +import type { BaseChatModel, LangChainMessage } from '../langchain/types'; import { normalizeLangChainMessages } from '../langchain/utils'; import { startSpan } from '../trace'; import { LANGGRAPH_ORIGIN } from './constants'; import type { CompiledGraph, LangGraphOptions } from './types'; -import { extractToolsFromCompiledGraph, setResponseAttributes } from './utils'; +import { extractLLMFromParams, extractToolsFromCompiledGraph, setResponseAttributes } from './utils'; /** * Instruments StateGraph's compile method to create spans for agent creation and invocation @@ -90,9 +91,11 @@ function instrumentCompiledGraphInvoke( graphInstance: CompiledGraph, compileOptions: Record, options: LangGraphOptions, + llm?: BaseChatModel | null, ): (...args: unknown[]) => Promise { return new Proxy(originalInvoke, { apply(target, thisArg, args: unknown[]): Promise { + const modelName = llm?.modelName; return startSpan( { op: 'gen_ai.invoke_agent', @@ -112,6 +115,9 @@ function instrumentCompiledGraphInvoke( span.setAttribute(GEN_AI_AGENT_NAME_ATTRIBUTE, graphName); span.updateName(`invoke_agent ${graphName}`); } + if (modelName) { + span.setAttribute(GEN_AI_REQUEST_MODEL_ATTRIBUTE, modelName); + } // Extract available tools from the graph instance const tools = extractToolsFromCompiledGraph(graphInstance); @@ -167,6 +173,7 @@ export function instrumentCreateReactAgent( ): (...args: unknown[]) => CompiledGraph { return new Proxy(originalCreateReactAgent, { apply(target, thisArg, args: unknown[]): CompiledGraph { + const llm = extractLLMFromParams(args); return startSpan( { op: 'gen_ai.create_agent', @@ -188,6 +195,7 @@ export function instrumentCreateReactAgent( compiledGraph, compiledOptions, options, + llm, ) as typeof originalInvoke; } diff --git a/packages/core/src/tracing/langgraph/utils.ts b/packages/core/src/tracing/langgraph/utils.ts index 4b1990058924..f2c64052c071 100644 --- a/packages/core/src/tracing/langgraph/utils.ts +++ b/packages/core/src/tracing/langgraph/utils.ts @@ -8,10 +8,25 @@ import { GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE, GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE, } from '../ai/gen-ai-attributes'; -import type { LangChainMessage } from '../langchain/types'; +import type { BaseChatModel, LangChainMessage } from '../langchain/types'; import { normalizeLangChainMessages } from '../langchain/utils'; import type { CompiledGraph, LangGraphTool } from './types'; +/** + * Extract LLM model object from createReactAgent params + */ +export function extractLLMFromParams(args: unknown[]): null | BaseChatModel { + const arg = args[0]; + return typeof arg === 'object' && + !!arg && + 'llm' in arg && + !!arg.llm && + typeof arg.llm === 'object' && + typeof (arg.llm as BaseChatModel).modelName === 'string' + ? (arg.llm as BaseChatModel) + : null; +} + /** * Extract tool calls from messages */ @@ -139,7 +154,9 @@ export function setResponseAttributes(span: Span, inputMessages: LangChainMessag } // Get new messages (delta between input and output) + /* v8 ignore start - coverage gets confused by this somehow */ const inputCount = inputMessages?.length ?? 0; + /* v8 ignore stop */ const newMessages = outputMessages.length > inputCount ? outputMessages.slice(inputCount) : []; if (newMessages.length === 0) { diff --git a/packages/core/test/lib/utils/langraph-utils.test.ts b/packages/core/test/lib/utils/langraph-utils.test.ts new file mode 100644 index 000000000000..bd8d5c7193ec --- /dev/null +++ b/packages/core/test/lib/utils/langraph-utils.test.ts @@ -0,0 +1,255 @@ +import { beforeEach, describe, expect, it } from 'vitest'; +import type { Span } from '../../../src'; +import { + extractLLMFromParams, + extractModelMetadata, + extractTokenUsageFromMessage, + extractToolCalls, + extractToolsFromCompiledGraph, + setResponseAttributes, +} from '../../../src/tracing/langgraph/utils'; + +describe('extractLLMFromParams', () => { + it('handles invalid args or missing llm object', () => { + // @ts-expect-error should be arguments array, at least. + expect(extractLLMFromParams({})).toBe(null); + expect(extractLLMFromParams([])).toBe(null); + expect(extractLLMFromParams([null])).toBe(null); + expect(extractLLMFromParams([{}])).toBe(null); + expect(extractLLMFromParams([{ llm: false }])).toBe(null); + expect(extractLLMFromParams([{ llm: 123 }])).toBe(null); + expect(extractLLMFromParams([{ llm: {} }])).toBe(null); + }); + it('extracts llm object if found', () => { + expect(extractLLMFromParams([{ llm: { modelName: 'model-name-1' } }])).toStrictEqual({ modelName: 'model-name-1' }); + }); +}); + +describe('extractToolCalls', () => { + it('returns null for missing/empty messages', () => { + expect(extractToolCalls(null)).toBe(null); + expect(extractToolCalls([])).toBe(null); + expect(extractToolCalls([{}])).toBe(null); + expect(extractToolCalls([{ tool_calls: null }])).toBe(null); + expect(extractToolCalls([{ tool_calls: [] }])).toBe(null); + }); + it('extracts tool call from messages array', () => { + expect( + extractToolCalls([ + { tool_calls: [{ name: 'tool a' }] }, + { tool_calls: [{ name: 'tool b' }, { name: 'tool c' }] }, + ]), + ).toStrictEqual([{ name: 'tool a' }, { name: 'tool b' }, { name: 'tool c' }]); + }); +}); + +describe('extractTokenUsageFromMessage', () => { + it('extracts from usage_metadata', () => { + const inputs = [{}, { input_tokens: 10 }]; + const outputs = [{}, { output_tokens: 20 }]; + const totals = [{}, { total_tokens: 30 }]; + for (const i of inputs) { + for (const o of outputs) { + for (const t of totals) { + expect( + extractTokenUsageFromMessage({ + usage_metadata: { + ...i, + ...o, + ...t, + }, + }), + ).toStrictEqual({ + inputTokens: i?.input_tokens ?? 0, + outputTokens: o?.output_tokens ?? 0, + totalTokens: t?.total_tokens ?? 0, + }); + } + } + } + }); + it('falls back to response_metadata', () => { + const inputs = [{}, { promptTokens: 10 }]; + const outputs = [{}, { completionTokens: 20 }]; + const totals = [{}, { totalTokens: 30 }]; + for (const i of inputs) { + for (const o of outputs) { + for (const t of totals) { + expect( + extractTokenUsageFromMessage({ + response_metadata: { + // @ts-expect-error using old tokenUsage field + tokenUsage: { + ...i, + ...o, + ...t, + }, + }, + }), + ).toStrictEqual({ + inputTokens: i?.promptTokens ?? 0, + outputTokens: o?.completionTokens ?? 0, + totalTokens: t?.totalTokens ?? 0, + }); + } + } + } + }); +}); + +describe('extractModelMetadata', () => { + let attributes: Record = {}; + const span = { + setAttribute(key: string, value: unknown) { + attributes[key] = value; + }, + } as unknown as Span; + beforeEach(() => (attributes = {})); + + it('handles lacking metadata ok', () => { + extractModelMetadata(span, {}); + expect(attributes).toStrictEqual({}); + }); + + it('extracts response model name from metadata', () => { + extractModelMetadata(span, { + response_metadata: { + model_name: 'model-name', + finish_reason: 'stop', + }, + }); + expect(attributes).toStrictEqual({ + 'gen_ai.response.model': 'model-name', + 'gen_ai.response.finish_reasons': ['stop'], + }); + }); +}); + +describe('extractToolsFromCompiledGraph', () => { + it('returns null if no tools found', () => { + expect(extractToolsFromCompiledGraph({})).toBe(null); + expect( + extractToolsFromCompiledGraph({ + builder: { nodes: { tools: { runnable: {} } } }, + }), + ).toBe(null); + expect( + extractToolsFromCompiledGraph({ + // @ts-expect-error Wants LangGraphTool[] + builder: { nodes: { tools: { runnable: { tools: 'not an array' } } } }, + }), + ).toBe(null); + expect( + extractToolsFromCompiledGraph({ + builder: { nodes: { tools: { runnable: { tools: [] } } } }, + }), + ).toBe(null); + }); + it('returns the tools found', () => { + expect( + extractToolsFromCompiledGraph({ + builder: { + nodes: { + tools: { + runnable: { + tools: [ + {}, + { lc_kwargs: { name: 'name' } }, + { lc_kwargs: { name: 'name', description: 'desc' } }, + { lc_kwargs: { name: 'name', description: 'desc', schema: 'schema' } }, + ], + }, + }, + }, + }, + }), + ).toStrictEqual([ + { name: undefined, description: undefined, schema: undefined }, + { name: 'name', description: undefined, schema: undefined }, + { name: 'name', description: 'desc', schema: undefined }, + { name: 'name', description: 'desc', schema: 'schema' }, + ]); + }); +}); + +describe('setResponseAttribute', () => { + let attributes: Record = {}; + const span = { + setAttribute(key: string, value: unknown) { + attributes[key] = value; + }, + } as unknown as Span; + beforeEach(() => (attributes = {})); + + it('handles lack of messages', () => { + setResponseAttributes(span, [], undefined); + expect(attributes).toStrictEqual({}); + + setResponseAttributes(span, null, undefined); + expect(attributes).toStrictEqual({}); + + setResponseAttributes(span, null, {}); + expect(attributes).toStrictEqual({}); + + setResponseAttributes(span, null, { messages: null }); + expect(attributes).toStrictEqual({}); + + // no new messages + setResponseAttributes(span, [{}], { messages: [{}] }); + expect(attributes).toStrictEqual({}); + setResponseAttributes(span, [], { messages: [] }); + expect(attributes).toStrictEqual({}); + + // @ts-expect-error cover excessive type safety case + setResponseAttributes(span, { length: undefined }, []); + expect(attributes).toStrictEqual({}); + }); + + it('extracts tool calls', () => { + setResponseAttributes(span, [], { + messages: [{ tool_calls: [{ name: 'tool a' }] }], + }); + expect(attributes).toStrictEqual({ + 'gen_ai.response.text': '[{"role":"user"}]', + 'gen_ai.response.tool_calls': JSON.stringify([{ name: 'tool a' }]), + }); + }); + + it('extracts token usage', () => { + setResponseAttributes(span, [], { + messages: [ + { + usage_metadata: { + input_tokens: 1, + output_tokens: 2, + total_tokens: 3, + }, + }, + ], + }); + expect(attributes).toStrictEqual({ + 'gen_ai.response.text': '[{"role":"user"}]', + 'gen_ai.usage.input_tokens': 1, + 'gen_ai.usage.output_tokens': 2, + 'gen_ai.usage.total_tokens': 3, + }); + }); + + it('extracts model metadata', () => { + setResponseAttributes(span, [], { + messages: [ + { + response_metadata: { + model_name: 'model-name-1', + finish_reason: 'stop', + }, + }, + ], + }); + expect(attributes).toStrictEqual({ + 'gen_ai.response.text': '[{"role":"user"}]', + 'gen_ai.response.model': 'model-name-1', + 'gen_ai.response.finish_reasons': ['stop'], + }); + }); +}); From ffe34fb6a27d61017d6d5d2975bf0da2c6f893bb Mon Sep 17 00:00:00 2001 From: isaacs Date: Mon, 22 Dec 2025 09:45:48 -0800 Subject: [PATCH 17/20] fix(tracing): patch @langchain/langgraph/prebuilt in ESM mode This patches the `@langchain/langgraph/prebuilt` submodule export, when loading in ESM mode. Requires an update to `@opentelemetry/instrumentation`, in order to not block the patching of ESM submodule exports. depends-on: https://github.com/open-telemetry/opentelemetry-js/pull/6246 --- .../tracing/langgraph/instrumentation.ts | 34 ++++++++++++++++--- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/packages/node/src/integrations/tracing/langgraph/instrumentation.ts b/packages/node/src/integrations/tracing/langgraph/instrumentation.ts index 9cfa98bb2932..b3f897e70363 100644 --- a/packages/node/src/integrations/tracing/langgraph/instrumentation.ts +++ b/packages/node/src/integrations/tracing/langgraph/instrumentation.ts @@ -52,6 +52,18 @@ export class SentryLangGraphInstrumentation extends InstrumentationBase exports, ), + new InstrumentationNodeModuleFile( + /** + * In CJS, LangGraph packages re-export from dist/prebuilt/index.cjs files. + * Patching only the root module sometimes misses the real implementation or + * gets overwritten when that file is loaded. We add a file-level patch so that + * _patch runs again on the concrete implementation + */ + '@langchain/langgraph/dist/prebuilt/index.cjs', + supportedVersions, + this._patch.bind(this), + exports => exports, + ), ], ), new InstrumentationNodeModuleDefinition( @@ -61,7 +73,24 @@ export class SentryLangGraphInstrumentation extends InstrumentationBase exports, [ new InstrumentationNodeModuleFile( - '@langchain/langgraph/dist/prebuilt/index.js', + /** + * ESM builds use dist/prebuilt/index.js (without .cjs extension) + * This catches ESM imports that resolve through the main package, + * using the package.json submodule export + */ + '@langchain/langgraph/prebuilt', + supportedVersions, + this._patch.bind(this), + exports => exports, + ), + new InstrumentationNodeModuleFile( + /** + * In CJS, LangGraph packages re-export from dist/prebuilt/index.cjs files. + * Patching only the root module sometimes misses the real implementation or + * gets overwritten when that file is loaded. We add a file-level patch so that + * _patch runs again on the concrete implementation + */ + '@langchain/langgraph/dist/prebuilt/index.cjs', supportedVersions, this._patch.bind(this), exports => exports, @@ -75,7 +104,6 @@ export class SentryLangGraphInstrumentation extends InstrumentationBase; }; @@ -103,7 +130,6 @@ export class SentryLangGraphInstrumentation extends InstrumentationBase CompiledGraph, options), From 180e987726407f6ac98f26fd8c3b375345795750 Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Tue, 23 Dec 2025 11:01:47 +0100 Subject: [PATCH 18/20] iitm 2.0.0 --- package.json | 3 +-- packages/node-core/package.json | 2 +- packages/node/package.json | 2 +- yarn.lock | 2 +- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 25bfcbba603d..b0f32aa93a50 100644 --- a/package.json +++ b/package.json @@ -150,8 +150,7 @@ "gauge/strip-ansi": "6.0.1", "wide-align/string-width": "4.2.3", "cliui/wrap-ansi": "7.0.0", - "sucrase": "getsentry/sucrase#es2020-polyfills", - "import-in-the-middle": "^2.0.1" + "sucrase": "getsentry/sucrase#es2020-polyfills" }, "version": "0.0.0", "name": "sentry-javascript", diff --git a/packages/node-core/package.json b/packages/node-core/package.json index ad99efc5fcba..5308dd52ae48 100644 --- a/packages/node-core/package.json +++ b/packages/node-core/package.json @@ -69,7 +69,7 @@ "@apm-js-collab/tracing-hooks": "^0.3.1", "@sentry/core": "10.32.1", "@sentry/opentelemetry": "10.32.1", - "import-in-the-middle": "^2.0.1" + "import-in-the-middle": "^2" }, "devDependencies": { "@apm-js-collab/code-transformer": "^0.8.2", diff --git a/packages/node/package.json b/packages/node/package.json index bd312657e92f..7a4926837e52 100644 --- a/packages/node/package.json +++ b/packages/node/package.json @@ -98,7 +98,7 @@ "@sentry/core": "10.32.1", "@sentry/node-core": "10.32.1", "@sentry/opentelemetry": "10.32.1", - "import-in-the-middle": "^2.0.1", + "import-in-the-middle": "^2", "minimatch": "^9.0.0" }, "devDependencies": { diff --git a/yarn.lock b/yarn.lock index 74c50e69e987..a23e2252a810 100644 --- a/yarn.lock +++ b/yarn.lock @@ -19238,7 +19238,7 @@ import-fresh@^3.2.1: parent-module "^1.0.0" resolve-from "^4.0.0" -import-in-the-middle@^2.0.0, import-in-the-middle@^2.0.1: +import-in-the-middle@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/import-in-the-middle/-/import-in-the-middle-2.0.1.tgz#8d1aa2db18374f2c811de2aa4756ebd6e9859243" integrity sha512-bruMpJ7xz+9jwGzrwEhWgvRrlKRYCRDBrfU+ur3FcasYXLJDxTruJ//8g2Noj+QFyRBeqbpj8Bhn4Fbw6HjvhA== From 7cf879633361bab8424a599aa26430c9f011666c Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Tue, 23 Dec 2025 11:05:45 +0100 Subject: [PATCH 19/20] revert yarn.lock --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index a23e2252a810..82a6d2eaa541 100644 --- a/yarn.lock +++ b/yarn.lock @@ -19238,10 +19238,10 @@ import-fresh@^3.2.1: parent-module "^1.0.0" resolve-from "^4.0.0" -import-in-the-middle@^2.0.0: +import-in-the-middle@^2, import-in-the-middle@^2.0.0: version "2.0.1" - resolved "https://registry.yarnpkg.com/import-in-the-middle/-/import-in-the-middle-2.0.1.tgz#8d1aa2db18374f2c811de2aa4756ebd6e9859243" - integrity sha512-bruMpJ7xz+9jwGzrwEhWgvRrlKRYCRDBrfU+ur3FcasYXLJDxTruJ//8g2Noj+QFyRBeqbpj8Bhn4Fbw6HjvhA== + resolved "https://registry.yarnpkg.com/import-in-the-middle/-/import-in-the-middle-2.0.0.tgz#295948cee94d0565314824c6bd75379d13e5b1a5" + integrity sha512-yNZhyQYqXpkT0AKq3F3KLasUSK4fHvebNH5hOsKQw2dhGSALvQ4U0BqUc5suziKvydO5u5hgN2hy1RJaho8U5A== dependencies: acorn "^8.14.0" acorn-import-attributes "^1.9.5" From 957b6ed5d544feade4d7f3f95716da7c5eba4fdd Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Tue, 23 Dec 2025 11:06:10 +0100 Subject: [PATCH 20/20] revert yarn.lock --- yarn.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yarn.lock b/yarn.lock index 82a6d2eaa541..eaceb4f6e0da 100644 --- a/yarn.lock +++ b/yarn.lock @@ -19239,7 +19239,7 @@ import-fresh@^3.2.1: resolve-from "^4.0.0" import-in-the-middle@^2, import-in-the-middle@^2.0.0: - version "2.0.1" + version "2.0.0" resolved "https://registry.yarnpkg.com/import-in-the-middle/-/import-in-the-middle-2.0.0.tgz#295948cee94d0565314824c6bd75379d13e5b1a5" integrity sha512-yNZhyQYqXpkT0AKq3F3KLasUSK4fHvebNH5hOsKQw2dhGSALvQ4U0BqUc5suziKvydO5u5hgN2hy1RJaho8U5A== dependencies: