From cee1039057b01879d2afa3987177c5fdeabef8a8 Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Thu, 4 Dec 2025 11:55:09 +0100 Subject: [PATCH 1/2] 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 2/2] 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; } }