Skip to content
Merged
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
3 changes: 2 additions & 1 deletion dev-packages/cloudflare-integration-tests/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
"test:watch": "yarn test --watch"
},
"dependencies": {
"@sentry/cloudflare": "10.25.0"
"@sentry/cloudflare": "10.25.0",
"@langchain/langgraph": "^1.0.1"
},
"devDependencies": {
"@cloudflare/workers-types": "^4.20250922.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { END, MessagesAnnotation, START, StateGraph } from '@langchain/langgraph';
import * as Sentry from '@sentry/cloudflare';

interface Env {
SENTRY_DSN: string;
}

export default Sentry.withSentry(
(env: Env) => ({
dsn: env.SENTRY_DSN,
tracesSampleRate: 1.0,
sendDefaultPii: true,
}),
{
async fetch(_request, _env, _ctx) {
// Define simple mock LLM function
const mockLlm = (): {
messages: {
role: string;
content: string;
response_metadata: {
model_name: string;
finish_reason: string;
tokenUsage: { promptTokens: number; completionTokens: number; totalTokens: number };
};
tool_calls: never[];
}[];
} => {
return {
messages: [
{
role: 'assistant',
content: 'Mock response from LangGraph agent',
response_metadata: {
model_name: 'mock-model',
finish_reason: 'stop',
tokenUsage: {
promptTokens: 20,
completionTokens: 10,
totalTokens: 30,
},
},
tool_calls: [],
},
],
};
};

// Create and instrument the graph
const graph = new StateGraph(MessagesAnnotation)
.addNode('agent', mockLlm)
.addEdge(START, 'agent')
.addEdge('agent', END);

Sentry.instrumentLangGraph(graph, { recordInputs: true, recordOutputs: true });

const compiled = graph.compile({ name: 'weather_assistant' });

await compiled.invoke({
messages: [{ role: 'user', content: 'What is the weather in SF?' }],
});

return new Response(JSON.stringify({ success: true }));
},
},
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { expect, it } from 'vitest';
import { createRunner } from '../../../runner';

// These tests are not exhaustive because the instrumentation is
// already tested in the node integration tests and we merely
// want to test that the instrumentation does not break in our
// cloudflare SDK.

it('traces langgraph compile and invoke operations', async ({ signal }) => {
const runner = createRunner(__dirname)
.ignore('event')
.expect(envelope => {
const transactionEvent = envelope[1]?.[0]?.[1] as any;

expect(transactionEvent.transaction).toBe('GET /');

// Check create_agent span
const createAgentSpan = transactionEvent.spans.find((span: any) => span.op === 'gen_ai.create_agent');
expect(createAgentSpan).toMatchObject({
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',
});

// Check invoke_agent span
const invokeAgentSpan = transactionEvent.spans.find((span: any) => span.op === 'gen_ai.invoke_agent');
expect(invokeAgentSpan).toMatchObject({
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': '[{"role":"user","content":"What is the weather in SF?"}]',
'gen_ai.response.model': 'mock-model',
'gen_ai.usage.input_tokens': 20,
'gen_ai.usage.output_tokens': 10,
'gen_ai.usage.total_tokens': 30,
}),
description: 'invoke_agent weather_assistant',
op: 'gen_ai.invoke_agent',
origin: 'auto.ai.langgraph',
});

// Verify tools are captured
if (invokeAgentSpan.data['gen_ai.request.available_tools']) {
expect(invokeAgentSpan.data['gen_ai.request.available_tools']).toMatch(/get_weather/);
}
})
.start(signal);
await runner.makeRequest('get', '/');
await runner.completed();
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"name": "worker-name",
"compatibility_date": "2025-06-17",
"main": "index.ts",
"compatibility_flags": ["nodejs_compat"],
}
1 change: 1 addition & 0 deletions packages/cloudflare/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ export {
growthbookIntegration,
logger,
metrics,
instrumentLangGraph,
} from '@sentry/core';

export { withSentry } from './handler';
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 @@ -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 } from './tracing/langgraph';
export { instrumentStateGraphCompile, 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
35 changes: 35 additions & 0 deletions packages/core/src/tracing/langgraph/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,3 +155,38 @@ function instrumentCompiledGraphInvoke(
},
}) as (...args: unknown[]) => Promise<unknown>;
}

