Skip to content

Commit 60eb2d1

Browse files
committed
update with tests
1 parent b81b712 commit 60eb2d1

File tree

8 files changed

+112
-124
lines changed

8 files changed

+112
-124
lines changed

dev-packages/node-integration-tests/suites/tracing/google-genai/instrument-with-options.mjs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,17 @@ Sentry.init({
77
tracesSampleRate: 1.0,
88
sendDefaultPii: false,
99
transport: loggingTransport,
10-
registerEsmLoaderHooks: false,
1110
integrations: [
1211
Sentry.googleGenAIIntegration({
1312
recordInputs: true,
1413
recordOutputs: true,
1514
}),
1615
],
16+
beforeSendTransaction: event => {
17+
// Filter out mock express server transactions
18+
if (event.transaction.includes('/v1beta/')) {
19+
return null;
20+
}
21+
return event;
22+
},
1723
});

dev-packages/node-integration-tests/suites/tracing/google-genai/instrument-with-pii.mjs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ Sentry.init({
77
tracesSampleRate: 1.0,
88
sendDefaultPii: true,
99
transport: loggingTransport,
10-
registerEsmLoaderHooks: false,
1110
integrations: [Sentry.googleGenAIIntegration()],
11+
beforeSendTransaction: event => {
12+
// Filter out mock express server transactions
13+
if (event.transaction.includes('/v1beta/')) {
14+
return null;
15+
}
16+
return event;
17+
},
1218
});

dev-packages/node-integration-tests/suites/tracing/google-genai/instrument.mjs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ Sentry.init({
77
tracesSampleRate: 1.0,
88
sendDefaultPii: false,
99
transport: loggingTransport,
10-
registerEsmLoaderHooks: false,
1110
integrations: [Sentry.googleGenAIIntegration()],
11+
beforeSendTransaction: event => {
12+
// Filter out mock express server transactions
13+
if (event.transaction.includes('/v1beta')) {
14+
return null;
15+
}
16+
return event;
17+
},
1218
});
Lines changed: 45 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,89 +1,59 @@
1-
import { instrumentGoogleGenAIClient } from '@sentry/core';
21
import * as Sentry from '@sentry/node';
2+
import { GoogleGenAI } from '@google/genai';
33

