diff --git a/.size-limit.js b/.size-limit.js index 4100751f2c40..2d5baacbfd88 100644 --- a/.size-limit.js +++ b/.size-limit.js @@ -234,7 +234,7 @@ module.exports = [ path: createCDNPath('bundle.min.js'), gzip: false, brotli: false, - limit: '83 KB', + limit: '83.5 KB', }, { name: 'CDN Bundle (incl. Tracing) - uncompressed', diff --git a/packages/core/src/integrations/conversationId.ts b/packages/core/src/integrations/conversationId.ts index c11b587d3a71..445e3327419b 100644 --- a/packages/core/src/integrations/conversationId.ts +++ b/packages/core/src/integrations/conversationId.ts @@ -4,6 +4,7 @@ import { defineIntegration } from '../integration'; import { GEN_AI_CONVERSATION_ID_ATTRIBUTE } from '../semanticAttributes'; import type { IntegrationFn } from '../types-hoist/integration'; import type { Span } from '../types-hoist/span'; +import { spanToJSON } from '../utils/spanUtils'; const INTEGRATION_NAME = 'ConversationId'; @@ -18,6 +19,16 @@ const _conversationIdIntegration = (() => { const conversationId = scopeData.conversationId || isolationScopeData.conversationId; if (conversationId) { + const { op, data: attributes, description: name } = spanToJSON(span); + + // Only apply conversation ID to gen_ai spans. + // We also check for Vercel AI spans (ai.operationId attribute or ai.* span name) + // because the Vercel AI integration sets the gen_ai.* op in its own spanStart handler + // which fires after this, so the op is not yet available at this point. + if (!op?.startsWith('gen_ai.') && !attributes['ai.operationId'] && !name?.startsWith('ai.')) { + return; + } + span.setAttribute(GEN_AI_CONVERSATION_ID_ATTRIBUTE, conversationId); } }); diff --git a/packages/core/test/lib/integrations/conversationId.test.ts b/packages/core/test/lib/integrations/conversationId.test.ts index e9ea9cc50d45..be69a1476e83 100644 --- a/packages/core/test/lib/integrations/conversationId.test.ts +++ b/packages/core/test/lib/integrations/conversationId.test.ts @@ -26,7 +26,7 @@ describe('ConversationId', () => { it('applies conversation ID from current scope to span', () => { getCurrentScope().setConversationId('conv_test_123'); - startSpan({ name: 'test-span' }, span => { + startSpan({ name: 'test-span', op: 'gen_ai.chat' }, span => { const spanJSON = spanToJSON(span); expect(spanJSON.data[GEN_AI_CONVERSATION_ID_ATTRIBUTE]).toBe('conv_test_123'); }); @@ -35,7 +35,7 @@ describe('ConversationId', () => { it('applies conversation ID from isolation scope when current scope does not have one', () => { getIsolationScope().setConversationId('conv_isolation_456'); - startSpan({ name: 'test-span' }, span => { + startSpan({ name: 'test-span', op: 'gen_ai.chat' }, span => { const spanJSON = spanToJSON(span); expect(spanJSON.data[GEN_AI_CONVERSATION_ID_ATTRIBUTE]).toBe('conv_isolation_456'); }); @@ -45,14 +45,14 @@ describe('ConversationId', () => { getCurrentScope().setConversationId('conv_current_789'); getIsolationScope().setConversationId('conv_isolation_999'); - startSpan({ name: 'test-span' }, span => { + startSpan({ name: 'test-span', op: 'gen_ai.chat' }, span => { const spanJSON = spanToJSON(span); expect(spanJSON.data[GEN_AI_CONVERSATION_ID_ATTRIBUTE]).toBe('conv_current_789'); }); }); it('does not apply conversation ID when not set in scope', () => { - startSpan({ name: 'test-span' }, span => { + startSpan({ name: 'test-span', op: 'gen_ai.chat' }, span => { const spanJSON = spanToJSON(span); expect(spanJSON.data[GEN_AI_CONVERSATION_ID_ATTRIBUTE]).toBeUndefined(); }); @@ -62,7 +62,7 @@ describe('ConversationId', () => { getCurrentScope().setConversationId('conv_test_123'); getCurrentScope().setConversationId(null); - startSpan({ name: 'test-span' }, span => { + startSpan({ name: 'test-span', op: 'gen_ai.chat' }, span => { const spanJSON = spanToJSON(span); expect(spanJSON.data[GEN_AI_CONVERSATION_ID_ATTRIBUTE]).toBeUndefined(); }); @@ -71,8 +71,8 @@ describe('ConversationId', () => { it('applies conversation ID to nested spans', () => { getCurrentScope().setConversationId('conv_nested_abc'); - startSpan({ name: 'parent-span' }, () => { - startSpan({ name: 'child-span' }, childSpan => { + startSpan({ name: 'parent-span', op: 'gen_ai.invoke_agent' }, () => { + startSpan({ name: 'child-span', op: 'gen_ai.chat' }, childSpan => { const childJSON = spanToJSON(childSpan); expect(childJSON.data[GEN_AI_CONVERSATION_ID_ATTRIBUTE]).toBe('conv_nested_abc'); }); @@ -85,6 +85,7 @@ describe('ConversationId', () => { startSpan( { name: 'test-span', + op: 'gen_ai.chat', attributes: { [GEN_AI_CONVERSATION_ID_ATTRIBUTE]: 'conv_explicit', }, @@ -95,4 +96,13 @@ describe('ConversationId', () => { }, ); }); + + it('does not apply conversation ID to non-gen_ai spans', () => { + getCurrentScope().setConversationId('conv_test_123'); + + startSpan({ name: 'db-query', op: 'db.query' }, span => { + const spanJSON = spanToJSON(span); + expect(spanJSON.data[GEN_AI_CONVERSATION_ID_ATTRIBUTE]).toBeUndefined(); + }); + }); });