Skip to content

Commit 855d550

Browse files
feat(core): Add enableTruncation option to Google GenAI integration (#20184)
This PR adds an `enableTruncation` option to the Google GenAI integration that allows users to disable input message truncation. It defaults to `true` to preserve existing behavior. Also refactors the truncation to use the shared `getTruncatedJsonString`/`getJsonString` utilities instead of calling `truncateGenAiMessages` directly. Closes: #20137 --------- Co-authored-by: Nicolas Hrubec <nico.hrubec@sentry.io> Co-authored-by: Nicolas Hrubec <nicolas.hrubec@outlook.com>
1 parent 468e038 commit 855d550

File tree

5 files changed

+120
-6
lines changed

5 files changed

+120
-6
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: false,
9+
transport: loggingTransport,
10+
integrations: [
11+
Sentry.googleGenAIIntegration({
12+
recordInputs: true,
13+
recordOutputs: true,
14+
enableTruncation: false,
15+
}),
16+
],
17+
});
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { instrumentGoogleGenAIClient } from '@sentry/core';
2+
import * as Sentry from '@sentry/node';
3+
4+
class MockGoogleGenerativeAI {
5+
constructor(config) {
6+
this.apiKey = config.apiKey;
7+
this.models = {
8+
generateContent: this._generateContent.bind(this),
9+
};
10+
}
11+
12+
async _generateContent() {
13+
await new Promise(resolve => setTimeout(resolve, 10));
14+
return {
15+
response: {
16+
text: () => 'Response',
17+
usageMetadata: { promptTokenCount: 10, candidatesTokenCount: 5, totalTokenCount: 15 },
18+
candidates: [
19+
{
20+
content: { parts: [{ text: 'Response' }], role: 'model' },
21+
finishReason: 'STOP',
22+
},
23+
],
24+
},
25+
};
26+
}
27+
}
28+
29+
async function run() {
30+
await Sentry.startSpan({ op: 'function', name: 'main' }, async () => {
31+
const mockClient = new MockGoogleGenerativeAI({ apiKey: 'mock-api-key' });
32+
const client = instrumentGoogleGenAIClient(mockClient, { enableTruncation: false, recordInputs: true });
33+
34+
// Long content that would normally be truncated
35+
const longContent = 'A'.repeat(50_000);
36+
await client.models.generateContent({
37+
model: 'gemini-1.5-flash',
38+
contents: [
39+
{ role: 'user', parts: [{ text: longContent }] },
40+
{ role: 'model', parts: [{ text: 'Some reply' }] },
41+
{ role: 'user', parts: [{ text: 'Follow-up question' }] },
42+
],
43+
});
44+
});
45+
}
46+
47+
run();

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

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -653,4 +653,37 @@ describe('Google GenAI integration', () => {
653653
.completed();
654654
});
655655
});
656+
657+
const longContent = 'A'.repeat(50_000);
658+
659+
const EXPECTED_TRANSACTION_NO_TRUNCATION = {
660+
transaction: 'main',
661+
spans: expect.arrayContaining([
662+
expect.objectContaining({
663+
data: expect.objectContaining({
664+
[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]: JSON.stringify([
665+
{ role: 'user', parts: [{ text: longContent }] },
666+
{ role: 'model', parts: [{ text: 'Some reply' }] },
667+
{ role: 'user', parts: [{ text: 'Follow-up question' }] },
668+
]),
669+
[GEN_AI_INPUT_MESSAGES_ORIGINAL_LENGTH_ATTRIBUTE]: 3,
670+
}),
671+
}),
672+
]),
673+
};
674+
675+
createEsmAndCjsTests(
676+
__dirname,
677+
'scenario-no-truncation.mjs',
678+
'instrument-no-truncation.mjs',
679+
(createRunner, test) => {
680+
test('does not truncate input messages when enableTruncation is false', async () => {
681+
await createRunner()
682+
.ignore('event')
683+
.expect({ transaction: EXPECTED_TRANSACTION_NO_TRUNCATION })
684+
.start()
685+
.completed();
686+
});
687+
},
688+
);
656689
});

packages/core/src/tracing/google-genai/index.ts

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,14 @@ import {
2727
GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE,
2828
GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE,
2929
} from '../ai/gen-ai-attributes';
30-
import { truncateGenAiMessages } from '../ai/messageTruncation';
3130
import type { InstrumentedMethodEntry } from '../ai/utils';
32-
import { buildMethodPath, extractSystemInstructions, resolveAIRecordingOptions } from '../ai/utils';
31+
import {
32+
buildMethodPath,
33+
extractSystemInstructions,
34+
getJsonString,
35+
getTruncatedJsonString,
36+
resolveAIRecordingOptions,
37+
} from '../ai/utils';
3338
import { GOOGLE_GENAI_METHOD_REGISTRY, GOOGLE_GENAI_SYSTEM_NAME } from './constants';
3439
import { instrumentStream } from './streaming';
3540
import type { Candidate, ContentPart, GoogleGenAIOptions, GoogleGenAIResponse } from './types';
@@ -134,7 +139,12 @@ function extractRequestAttributes(
134139
* This is only recorded if recordInputs is true.
135140
* Handles different parameter formats for different Google GenAI methods.
136141
*/
137-
function addPrivateRequestAttributes(span: Span, params: Record<string, unknown>, isEmbeddings: boolean): void {
142+
function addPrivateRequestAttributes(
143+
span: Span,
144+
params: Record<string, unknown>,
145+
isEmbeddings: boolean,
146+
enableTruncation: boolean,
147+
): void {
138148
if (isEmbeddings) {
139149
const contents = params.contents;
140150
if (contents != null) {
@@ -184,7 +194,9 @@ function addPrivateRequestAttributes(span: Span, params: Record<string, unknown>
184194
const filteredLength = Array.isArray(filteredMessages) ? filteredMessages.length : 0;
185195
span.setAttributes({
186196
[GEN_AI_INPUT_MESSAGES_ORIGINAL_LENGTH_ATTRIBUTE]: filteredLength,
187-
[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]: JSON.stringify(truncateGenAiMessages(filteredMessages as unknown[])),
197+
[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]: enableTruncation
198+
? getTruncatedJsonString(filteredMessages)
199+
: getJsonString(filteredMessages),
188200
});
189201
}
190202
}
@@ -285,7 +297,7 @@ function instrumentMethod<T extends unknown[], R>(
285297
async (span: Span) => {
286298
try {
287299
if (options.recordInputs && params) {
288-
addPrivateRequestAttributes(span, params, isEmbeddings);
300+
addPrivateRequestAttributes(span, params, isEmbeddings, options.enableTruncation ?? true);
289301
}
290302
const stream = await target.apply(context, args);
291303
return instrumentStream(stream, span, Boolean(options.recordOutputs)) as R;
@@ -313,7 +325,7 @@ function instrumentMethod<T extends unknown[], R>(
313325
},
314326
(span: Span) => {
315327
if (options.recordInputs && params) {
316-
addPrivateRequestAttributes(span, params, isEmbeddings);
328+
addPrivateRequestAttributes(span, params, isEmbeddings, options.enableTruncation ?? true);
317329
}
318330

319331
return handleCallbackErrors(

packages/core/src/tracing/google-genai/types.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@ export interface GoogleGenAIOptions {
99
* Enable or disable output recording.
1010
*/
1111
recordOutputs?: boolean;
12+
/**
13+
* Enable or disable truncation of recorded input messages.
14+
* Defaults to `true`.
15+
*/
16+
enableTruncation?: boolean;
1217
}
1318

1419
/**

0 commit comments

Comments
 (0)