Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion .size-limit.js
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ module.exports = [
import: createImport('init'),
ignore: [...builtinModules, ...nodePrefixedBuiltinModules],
gzip: true,
limit: '158 KB',
limit: '159 KB',
},
{
name: '@sentry/node - without tracing',
Expand Down
1 change: 1 addition & 0 deletions dev-packages/node-integration-tests/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"@hono/node-server": "^1.19.4",
"@langchain/anthropic": "^0.3.10",
"@langchain/core": "^0.3.28",
"@langchain/langgraph": "^0.2.32",
"@nestjs/common": "^11",
"@nestjs/core": "^11",
"@nestjs/platform-express": "^11",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import * as Sentry from '@sentry/node';
import { loggingTransport } from '@sentry-internal/node-integration-tests';

Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
release: '1.0',
tracesSampleRate: 1.0,
sendDefaultPii: true,
transport: loggingTransport,
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import * as Sentry from '@sentry/node';
import { loggingTransport } from '@sentry-internal/node-integration-tests';

Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
release: '1.0',
tracesSampleRate: 1.0,
sendDefaultPii: false,
transport: loggingTransport,
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { tool } from '@langchain/core/tools';
import { END, MessagesAnnotation, START, StateGraph } from '@langchain/langgraph';
import { ToolNode } from '@langchain/langgraph/prebuilt';
import * as Sentry from '@sentry/node';
import { z } from 'zod';

async function run() {
await Sentry.startSpan({ op: 'function', name: 'langgraph-tools-test' }, async () => {
// Define tools
const getWeatherTool = tool(
async ({ city }) => {
return JSON.stringify({ city, temperature: 72, condition: 'sunny' });
},
{
name: 'get_weather',
description: 'Get the current weather for a given city',
schema: z.object({
city: z.string().describe('The city to get weather for'),
}),
},
);

const getTimeTool = tool(
async () => {
return new Date().toISOString();
},
{
name: 'get_time',
description: 'Get the current time',
schema: z.object({}),
},
);

const tools = [getWeatherTool, getTimeTool];
const toolNode = new ToolNode(tools);

// Define mock LLM function that returns without tool calls
const mockLlm = () => {
return {
messages: [
{
role: 'assistant',
content: 'Response without calling tools',
response_metadata: {
model_name: 'gpt-4-0613',
finish_reason: 'stop',
tokenUsage: {
promptTokens: 25,
completionTokens: 15,
totalTokens: 40,
},
},
tool_calls: [],
},
],
};
};

// Routing function - check if there are tool calls
const shouldContinue = state => {
const messages = state.messages;
const lastMessage = messages[messages.length - 1];

// If the last message has tool_calls, route to tools, otherwise end
if (lastMessage.tool_calls && lastMessage.tool_calls.length > 0) {
return 'tools';
}
return END;
};

// Create graph with conditional edge to tools
const graph = new StateGraph(MessagesAnnotation)
.addNode('agent', mockLlm)
.addNode('tools', toolNode)
.addEdge(START, 'agent')
.addConditionalEdges('agent', shouldContinue, {
tools: 'tools',
[END]: END,
})
.addEdge('tools', 'agent')
.compile({ name: 'tool_agent' });

// Simple invocation - won't call tools since mockLlm returns empty tool_calls
await graph.invoke({
messages: [{ role: 'user', content: 'What is the weather?' }],
});
});

await Sentry.flush(2000);
}

run();
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { END, MessagesAnnotation, START, StateGraph } from '@langchain/langgraph';
import * as Sentry from '@sentry/node';

async function run() {
await Sentry.startSpan({ op: 'function', name: 'langgraph-test' }, async () => {
// Define a simple mock LLM function
const mockLlm = () => {
return {
messages: [
{
role: 'assistant',
content: 'Mock LLM response',
response_metadata: {
model_name: 'mock-model',
finish_reason: 'stop',
tokenUsage: {
promptTokens: 20,
completionTokens: 10,
totalTokens: 30,
},
},
},
],
};
};

// Create and compile the graph
const graph = new StateGraph(MessagesAnnotation)
.addNode('agent', mockLlm)
.addEdge(START, 'agent')
.addEdge('agent', END)
.compile({ name: 'weather_assistant' });

// Test: basic invocation
await graph.invoke({
messages: [{ role: 'user', content: 'What is the weather today?' }],
});

// Test: invocation with multiple messages
await graph.invoke({
messages: [
{ role: 'user', content: 'Hello' },
{ role: 'assistant', content: 'Hi there!' },
{ role: 'user', content: 'Tell me about the weather' },
],
});
});

await Sentry.flush(2000);
}

run();
171 changes: 171 additions & 0 deletions dev-packages/node-integration-tests/suites/tracing/langgraph/test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
import { afterAll, describe, expect } from 'vitest';
import { cleanupChildProcesses, createEsmAndCjsTests } from '../../../utils/runner';

describe('LangGraph integration', () => {
afterAll(() => {
cleanupChildProcesses();
});

const EXPECTED_TRANSACTION_DEFAULT_PII_FALSE = {
transaction: 'langgraph-test',
spans: expect.arrayContaining([
// create_agent span
expect.objectContaining({
data: {
'gen_ai.operation.name': 'create_agent',
'sentry.op': 'gen_ai.create_agent',
'sentry.origin': 'auto.ai.langgraph',
'gen_ai.agent.name': 'weather_assistant',
},
description: 'create_agent weather_assistant',
op: 'gen_ai.create_agent',
origin: 'auto.ai.langgraph',
status: 'ok',
}),
// First 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',
'gen_ai.agent.name': 'weather_assistant',
'gen_ai.pipeline.name': 'weather_assistant',
}),
description: 'invoke_agent weather_assistant',
op: 'gen_ai.invoke_agent',
origin: 'auto.ai.langgraph',
status: 'ok',
}),
// Second 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',
'gen_ai.agent.name': 'weather_assistant',
'gen_ai.pipeline.name': 'weather_assistant',
}),
description: 'invoke_agent weather_assistant',
op: 'gen_ai.invoke_agent',
origin: 'auto.ai.langgraph',
status: 'ok',
}),
]),
};

