Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
cee1039
Add failing test
nicohrubec Dec 4, 2025
877b10e
Try to instrument createReactAgent (doesn't work yet)
nicohrubec Dec 4, 2025
da96451
Merge branch 'develop' into nh/langgraph-no-invoke-agent
nicohrubec Dec 10, 2025
335ee5a
Use two modules
nicohrubec Dec 10, 2025
40cde73
clean comment
nicohrubec Dec 10, 2025
819cf39
Split patch for main and prebuilt langgraph modules
nicohrubec Dec 10, 2025
435ee61
revert stuff
nicohrubec Dec 11, 2025
0246624
now we get some spans for cjs
nicohrubec Dec 11, 2025
e9862c0
try to fix esm with explicit file wrap
nicohrubec Dec 11, 2025
d503485
Fix formatting
nicohrubec Dec 11, 2025
481fc12
try catch
nicohrubec Dec 11, 2025
5ec4427
Revert "try catch"
nicohrubec Dec 11, 2025
25a7524
Merge branch 'develop' into nh/langgraph-no-invoke-agent
nicohrubec Dec 18, 2025
cade4aa
bump iitm
nicohrubec Dec 19, 2025
c529f1b
Merge branch 'develop' into nh/langgraph-no-invoke-agent
nicohrubec Dec 19, 2025
5ac942f
resolve iitm
nicohrubec Dec 19, 2025
703c29a
remove file
nicohrubec Dec 19, 2025
7e81978
try as separate module
nicohrubec Dec 19, 2025
8df7871
Merge branch 'develop' into nh/langgraph-no-invoke-agent
nicohrubec Dec 19, 2025
f21ecda
Merge branch 'develop' into nh/langgraph-no-invoke-agent
nicohrubec Dec 22, 2025
e22b707
fix(tracing): get llm request model in createReactAgent
isaacs Dec 15, 2025
ffe34fb
fix(tracing): patch @langchain/langgraph/prebuilt in ESM mode
isaacs Dec 22, 2025
b3ad20a
Merge branch 'develop' into nh/langgraph-no-invoke-agent
nicohrubec Dec 23, 2025
180e987
iitm 2.0.0
nicohrubec Dec 23, 2025
7cf8796
revert yarn.lock
nicohrubec Dec 23, 2025
957b6ed
revert yarn.lock
nicohrubec Dec 23, 2025
8231651
Merge branch 'develop' into nh/langgraph-no-invoke-agent
nicohrubec Jan 30, 2026
4fa35c7
Merge branch 'develop' into nh/langgraph-no-invoke-agent
nicohrubec Jan 30, 2026
df9c9d2
chore: update @opentelemetry dependencies
isaacs Feb 16, 2026
021b2be
Merge remote-tracking branch 'origin/develop' into nh/langgraph-no-in…
isaacs Feb 16, 2026
8b05eaa
fix opentelemetry instrumentation in node unit test
isaacs Feb 16, 2026
121bd49
fix lint
isaacs Feb 17, 2026
bf435e6
fix: load langgraph/prebuilt ESM file properly
isaacs Feb 17, 2026
7be8df2
fix flaky langgraph test, exclude Express spans
isaacs Feb 17, 2026
b086742
Merge branch 'develop' into nh/langgraph-no-invoke-agent
nicohrubec Feb 18, 2026
0920edf
constants
nicohrubec Feb 18, 2026
11a976f
fix file formatting
nicohrubec Feb 18, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,10 @@
"SENTRY_DSN": "https://username@domain/123",
"SENTRY_ENVIRONMENT": "qa",
"SENTRY_TRACES_SAMPLE_RATE": "1.0",
"SENTRY_TUNNEL": "http://localhost:3031/"
"SENTRY_TUNNEL": "http://localhost:3031/",
},
"assets": {
"binding": "ASSETS",
"directory": "./dist"
}
"directory": "./dist",
},
}

Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@
},
"dependencies": {
"@opentelemetry/api": "^1.9.0",
"@opentelemetry/context-async-hooks": "^2.5.0",
"@opentelemetry/core": "^2.5.0",
"@opentelemetry/instrumentation": "^0.211.0",
"@opentelemetry/instrumentation-http": "^0.211.0",
"@opentelemetry/resources": "^2.5.0",
"@opentelemetry/sdk-trace-node": "^2.5.0",
"@opentelemetry/context-async-hooks": "^2.5.1",
"@opentelemetry/core": "^2.5.1",
"@opentelemetry/instrumentation": "^0.212.0",
"@opentelemetry/instrumentation-http": "^0.212.0",
"@opentelemetry/resources": "^2.5.1",
"@opentelemetry/sdk-trace-node": "^2.5.1",
"@opentelemetry/semantic-conventions": "^1.39.0",
"@sentry/node-core": "latest || *",
"@sentry/opentelemetry": "latest || *",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@
},
"dependencies": {
"@opentelemetry/api": "^1.9.0",
"@opentelemetry/context-async-hooks": "^2.5.0",
"@opentelemetry/core": "^2.5.0",
"@opentelemetry/instrumentation": "^0.211.0",
"@opentelemetry/instrumentation-http": "^0.211.0",
"@opentelemetry/resources": "^2.5.0",
"@opentelemetry/sdk-trace-node": "^2.5.0",
"@opentelemetry/context-async-hooks": "^2.5.1",
"@opentelemetry/core": "^2.5.1",
"@opentelemetry/instrumentation": "^0.212.0",
"@opentelemetry/instrumentation-http": "^0.212.0",
"@opentelemetry/resources": "^2.5.1",
"@opentelemetry/sdk-trace-node": "^2.5.1",
"@opentelemetry/semantic-conventions": "^1.39.0",
"@opentelemetry/sdk-node": "^0.211.0",
"@opentelemetry/exporter-trace-otlp-http": "^0.211.0",
"@opentelemetry/sdk-node": "^0.212.0",
"@opentelemetry/exporter-trace-otlp-http": "^0.212.0",
"@sentry/node-core": "latest || *",
"@sentry/opentelemetry": "latest || *",
"@types/express": "4.17.17",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@
"@sentry/node-core": "latest || *",
"@sentry/opentelemetry": "latest || *",
"@opentelemetry/api": "^1.9.0",
"@opentelemetry/context-async-hooks": "^2.5.0",
"@opentelemetry/core": "^2.5.0",
"@opentelemetry/instrumentation": "^0.211.0",
"@opentelemetry/instrumentation-http": "^0.211.0",
"@opentelemetry/resources": "^2.5.0",
"@opentelemetry/sdk-trace-node": "^2.5.0",
"@opentelemetry/context-async-hooks": "^2.5.1",
"@opentelemetry/core": "^2.5.1",
"@opentelemetry/instrumentation": "^0.212.0",
"@opentelemetry/instrumentation-http": "^0.212.0",
"@opentelemetry/resources": "^2.5.1",
"@opentelemetry/sdk-trace-node": "^2.5.1",
"@opentelemetry/semantic-conventions": "^1.39.0",
"@types/express": "^4.17.21",
"@types/node": "^18.19.1",
Expand Down
12 changes: 6 additions & 6 deletions dev-packages/node-core-integration-tests/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,12 @@
"@nestjs/core": "^11",
"@nestjs/platform-express": "^11",
"@opentelemetry/api": "^1.9.0",
"@opentelemetry/context-async-hooks": "^2.5.0",
"@opentelemetry/core": "^2.5.0",
"@opentelemetry/instrumentation": "^0.211.0",
"@opentelemetry/instrumentation-http": "0.211.0",
"@opentelemetry/resources": "^2.5.0",
"@opentelemetry/sdk-trace-base": "^2.5.0",
"@opentelemetry/context-async-hooks": "^2.5.1",
"@opentelemetry/core": "^2.5.1",
"@opentelemetry/instrumentation": "^0.212.0",
"@opentelemetry/instrumentation-http": "0.212.0",
"@opentelemetry/resources": "^2.5.1",
"@opentelemetry/sdk-trace-base": "^2.5.1",
"@opentelemetry/semantic-conventions": "^1.39.0",
"@sentry/core": "10.39.0",
"@sentry/node-core": "10.39.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { ChatAnthropic } from '@langchain/anthropic';
import { HumanMessage, SystemMessage } from '@langchain/core/messages';
import { createReactAgent } from '@langchain/langgraph/prebuilt';
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();
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,11 @@ Sentry.init({
tracesSampleRate: 1.0,
sendDefaultPii: true,
transport: loggingTransport,
beforeSendTransaction: event => {
// Filter out mock express server transactions
if (event.transaction.includes('/v1/messages')) {
return null;
}
return event;
},
});
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,11 @@ Sentry.init({
tracesSampleRate: 1.0,
sendDefaultPii: false,
transport: loggingTransport,
beforeSendTransaction: event => {
// Filter out mock express server transactions
if (event.transaction.includes('/v1/messages')) {
return null;
}
return event;
},
});
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,46 @@ describe('LangGraph integration', () => {
});
});

