diff --git a/containers/api-proxy/otel.js b/containers/api-proxy/otel.js index 05de05c50..286cce7b2 100644 --- a/containers/api-proxy/otel.js +++ b/containers/api-proxy/otel.js @@ -445,21 +445,21 @@ function setTokenAttributes(span, { provider, model, normalizedUsage, streaming span.setAttribute('gen_ai.response.model', model); } span.setAttributes({ - // Standard GenAI semconv (Sentry recognizes these) + // Standard GenAI semconv (Sentry recognizes these as numeric) 'gen_ai.usage.input_tokens': normalizedUsage.input_tokens, 'gen_ai.usage.output_tokens': normalizedUsage.output_tokens, 'gen_ai.request.stream': streaming, - // Cache and reasoning — use Sentry measurement naming (no dots, _count suffix) - 'cache_read_tokens_count': normalizedUsage.cache_read_tokens, - 'cache_write_tokens_count': normalizedUsage.cache_write_tokens, - 'reasoning_tokens_count': normalizedUsage.reasoning_tokens || 0, + // Cache and reasoning as strings (Sentry drops unknown numeric attrs but keeps strings) + 'awf.cache_read_tokens': String(normalizedUsage.cache_read_tokens), + 'awf.cache_write_tokens': String(normalizedUsage.cache_write_tokens), + 'awf.reasoning_tokens': String(normalizedUsage.reasoning_tokens || 0), }); span.addEvent('gen_ai.usage', { 'gen_ai.usage.input_tokens': normalizedUsage.input_tokens, 'gen_ai.usage.output_tokens': normalizedUsage.output_tokens, - 'cache_read_tokens_count': normalizedUsage.cache_read_tokens, - 'cache_write_tokens_count': normalizedUsage.cache_write_tokens, - 'reasoning_tokens_count': normalizedUsage.reasoning_tokens || 0, + 'awf.cache_read_tokens': String(normalizedUsage.cache_read_tokens), + 'awf.cache_write_tokens': String(normalizedUsage.cache_write_tokens), + 'awf.reasoning_tokens': String(normalizedUsage.reasoning_tokens || 0), }); } catch { /* best-effort */ } } diff --git a/containers/api-proxy/otel.test.js b/containers/api-proxy/otel.test.js index 0d6396703..8d655eccb 100644 --- a/containers/api-proxy/otel.test.js +++ b/containers/api-proxy/otel.test.js @@ -235,16 +235,16 @@ describe('otel — setTokenAttributes', () => { expect(s.attributes['gen_ai.response.model']).toBe('gpt-4o'); expect(s.attributes['gen_ai.usage.input_tokens']).toBe(1000); expect(s.attributes['gen_ai.usage.output_tokens']).toBe(500); - expect(s.attributes['cache_read_tokens_count']).toBe(200); - expect(s.attributes['cache_write_tokens_count']).toBe(50); + expect(s.attributes['awf.cache_read_tokens']).toBe('200'); + expect(s.attributes['awf.cache_write_tokens']).toBe('50'); expect(s.attributes['gen_ai.request.stream']).toBe(false); const usageEvent = s.events.find(e => e.name === 'gen_ai.usage'); expect(usageEvent).toBeDefined(); expect(usageEvent.attributes['gen_ai.usage.input_tokens']).toBe(1000); expect(usageEvent.attributes['gen_ai.usage.output_tokens']).toBe(500); - expect(usageEvent.attributes['cache_read_tokens_count']).toBe(200); - expect(usageEvent.attributes['cache_write_tokens_count']).toBe(50); + expect(usageEvent.attributes['awf.cache_read_tokens']).toBe('200'); + expect(usageEvent.attributes['awf.cache_write_tokens']).toBe('50'); }); test('does not set gen_ai.response.model when model is "unknown"', async () => {