Skip to content

Commit b822f8f

Browse files
authored
feat(browser): Expose langgraph instrumentation (#18345)
We are exposing AI instrument methods for Langgraph enabling users to use it directly from the browser SDK.
1 parent 9afaabf commit b822f8f

File tree

9 files changed

+104
-0
lines changed

9 files changed

+104
-0
lines changed
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import * as Sentry from '@sentry/browser';
2+
3+
window.Sentry = Sentry;
4+
5+
Sentry.init({
6+
dsn: 'https://public@dsn.ingest.sentry.io/1337',
7+
tracesSampleRate: 1,
8+
debug: true,
9+
});
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// Mock LangGraph graph for browser testing
2+
export class MockStateGraph {
3+
compile(options = {}) {
4+
const compiledGraph = {
5+
name: options.name,
6+
graph_name: options.name,
7+
lc_kwargs: {
8+
name: options.name,
9+
},
10+
builder: {
11+
nodes: {},
12+
},
13+
invoke: async input => {
14+
const messages = input?.messages;
15+
return {
16+
messages: [
17+
...messages,
18+
{
19+
role: 'assistant',
20+
content: 'Mock response from LangGraph',
21+
},
22+
],
23+
};
24+
},
25+
};
26+
27+
return compiledGraph;
28+
}
29+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { MockStateGraph } from './mocks.js';
2+
import { instrumentLangGraph } from '@sentry/browser';
3+
4+
// Test that manual instrumentation doesn't crash the browser
5+
// The instrumentation automatically creates spans
6+
// Test both agent creation and invocation
7+
8+
const graph = new MockStateGraph();
9+
instrumentLangGraph(graph, { recordInputs: false, recordOutputs: false });
10+
const compiledGraph = graph.compile({ name: 'mock-graph' });
11+
12+
const response = await compiledGraph.invoke({
13+
messages: [{ role: 'user', content: 'What is the capital of France?' }],
14+
});
15+
16+
console.log('Received response', response);
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { expect } from '@playwright/test';
2+
import { sentryTest } from '../../../../utils/fixtures';
3+
import { envelopeRequestParser, waitForTransactionRequest } from '../../../../utils/helpers';
4+
5+
// These tests are not exhaustive because the instrumentation is
6+
// already tested in the node integration tests and we merely
7+
// want to test that the instrumentation does not crash in the browser
8+
// and that gen_ai transactions are sent.
9+
10+
sentryTest('manual LangGraph instrumentation sends gen_ai transactions', async ({ getLocalTestUrl, page }) => {
11+
const createTransactionPromise = waitForTransactionRequest(page, event => {
12+
return !!event.transaction?.includes('create_agent mock-graph');
13+
});
14+
15+
const invokeTransactionPromise = waitForTransactionRequest(page, event => {
16+
return !!event.transaction?.includes('invoke_agent mock-graph');
17+
});
18+
19+
const url = await getLocalTestUrl({ testDir: __dirname });
20+
await page.goto(url);
21+
22+
const createReq = await createTransactionPromise;
23+
const invokeReq = await invokeTransactionPromise;
24+
25+
const createEventData = envelopeRequestParser(createReq);
26+
const invokeEventData = envelopeRequestParser(invokeReq);
27+
28+
// Verify create_agent transaction
29+
expect(createEventData.transaction).toBe('create_agent mock-graph');
30+
expect(createEventData.contexts?.trace?.op).toBe('gen_ai.create_agent');
31+
expect(createEventData.contexts?.trace?.origin).toBe('auto.ai.langgraph');
32+
expect(createEventData.contexts?.trace?.data).toMatchObject({
33+
'gen_ai.operation.name': 'create_agent',
34+
'gen_ai.agent.name': 'mock-graph',
35+
});
36+
37+
// Verify invoke_agent transaction
38+
expect(invokeEventData.transaction).toBe('invoke_agent mock-graph');
39+
expect(invokeEventData.contexts?.trace?.op).toBe('gen_ai.invoke_agent');
40+
expect(invokeEventData.contexts?.trace?.origin).toBe('auto.ai.langgraph');
41+
expect(invokeEventData.contexts?.trace?.data).toMatchObject({
42+
'gen_ai.operation.name': 'invoke_agent',
43+
'gen_ai.agent.name': 'mock-graph',
44+
});
45+
});

dev-packages/browser-integration-tests/utils/generatePlugin.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ const IMPORTED_INTEGRATION_CDN_BUNDLE_PATHS: Record<string, string> = {
4040
instrumentAnthropicAiClient: 'instrumentanthropicaiclient',
4141
instrumentOpenAiClient: 'instrumentopenaiclient',
4242
instrumentGoogleGenAIClient: 'instrumentgooglegenaiclient',
43+
instrumentLangGraph: 'instrumentlanggraph',
4344
// technically, this is not an integration, but let's add it anyway for simplicity
4445
makeMultiplexedTransport: 'multiplexedtransport',
4546
};

packages/browser/rollup.bundle.config.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ const reexportedPluggableIntegrationFiles = [
1616
'instrumentanthropicaiclient',
1717
'instrumentopenaiclient',
1818
'instrumentgooglegenaiclient',
19+
'instrumentlanggraph',
1920
];
2021

2122
browserPluggableIntegrationFiles.forEach(integrationName => {

packages/browser/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ export {
6666
instrumentAnthropicAiClient,
6767
instrumentOpenAiClient,
6868
instrumentGoogleGenAIClient,
69+
instrumentLangGraph,
6970
logger,
7071
} from '@sentry/core';
7172
export type { Span, FeatureFlagsIntegration } from '@sentry/core';
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { instrumentLangGraph } from '@sentry/core';

packages/browser/src/utils/lazyLoadIntegration.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ const LazyLoadableIntegrations = {
2424
instrumentAnthropicAiClient: 'instrumentanthropicaiclient',
2525
instrumentOpenAiClient: 'instrumentopenaiclient',
2626
instrumentGoogleGenAIClient: 'instrumentgooglegenaiclient',
27+
instrumentLangGraph: 'instrumentlanggraph',
2728
} as const;
2829

2930
const WindowWithMaybeIntegration = WINDOW as {

0 commit comments

Comments
 (0)