const EXPECTED_TRANSACTION_REACT_AGENT = {
transaction: 'main',
spans: expect.arrayContaining([
// create_agent span
expect.objectContaining({
data: expect.objectContaining({
[GEN_AI_OPERATION_NAME_ATTRIBUTE]: 'create_agent',
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'gen_ai.create_agent',
[SEMANTIC_ATTRIBUTE_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_ATTRIBUTE]: 'invoke_agent',
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'gen_ai.invoke_agent',
[SEMANTIC_ATTRIBUTE_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();
});
});

// Test for thread_id (conversation ID) support
const EXPECTED_TRANSACTION_THREAD_ID = {
transaction: 'langgraph-thread-id-test',
Expand Down
2 changes: 1 addition & 1 deletion packages/aws-serverless/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@
},
"dependencies": {
"@opentelemetry/api": "^1.9.0",
"@opentelemetry/instrumentation": "^0.211.0",
"@opentelemetry/instrumentation": "^0.212.0",
"@opentelemetry/instrumentation-aws-sdk": "0.66.0",
"@opentelemetry/semantic-conventions": "^1.39.0",
"@sentry/core": "10.39.0",
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,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';
Expand Down
8 changes: 8 additions & 0 deletions packages/core/src/tracing/langchain/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ export interface LangChainSerialized {
kwargs?: Record<string, unknown>;
}

/**
* 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
Expand Down
64 changes: 62 additions & 2 deletions packages/core/src/tracing/langgraph/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,17 @@ import {
GEN_AI_OPERATION_NAME_ATTRIBUTE,
GEN_AI_PIPELINE_NAME_ATTRIBUTE,
GEN_AI_REQUEST_AVAILABLE_TOOLS_ATTRIBUTE,
GEN_AI_REQUEST_MODEL_ATTRIBUTE,
GEN_AI_SYSTEM_INSTRUCTIONS_ATTRIBUTE,
} from '../ai/gen-ai-attributes';
import { truncateGenAiMessages } from '../ai/messageTruncation';
import { extractSystemInstructions } from '../ai/utils';
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
Expand Down Expand Up @@ -94,9 +95,11 @@ function instrumentCompiledGraphInvoke(
graphInstance: CompiledGraph,
compileOptions: Record<string, unknown>,
options: LangGraphOptions,
llm?: BaseChatModel | null,
): (...args: unknown[]) => Promise<unknown> {
return new Proxy(originalInvoke, {
apply(target, thisArg, args: unknown[]): Promise<unknown> {
const modelName = llm?.modelName;
return startSpan(
{
op: 'gen_ai.invoke_agent',
Expand All @@ -116,6 +119,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 thread_id from the config (second argument)
// LangGraph uses config.configurable.thread_id for conversation/session linking
Expand Down Expand Up @@ -179,6 +185,60 @@ function instrumentCompiledGraphInvoke(
}) as (...args: unknown[]) => Promise<unknown>;
}

/**
* 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 {
const llm = extractLLMFromParams(args);
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',
},
},
span => {
try {
const compiledGraph = Reflect.apply(target, thisArg, args);
const compiledOptions = args.length > 0 ? (args[0] as Record<string, unknown>) : {};
const originalInvoke = compiledGraph.invoke;
if (originalInvoke && typeof originalInvoke === 'function') {
compiledGraph.invoke = instrumentCompiledGraphInvoke(
originalInvoke.bind(compiledGraph) as (...args: unknown[]) => Promise<unknown>,
compiledGraph,
compiledOptions,
options,
llm,
) as typeof originalInvoke;
}

return compiledGraph;
} catch (error) {
span.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' });
captureException(error, {
mechanism: {
handled: false,
type: 'auto.ai.langgraph.error',
},
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mechanism type doesn't align with span origin

Low Severity

In the new instrumentCreateReactAgent function, the mechanism.type is set to 'auto.ai.langgraph.error', which doesn't align with the SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN value of 'auto.ai.langgraph'. Other AI integrations (Anthropic uses 'auto.ai.anthropic', Google GenAI uses 'auto.ai.google_genai') set the mechanism type to match the origin exactly. The .error suffix deviates from that convention. This also applies to the pre-existing instances at lines 77 and 177, but since this is new code, it's a good opportunity to align with the convention.

Fix in Cursor Fix in Web

Triggered by project rule: PR Review Guidelines for Cursor Bot

throw error;
}
},
);
},
}) as (...args: unknown[]) => CompiledGraph;
}

/**
* Directly instruments a StateGraph instance to add tracing spans
*
Expand Down
Loading
Loading