/**
* Directly instruments a StateGraph instance to add tracing spans
*
* This function can be used to manually instrument LangGraph StateGraph instances
* in environments where automatic instrumentation is not available or desired.
*
* @param stateGraph - The StateGraph instance to instrument
* @param options - Optional configuration for recording inputs/outputs
*
* @example
* ```typescript
* import { instrumentLangGraph } from '@sentry/cloudflare';
* import { StateGraph } from '@langchain/langgraph';
*
* const graph = new StateGraph(MessagesAnnotation)
* .addNode('agent', mockLlm)
* .addEdge(START, 'agent')
* .addEdge('agent', END);
*
* instrumentLangGraph(graph, { recordInputs: true, recordOutputs: true });
* const compiled = graph.compile({ name: 'my_agent' });
* ```
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function instrumentLangGraph<T extends { compile: (...args: any[]) => any }>(
stateGraph: T,
options?: LangGraphOptions,
): T {
const _options: LangGraphOptions = options || {};

stateGraph.compile = instrumentStateGraphCompile(stateGraph.compile.bind(stateGraph), _options);

return stateGraph;
}
1 change: 1 addition & 0 deletions packages/vercel-edge/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ export {
// eslint-disable-next-line deprecation/deprecation
inboundFiltersIntegration,
instrumentOpenAiClient,
instrumentLangGraph,
instrumentGoogleGenAIClient,
instrumentAnthropicAiClient,
eventFiltersIntegration,
Expand Down
25 changes: 25 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4931,6 +4931,13 @@
zod "^3.25.32"
zod-to-json-schema "^3.22.3"

"@langchain/langgraph-checkpoint@^1.0.0":
version "1.0.0"
resolved "https://registry.npmjs.org/@langchain/langgraph-checkpoint/-/langgraph-checkpoint-1.0.0.tgz#ece2ede439d0d0b0b532c4be7817fd5029afe4f8"
integrity sha512-xrclBGvNCXDmi0Nz28t3vjpxSH6UYx6w5XAXSiiB1WEdc2xD2iY/a913I3x3a31XpInUW/GGfXXfePfaghV54A==
dependencies:
uuid "^10.0.0"

"@langchain/langgraph-checkpoint@~0.0.17":
version "0.0.18"
resolved "https://registry.npmjs.org/@langchain/langgraph-checkpoint/-/langgraph-checkpoint-0.0.18.tgz#2f7a9cdeda948ccc8d312ba9463810709d71d0b8"
Expand All @@ -4948,6 +4955,15 @@
p-retry "4"
uuid "^9.0.0"

"@langchain/langgraph-sdk@~1.0.0":
version "1.0.0"
resolved "https://registry.npmjs.org/@langchain/langgraph-sdk/-/langgraph-sdk-1.0.0.tgz#16faca6cc426432dee9316428d0aecd94e5b7989"
integrity sha512-g25ti2W7Dl5wUPlNK+0uIGbeNFqf98imhHlbdVVKTTkDYLhi/pI1KTgsSSkzkeLuBIfvt2b0q6anQwCs7XBlbw==
dependencies:
p-queue "^6.6.2"
p-retry "4"
uuid "^9.0.0"

"@langchain/langgraph@^0.2.32":
version "0.2.74"
resolved "https://registry.npmjs.org/@langchain/langgraph/-/langgraph-0.2.74.tgz#37367a1e8bafda3548037a91449a69a84f285def"
Expand All @@ -4958,6 +4974,15 @@
uuid "^10.0.0"
zod "^3.23.8"

"@langchain/langgraph@^1.0.1":
version "1.0.2"
resolved "https://registry.npmjs.org/@langchain/langgraph/-/langgraph-1.0.2.tgz#62de931edac0dd850daf708bd6f8f3835cf25a5e"
integrity sha512-syxzzWTnmpCL+RhUEvalUeOXFoZy/KkzHa2Da2gKf18zsf9Dkbh3rfnRDrTyUGS1XSTejq07s4rg1qntdEDs2A==
dependencies:
"@langchain/langgraph-checkpoint" "^1.0.0"
"@langchain/langgraph-sdk" "~1.0.0"
uuid "^10.0.0"

"@leichtgewicht/ip-codec@^2.0.1":
version "2.0.4"
resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz#b2ac626d6cb9c8718ab459166d4bb405b8ffa78b"
Expand Down
Loading