Skip to content

Commit c9782ae

Browse files
feat(core): Add enableTruncation option to LangGraph integration (#20183)
This PR adds an `enableTruncation` option to the LangGraph integration that allows users to disable input message truncation. It defaults to `true` to preserve existing behavior. Also refactors to use the shared getTruncatedJsonString/getJsonString utilities. Closes: #20139 --------- Co-authored-by: Nicolas Hrubec <nicolas.hrubec@outlook.com>
1 parent bf21665 commit c9782ae

File tree

5 files changed

+112
-4
lines changed

5 files changed

+112
-4
lines changed
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import * as Sentry from '@sentry/node';
2+
import { loggingTransport } from '@sentry-internal/node-integration-tests';
3+
4+
Sentry.init({
5+
dsn: 'https://public@dsn.ingest.sentry.io/1337',
6+
release: '1.0',
7+
tracesSampleRate: 1.0,
8+
sendDefaultPii: true,
9+
transport: loggingTransport,
10+
integrations: [
11+
Sentry.langGraphIntegration({
12+
recordInputs: true,
13+
recordOutputs: true,
14+
enableTruncation: false,
15+
}),
16+
],
17+
});
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { END, MessagesAnnotation, START, StateGraph } from '@langchain/langgraph';
2+
import * as Sentry from '@sentry/node';
3+
4+
async function run() {
5+
await Sentry.startSpan({ op: 'function', name: 'langgraph-test' }, async () => {
6+
const mockLlm = () => {
7+
return {
8+
messages: [
9+
{
10+
role: 'assistant',
11+
content: 'Mock LLM response',
12+
response_metadata: {
13+
model_name: 'mock-model',
14+
finish_reason: 'stop',
15+
tokenUsage: {
16+
promptTokens: 20,
17+
completionTokens: 10,
18+
totalTokens: 30,
19+
},
20+
},
21+
},
22+
],
23+
};
24+
};
25+
26+
const graph = new StateGraph(MessagesAnnotation)
27+
.addNode('agent', mockLlm)
28+
.addEdge(START, 'agent')
29+
.addEdge('agent', END)
30+
.compile({ name: 'weather_assistant' });
31+
32+
// Multiple messages with long content (would normally be truncated and popped to last message only)
33+
const longContent = 'A'.repeat(50_000);
34+
await graph.invoke({
35+
messages: [
36+
{ role: 'user', content: longContent },
37+
{ role: 'assistant', content: 'Some reply' },
38+
{ role: 'user', content: 'Follow-up question' },
39+
],
40+
});
41+
});
42+
43+
await Sentry.flush(2000);
44+
}
45+
46+
run();

dev-packages/node-integration-tests/suites/tracing/langgraph/test.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
GEN_AI_AGENT_NAME_ATTRIBUTE,
55
GEN_AI_CONVERSATION_ID_ATTRIBUTE,
66
GEN_AI_INPUT_MESSAGES_ATTRIBUTE,
7+
GEN_AI_INPUT_MESSAGES_ORIGINAL_LENGTH_ATTRIBUTE,
78
GEN_AI_OPERATION_NAME_ATTRIBUTE,
89
GEN_AI_PIPELINE_NAME_ATTRIBUTE,
910
GEN_AI_REQUEST_AVAILABLE_TOOLS_ATTRIBUTE,
@@ -364,4 +365,37 @@ describe('LangGraph integration', () => {
364365
await createRunner().ignore('event').expect({ transaction: EXPECTED_TRANSACTION_RESUME }).start().completed();
365366
});
366367
});
368+
369+
const longContent = 'A'.repeat(50_000);
370+
371+
const EXPECTED_TRANSACTION_NO_TRUNCATION = {
372+
transaction: 'langgraph-test',
373+
spans: expect.arrayContaining([
374+
expect.objectContaining({
375+
data: expect.objectContaining({
376+
[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]: JSON.stringify([
377+
{ role: 'user', content: longContent },
378+
{ role: 'assistant', content: 'Some reply' },
379+
{ role: 'user', content: 'Follow-up question' },
380+
]),
381+
[GEN_AI_INPUT_MESSAGES_ORIGINAL_LENGTH_ATTRIBUTE]: 3,
382+
}),
383+
}),
384+
]),
385+
};
386+
387+
createEsmAndCjsTests(
388+
__dirname,
389+
'scenario-no-truncation.mjs',
390+
'instrument-no-truncation.mjs',
391+
(createRunner, test) => {
392+
test('does not truncate input messages when enableTruncation is false', async () => {
393+
await createRunner()
394+
.ignore('event')
395+
.expect({ transaction: EXPECTED_TRANSACTION_NO_TRUNCATION })
396+
.start()
397+
.completed();
398+
});
399+
},
400+
);
367401
});

packages/core/src/tracing/langgraph/index.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,12 @@ import {
1212
GEN_AI_REQUEST_AVAILABLE_TOOLS_ATTRIBUTE,
1313
GEN_AI_SYSTEM_INSTRUCTIONS_ATTRIBUTE,
1414
} from '../ai/gen-ai-attributes';
15-
import { truncateGenAiMessages } from '../ai/messageTruncation';
16-
import { extractSystemInstructions, resolveAIRecordingOptions } from '../ai/utils';
15+
import {
16+
extractSystemInstructions,
17+
getJsonString,
18+
getTruncatedJsonString,
19+
resolveAIRecordingOptions,
20+
} from '../ai/utils';
1721
import type { LangChainMessage } from '../langchain/types';
1822
import { normalizeLangChainMessages } from '../langchain/utils';
1923
import { startSpan } from '../trace';
@@ -146,10 +150,12 @@ function instrumentCompiledGraphInvoke(
146150
span.setAttribute(GEN_AI_SYSTEM_INSTRUCTIONS_ATTRIBUTE, systemInstructions);
147151
}
148152

149-
const truncatedMessages = truncateGenAiMessages(filteredMessages as unknown[]);
153+
const enableTruncation = options.enableTruncation ?? true;
150154
const filteredLength = Array.isArray(filteredMessages) ? filteredMessages.length : 0;
151155
span.setAttributes({
152-
[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]: JSON.stringify(truncatedMessages),
156+
[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]: enableTruncation
157+
? getTruncatedJsonString(filteredMessages)
158+
: getJsonString(filteredMessages),
153159
[GEN_AI_INPUT_MESSAGES_ORIGINAL_LENGTH_ATTRIBUTE]: filteredLength,
154160
});
155161
}

packages/core/src/tracing/langgraph/types.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ export interface LangGraphOptions {
77
* Enable or disable output recording.
88
*/
99
recordOutputs?: boolean;
10+
/**
11+
* Enable or disable truncation of recorded input messages.
12+
* Defaults to `true`.
13+
*/
14+
enableTruncation?: boolean;
1015
}
1116

1217
/**

0 commit comments

Comments
 (0)