const EXPECTED_TRANSACTION_DEFAULT_PII_TRUE = {
transaction: 'langgraph-test',
spans: expect.arrayContaining([
// create_agent span (PII enabled doesn't affect this span)
expect.objectContaining({
data: {
'gen_ai.operation.name': 'create_agent',
'sentry.op': 'gen_ai.create_agent',
'sentry.origin': 'auto.ai.langgraph',
'gen_ai.agent.name': 'weather_assistant',
},
description: 'create_agent weather_assistant',
op: 'gen_ai.create_agent',
origin: 'auto.ai.langgraph',
status: 'ok',
}),
// First invoke_agent span with PII
expect.objectContaining({
data: expect.objectContaining({
'gen_ai.operation.name': 'invoke_agent',
'sentry.op': 'gen_ai.invoke_agent',
'sentry.origin': 'auto.ai.langgraph',
'gen_ai.agent.name': 'weather_assistant',
'gen_ai.pipeline.name': 'weather_assistant',
'gen_ai.request.messages': expect.stringContaining('What is the weather today?'),
}),
description: 'invoke_agent weather_assistant',
op: 'gen_ai.invoke_agent',
origin: 'auto.ai.langgraph',
status: 'ok',
}),
// Second invoke_agent span with PII and multiple messages
expect.objectContaining({
data: expect.objectContaining({
'gen_ai.operation.name': 'invoke_agent',
'sentry.op': 'gen_ai.invoke_agent',
'sentry.origin': 'auto.ai.langgraph',
'gen_ai.agent.name': 'weather_assistant',
'gen_ai.pipeline.name': 'weather_assistant',
'gen_ai.request.messages': expect.stringContaining('Tell me about the weather'),
}),
description: 'invoke_agent weather_assistant',
op: 'gen_ai.invoke_agent',
origin: 'auto.ai.langgraph',
status: 'ok',
}),
]),
};

