Skip to content

Commit 3e992d3

Browse files
committed
no chats create span
1 parent 114e21e commit 3e992d3

6 files changed

Lines changed: 41 additions & 123 deletions

File tree

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

Lines changed: 14 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -31,24 +31,7 @@ describe('Google GenAI integration', () => {
3131
const EXPECTED_TRANSACTION_DEFAULT_PII_FALSE = {
3232
transaction: 'main',
3333
spans: expect.arrayContaining([
34-
// First span - chats.create
35-
expect.objectContaining({
36-
data: {
37-
[GEN_AI_OPERATION_NAME_ATTRIBUTE]: 'chat',
38-
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'gen_ai.chat',
39-
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ai.google_genai',
40-
[GEN_AI_SYSTEM_ATTRIBUTE]: 'google_genai',
41-
[GEN_AI_REQUEST_MODEL_ATTRIBUTE]: 'gemini-1.5-pro',
42-
[GEN_AI_REQUEST_TEMPERATURE_ATTRIBUTE]: 0.8,
43-
[GEN_AI_REQUEST_TOP_P_ATTRIBUTE]: 0.9,
44-
[GEN_AI_REQUEST_MAX_TOKENS_ATTRIBUTE]: 150,
45-
},
46-
description: 'chat gemini-1.5-pro create',
47-
op: 'gen_ai.chat',
48-
origin: 'auto.ai.google_genai',
49-
status: 'ok',
50-
}),
51-
// Second span - chat.sendMessage (should get model from context)
34+
// chat.sendMessage (should get model from context)
5235
expect.objectContaining({
5336
data: {
5437
[GEN_AI_OPERATION_NAME_ATTRIBUTE]: 'chat',
@@ -65,7 +48,7 @@ describe('Google GenAI integration', () => {
6548
origin: 'auto.ai.google_genai',
6649
status: 'ok',
6750
}),
68-
// Third span - models.generateContent
51+
// models.generateContent
6952
expect.objectContaining({
7053
data: {
7154
[GEN_AI_OPERATION_NAME_ATTRIBUTE]: 'generate_content',
@@ -85,7 +68,7 @@ describe('Google GenAI integration', () => {
8568
origin: 'auto.ai.google_genai',
8669
status: 'ok',
8770
}),
88-
// Fourth span - error handling
71+
// error handling
8972
expect.objectContaining({
9073
data: {
9174
[GEN_AI_OPERATION_NAME_ATTRIBUTE]: 'generate_content',
@@ -105,25 +88,7 @@ describe('Google GenAI integration', () => {
10588
const EXPECTED_TRANSACTION_DEFAULT_PII_TRUE = {
10689
transaction: 'main',
10790
spans: expect.arrayContaining([
108-
// First span - chats.create with PII
109-
expect.objectContaining({
110-
data: expect.objectContaining({
111-
[GEN_AI_OPERATION_NAME_ATTRIBUTE]: 'chat',
112-
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'gen_ai.chat',
113-
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ai.google_genai',
114-
[GEN_AI_SYSTEM_ATTRIBUTE]: 'google_genai',
115-
[GEN_AI_REQUEST_MODEL_ATTRIBUTE]: 'gemini-1.5-pro',
116-
[GEN_AI_REQUEST_TEMPERATURE_ATTRIBUTE]: 0.8,
117-
[GEN_AI_REQUEST_TOP_P_ATTRIBUTE]: 0.9,
118-
[GEN_AI_REQUEST_MAX_TOKENS_ATTRIBUTE]: 150,
119-
[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]: '[{"role":"user","parts":[{"text":"Hello, how are you?"}]}]',
120-
}),
121-
description: 'chat gemini-1.5-pro create',
122-
op: 'gen_ai.chat',
123-
origin: 'auto.ai.google_genai',
124-
status: 'ok',
125-
}),
126-
// Second span - chat.sendMessage with PII
91+
// chat.sendMessage with PII
12792
expect.objectContaining({
12893
data: expect.objectContaining({
12994
[GEN_AI_OPERATION_NAME_ATTRIBUTE]: 'chat',
@@ -142,7 +107,7 @@ describe('Google GenAI integration', () => {
142107
origin: 'auto.ai.google_genai',
143108
status: 'ok',
144109
}),
145-
// Third span - models.generateContent with PII
110+
// models.generateContent with PII
146111
expect.objectContaining({
147112
data: expect.objectContaining({
148113
[GEN_AI_OPERATION_NAME_ATTRIBUTE]: 'generate_content',
@@ -164,7 +129,7 @@ describe('Google GenAI integration', () => {
164129
origin: 'auto.ai.google_genai',
165130
status: 'ok',
166131
}),
167-
// Fourth span - error handling with PII
132+
// error handling with PII
168133
expect.objectContaining({
169134
data: expect.objectContaining({
170135
[GEN_AI_OPERATION_NAME_ATTRIBUTE]: 'generate_content',
@@ -308,7 +273,7 @@ describe('Google GenAI integration', () => {
308273
const EXPECTED_TRANSACTION_STREAMING = {
309274
transaction: 'main',
310275
spans: expect.arrayContaining([
311-
// First span - models.generateContentStream (streaming)
276+
// models.generateContentStream (streaming)
312277
expect.objectContaining({
313278
data: expect.objectContaining({
314279
[GEN_AI_OPERATION_NAME_ATTRIBUTE]: 'generate_content',
@@ -332,24 +297,7 @@ describe('Google GenAI integration', () => {
332297
origin: 'auto.ai.google_genai',
333298
status: 'ok',
334299
}),
335-
// Second span - chat.create
336-
expect.objectContaining({
337-
data: expect.objectContaining({
338-
[GEN_AI_OPERATION_NAME_ATTRIBUTE]: 'chat',
339-
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'gen_ai.chat',
340-
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ai.google_genai',
341-
[GEN_AI_SYSTEM_ATTRIBUTE]: 'google_genai',
342-
[GEN_AI_REQUEST_MODEL_ATTRIBUTE]: 'gemini-1.5-pro',
343-
[GEN_AI_REQUEST_TEMPERATURE_ATTRIBUTE]: 0.8,
344-
[GEN_AI_REQUEST_TOP_P_ATTRIBUTE]: 0.9,
345-
[GEN_AI_REQUEST_MAX_TOKENS_ATTRIBUTE]: 150,
346-
}),
347-
description: 'chat gemini-1.5-pro create',
348-
op: 'gen_ai.chat',
349-
origin: 'auto.ai.google_genai',
350-
status: 'ok',
351-
}),
352-
// Third span - chat.sendMessageStream (streaming)
300+
// chat.sendMessageStream (streaming)
353301
expect.objectContaining({
354302
data: expect.objectContaining({
355303
[GEN_AI_OPERATION_NAME_ATTRIBUTE]: 'chat',
@@ -366,7 +314,7 @@ describe('Google GenAI integration', () => {
366314
origin: 'auto.ai.google_genai',
367315
status: 'ok',
368316
}),
369-
// Fourth span - blocked content streaming
317+
// blocked content streaming
370318
expect.objectContaining({
371319
data: expect.objectContaining({
372320
[GEN_AI_OPERATION_NAME_ATTRIBUTE]: 'generate_content',
@@ -378,7 +326,7 @@ describe('Google GenAI integration', () => {
378326
origin: 'auto.ai.google_genai',
379327
status: 'internal_error',
380328
}),
381-
// Fifth span - error handling for streaming
329+
// error handling for streaming
382330
expect.objectContaining({
383331
data: expect.objectContaining({
384332
[GEN_AI_OPERATION_NAME_ATTRIBUTE]: 'generate_content',
@@ -396,7 +344,7 @@ describe('Google GenAI integration', () => {
396344
const EXPECTED_TRANSACTION_STREAMING_PII_TRUE = {
397345
transaction: 'main',
398346
spans: expect.arrayContaining([
399-
// First span - models.generateContentStream (streaming) with PII
347+
// models.generateContentStream (streaming) with PII
400348
expect.objectContaining({
401349
data: expect.objectContaining({
402350
[GEN_AI_OPERATION_NAME_ATTRIBUTE]: 'generate_content',
@@ -421,24 +369,7 @@ describe('Google GenAI integration', () => {
421369
origin: 'auto.ai.google_genai',
422370
status: 'ok',
423371
}),
424-
// Second span - chat.create
425-
expect.objectContaining({
426-
data: expect.objectContaining({
427-
[GEN_AI_OPERATION_NAME_ATTRIBUTE]: 'chat',
428-
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'gen_ai.chat',
429-
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ai.google_genai',
430-
[GEN_AI_SYSTEM_ATTRIBUTE]: 'google_genai',
431-
[GEN_AI_REQUEST_MODEL_ATTRIBUTE]: 'gemini-1.5-pro',
432-
[GEN_AI_REQUEST_TEMPERATURE_ATTRIBUTE]: 0.8,
433-
[GEN_AI_REQUEST_TOP_P_ATTRIBUTE]: 0.9,
434-
[GEN_AI_REQUEST_MAX_TOKENS_ATTRIBUTE]: 150,
435-
}),
436-
description: 'chat gemini-1.5-pro create',
437-
op: 'gen_ai.chat',
438-
origin: 'auto.ai.google_genai',
439-
status: 'ok',
440-
}),
441-
// Third span - chat.sendMessageStream (streaming) with PII
372+
// chat.sendMessageStream (streaming) with PII
442373
expect.objectContaining({
443374
data: expect.objectContaining({
444375
[GEN_AI_OPERATION_NAME_ATTRIBUTE]: 'chat',
@@ -460,7 +391,7 @@ describe('Google GenAI integration', () => {
460391
origin: 'auto.ai.google_genai',
461392
status: 'ok',
462393
}),
463-
// Fourth span - blocked content stream with PII
394+
// blocked content stream with PII
464395
expect.objectContaining({
465396
data: expect.objectContaining({
466397
[GEN_AI_OPERATION_NAME_ATTRIBUTE]: 'generate_content',
@@ -477,7 +408,7 @@ describe('Google GenAI integration', () => {
477408
origin: 'auto.ai.google_genai',
478409
status: 'internal_error',
479410
}),
480-
// Fifth span - error handling for streaming with PII
411+
// error handling for streaming with PII
481412
expect.objectContaining({
482413
data: expect.objectContaining({
483414
[GEN_AI_OPERATION_NAME_ATTRIBUTE]: 'generate_content',

‎packages/core/src/tracing/ai/utils.ts‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,12 @@ export interface AIRecordingOptions {
2222
* which gen_ai operation it maps to and whether it is intrinsically streaming.
2323
*/
2424
export interface InstrumentedMethodEntry {
25-
/** Operation name (e.g. 'chat', 'embeddings', 'generate_content') */
26-
operation: string;
25+
/** Operation name (e.g. 'chat', 'embeddings', 'generate_content'). Omit for factory methods that only need result proxying. */
26+
operation?: string;
2727
/** True if the method itself is always streaming (not param-based) */
2828
streaming?: boolean;
29+
/** When set, the method's return value is re-proxied with this as the base path */
30+
proxyResultPath?: string;
2931
}
3032

3133
/**

‎packages/core/src/tracing/anthropic-ai/index.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,7 @@ function instrumentMethod<T extends unknown[], R>(
265265
): (...args: T) => R | Promise<R> {
266266
return new Proxy(originalMethod, {
267267
apply(target, thisArg, args: T): R | Promise<R> {
268-
const operationName = instrumentedMethod.operation;
268+
const operationName = instrumentedMethod.operation as string;
269269
const requestAttributes = extractRequestAttributes(args, methodPath, operationName);
270270
const model = requestAttributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE] ?? 'unknown';
271271

‎packages/core/src/tracing/google-genai/constants.ts‎

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,10 @@ export const GOOGLE_GENAI_INTEGRATION_NAME = 'Google_GenAI';
99
export const GOOGLE_GENAI_METHOD_REGISTRY: InstrumentedMethodRegistry = {
1010
'models.generateContent': { operation: 'generate_content' },
1111
'models.generateContentStream': { operation: 'generate_content', streaming: true },
12-
'chats.create': { operation: 'chat' },
13-
// chat.* paths are built by createDeepProxy when it proxies the chat instance with CHAT_PATH as base
12+
'chats.create': { proxyResultPath: 'chat' },
1413
'chat.sendMessage': { operation: 'chat' },
1514
'chat.sendMessageStream': { operation: 'chat', streaming: true },
1615
};
1716

1817
// Constants for internal use
1918
export const GOOGLE_GENAI_SYSTEM_NAME = 'google_genai';
20-
export const CHATS_CREATE_METHOD = 'chats.create';
21-
export const CHAT_PATH = 'chat';

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

Lines changed: 20 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import {
2828
import { truncateGenAiMessages } from '../ai/messageTruncation';
2929
import type { InstrumentedMethodEntry } from '../ai/utils';
3030
import { buildMethodPath, extractSystemInstructions, resolveAIRecordingOptions } from '../ai/utils';
31-
import { CHAT_PATH, CHATS_CREATE_METHOD, GOOGLE_GENAI_METHOD_REGISTRY, GOOGLE_GENAI_SYSTEM_NAME } from './constants';
31+
import { GOOGLE_GENAI_METHOD_REGISTRY, GOOGLE_GENAI_SYSTEM_NAME } from './constants';
3232
import { instrumentStream } from './streaming';
3333
import type { Candidate, ContentPart, GoogleGenAIOptions, GoogleGenAIResponse } from './types';
3434
import type { ContentListUnion, ContentUnion, Message, PartListUnion } from './utils';
@@ -240,8 +240,8 @@ function addResponseAttributes(span: Span, response: GoogleGenAIResponse, record
240240
}
241241

242242
/**
243-
* Instrument any async or synchronous genai method with Sentry spans
244-
* Handles operations like models.generateContent and chat.sendMessage and chats.create
243+
* Instrument any async genai method with Sentry spans
244+
* Handles operations like models.generateContent and chat.sendMessage
245245
* @see https://docs.sentry.io/platforms/javascript/guides/node/tracing/instrumentation/ai-agents-module/#manual-instrumentation
246246
*/
247247
function instrumentMethod<T extends unknown[], R>(
@@ -251,11 +251,9 @@ function instrumentMethod<T extends unknown[], R>(
251251
context: unknown,
252252
options: GoogleGenAIOptions,
253253
): (...args: T) => R | Promise<R> {
254-
const isSyncCreate = methodPath === CHATS_CREATE_METHOD;
255-
256254
return new Proxy(originalMethod, {
257255
apply(target, _, args: T): R | Promise<R> {
258-
const operationName = instrumentedMethod.operation;
256+
const operationName = instrumentedMethod.operation as string;
259257
const params = args[0] as Record<string, unknown> | undefined;
260258
const requestAttributes = extractRequestAttributes(operationName, params, context);
261259
const model = requestAttributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE] ?? 'unknown';
@@ -291,10 +289,10 @@ function instrumentMethod<T extends unknown[], R>(
291289
},
292290
);
293291
}
294-
// Single span for both sync and async operations
292+
295293
return startSpan(
296294
{
297-
name: isSyncCreate ? `${operationName} ${model} create` : `${operationName} ${model}`,
295+
name: `${operationName} ${model}`,
298296
op: `gen_ai.${operationName}`,
299297
attributes: requestAttributes,
300298
},
@@ -312,10 +310,7 @@ function instrumentMethod<T extends unknown[], R>(
312310
},
313311
() => {},
314312
result => {
315-
// Only add response attributes for content-producing methods, not for chats.create
316-
if (!isSyncCreate) {
317-
addResponseAttributes(span, result, options.recordOutputs);
318-
}
313+
addResponseAttributes(span, result, options.recordOutputs);
319314
},
320315
);
321316
},
@@ -326,42 +321,35 @@ function instrumentMethod<T extends unknown[], R>(
326321

327322
/**
328323
* Create a deep proxy for Google GenAI client instrumentation
329-
* Recursively instruments methods and handles special cases like chats.create
330324
*/
331325
function createDeepProxy<T extends object>(target: T, currentPath = '', options: GoogleGenAIOptions): T {
332326
return new Proxy(target, {
333327
get: (t, prop, receiver) => {
334328
const value = Reflect.get(t, prop, receiver);
335329
const methodPath = buildMethodPath(currentPath, String(prop));
336330

337-
const instrumentedMethod = GOOGLE_GENAI_METHOD_REGISTRY[methodPath];
338-
if (typeof value === 'function' && instrumentedMethod) {
339-
// Special case: chats.create is synchronous but needs both instrumentation AND result proxying
340-
if (methodPath === CHATS_CREATE_METHOD) {
341-
const wrappedMethod = instrumentMethod(
342-
value as (...args: unknown[]) => unknown,
331+
const registryEntry = GOOGLE_GENAI_METHOD_REGISTRY[methodPath];
332+
if (typeof value === 'function' && registryEntry) {
333+
if (registryEntry.operation) {
334+
return instrumentMethod(
335+
value as (...args: unknown[]) => Promise<unknown>,
343336
methodPath,
344-
instrumentedMethod,
337+
registryEntry,
345338
t,
346339
options,
347340
);
348-
return function instrumentedAndProxiedCreate(...args: unknown[]): unknown {
349-
const result = wrappedMethod(...args);
350-
// If the result is an object (like a chat instance), proxy it too
341+
}
342+
343+
if (registryEntry.proxyResultPath) {
344+
const proxyPath = registryEntry.proxyResultPath;
345+
return function proxyFactory(this: unknown, ...args: unknown[]): unknown {
346+
const result = (value as (...a: unknown[]) => unknown).apply(t, args);
351347
if (result && typeof result === 'object') {
352-
return createDeepProxy(result, CHAT_PATH, options);
348+
return createDeepProxy(result as object, proxyPath, options);
353349
}
354350
return result;
355351
};
356352
}
357-
358-
return instrumentMethod(
359-
value as (...args: unknown[]) => Promise<unknown>,
360-
methodPath,
361-
instrumentedMethod,
362-
t,
363-
options,
364-
);
365353
}
366354

367355
if (typeof value === 'function') {

‎packages/core/src/tracing/openai/index.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ function instrumentMethod<T extends unknown[], R>(
180180
options: OpenAiOptions,
181181
): (...args: T) => Promise<R> {
182182
return function instrumentedCall(...args: T): Promise<R> {
183-
const operationName = instrumentedMethod.operation;
183+
const operationName = instrumentedMethod.operation as string;
184184
const requestAttributes = extractRequestAttributes(args, operationName);
185185
const model = (requestAttributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE] as string) || 'unknown';
186186

0 commit comments

Comments
 (0)