4-
class MockGoogleGenAI {
5-
constructor(config) {
6-
this.apiKey = config.apiKey;
4+
import express from 'express';
75

8-
this.models = {
9-
generateContent: async params => {
10-
// Simulate processing time
11-
await new Promise(resolve => setTimeout(resolve, 10));
6+
const PORT = 3333;
127

13-
if (params.model === 'error-model') {
14-
const error = new Error('Model not found');
15-
error.status = 404;
16-
throw error;
17-
}
8+
function startMockGoogleGenAIServer() {
9+
const app = express();
10+
app.use(express.json());
1811

19-
return {
20-
candidates: [
21-
{
22-
content: {
23-
parts: [
24-
{
25-
text: params.contents ? 'The capital of France is Paris.' : 'Mock response from Google GenAI!',
26-
},
27-
],
28-
role: 'model',
29-
},
30-
finishReason: 'stop',
31-
index: 0,
32-
},
33-
],
34-
usageMetadata: {
35-
promptTokenCount: 8,
36-
candidatesTokenCount: 12,
37-
totalTokenCount: 20,
38-
},
39-
};
40-
},
41-
};
12+
app.post('/v1beta/models/:model\\:generateContent', (req, res) => {
13+
const model = req.params.model;
4214

43-
this.chats = {
44-
create: options => {
45-
// Return a chat instance with sendMessage method and model info
46-
return {
47-
model: options?.model || 'unknown', // Include model from create options
48-
sendMessage: async () => {
49-
// Simulate processing time
50-
await new Promise(resolve => setTimeout(resolve, 10));
15+
if (model === 'error-model') {
16+
res.status(404).set('x-request-id', 'mock-request-123').end('Model not found');
17+
return;
18+
}
5119

52-
return {
53-
candidates: [
54-
{
55-
content: {
56-
parts: [
57-
{
58-
text: 'Mock response from Google GenAI!',
59-
},
60-
],
61-
role: 'model',
62-
},
63-
finishReason: 'stop',
64-
index: 0,
65-
},
66-
],
67-
usageMetadata: {
68-
promptTokenCount: 10,
69-
candidatesTokenCount: 15,
70-
totalTokenCount: 25,
20+
res.send({
21+
candidates: [
22+
{
23+
content: {
24+
parts: [
25+
{
26+
text: 'Mock response from Google GenAI!',
7127
},
72-
};
28+
],
29+
role: 'model',
7330
},
74-
};
31+
finishReason: 'stop',
32+
index: 0,
33+
},
34+
],
35+
usageMetadata: {
36+
promptTokenCount: 8,
37+
candidatesTokenCount: 12,
38+
totalTokenCount: 20,
7539
},
76-
};
77-
}
40+
});
41+
});
42+
43+
return app.listen(PORT);
7844
}
7945

8046
async function run() {
81-
const genAI = new MockGoogleGenAI({ apiKey: 'test-api-key' });
82-
const instrumentedClient = instrumentGoogleGenAIClient(genAI);
47+
const server = startMockGoogleGenAIServer();
48+
49+
await Sentry.startSpan({ op: 'function', name: 'main' }, async () => {
50+
const client = new GoogleGenAI({
51+
apiKey: 'mock-api-key',
52+
httpOptions: { baseUrl: `http://localhost:${PORT}` }
53+
});
8354

84-
await Sentry.startSpan({ name: 'main', op: 'function' }, async () => {
8555
// Test 1: chats.create and sendMessage flow
86-
const chat = instrumentedClient.chats.create({
56+
const chat = client.chats.create({
8757
model: 'gemini-1.5-pro',
8858
config: {
8959
temperature: 0.8,
@@ -103,7 +73,7 @@ async function run() {
10373
});
10474

10575
// Test 2: models.generateContent
106-
await instrumentedClient.models.generateContent({
76+
await client.models.generateContent({
10777
model: 'gemini-1.5-flash',
10878
config: {
10979
temperature: 0.7,
@@ -120,7 +90,7 @@ async function run() {
12090

12191
// Test 3: Error handling
12292
try {
123-
await instrumentedClient.models.generateContent({
93+
await client.models.generateContent({
12494
model: 'error-model',
12595
contents: [
12696
{
@@ -133,6 +103,8 @@ async function run() {
133103
// Expected error
134104
}
135105
});
106+
107+
server.close();
136108
}
137109

138110
run();

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

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,9 @@ describe('Google GenAI integration', () => {
3434
'sentry.origin': 'auto.ai.google_genai',
3535
'gen_ai.system': 'google_genai',
3636
'gen_ai.request.model': 'gemini-1.5-pro', // Should get from chat context
37-
'gen_ai.usage.input_tokens': 10,
38-
'gen_ai.usage.output_tokens': 15,
39-
'gen_ai.usage.total_tokens': 25,
37+
'gen_ai.usage.input_tokens': 8,
38+
'gen_ai.usage.output_tokens': 12,
39+
'gen_ai.usage.total_tokens': 20,
4040
},
4141
description: 'chat gemini-1.5-pro',
4242
op: 'gen_ai.chat',
@@ -111,9 +111,9 @@ describe('Google GenAI integration', () => {
111111
'gen_ai.request.model': 'gemini-1.5-pro',
112112
'gen_ai.request.messages': expect.any(String), // Should include message when recordInputs: true
113113
'gen_ai.response.text': expect.any(String), // Should include response when recordOutputs: true
114-
'gen_ai.usage.input_tokens': 10,
115-
'gen_ai.usage.output_tokens': 15,
116-
'gen_ai.usage.total_tokens': 25,
114+
'gen_ai.usage.input_tokens': 8,
115+
'gen_ai.usage.output_tokens': 12,
116+
'gen_ai.usage.total_tokens': 20,
117117
}),
118118
description: 'chat gemini-1.5-pro',
119119
op: 'gen_ai.chat',

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

Lines changed: 14 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import { captureException } from '../../exports';
33
import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '../../semanticAttributes';
44
import { startSpan } from '../../tracing/trace';
55
import type { Span, SpanAttributeValue } from '../../types-hoist/span';
6-
import { handleCallbackErrors } from '../handleCallbackErrors';
76
import {
87
GEN_AI_OPERATION_NAME_ATTRIBUTE,
98
GEN_AI_REQUEST_FREQUENCY_PENALTY_ATTRIBUTE,
@@ -21,11 +20,11 @@ import {
2120
GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE,
2221
} from '../ai/gen-ai-attributes';
2322
import { buildMethodPath, getFinalOperationName, getSpanOperation } from '../ai/utils';
24-
import { CHAT_PATH, CHATS_CREATE_METHOD, GOOGLE_GENAI_INTEGRATION_NAME, GOOGLE_GENAI_SYSTEM_NAME } from './constants';
23+
import { handleCallbackErrors } from '../handleCallbackErrors';
24+
import { CHAT_PATH, CHATS_CREATE_METHOD, GOOGLE_GENAI_SYSTEM_NAME } from './constants';
2525
import type {
2626
Candidate,
2727
ContentPart,
28-
GoogleGenAIIntegration,
2928
GoogleGenAIIstrumentedMethod,
3029
GoogleGenAIOptions,
3130
GoogleGenAIResponse,
@@ -189,21 +188,6 @@ function addResponseAttributes(span: Span, response: GoogleGenAIResponse, record
189188
}
190189
}
191190

192-
/**
193-
* Get recording options from the Sentry integration configuration
194-
* Falls back to sendDefaultPii setting if integration options are not specified
195-
*/
196-
function getRecordingOptionsFromIntegration(): GoogleGenAIOptions {
197-
const client = getClient();
198-
const integration = client?.getIntegrationByName<GoogleGenAIIntegration>(GOOGLE_GENAI_INTEGRATION_NAME);
199-
const shouldRecordInputsAndOutputs = Boolean(client?.getOptions().sendDefaultPii);
200-
201-
return {
202-
recordInputs: integration?.options?.recordInputs ?? shouldRecordInputsAndOutputs,
203-
recordOutputs: integration?.options?.recordOutputs ?? shouldRecordInputsAndOutputs,
204-
};
205-
}
206-
207191
/**
208192
* Instrument any async or synchronous genai method with Sentry spans
209193
* Handles operations like models.generateContent and chat.sendMessage and chats.create
@@ -213,12 +197,11 @@ function instrumentMethod<T extends unknown[], R>(
213197
originalMethod: (...args: T) => R | Promise<R>,
214198
methodPath: GoogleGenAIIstrumentedMethod,
215199
context: unknown,
216-
options?: GoogleGenAIOptions,
200+
options: GoogleGenAIOptions,
217201
): (...args: T) => R | Promise<R> {
218202
const isSyncCreate = methodPath === CHATS_CREATE_METHOD;
219203

220204
const run = (...args: T): R | Promise<R> => {
221-
const finalOptions = options || getRecordingOptionsFromIntegration();
222205
const requestAttributes = extractRequestAttributes(args, methodPath, context);
223206
const model = requestAttributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE] ?? 'unknown';
224207
const operationName = getFinalOperationName(methodPath);
@@ -231,7 +214,7 @@ function instrumentMethod<T extends unknown[], R>(
231214
attributes: requestAttributes,
232215
},
233216
(span: Span) => {
234-
if (finalOptions.recordInputs && args[0] && typeof args[0] === 'object') {
217+
if (options.recordInputs && args[0] && typeof args[0] === 'object') {
235218
addPrivateRequestAttributes(span, args[0] as Record<string, unknown>);
236219
}
237220

@@ -246,7 +229,7 @@ function instrumentMethod<T extends unknown[], R>(
246229
result => {
247230
// Only add response attributes for content-producing methods, not for chats.create
248231
if (!isSyncCreate) {
249-
addResponseAttributes(span, result, finalOptions.recordOutputs);
232+
addResponseAttributes(span, result, options.recordOutputs);
250233
}
251234
},
252235
);
@@ -261,7 +244,7 @@ function instrumentMethod<T extends unknown[], R>(
261244
* Create a deep proxy for Google GenAI client instrumentation
262245
* Recursively instruments methods and handles special cases like chats.create
263246
*/
264-
function createDeepProxy<T extends object>(target: T, currentPath = '', options?: GoogleGenAIOptions): T {
247+
function createDeepProxy<T extends object>(target: T, currentPath = '', options: GoogleGenAIOptions): T {
265248
return new Proxy(target, {
266249
get: (t, prop, receiver) => {
267250
const value = Reflect.get(t, prop, receiver);
@@ -321,5 +304,12 @@ function createDeepProxy<T extends object>(target: T, currentPath = '', options?
321304
* ```
322305
*/
323306
export function instrumentGoogleGenAIClient<T extends object>(client: T, options?: GoogleGenAIOptions): T {
324-
return createDeepProxy(client, '', options);
307+
const sendDefaultPii = Boolean(getClient()?.getOptions().sendDefaultPii);
308+
309+
const _options = {
310+
recordInputs: sendDefaultPii,
311+
recordOutputs: sendDefaultPii,
312+
...options,
313+
};
314+
return createDeepProxy(client, '', _options);
325315
}

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
import type { InstrumentationConfig } from '@opentelemetry/instrumentation';
21
import type { GOOGLE_GENAI_INSTRUMENTED_METHODS } from './constants';
32

4-
export interface GoogleGenAIOptions extends InstrumentationConfig {
3+
export interface GoogleGenAIOptions {
54
/**
65
* Enable or disable input recording.
76
*/

0 commit comments

Comments
 (0)