const EXPECTED_TRANSACTION_WITH_TOOLS = {
transaction: 'langgraph-tools-test',
spans: expect.arrayContaining([
// create_agent span
expect.objectContaining({
data: {
'gen_ai.operation.name': 'create_agent',
'sentry.op': 'gen_ai.create_agent',
'sentry.origin': 'auto.ai.langgraph',
'gen_ai.agent.name': 'tool_agent',
},
description: 'create_agent tool_agent',
op: 'gen_ai.create_agent',
origin: 'auto.ai.langgraph',
status: 'ok',
}),
// invoke_agent span with tools
expect.objectContaining({
data: expect.objectContaining({
'gen_ai.operation.name': 'invoke_agent',
'sentry.op': 'gen_ai.invoke_agent',
'sentry.origin': 'auto.ai.langgraph',
'gen_ai.agent.name': 'tool_agent',
'gen_ai.pipeline.name': 'tool_agent',
'gen_ai.request.available_tools': expect.stringContaining('get_weather'),
'gen_ai.request.messages': expect.stringContaining('What is the weather?'),
'gen_ai.response.model': 'gpt-4-0613',
'gen_ai.response.finish_reasons': ['stop'],
'gen_ai.response.text': expect.stringContaining('Response without calling tools'),
'gen_ai.usage.input_tokens': 25,
'gen_ai.usage.output_tokens': 15,
'gen_ai.usage.total_tokens': 40,
}),
description: 'invoke_agent tool_agent',
op: 'gen_ai.invoke_agent',
origin: 'auto.ai.langgraph',
status: 'ok',
}),
]),
};

createEsmAndCjsTests(__dirname, 'scenario.mjs', 'instrument.mjs', (createRunner, test) => {
test('should instrument LangGraph with default PII settings', async () => {
await createRunner()
.ignore('event')
.expect({ transaction: EXPECTED_TRANSACTION_DEFAULT_PII_FALSE })
.start()
.completed();
});
});

createEsmAndCjsTests(__dirname, 'scenario.mjs', 'instrument-with-pii.mjs', (createRunner, test) => {
test('should instrument LangGraph with sendDefaultPii: true', async () => {
await createRunner()
.ignore('event')
.expect({ transaction: EXPECTED_TRANSACTION_DEFAULT_PII_TRUE })
.start()
.completed();
});
});

createEsmAndCjsTests(__dirname, 'scenario-tools.mjs', 'instrument-with-pii.mjs', (createRunner, test) => {
test('should capture tools from LangGraph agent', { timeout: 30000 }, async () => {
await createRunner().ignore('event').expect({ transaction: EXPECTED_TRANSACTION_WITH_TOOLS }).start().completed();
});
});
});
1 change: 1 addition & 0 deletions packages/astro/src/index.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ export {
onUnhandledRejectionIntegration,
openAIIntegration,
langChainIntegration,
langgraphIntegration,
parameterize,
pinoIntegration,
postgresIntegration,
Expand Down
1 change: 1 addition & 0 deletions packages/aws-serverless/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export {
onUnhandledRejectionIntegration,
openAIIntegration,
langChainIntegration,
langgraphIntegration,
modulesIntegration,
contextLinesIntegration,
nodeContextIntegration,
Expand Down
1 change: 1 addition & 0 deletions packages/bun/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ export {
onUnhandledRejectionIntegration,
openAIIntegration,
langChainIntegration,
langgraphIntegration,
modulesIntegration,
contextLinesIntegration,
nodeContextIntegration,
Expand Down
3 changes: 3 additions & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,9 @@ export type { GoogleGenAIResponse } from './utils/google-genai/types';
export { createLangChainCallbackHandler } from './utils/langchain';
export { LANGCHAIN_INTEGRATION_NAME } from './utils/langchain/constants';
export type { LangChainOptions, LangChainIntegration } from './utils/langchain/types';
export { instrumentStateGraphCompile } 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 './utils/openai/types';
export type {
AnthropicAiClient,
Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/tracing/langgraph/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const LANGGRAPH_INTEGRATION_NAME = 'LangGraph';
export const LANGGRAPH_ORIGIN = 'auto.ai.langgraph';
Loading
Loading