From f2689a30d087f5181771de6774c537bc5a540903 Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Thu, 23 Jul 2026 14:17:47 +0200 Subject: [PATCH 1/2] ref(core): Use `@sentry/conventions` for gen_ai attributes Migrate all AI integrations to import gen_ai attribute keys from `@sentry/conventions/attributes` where an equivalent exists, shrinking `gen-ai-attributes.ts` to only the constants conventions does not cover. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../suites/tracing/anthropic-ai/test.ts | 42 +- .../suites/tracing/google-genai/test.ts | 56 +- .../suites/tracing/langchain/test.ts | 48 +- .../suites/tracing/langgraph/test.ts | 38 +- .../suites/tracing/openai/test.ts | 42 +- .../suites/tracing/vercelai/als_v6/test.ts | 20 +- .../suites/tracing/vercelai/compat_v6/test.ts | 20 +- .../suites/tracing/workers-ai/test.ts | 50 +- .../suites/tracing/anthropic/test.ts | 260 +++++---- .../suites/tracing/google-genai/test.ts | 294 ++++++----- .../suites/tracing/langchain/test.ts | 172 +++--- .../suites/tracing/langchain/v1/test.ts | 184 +++---- .../suites/tracing/langgraph/test.ts | 133 +++-- .../tracing/openai/openai-tool-calls/test.ts | 232 +++++---- .../suites/tracing/openai/test.ts | 493 +++++++++--------- .../suites/tracing/openai/v6/test.ts | 384 +++++++------- .../vercelai/span-streaming-v4/test.ts | 202 ++++--- .../vercelai/span-streaming-v6/test.ts | 194 +++---- .../suites/tracing/vercelai/test.ts | 197 ++++--- .../suites/tracing/vercelai/v5/test.ts | 117 ++--- .../suites/tracing/vercelai/v6_v7/test.ts | 231 ++++---- packages/core/src/shared-exports.ts | 6 +- .../core/src/tracing/ai/gen-ai-attributes.ts | 208 +------- packages/core/src/tracing/ai/utils.ts | 51 +- .../core/src/tracing/anthropic-ai/index.ts | 78 +-- .../core/src/tracing/anthropic-ai/utils.ts | 13 +- .../core/src/tracing/google-genai/index.ts | 92 ++-- .../core/src/tracing/langchain/embeddings.ts | 25 +- packages/core/src/tracing/langchain/index.ts | 37 +- packages/core/src/tracing/langchain/utils.ts | 91 ++-- packages/core/src/tracing/langgraph/index.ts | 45 +- packages/core/src/tracing/langgraph/utils.ts | 66 +-- packages/core/src/tracing/openai/index.ts | 39 +- packages/core/src/tracing/openai/utils.ts | 77 +-- packages/core/src/tracing/vercel-ai/index.ts | 127 +++-- packages/core/src/tracing/vercel-ai/utils.ts | 47 +- packages/core/src/tracing/workers-ai/utils.ts | 21 +- .../lib/tracing/langchain-embeddings.test.ts | 30 +- .../test/lib/tracing/langchain-utils.test.ts | 4 +- .../vercel-ai-request-messages.test.ts | 13 +- .../lib/tracing/workers-ai-streaming.test.ts | 60 +-- .../test/lib/utils/workers-ai-utils.test.ts | 32 +- .../integrations/tracing-channel/anthropic.ts | 4 +- .../tracing-channel/google-genai.ts | 4 +- .../src/vercel-ai/vercel-ai-dc-subscriber.ts | 4 +- 45 files changed, 2195 insertions(+), 2388 deletions(-) diff --git a/dev-packages/cloudflare-integration-tests/suites/tracing/anthropic-ai/test.ts b/dev-packages/cloudflare-integration-tests/suites/tracing/anthropic-ai/test.ts index 6817843cde27..4ba21f73c6c0 100644 --- a/dev-packages/cloudflare-integration-tests/suites/tracing/anthropic-ai/test.ts +++ b/dev-packages/cloudflare-integration-tests/suites/tracing/anthropic-ai/test.ts @@ -1,16 +1,16 @@ import { expect, it } from 'vitest'; import { - GEN_AI_OPERATION_NAME_ATTRIBUTE, - GEN_AI_REQUEST_MAX_TOKENS_ATTRIBUTE, - GEN_AI_REQUEST_MODEL_ATTRIBUTE, - GEN_AI_REQUEST_TEMPERATURE_ATTRIBUTE, - GEN_AI_RESPONSE_ID_ATTRIBUTE, - GEN_AI_RESPONSE_MODEL_ATTRIBUTE, - GEN_AI_SYSTEM_ATTRIBUTE, - GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE, - GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE, - GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE, -} from '../../../../../packages/core/src/tracing/ai/gen-ai-attributes'; + GEN_AI_OPERATION_NAME, + GEN_AI_REQUEST_MAX_TOKENS, + GEN_AI_REQUEST_MODEL, + GEN_AI_REQUEST_TEMPERATURE, + GEN_AI_RESPONSE_ID, + GEN_AI_RESPONSE_MODEL, + GEN_AI_SYSTEM, + GEN_AI_USAGE_INPUT_TOKENS, + GEN_AI_USAGE_OUTPUT_TOKENS, + GEN_AI_USAGE_TOTAL_TOKENS, +} from '@sentry/conventions/attributes'; import { createRunner } from '../../../runner'; // This test runs the `@anthropic-ai/sdk` on the Workers runtime (with a @@ -40,16 +40,16 @@ it('traces a basic message creation request with the anthropic SDK', async ({ si attributes: { 'sentry.origin': { value: 'auto.ai.anthropic', type: 'string' }, 'sentry.op': { value: 'gen_ai.chat', type: 'string' }, - [GEN_AI_SYSTEM_ATTRIBUTE]: { value: 'anthropic', type: 'string' }, - [GEN_AI_OPERATION_NAME_ATTRIBUTE]: { value: 'chat', type: 'string' }, - [GEN_AI_REQUEST_MODEL_ATTRIBUTE]: { value: 'claude-3-haiku-20240307', type: 'string' }, - [GEN_AI_REQUEST_TEMPERATURE_ATTRIBUTE]: { value: 0.7, type: 'double' }, - [GEN_AI_REQUEST_MAX_TOKENS_ATTRIBUTE]: { value: 100, type: 'integer' }, - [GEN_AI_RESPONSE_ID_ATTRIBUTE]: { value: 'msg_mock123', type: 'string' }, - [GEN_AI_RESPONSE_MODEL_ATTRIBUTE]: { value: 'claude-3-haiku-20240307', type: 'string' }, - [GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]: { value: 10, type: 'integer' }, - [GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE]: { value: 15, type: 'integer' }, - [GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE]: { value: 25, type: 'integer' }, + [GEN_AI_SYSTEM]: { value: 'anthropic', type: 'string' }, + [GEN_AI_OPERATION_NAME]: { value: 'chat', type: 'string' }, + [GEN_AI_REQUEST_MODEL]: { value: 'claude-3-haiku-20240307', type: 'string' }, + [GEN_AI_REQUEST_TEMPERATURE]: { value: 0.7, type: 'double' }, + [GEN_AI_REQUEST_MAX_TOKENS]: { value: 100, type: 'integer' }, + [GEN_AI_RESPONSE_ID]: { value: 'msg_mock123', type: 'string' }, + [GEN_AI_RESPONSE_MODEL]: { value: 'claude-3-haiku-20240307', type: 'string' }, + [GEN_AI_USAGE_INPUT_TOKENS]: { value: 10, type: 'integer' }, + [GEN_AI_USAGE_OUTPUT_TOKENS]: { value: 15, type: 'integer' }, + [GEN_AI_USAGE_TOTAL_TOKENS]: { value: 25, type: 'integer' }, }, }); }) diff --git a/dev-packages/cloudflare-integration-tests/suites/tracing/google-genai/test.ts b/dev-packages/cloudflare-integration-tests/suites/tracing/google-genai/test.ts index 30941df9b7ea..0115791c3c22 100644 --- a/dev-packages/cloudflare-integration-tests/suites/tracing/google-genai/test.ts +++ b/dev-packages/cloudflare-integration-tests/suites/tracing/google-genai/test.ts @@ -1,16 +1,16 @@ import { expect, it } from 'vitest'; import type { SerializedStreamedSpan } from '@sentry/core'; import { - GEN_AI_OPERATION_NAME_ATTRIBUTE, - GEN_AI_REQUEST_MAX_TOKENS_ATTRIBUTE, - GEN_AI_REQUEST_MODEL_ATTRIBUTE, - GEN_AI_REQUEST_TEMPERATURE_ATTRIBUTE, - GEN_AI_REQUEST_TOP_P_ATTRIBUTE, - GEN_AI_SYSTEM_ATTRIBUTE, - GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE, - GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE, - GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE, -} from '../../../../../packages/core/src/tracing/ai/gen-ai-attributes'; + GEN_AI_OPERATION_NAME, + GEN_AI_REQUEST_MAX_TOKENS, + GEN_AI_REQUEST_MODEL, + GEN_AI_REQUEST_TEMPERATURE, + GEN_AI_REQUEST_TOP_P, + GEN_AI_SYSTEM, + GEN_AI_USAGE_INPUT_TOKENS, + GEN_AI_USAGE_OUTPUT_TOKENS, + GEN_AI_USAGE_TOTAL_TOKENS, +} from '@sentry/conventions/attributes'; import { createRunner } from '../../../runner'; // This test runs the `@google/genai` SDK on the Workers runtime (with a @@ -43,12 +43,12 @@ it('traces Google GenAI chat, generateContent, and embedContent calls', async ({ attributes: { 'sentry.origin': { value: 'auto.ai.google_genai', type: 'string' }, 'sentry.op': { value: 'gen_ai.chat', type: 'string' }, - [GEN_AI_SYSTEM_ATTRIBUTE]: { value: 'google_genai', type: 'string' }, - [GEN_AI_OPERATION_NAME_ATTRIBUTE]: { value: 'chat', type: 'string' }, - [GEN_AI_REQUEST_MODEL_ATTRIBUTE]: { value: 'gemini-1.5-pro', type: 'string' }, - [GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]: { value: 8, type: 'integer' }, - [GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE]: { value: 12, type: 'integer' }, - [GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE]: { value: 20, type: 'integer' }, + [GEN_AI_SYSTEM]: { value: 'google_genai', type: 'string' }, + [GEN_AI_OPERATION_NAME]: { value: 'chat', type: 'string' }, + [GEN_AI_REQUEST_MODEL]: { value: 'gemini-1.5-pro', type: 'string' }, + [GEN_AI_USAGE_INPUT_TOKENS]: { value: 8, type: 'integer' }, + [GEN_AI_USAGE_OUTPUT_TOKENS]: { value: 12, type: 'integer' }, + [GEN_AI_USAGE_TOTAL_TOKENS]: { value: 20, type: 'integer' }, }, }); @@ -64,15 +64,15 @@ it('traces Google GenAI chat, generateContent, and embedContent calls', async ({ attributes: { 'sentry.origin': { value: 'auto.ai.google_genai', type: 'string' }, 'sentry.op': { value: 'gen_ai.generate_content', type: 'string' }, - [GEN_AI_SYSTEM_ATTRIBUTE]: { value: 'google_genai', type: 'string' }, - [GEN_AI_OPERATION_NAME_ATTRIBUTE]: { value: 'generate_content', type: 'string' }, - [GEN_AI_REQUEST_MODEL_ATTRIBUTE]: { value: 'gemini-1.5-flash', type: 'string' }, - [GEN_AI_REQUEST_TEMPERATURE_ATTRIBUTE]: { value: 0.7, type: 'double' }, - [GEN_AI_REQUEST_TOP_P_ATTRIBUTE]: { value: 0.9, type: 'double' }, - [GEN_AI_REQUEST_MAX_TOKENS_ATTRIBUTE]: { value: 100, type: 'integer' }, - [GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]: { value: 8, type: 'integer' }, - [GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE]: { value: 12, type: 'integer' }, - [GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE]: { value: 20, type: 'integer' }, + [GEN_AI_SYSTEM]: { value: 'google_genai', type: 'string' }, + [GEN_AI_OPERATION_NAME]: { value: 'generate_content', type: 'string' }, + [GEN_AI_REQUEST_MODEL]: { value: 'gemini-1.5-flash', type: 'string' }, + [GEN_AI_REQUEST_TEMPERATURE]: { value: 0.7, type: 'double' }, + [GEN_AI_REQUEST_TOP_P]: { value: 0.9, type: 'double' }, + [GEN_AI_REQUEST_MAX_TOKENS]: { value: 100, type: 'integer' }, + [GEN_AI_USAGE_INPUT_TOKENS]: { value: 8, type: 'integer' }, + [GEN_AI_USAGE_OUTPUT_TOKENS]: { value: 12, type: 'integer' }, + [GEN_AI_USAGE_TOTAL_TOKENS]: { value: 20, type: 'integer' }, }, }); @@ -88,9 +88,9 @@ it('traces Google GenAI chat, generateContent, and embedContent calls', async ({ attributes: { 'sentry.origin': { value: 'auto.ai.google_genai', type: 'string' }, 'sentry.op': { value: 'gen_ai.embeddings', type: 'string' }, - [GEN_AI_SYSTEM_ATTRIBUTE]: { value: 'google_genai', type: 'string' }, - [GEN_AI_OPERATION_NAME_ATTRIBUTE]: { value: 'embeddings', type: 'string' }, - [GEN_AI_REQUEST_MODEL_ATTRIBUTE]: { value: 'text-embedding-004', type: 'string' }, + [GEN_AI_SYSTEM]: { value: 'google_genai', type: 'string' }, + [GEN_AI_OPERATION_NAME]: { value: 'embeddings', type: 'string' }, + [GEN_AI_REQUEST_MODEL]: { value: 'text-embedding-004', type: 'string' }, }, }); }) diff --git a/dev-packages/cloudflare-integration-tests/suites/tracing/langchain/test.ts b/dev-packages/cloudflare-integration-tests/suites/tracing/langchain/test.ts index 927057035ccc..09fabdefb3d3 100644 --- a/dev-packages/cloudflare-integration-tests/suites/tracing/langchain/test.ts +++ b/dev-packages/cloudflare-integration-tests/suites/tracing/langchain/test.ts @@ -1,18 +1,18 @@ import { expect, it } from 'vitest'; import { - GEN_AI_OPERATION_NAME_ATTRIBUTE, - GEN_AI_REQUEST_MAX_TOKENS_ATTRIBUTE, - GEN_AI_REQUEST_MODEL_ATTRIBUTE, - GEN_AI_REQUEST_TEMPERATURE_ATTRIBUTE, - GEN_AI_RESPONSE_FINISH_REASONS_ATTRIBUTE, - GEN_AI_RESPONSE_ID_ATTRIBUTE, - GEN_AI_RESPONSE_MODEL_ATTRIBUTE, - GEN_AI_RESPONSE_STOP_REASON_ATTRIBUTE, - GEN_AI_SYSTEM_ATTRIBUTE, - GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE, - GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE, - GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE, -} from '../../../../../packages/core/src/tracing/ai/gen-ai-attributes'; + GEN_AI_OPERATION_NAME, + GEN_AI_REQUEST_MAX_TOKENS, + GEN_AI_REQUEST_MODEL, + GEN_AI_REQUEST_TEMPERATURE, + GEN_AI_RESPONSE_FINISH_REASONS, + GEN_AI_RESPONSE_ID, + GEN_AI_RESPONSE_MODEL, + GEN_AI_SYSTEM, + GEN_AI_USAGE_INPUT_TOKENS, + GEN_AI_USAGE_OUTPUT_TOKENS, + GEN_AI_USAGE_TOTAL_TOKENS, +} from '@sentry/conventions/attributes'; +import { GEN_AI_RESPONSE_STOP_REASON_ATTRIBUTE } from '../../../../../packages/core/src/tracing/ai/gen-ai-attributes'; import { createRunner } from '../../../runner'; // This test runs the `@langchain/openai` model (backed by the @@ -42,17 +42,17 @@ it('traces a LangChain chat model invocation', async ({ signal }) => { attributes: { 'sentry.origin': { value: 'auto.ai.langchain', type: 'string' }, 'sentry.op': { value: 'gen_ai.chat', type: 'string' }, - [GEN_AI_SYSTEM_ATTRIBUTE]: { value: 'openai', type: 'string' }, - [GEN_AI_OPERATION_NAME_ATTRIBUTE]: { value: 'chat', type: 'string' }, - [GEN_AI_REQUEST_MODEL_ATTRIBUTE]: { value: 'gpt-3.5-turbo', type: 'string' }, - [GEN_AI_REQUEST_TEMPERATURE_ATTRIBUTE]: { value: 0.7, type: 'double' }, - [GEN_AI_REQUEST_MAX_TOKENS_ATTRIBUTE]: { value: 100, type: 'integer' }, - [GEN_AI_RESPONSE_FINISH_REASONS_ATTRIBUTE]: { value: '["stop"]', type: 'string' }, - [GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]: { value: 10, type: 'integer' }, - [GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE]: { value: 15, type: 'integer' }, - [GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE]: { value: 25, type: 'integer' }, - [GEN_AI_RESPONSE_MODEL_ATTRIBUTE]: { value: 'gpt-3.5-turbo', type: 'string' }, - [GEN_AI_RESPONSE_ID_ATTRIBUTE]: { value: 'chatcmpl-mock123', type: 'string' }, + [GEN_AI_SYSTEM]: { value: 'openai', type: 'string' }, + [GEN_AI_OPERATION_NAME]: { value: 'chat', type: 'string' }, + [GEN_AI_REQUEST_MODEL]: { value: 'gpt-3.5-turbo', type: 'string' }, + [GEN_AI_REQUEST_TEMPERATURE]: { value: 0.7, type: 'double' }, + [GEN_AI_REQUEST_MAX_TOKENS]: { value: 100, type: 'integer' }, + [GEN_AI_RESPONSE_FINISH_REASONS]: { value: '["stop"]', type: 'string' }, + [GEN_AI_USAGE_INPUT_TOKENS]: { value: 10, type: 'integer' }, + [GEN_AI_USAGE_OUTPUT_TOKENS]: { value: 15, type: 'integer' }, + [GEN_AI_USAGE_TOTAL_TOKENS]: { value: 25, type: 'integer' }, + [GEN_AI_RESPONSE_MODEL]: { value: 'gpt-3.5-turbo', type: 'string' }, + [GEN_AI_RESPONSE_ID]: { value: 'chatcmpl-mock123', type: 'string' }, [GEN_AI_RESPONSE_STOP_REASON_ATTRIBUTE]: { value: 'stop', type: 'string' }, }, }); diff --git a/dev-packages/cloudflare-integration-tests/suites/tracing/langgraph/test.ts b/dev-packages/cloudflare-integration-tests/suites/tracing/langgraph/test.ts index aad02e48ee73..6d3cffa6d14e 100644 --- a/dev-packages/cloudflare-integration-tests/suites/tracing/langgraph/test.ts +++ b/dev-packages/cloudflare-integration-tests/suites/tracing/langgraph/test.ts @@ -1,15 +1,15 @@ import { expect, it } from 'vitest'; import type { SerializedStreamedSpan } from '@sentry/core'; import { - GEN_AI_AGENT_NAME_ATTRIBUTE, - GEN_AI_INPUT_MESSAGES_ATTRIBUTE, - GEN_AI_OPERATION_NAME_ATTRIBUTE, - GEN_AI_PIPELINE_NAME_ATTRIBUTE, - GEN_AI_RESPONSE_MODEL_ATTRIBUTE, - GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE, - GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE, - GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE, -} from '../../../../../packages/core/src/tracing/ai/gen-ai-attributes'; + GEN_AI_AGENT_NAME, + GEN_AI_INPUT_MESSAGES, + GEN_AI_OPERATION_NAME, + GEN_AI_PIPELINE_NAME, + GEN_AI_RESPONSE_MODEL, + GEN_AI_USAGE_INPUT_TOKENS, + GEN_AI_USAGE_OUTPUT_TOKENS, + GEN_AI_USAGE_TOTAL_TOKENS, +} from '@sentry/conventions/attributes'; import { createRunner } from '../../../runner'; // These tests are not exhaustive because the instrumentation is @@ -40,13 +40,13 @@ it('traces langgraph compile and invoke operations', async ({ signal }) => { ); expect(createAgentSpan).toBeDefined(); expect(createAgentSpan!.status).toBe('ok'); - expect(createAgentSpan!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE]).toEqual({ + expect(createAgentSpan!.attributes[GEN_AI_OPERATION_NAME]).toEqual({ type: 'string', value: 'create_agent', }); expect(createAgentSpan!.attributes['sentry.op']).toEqual({ type: 'string', value: 'gen_ai.create_agent' }); expect(createAgentSpan!.attributes['sentry.origin']).toEqual({ type: 'string', value: 'auto.ai.langgraph' }); - expect(createAgentSpan!.attributes[GEN_AI_AGENT_NAME_ATTRIBUTE]).toEqual({ + expect(createAgentSpan!.attributes[GEN_AI_AGENT_NAME]).toEqual({ type: 'string', value: 'weather_assistant', }); @@ -56,37 +56,37 @@ it('traces langgraph compile and invoke operations', async ({ signal }) => { ); expect(invokeAgentSpan).toBeDefined(); expect(invokeAgentSpan!.status).toBe('ok'); - expect(invokeAgentSpan!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE]).toEqual({ + expect(invokeAgentSpan!.attributes[GEN_AI_OPERATION_NAME]).toEqual({ type: 'string', value: 'invoke_agent', }); expect(invokeAgentSpan!.attributes['sentry.op']).toEqual({ type: 'string', value: 'gen_ai.invoke_agent' }); expect(invokeAgentSpan!.attributes['sentry.origin']).toEqual({ type: 'string', value: 'auto.ai.langgraph' }); - expect(invokeAgentSpan!.attributes[GEN_AI_AGENT_NAME_ATTRIBUTE]).toEqual({ + expect(invokeAgentSpan!.attributes[GEN_AI_AGENT_NAME]).toEqual({ type: 'string', value: 'weather_assistant', }); - expect(invokeAgentSpan!.attributes[GEN_AI_PIPELINE_NAME_ATTRIBUTE]).toEqual({ + expect(invokeAgentSpan!.attributes[GEN_AI_PIPELINE_NAME]).toEqual({ type: 'string', value: 'weather_assistant', }); - expect(invokeAgentSpan!.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]).toEqual({ + expect(invokeAgentSpan!.attributes[GEN_AI_INPUT_MESSAGES]).toEqual({ type: 'string', value: '[{"role":"user","content":"What is the weather in SF?"}]', }); - expect(invokeAgentSpan!.attributes[GEN_AI_RESPONSE_MODEL_ATTRIBUTE]).toEqual({ + expect(invokeAgentSpan!.attributes[GEN_AI_RESPONSE_MODEL]).toEqual({ type: 'string', value: 'mock-model', }); - expect(invokeAgentSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]).toEqual({ + expect(invokeAgentSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS]).toEqual({ type: 'integer', value: 20, }); - expect(invokeAgentSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE]).toEqual({ + expect(invokeAgentSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS]).toEqual({ type: 'integer', value: 10, }); - expect(invokeAgentSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE]).toEqual({ + expect(invokeAgentSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS]).toEqual({ type: 'integer', value: 30, }); diff --git a/dev-packages/cloudflare-integration-tests/suites/tracing/openai/test.ts b/dev-packages/cloudflare-integration-tests/suites/tracing/openai/test.ts index 87e11a18e618..e0e55cf7ec79 100644 --- a/dev-packages/cloudflare-integration-tests/suites/tracing/openai/test.ts +++ b/dev-packages/cloudflare-integration-tests/suites/tracing/openai/test.ts @@ -1,16 +1,16 @@ import { expect, it } from 'vitest'; import { - GEN_AI_OPERATION_NAME_ATTRIBUTE, - GEN_AI_REQUEST_MODEL_ATTRIBUTE, - GEN_AI_REQUEST_TEMPERATURE_ATTRIBUTE, - GEN_AI_RESPONSE_FINISH_REASONS_ATTRIBUTE, - GEN_AI_RESPONSE_ID_ATTRIBUTE, - GEN_AI_RESPONSE_MODEL_ATTRIBUTE, - GEN_AI_SYSTEM_ATTRIBUTE, - GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE, - GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE, - GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE, -} from '../../../../../packages/core/src/tracing/ai/gen-ai-attributes'; + GEN_AI_OPERATION_NAME, + GEN_AI_REQUEST_MODEL, + GEN_AI_REQUEST_TEMPERATURE, + GEN_AI_RESPONSE_FINISH_REASONS, + GEN_AI_RESPONSE_ID, + GEN_AI_RESPONSE_MODEL, + GEN_AI_SYSTEM, + GEN_AI_USAGE_INPUT_TOKENS, + GEN_AI_USAGE_OUTPUT_TOKENS, + GEN_AI_USAGE_TOTAL_TOKENS, +} from '@sentry/conventions/attributes'; import { createRunner } from '../../../runner'; // This test runs the `openai` SDK on the Workers runtime (with a canned @@ -40,16 +40,16 @@ it('traces a basic chat completion request with the openai SDK', async ({ signal attributes: { 'sentry.origin': { value: 'auto.ai.openai', type: 'string' }, 'sentry.op': { value: 'gen_ai.chat', type: 'string' }, - [GEN_AI_SYSTEM_ATTRIBUTE]: { value: 'openai', type: 'string' }, - [GEN_AI_OPERATION_NAME_ATTRIBUTE]: { value: 'chat', type: 'string' }, - [GEN_AI_REQUEST_MODEL_ATTRIBUTE]: { value: 'gpt-3.5-turbo', type: 'string' }, - [GEN_AI_REQUEST_TEMPERATURE_ATTRIBUTE]: { value: 0.7, type: 'double' }, - [GEN_AI_RESPONSE_ID_ATTRIBUTE]: { value: 'chatcmpl-mock123', type: 'string' }, - [GEN_AI_RESPONSE_MODEL_ATTRIBUTE]: { value: 'gpt-3.5-turbo', type: 'string' }, - [GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]: { value: 10, type: 'integer' }, - [GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE]: { value: 15, type: 'integer' }, - [GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE]: { value: 25, type: 'integer' }, - [GEN_AI_RESPONSE_FINISH_REASONS_ATTRIBUTE]: { value: '["stop"]', type: 'string' }, + [GEN_AI_SYSTEM]: { value: 'openai', type: 'string' }, + [GEN_AI_OPERATION_NAME]: { value: 'chat', type: 'string' }, + [GEN_AI_REQUEST_MODEL]: { value: 'gpt-3.5-turbo', type: 'string' }, + [GEN_AI_REQUEST_TEMPERATURE]: { value: 0.7, type: 'double' }, + [GEN_AI_RESPONSE_ID]: { value: 'chatcmpl-mock123', type: 'string' }, + [GEN_AI_RESPONSE_MODEL]: { value: 'gpt-3.5-turbo', type: 'string' }, + [GEN_AI_USAGE_INPUT_TOKENS]: { value: 10, type: 'integer' }, + [GEN_AI_USAGE_OUTPUT_TOKENS]: { value: 15, type: 'integer' }, + [GEN_AI_USAGE_TOTAL_TOKENS]: { value: 25, type: 'integer' }, + [GEN_AI_RESPONSE_FINISH_REASONS]: { value: '["stop"]', type: 'string' }, }, }); }) diff --git a/dev-packages/cloudflare-integration-tests/suites/tracing/vercelai/als_v6/test.ts b/dev-packages/cloudflare-integration-tests/suites/tracing/vercelai/als_v6/test.ts index 5e615a51d204..65fb16e9ab1e 100644 --- a/dev-packages/cloudflare-integration-tests/suites/tracing/vercelai/als_v6/test.ts +++ b/dev-packages/cloudflare-integration-tests/suites/tracing/vercelai/als_v6/test.ts @@ -1,10 +1,10 @@ import { expect, it } from 'vitest'; import { - GEN_AI_OPERATION_NAME_ATTRIBUTE, - GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE, - GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE, - GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE, -} from '../../../../../../packages/core/src/tracing/ai/gen-ai-attributes'; + GEN_AI_OPERATION_NAME, + GEN_AI_USAGE_INPUT_TOKENS, + GEN_AI_USAGE_OUTPUT_TOKENS, + GEN_AI_USAGE_TOTAL_TOKENS, +} from '@sentry/conventions/attributes'; import { createRunner } from '../../../../runner'; it('captures a transaction with Vercel AI v6 spans via @sentry/cloudflare vercelAIIntegration', async ({ signal }) => { @@ -32,10 +32,10 @@ it('captures a transaction with Vercel AI v6 spans via @sentry/cloudflare vercel attributes: expect.objectContaining({ 'sentry.op': { type: 'string', value: 'gen_ai.invoke_agent' }, 'sentry.origin': { type: 'string', value: 'auto.vercelai.otel' }, - [GEN_AI_OPERATION_NAME_ATTRIBUTE]: { type: 'string', value: 'invoke_agent' }, - [GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]: { type: 'integer', value: 10 }, - [GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE]: { type: 'integer', value: 20 }, - [GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE]: { type: 'integer', value: 30 }, + [GEN_AI_OPERATION_NAME]: { type: 'string', value: 'invoke_agent' }, + [GEN_AI_USAGE_INPUT_TOKENS]: { type: 'integer', value: 10 }, + [GEN_AI_USAGE_OUTPUT_TOKENS]: { type: 'integer', value: 20 }, + [GEN_AI_USAGE_TOTAL_TOKENS]: { type: 'integer', value: 30 }, }), }, { @@ -50,7 +50,7 @@ it('captures a transaction with Vercel AI v6 spans via @sentry/cloudflare vercel attributes: expect.objectContaining({ 'sentry.op': { type: 'string', value: 'gen_ai.generate_content' }, 'sentry.origin': { type: 'string', value: 'auto.vercelai.otel' }, - [GEN_AI_OPERATION_NAME_ATTRIBUTE]: { type: 'string', value: 'generate_content' }, + [GEN_AI_OPERATION_NAME]: { type: 'string', value: 'generate_content' }, }), }, ]), diff --git a/dev-packages/cloudflare-integration-tests/suites/tracing/vercelai/compat_v6/test.ts b/dev-packages/cloudflare-integration-tests/suites/tracing/vercelai/compat_v6/test.ts index 0b22ff6365f7..1f961f202119 100644 --- a/dev-packages/cloudflare-integration-tests/suites/tracing/vercelai/compat_v6/test.ts +++ b/dev-packages/cloudflare-integration-tests/suites/tracing/vercelai/compat_v6/test.ts @@ -1,10 +1,10 @@ import { expect, it } from 'vitest'; import { - GEN_AI_OPERATION_NAME_ATTRIBUTE, - GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE, - GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE, - GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE, -} from '../../../../../../packages/core/src/tracing/ai/gen-ai-attributes'; + GEN_AI_OPERATION_NAME, + GEN_AI_USAGE_INPUT_TOKENS, + GEN_AI_USAGE_OUTPUT_TOKENS, + GEN_AI_USAGE_TOTAL_TOKENS, +} from '@sentry/conventions/attributes'; import { createRunner } from '../../../../runner'; it('captures a transaction with Vercel AI v6 spans via @sentry/cloudflare/nodejs_compat vercelAIIntegration', async ({ @@ -34,10 +34,10 @@ it('captures a transaction with Vercel AI v6 spans via @sentry/cloudflare/nodejs attributes: expect.objectContaining({ 'sentry.op': { type: 'string', value: 'gen_ai.invoke_agent' }, 'sentry.origin': { type: 'string', value: 'auto.vercelai.otel' }, - [GEN_AI_OPERATION_NAME_ATTRIBUTE]: { type: 'string', value: 'invoke_agent' }, - [GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]: { type: 'integer', value: 10 }, - [GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE]: { type: 'integer', value: 20 }, - [GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE]: { type: 'integer', value: 30 }, + [GEN_AI_OPERATION_NAME]: { type: 'string', value: 'invoke_agent' }, + [GEN_AI_USAGE_INPUT_TOKENS]: { type: 'integer', value: 10 }, + [GEN_AI_USAGE_OUTPUT_TOKENS]: { type: 'integer', value: 20 }, + [GEN_AI_USAGE_TOTAL_TOKENS]: { type: 'integer', value: 30 }, }), }, { @@ -52,7 +52,7 @@ it('captures a transaction with Vercel AI v6 spans via @sentry/cloudflare/nodejs attributes: expect.objectContaining({ 'sentry.op': { type: 'string', value: 'gen_ai.generate_content' }, 'sentry.origin': { type: 'string', value: 'auto.vercelai.otel' }, - [GEN_AI_OPERATION_NAME_ATTRIBUTE]: { type: 'string', value: 'generate_content' }, + [GEN_AI_OPERATION_NAME]: { type: 'string', value: 'generate_content' }, }), }, ]), diff --git a/dev-packages/cloudflare-integration-tests/suites/tracing/workers-ai/test.ts b/dev-packages/cloudflare-integration-tests/suites/tracing/workers-ai/test.ts index e5c835f37508..915b0e06a90c 100644 --- a/dev-packages/cloudflare-integration-tests/suites/tracing/workers-ai/test.ts +++ b/dev-packages/cloudflare-integration-tests/suites/tracing/workers-ai/test.ts @@ -1,16 +1,16 @@ -import { GEN_AI_PROVIDER_NAME } from '@sentry/conventions/attributes'; -import { expect, it } from 'vitest'; import { - GEN_AI_OPERATION_NAME_ATTRIBUTE, - GEN_AI_REQUEST_MAX_TOKENS_ATTRIBUTE, - GEN_AI_REQUEST_MODEL_ATTRIBUTE, - GEN_AI_REQUEST_STREAM_ATTRIBUTE, - GEN_AI_REQUEST_TEMPERATURE_ATTRIBUTE, - GEN_AI_RESPONSE_STREAMING_ATTRIBUTE, - GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE, - GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE, - GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE, -} from '../../../../../packages/core/src/tracing/ai/gen-ai-attributes'; + GEN_AI_OPERATION_NAME, + GEN_AI_PROVIDER_NAME, + GEN_AI_REQUEST_MAX_TOKENS, + GEN_AI_REQUEST_MODEL, + GEN_AI_REQUEST_TEMPERATURE, + GEN_AI_RESPONSE_STREAMING, + GEN_AI_USAGE_INPUT_TOKENS, + GEN_AI_USAGE_OUTPUT_TOKENS, + GEN_AI_USAGE_TOTAL_TOKENS, +} from '@sentry/conventions/attributes'; +import { expect, it } from 'vitest'; +import { GEN_AI_REQUEST_STREAM_ATTRIBUTE } from '../../../../../packages/core/src/tracing/ai/gen-ai-attributes'; import { createRunner } from '../../../runner'; // These tests are not exhaustive because the instrumentation is @@ -46,13 +46,13 @@ it('traces a basic Workers AI text generation request', async ({ signal }) => { 'sentry.origin': 'auto.ai.cloudflare.workers_ai', 'sentry.op': 'gen_ai.chat', [GEN_AI_PROVIDER_NAME]: 'cloudflare.workers_ai', - [GEN_AI_OPERATION_NAME_ATTRIBUTE]: 'chat', - [GEN_AI_REQUEST_MODEL_ATTRIBUTE]: '@cf/meta/llama-3.1-8b-instruct', - [GEN_AI_REQUEST_TEMPERATURE_ATTRIBUTE]: 0.7, - [GEN_AI_REQUEST_MAX_TOKENS_ATTRIBUTE]: 100, - [GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]: 12, - [GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE]: 7, - [GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE]: 19, + [GEN_AI_OPERATION_NAME]: 'chat', + [GEN_AI_REQUEST_MODEL]: '@cf/meta/llama-3.1-8b-instruct', + [GEN_AI_REQUEST_TEMPERATURE]: 0.7, + [GEN_AI_REQUEST_MAX_TOKENS]: 100, + [GEN_AI_USAGE_INPUT_TOKENS]: 12, + [GEN_AI_USAGE_OUTPUT_TOKENS]: 7, + [GEN_AI_USAGE_TOTAL_TOKENS]: 19, }, }), ], @@ -91,13 +91,13 @@ it('traces a streaming Workers AI text generation request', async ({ signal }) = 'sentry.origin': 'auto.ai.cloudflare.workers_ai', 'sentry.op': 'gen_ai.chat', [GEN_AI_PROVIDER_NAME]: 'cloudflare.workers_ai', - [GEN_AI_OPERATION_NAME_ATTRIBUTE]: 'chat', - [GEN_AI_REQUEST_MODEL_ATTRIBUTE]: '@cf/meta/llama-3.1-8b-instruct', + [GEN_AI_OPERATION_NAME]: 'chat', + [GEN_AI_REQUEST_MODEL]: '@cf/meta/llama-3.1-8b-instruct', [GEN_AI_REQUEST_STREAM_ATTRIBUTE]: true, - [GEN_AI_RESPONSE_STREAMING_ATTRIBUTE]: true, - [GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]: 12, - [GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE]: 7, - [GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE]: 19, + [GEN_AI_RESPONSE_STREAMING]: true, + [GEN_AI_USAGE_INPUT_TOKENS]: 12, + [GEN_AI_USAGE_OUTPUT_TOKENS]: 7, + [GEN_AI_USAGE_TOTAL_TOKENS]: 19, }, }), ], diff --git a/dev-packages/node-integration-tests/suites/tracing/anthropic/test.ts b/dev-packages/node-integration-tests/suites/tracing/anthropic/test.ts index 5546aa8abd66..47318cdaecce 100644 --- a/dev-packages/node-integration-tests/suites/tracing/anthropic/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/anthropic/test.ts @@ -1,24 +1,26 @@ import { afterAll, describe, expect } from 'vitest'; import { - GEN_AI_INPUT_MESSAGES_ATTRIBUTE, + GEN_AI_INPUT_MESSAGES, + GEN_AI_OPERATION_NAME, + GEN_AI_REQUEST_AVAILABLE_TOOLS, + GEN_AI_REQUEST_MAX_TOKENS, + GEN_AI_REQUEST_MODEL, + GEN_AI_REQUEST_TEMPERATURE, + GEN_AI_RESPONSE_FINISH_REASONS, + GEN_AI_RESPONSE_ID, + GEN_AI_RESPONSE_MODEL, + GEN_AI_RESPONSE_STREAMING, + GEN_AI_RESPONSE_TEXT, + GEN_AI_RESPONSE_TOOL_CALLS, + GEN_AI_SYSTEM, + GEN_AI_SYSTEM_INSTRUCTIONS, + GEN_AI_USAGE_INPUT_TOKENS, + GEN_AI_USAGE_OUTPUT_TOKENS, + GEN_AI_USAGE_TOTAL_TOKENS, +} from '@sentry/conventions/attributes'; +import { GEN_AI_INPUT_MESSAGES_ORIGINAL_LENGTH_ATTRIBUTE, - GEN_AI_OPERATION_NAME_ATTRIBUTE, - GEN_AI_REQUEST_AVAILABLE_TOOLS_ATTRIBUTE, - GEN_AI_REQUEST_MAX_TOKENS_ATTRIBUTE, - GEN_AI_REQUEST_MODEL_ATTRIBUTE, GEN_AI_REQUEST_STREAM_ATTRIBUTE, - GEN_AI_REQUEST_TEMPERATURE_ATTRIBUTE, - GEN_AI_RESPONSE_FINISH_REASONS_ATTRIBUTE, - GEN_AI_RESPONSE_ID_ATTRIBUTE, - GEN_AI_RESPONSE_MODEL_ATTRIBUTE, - GEN_AI_RESPONSE_STREAMING_ATTRIBUTE, - GEN_AI_RESPONSE_TEXT_ATTRIBUTE, - GEN_AI_RESPONSE_TOOL_CALLS_ATTRIBUTE, - GEN_AI_SYSTEM_ATTRIBUTE, - GEN_AI_SYSTEM_INSTRUCTIONS_ATTRIBUTE, - GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE, - GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE, - GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE, } from '../../../../../packages/core/src/tracing/ai/gen-ai-attributes'; import { getStringAttributeValue, isOrchestrionEnabled } from '../../../utils'; import { cleanupChildProcesses, createEsmAndCjsTests } from '../../../utils/runner'; @@ -68,23 +70,23 @@ describe('Anthropic integration', () => { span: container => { expect(container.items).toHaveLength(3); const nonStreamingSpans = container.items.filter( - span => span.attributes[GEN_AI_RESPONSE_ID_ATTRIBUTE]?.value === 'msg_withresponse', + span => span.attributes[GEN_AI_RESPONSE_ID]?.value === 'msg_withresponse', ); expect(nonStreamingSpans).toHaveLength(2); for (const span of nonStreamingSpans) { expect(span.name).toBe('chat claude-3-haiku-20240307'); expect(span.status).toBe('ok'); - expect(span.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE].value).toBe('chat'); - expect(span.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE].value).toBe('claude-3-haiku-20240307'); + expect(span.attributes[GEN_AI_OPERATION_NAME].value).toBe('chat'); + expect(span.attributes[GEN_AI_REQUEST_MODEL].value).toBe('claude-3-haiku-20240307'); } const streamingSpan = container.items.find( - span => span.attributes[GEN_AI_RESPONSE_ID_ATTRIBUTE]?.value === 'msg_stream_withresponse', + span => span.attributes[GEN_AI_RESPONSE_ID]?.value === 'msg_stream_withresponse', ); expect(streamingSpan).toBeDefined(); expect(streamingSpan!.name).toBe('chat claude-3-haiku-20240307'); expect(streamingSpan!.status).toBe('ok'); - expect(streamingSpan!.attributes[GEN_AI_RESPONSE_STREAMING_ATTRIBUTE].value).toBe(true); + expect(streamingSpan!.attributes[GEN_AI_RESPONSE_STREAMING].value).toBe(true); }, }) .start() @@ -108,7 +110,7 @@ describe('Anthropic integration', () => { span: container => { expect(container.items).toHaveLength(5); const completionSpan = container.items.find( - span => span.attributes[GEN_AI_RESPONSE_ID_ATTRIBUTE]?.value === 'msg_mock123', + span => span.attributes[GEN_AI_RESPONSE_ID]?.value === 'msg_mock123', ); expect(completionSpan).toBeDefined(); expect(completionSpan!.name).toBe('chat claude-3-haiku-20240307'); @@ -122,7 +124,7 @@ describe('Anthropic integration', () => { span => span.name === 'chat claude-3-haiku-20240307' && span.status === 'ok' && - span.attributes[GEN_AI_RESPONSE_ID_ATTRIBUTE] === undefined, + span.attributes[GEN_AI_RESPONSE_ID] === undefined, ); expect(tokenCountingSpan).toBeDefined(); expect(tokenCountingSpan!.attributes['sentry.op'].value).toBe('gen_ai.chat'); @@ -133,12 +135,12 @@ describe('Anthropic integration', () => { expect(modelsSpan!.attributes['sentry.op'].value).toBe('gen_ai.models'); const streamingSpan = container.items.find( - span => span.attributes[GEN_AI_RESPONSE_ID_ATTRIBUTE]?.value === 'msg_stream123', + span => span.attributes[GEN_AI_RESPONSE_ID]?.value === 'msg_stream123', ); expect(streamingSpan).toBeDefined(); expect(streamingSpan!.name).toBe('chat claude-3-haiku-20240307'); expect(streamingSpan!.status).toBe('ok'); - expect(streamingSpan!.attributes[GEN_AI_RESPONSE_STREAMING_ATTRIBUTE].value).toBe(true); + expect(streamingSpan!.attributes[GEN_AI_RESPONSE_STREAMING].value).toBe(true); }, }) .expect({ event: EXPECTED_STREAM_EVENT_HANDLER_MESSAGE }) @@ -163,42 +165,40 @@ describe('Anthropic integration', () => { span: container => { expect(container.items).toHaveLength(5); const completionSpan = container.items.find( - span => span.attributes[GEN_AI_RESPONSE_ID_ATTRIBUTE]?.value === 'msg_mock123', + span => span.attributes[GEN_AI_RESPONSE_ID]?.value === 'msg_mock123', ); expect(completionSpan).toBeDefined(); expect(completionSpan!.name).toBe('chat claude-3-haiku-20240307'); expect(completionSpan!.status).toBe('ok'); - expect(completionSpan!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE].value).toBe('chat'); - expect(completionSpan!.attributes[GEN_AI_REQUEST_MAX_TOKENS_ATTRIBUTE].value).toBe(100); - expect(completionSpan!.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE].value).toBe('claude-3-haiku-20240307'); - expect(completionSpan!.attributes[GEN_AI_REQUEST_TEMPERATURE_ATTRIBUTE].value).toBe(0.7); - expect(completionSpan!.attributes[GEN_AI_RESPONSE_MODEL_ATTRIBUTE].value).toBe('claude-3-haiku-20240307'); - expect(completionSpan!.attributes[GEN_AI_RESPONSE_ID_ATTRIBUTE].value).toBe('msg_mock123'); - expect(completionSpan!.attributes[GEN_AI_RESPONSE_TEXT_ATTRIBUTE].value).toBe('Hello from Anthropic mock!'); - expect(completionSpan!.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE].value).toBe( + expect(completionSpan!.attributes[GEN_AI_OPERATION_NAME].value).toBe('chat'); + expect(completionSpan!.attributes[GEN_AI_REQUEST_MAX_TOKENS].value).toBe(100); + expect(completionSpan!.attributes[GEN_AI_REQUEST_MODEL].value).toBe('claude-3-haiku-20240307'); + expect(completionSpan!.attributes[GEN_AI_REQUEST_TEMPERATURE].value).toBe(0.7); + expect(completionSpan!.attributes[GEN_AI_RESPONSE_MODEL].value).toBe('claude-3-haiku-20240307'); + expect(completionSpan!.attributes[GEN_AI_RESPONSE_ID].value).toBe('msg_mock123'); + expect(completionSpan!.attributes[GEN_AI_RESPONSE_TEXT].value).toBe('Hello from Anthropic mock!'); + expect(completionSpan!.attributes[GEN_AI_INPUT_MESSAGES].value).toBe( '[{"role":"user","content":"What is the capital of France?"}]', ); - expect(completionSpan!.attributes[GEN_AI_SYSTEM_ATTRIBUTE].value).toBe('anthropic'); - expect(completionSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE].value).toBe(10); - expect(completionSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE].value).toBe(15); - expect(completionSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE].value).toBe(25); + expect(completionSpan!.attributes[GEN_AI_SYSTEM].value).toBe('anthropic'); + expect(completionSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS].value).toBe(10); + expect(completionSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS].value).toBe(15); + expect(completionSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS].value).toBe(25); expect(completionSpan!.attributes['sentry.op'].value).toBe('gen_ai.chat'); expect(completionSpan!.attributes['sentry.origin'].value).toBe( isOrchestrionEnabled() ? 'auto.ai.orchestrion.anthropic' : 'auto.ai.anthropic', ); const errorSpan = container.items.find( - span => - span.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value === - '[{"role":"user","content":"This will fail"}]', + span => span.attributes[GEN_AI_INPUT_MESSAGES]?.value === '[{"role":"user","content":"This will fail"}]', ); expect(errorSpan).toBeDefined(); expect(errorSpan!.name).toBe('chat error-model'); expect(errorSpan!.status).toBe('error'); - expect(errorSpan!.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE].value).toBe('error-model'); + expect(errorSpan!.attributes[GEN_AI_REQUEST_MODEL].value).toBe('error-model'); const tokenCountingSpan = container.items.find( - span => span.attributes[GEN_AI_RESPONSE_TEXT_ATTRIBUTE]?.value === '15', + span => span.attributes[GEN_AI_RESPONSE_TEXT]?.value === '15', ); expect(tokenCountingSpan).toBeDefined(); expect(tokenCountingSpan!.name).toBe('chat claude-3-haiku-20240307'); @@ -213,19 +213,19 @@ describe('Anthropic integration', () => { // TODO: messages.stream() should produce its own distinct gen_ai span, but it // currently does not (pre-existing bug). Once fixed, add an additional indexed span assertion. const streamingSpan = container.items.find( - span => span.attributes[GEN_AI_RESPONSE_ID_ATTRIBUTE]?.value === 'msg_stream123', + span => span.attributes[GEN_AI_RESPONSE_ID]?.value === 'msg_stream123', ); expect(streamingSpan).toBeDefined(); expect(streamingSpan!.name).toBe('chat claude-3-haiku-20240307'); expect(streamingSpan!.status).toBe('ok'); - expect(streamingSpan!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE].value).toBe('chat'); - expect(streamingSpan!.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE].value).toBe('claude-3-haiku-20240307'); + expect(streamingSpan!.attributes[GEN_AI_OPERATION_NAME].value).toBe('chat'); + expect(streamingSpan!.attributes[GEN_AI_REQUEST_MODEL].value).toBe('claude-3-haiku-20240307'); expect(streamingSpan!.attributes[GEN_AI_REQUEST_STREAM_ATTRIBUTE].value).toBe(true); - expect(streamingSpan!.attributes[GEN_AI_RESPONSE_STREAMING_ATTRIBUTE].value).toBe(true); - expect(streamingSpan!.attributes[GEN_AI_RESPONSE_TEXT_ATTRIBUTE].value).toBe('Hello from stream!'); - expect(streamingSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE].value).toBe(10); - expect(streamingSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE].value).toBe(15); - expect(streamingSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE].value).toBe(25); + expect(streamingSpan!.attributes[GEN_AI_RESPONSE_STREAMING].value).toBe(true); + expect(streamingSpan!.attributes[GEN_AI_RESPONSE_TEXT].value).toBe('Hello from stream!'); + expect(streamingSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS].value).toBe(10); + expect(streamingSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS].value).toBe(15); + expect(streamingSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS].value).toBe(25); }, }) .expect({ event: EXPECTED_STREAM_EVENT_HANDLER_MESSAGE }) @@ -243,14 +243,14 @@ describe('Anthropic integration', () => { span: container => { expect(container.items).toHaveLength(5); const completionSpan = container.items.find( - span => span.attributes[GEN_AI_RESPONSE_ID_ATTRIBUTE]?.value === 'msg_mock123', + span => span.attributes[GEN_AI_RESPONSE_ID]?.value === 'msg_mock123', ); expect(completionSpan).toBeDefined(); expect(completionSpan!.name).toBe('chat claude-3-haiku-20240307'); expect(completionSpan!.status).toBe('ok'); expect(completionSpan!.attributes['sentry.op'].value).toBe('gen_ai.chat'); - expect(completionSpan!.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]).toBeDefined(); - expect(completionSpan!.attributes[GEN_AI_RESPONSE_TEXT_ATTRIBUTE]).toBeDefined(); + expect(completionSpan!.attributes[GEN_AI_INPUT_MESSAGES]).toBeDefined(); + expect(completionSpan!.attributes[GEN_AI_RESPONSE_TEXT]).toBeDefined(); const errorSpan = container.items.find(span => span.name === 'chat error-model'); expect(errorSpan).toBeDefined(); @@ -258,31 +258,31 @@ describe('Anthropic integration', () => { expect(errorSpan!.attributes['sentry.op'].value).toBe('gen_ai.chat'); const tokenCountingSpan = container.items.find( - span => span.attributes[GEN_AI_RESPONSE_TEXT_ATTRIBUTE]?.value === '15', + span => span.attributes[GEN_AI_RESPONSE_TEXT]?.value === '15', ); expect(tokenCountingSpan).toBeDefined(); expect(tokenCountingSpan!.name).toBe('chat claude-3-haiku-20240307'); expect(tokenCountingSpan!.status).toBe('ok'); expect(tokenCountingSpan!.attributes['sentry.op'].value).toBe('gen_ai.chat'); - expect(tokenCountingSpan!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE].value).toBe('chat'); + expect(tokenCountingSpan!.attributes[GEN_AI_OPERATION_NAME].value).toBe('chat'); const modelsSpan = container.items.find(span => span.name === 'models claude-3-haiku-20240307'); expect(modelsSpan).toBeDefined(); expect(modelsSpan!.status).toBe('ok'); - expect(modelsSpan!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE].value).toBe('models'); + expect(modelsSpan!.attributes[GEN_AI_OPERATION_NAME].value).toBe('models'); expect(modelsSpan!.attributes['sentry.op'].value).toBe('gen_ai.models'); - expect(modelsSpan!.attributes[GEN_AI_SYSTEM_ATTRIBUTE].value).toBe('anthropic'); - expect(modelsSpan!.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE].value).toBe('claude-3-haiku-20240307'); - expect(modelsSpan!.attributes[GEN_AI_RESPONSE_ID_ATTRIBUTE].value).toBe('claude-3-haiku-20240307'); - expect(modelsSpan!.attributes[GEN_AI_RESPONSE_MODEL_ATTRIBUTE].value).toBe('claude-3-haiku-20240307'); + expect(modelsSpan!.attributes[GEN_AI_SYSTEM].value).toBe('anthropic'); + expect(modelsSpan!.attributes[GEN_AI_REQUEST_MODEL].value).toBe('claude-3-haiku-20240307'); + expect(modelsSpan!.attributes[GEN_AI_RESPONSE_ID].value).toBe('claude-3-haiku-20240307'); + expect(modelsSpan!.attributes[GEN_AI_RESPONSE_MODEL].value).toBe('claude-3-haiku-20240307'); const streamingSpan = container.items.find( - span => span.attributes[GEN_AI_RESPONSE_ID_ATTRIBUTE]?.value === 'msg_stream123', + span => span.attributes[GEN_AI_RESPONSE_ID]?.value === 'msg_stream123', ); expect(streamingSpan).toBeDefined(); expect(streamingSpan!.name).toBe('chat claude-3-haiku-20240307'); expect(streamingSpan!.status).toBe('ok'); - expect(streamingSpan!.attributes[GEN_AI_RESPONSE_STREAMING_ATTRIBUTE].value).toBe(true); + expect(streamingSpan!.attributes[GEN_AI_RESPONSE_STREAMING].value).toBe(true); }, }) .expect({ event: EXPECTED_STREAM_EVENT_HANDLER_MESSAGE }) @@ -315,25 +315,21 @@ describe('Anthropic integration', () => { expect(span.name).toBe('chat claude-3-haiku-20240307'); expect(span.status).toBe('ok'); expect(span.attributes[GEN_AI_REQUEST_STREAM_ATTRIBUTE].value).toBe(true); - expect(span.attributes[GEN_AI_RESPONSE_STREAMING_ATTRIBUTE].value).toBe(true); - expect(span.attributes[GEN_AI_RESPONSE_ID_ATTRIBUTE].value).toBe('msg_stream_1'); + expect(span.attributes[GEN_AI_RESPONSE_STREAMING].value).toBe(true); + expect(span.attributes[GEN_AI_RESPONSE_ID].value).toBe('msg_stream_1'); } const detailedStreamSpan = requestStreamSpans.find( - span => span.attributes[GEN_AI_RESPONSE_FINISH_REASONS_ATTRIBUTE]?.value === '["end_turn"]', + span => span.attributes[GEN_AI_RESPONSE_FINISH_REASONS]?.value === '["end_turn"]', ); expect(detailedStreamSpan).toBeDefined(); - expect(detailedStreamSpan!.attributes[GEN_AI_SYSTEM_ATTRIBUTE].value).toBe('anthropic'); - expect(detailedStreamSpan!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE].value).toBe('chat'); - expect(detailedStreamSpan!.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE].value).toBe( - 'claude-3-haiku-20240307', - ); - expect(detailedStreamSpan!.attributes[GEN_AI_RESPONSE_MODEL_ATTRIBUTE].value).toBe( - 'claude-3-haiku-20240307', - ); - expect(detailedStreamSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE].value).toBe(10); - expect(detailedStreamSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE].value).toBe(15); - expect(detailedStreamSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE].value).toBe(25); + expect(detailedStreamSpan!.attributes[GEN_AI_SYSTEM].value).toBe('anthropic'); + expect(detailedStreamSpan!.attributes[GEN_AI_OPERATION_NAME].value).toBe('chat'); + expect(detailedStreamSpan!.attributes[GEN_AI_REQUEST_MODEL].value).toBe('claude-3-haiku-20240307'); + expect(detailedStreamSpan!.attributes[GEN_AI_RESPONSE_MODEL].value).toBe('claude-3-haiku-20240307'); + expect(detailedStreamSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS].value).toBe(10); + expect(detailedStreamSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS].value).toBe(15); + expect(detailedStreamSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS].value).toBe(25); const messagesStreamSpan = container.items.find( span => span.attributes[GEN_AI_REQUEST_STREAM_ATTRIBUTE] === undefined, @@ -341,19 +337,15 @@ describe('Anthropic integration', () => { expect(messagesStreamSpan).toBeDefined(); expect(messagesStreamSpan!.name).toBe('chat claude-3-haiku-20240307'); expect(messagesStreamSpan!.status).toBe('ok'); - expect(messagesStreamSpan!.attributes[GEN_AI_SYSTEM_ATTRIBUTE].value).toBe('anthropic'); - expect(messagesStreamSpan!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE].value).toBe('chat'); - expect(messagesStreamSpan!.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE].value).toBe( - 'claude-3-haiku-20240307', - ); - expect(messagesStreamSpan!.attributes[GEN_AI_RESPONSE_STREAMING_ATTRIBUTE].value).toBe(true); - expect(messagesStreamSpan!.attributes[GEN_AI_RESPONSE_MODEL_ATTRIBUTE].value).toBe( - 'claude-3-haiku-20240307', - ); - expect(messagesStreamSpan!.attributes[GEN_AI_RESPONSE_ID_ATTRIBUTE].value).toBe('msg_stream_1'); - expect(messagesStreamSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE].value).toBe(10); - expect(messagesStreamSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE].value).toBe(15); - expect(messagesStreamSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE].value).toBe(25); + expect(messagesStreamSpan!.attributes[GEN_AI_SYSTEM].value).toBe('anthropic'); + expect(messagesStreamSpan!.attributes[GEN_AI_OPERATION_NAME].value).toBe('chat'); + expect(messagesStreamSpan!.attributes[GEN_AI_REQUEST_MODEL].value).toBe('claude-3-haiku-20240307'); + expect(messagesStreamSpan!.attributes[GEN_AI_RESPONSE_STREAMING].value).toBe(true); + expect(messagesStreamSpan!.attributes[GEN_AI_RESPONSE_MODEL].value).toBe('claude-3-haiku-20240307'); + expect(messagesStreamSpan!.attributes[GEN_AI_RESPONSE_ID].value).toBe('msg_stream_1'); + expect(messagesStreamSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS].value).toBe(10); + expect(messagesStreamSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS].value).toBe(15); + expect(messagesStreamSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS].value).toBe(25); }, }) .start() @@ -376,9 +368,9 @@ describe('Anthropic integration', () => { for (const span of requestStreamSpans) { expect(span.name).toBe('chat claude-3-haiku-20240307'); expect(span.status).toBe('ok'); - expect(span.attributes[GEN_AI_RESPONSE_STREAMING_ATTRIBUTE].value).toBe(true); + expect(span.attributes[GEN_AI_RESPONSE_STREAMING].value).toBe(true); expect(span.attributes[GEN_AI_REQUEST_STREAM_ATTRIBUTE].value).toBe(true); - expect(span.attributes[GEN_AI_RESPONSE_TEXT_ATTRIBUTE].value).toBe('Hello from stream!'); + expect(span.attributes[GEN_AI_RESPONSE_TEXT].value).toBe('Hello from stream!'); } const messagesStreamSpan = container.items.find( @@ -387,8 +379,8 @@ describe('Anthropic integration', () => { expect(messagesStreamSpan).toBeDefined(); expect(messagesStreamSpan!.name).toBe('chat claude-3-haiku-20240307'); expect(messagesStreamSpan!.status).toBe('ok'); - expect(messagesStreamSpan!.attributes[GEN_AI_RESPONSE_STREAMING_ATTRIBUTE].value).toBe(true); - expect(messagesStreamSpan!.attributes[GEN_AI_RESPONSE_TEXT_ATTRIBUTE].value).toBe('Hello from stream!'); + expect(messagesStreamSpan!.attributes[GEN_AI_RESPONSE_STREAMING].value).toBe(true); + expect(messagesStreamSpan!.attributes[GEN_AI_RESPONSE_TEXT].value).toBe('Hello from stream!'); }, }) .start() @@ -420,7 +412,7 @@ describe('Anthropic integration', () => { .expect({ span: container => { const nestedSpan = container.items.find( - span => span.attributes[GEN_AI_RESPONSE_ID_ATTRIBUTE]?.value === 'msg_nested', + span => span.attributes[GEN_AI_RESPONSE_ID]?.value === 'msg_nested', ); expect(nestedSpan).toBeDefined(); expect(nestedSpan.attributes['sentry.op'].value).toBe('gen_ai.chat'); @@ -428,7 +420,7 @@ describe('Anthropic integration', () => { // The helper's own internal `create` delegation must be deduped: exactly one span // for the streamed response, not a duplicate child span. const streamingSpans = container.items.filter( - span => span.attributes[GEN_AI_RESPONSE_ID_ATTRIBUTE]?.value === 'msg_stream_1', + span => span.attributes[GEN_AI_RESPONSE_ID]?.value === 'msg_stream_1', ); expect(streamingSpans).toHaveLength(1); }, @@ -459,8 +451,8 @@ describe('Anthropic integration', () => { expect(firstSpan!.name).toBe('chat claude-3-haiku-20240307'); expect(firstSpan!.status).toBe('ok'); expect(firstSpan!.attributes['sentry.op'].value).toBe('gen_ai.chat'); - expect(firstSpan!.attributes[GEN_AI_REQUEST_AVAILABLE_TOOLS_ATTRIBUTE].value).toBe(EXPECTED_TOOLS_JSON); - expect(firstSpan!.attributes[GEN_AI_RESPONSE_TOOL_CALLS_ATTRIBUTE].value).toBe(EXPECTED_TOOL_CALLS_JSON); + expect(firstSpan!.attributes[GEN_AI_REQUEST_AVAILABLE_TOOLS].value).toBe(EXPECTED_TOOLS_JSON); + expect(firstSpan!.attributes[GEN_AI_RESPONSE_TOOL_CALLS].value).toBe(EXPECTED_TOOL_CALLS_JSON); }, }) .start() @@ -487,10 +479,10 @@ describe('Anthropic integration', () => { expect(span.name).toBe('chat claude-3-haiku-20240307'); expect(span.status).toBe('ok'); expect(span.attributes['sentry.op'].value).toBe('gen_ai.chat'); - expect(span.attributes[GEN_AI_RESPONSE_STREAMING_ATTRIBUTE].value).toBe(true); - expect(span.attributes[GEN_AI_RESPONSE_FINISH_REASONS_ATTRIBUTE].value).toBe('["tool_use"]'); - expect(span.attributes[GEN_AI_REQUEST_AVAILABLE_TOOLS_ATTRIBUTE].value).toBe(EXPECTED_TOOLS_JSON); - expect(span.attributes[GEN_AI_RESPONSE_TOOL_CALLS_ATTRIBUTE].value).toBe(EXPECTED_TOOL_CALLS_JSON); + expect(span.attributes[GEN_AI_RESPONSE_STREAMING].value).toBe(true); + expect(span.attributes[GEN_AI_RESPONSE_FINISH_REASONS].value).toBe('["tool_use"]'); + expect(span.attributes[GEN_AI_REQUEST_AVAILABLE_TOOLS].value).toBe(EXPECTED_TOOLS_JSON); + expect(span.attributes[GEN_AI_RESPONSE_TOOL_CALLS].value).toBe(EXPECTED_TOOL_CALLS_JSON); } // messages.create({ stream: true }) carries the request stream param; messages.stream() does not. @@ -524,7 +516,7 @@ describe('Anthropic integration', () => { expect(container.items).toHaveLength(4); const createInitErrorSpan = container.items.find( span => - span.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE]?.value === 'error-stream-init' && + span.attributes[GEN_AI_REQUEST_MODEL]?.value === 'error-stream-init' && span.attributes[GEN_AI_REQUEST_STREAM_ATTRIBUTE]?.value === true, ); expect(createInitErrorSpan).toBeDefined(); @@ -533,7 +525,7 @@ describe('Anthropic integration', () => { const streamInitErrorSpan = container.items.find( span => - span.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE]?.value === 'error-stream-init' && + span.attributes[GEN_AI_REQUEST_MODEL]?.value === 'error-stream-init' && span.attributes[GEN_AI_REQUEST_STREAM_ATTRIBUTE] === undefined, ); expect(streamInitErrorSpan).toBeDefined(); @@ -541,20 +533,16 @@ describe('Anthropic integration', () => { expect(streamInitErrorSpan!.status).toBe('error'); const createMidwayErrorSpan = container.items.find( - span => - span.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE]?.value === 'error-stream-midway' && - span.status === 'ok', + span => span.attributes[GEN_AI_REQUEST_MODEL]?.value === 'error-stream-midway' && span.status === 'ok', ); expect(createMidwayErrorSpan).toBeDefined(); expect(createMidwayErrorSpan!.name).toBe('chat error-stream-midway'); expect(createMidwayErrorSpan!.attributes[GEN_AI_REQUEST_STREAM_ATTRIBUTE].value).toBe(true); - expect(createMidwayErrorSpan!.attributes[GEN_AI_RESPONSE_STREAMING_ATTRIBUTE].value).toBe(true); - expect(createMidwayErrorSpan!.attributes[GEN_AI_RESPONSE_TEXT_ATTRIBUTE].value).toBe('This stream will '); + expect(createMidwayErrorSpan!.attributes[GEN_AI_RESPONSE_STREAMING].value).toBe(true); + expect(createMidwayErrorSpan!.attributes[GEN_AI_RESPONSE_TEXT].value).toBe('This stream will '); const streamMidwayErrorSpan = container.items.find( - span => - span.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE]?.value === 'error-stream-midway' && - span.status === 'error', + span => span.attributes[GEN_AI_REQUEST_MODEL]?.value === 'error-stream-midway' && span.status === 'error', ); expect(streamMidwayErrorSpan).toBeDefined(); expect(streamMidwayErrorSpan!.name).toBe('chat error-stream-midway'); @@ -582,19 +570,19 @@ describe('Anthropic integration', () => { const invalidFormatSpan = container.items.find(span => span.name === 'chat invalid-format'); expect(invalidFormatSpan).toBeDefined(); expect(invalidFormatSpan!.status).toBe('error'); - expect(invalidFormatSpan!.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE].value).toBe('invalid-format'); + expect(invalidFormatSpan!.attributes[GEN_AI_REQUEST_MODEL].value).toBe('invalid-format'); expect(invalidFormatSpan!.attributes['sentry.op'].value).toBe('gen_ai.chat'); const modelErrorSpan = container.items.find(span => span.name === 'models nonexistent-model'); expect(modelErrorSpan).toBeDefined(); expect(modelErrorSpan!.status).toBe('error'); - expect(modelErrorSpan!.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE].value).toBe('nonexistent-model'); + expect(modelErrorSpan!.attributes[GEN_AI_REQUEST_MODEL].value).toBe('nonexistent-model'); expect(modelErrorSpan!.attributes['sentry.op'].value).toBe('gen_ai.models'); const toolSuccessSpan = container.items.find(span => span.name === 'chat claude-3-haiku-20240307'); expect(toolSuccessSpan).toBeDefined(); expect(toolSuccessSpan!.status).toBe('ok'); - expect(toolSuccessSpan!.attributes[GEN_AI_RESPONSE_TOOL_CALLS_ATTRIBUTE].value).toContain('tool_ok_1'); + expect(toolSuccessSpan!.attributes[GEN_AI_RESPONSE_TOOL_CALLS].value).toContain('tool_ok_1'); }, }) .start() @@ -622,33 +610,31 @@ describe('Anthropic integration', () => { { role: 'user', content: 'This is a small message that fits within the limit' }, ]); const truncatedSpan = container.items.find(span => - getStringAttributeValue(span.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value)?.match( + getStringAttributeValue(span.attributes[GEN_AI_INPUT_MESSAGES]?.value)?.match( /^\[\{"role":"user","content":"C+"\}\]$/, ), ); expect(truncatedSpan).toBeDefined(); expect(truncatedSpan!.name).toBe('chat claude-3-haiku-20240307'); expect(truncatedSpan!.status).toBe('ok'); - expect(truncatedSpan!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE].value).toBe('chat'); + expect(truncatedSpan!.attributes[GEN_AI_OPERATION_NAME].value).toBe('chat'); expect(truncatedSpan!.attributes['sentry.op'].value).toBe('gen_ai.chat'); expect(truncatedSpan!.attributes['sentry.origin'].value).toBe('auto.ai.anthropic'); - expect(truncatedSpan!.attributes[GEN_AI_SYSTEM_ATTRIBUTE].value).toBe('anthropic'); - expect(truncatedSpan!.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE].value).toBe('claude-3-haiku-20240307'); + expect(truncatedSpan!.attributes[GEN_AI_SYSTEM].value).toBe('anthropic'); + expect(truncatedSpan!.attributes[GEN_AI_REQUEST_MODEL].value).toBe('claude-3-haiku-20240307'); expect(truncatedSpan!.attributes[GEN_AI_INPUT_MESSAGES_ORIGINAL_LENGTH_ATTRIBUTE].value).toBe(3); const smallMessageSpan = container.items.find( - span => span.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value === smallMsgValue, + span => span.attributes[GEN_AI_INPUT_MESSAGES]?.value === smallMsgValue, ); expect(smallMessageSpan).toBeDefined(); expect(smallMessageSpan!.name).toBe('chat claude-3-haiku-20240307'); expect(smallMessageSpan!.status).toBe('ok'); - expect(smallMessageSpan!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE].value).toBe('chat'); + expect(smallMessageSpan!.attributes[GEN_AI_OPERATION_NAME].value).toBe('chat'); expect(smallMessageSpan!.attributes['sentry.op'].value).toBe('gen_ai.chat'); expect(smallMessageSpan!.attributes['sentry.origin'].value).toBe('auto.ai.anthropic'); - expect(smallMessageSpan!.attributes[GEN_AI_SYSTEM_ATTRIBUTE].value).toBe('anthropic'); - expect(smallMessageSpan!.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE].value).toBe( - 'claude-3-haiku-20240307', - ); + expect(smallMessageSpan!.attributes[GEN_AI_SYSTEM].value).toBe('anthropic'); + expect(smallMessageSpan!.attributes[GEN_AI_REQUEST_MODEL].value).toBe('claude-3-haiku-20240307'); expect(smallMessageSpan!.attributes[GEN_AI_INPUT_MESSAGES_ORIGINAL_LENGTH_ATTRIBUTE].value).toBe(3); }, }) @@ -694,12 +680,12 @@ describe('Anthropic integration', () => { // [0] messages.create with media attachment — image data replaced, other fields preserved expect(firstSpan!.name).toBe('chat claude-3-haiku-20240307'); expect(firstSpan!.status).toBe('ok'); - expect(firstSpan!.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE].value).toBe(expectedMediaMessages); - expect(firstSpan!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE].value).toBe('chat'); + expect(firstSpan!.attributes[GEN_AI_INPUT_MESSAGES].value).toBe(expectedMediaMessages); + expect(firstSpan!.attributes[GEN_AI_OPERATION_NAME].value).toBe('chat'); expect(firstSpan!.attributes['sentry.op'].value).toBe('gen_ai.chat'); expect(firstSpan!.attributes['sentry.origin'].value).toBe('auto.ai.anthropic'); - expect(firstSpan!.attributes[GEN_AI_SYSTEM_ATTRIBUTE].value).toBe('anthropic'); - expect(firstSpan!.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE].value).toBe('claude-3-haiku-20240307'); + expect(firstSpan!.attributes[GEN_AI_SYSTEM].value).toBe('anthropic'); + expect(firstSpan!.attributes[GEN_AI_REQUEST_MODEL].value).toBe('claude-3-haiku-20240307'); expect(firstSpan!.attributes[GEN_AI_INPUT_MESSAGES_ORIGINAL_LENGTH_ATTRIBUTE].value).toBe(2); }, }) @@ -729,7 +715,7 @@ describe('Anthropic integration', () => { const [firstSpan] = container.items; // [0] messages.create — system instructions extracted into dedicated attribute - expect(firstSpan!.attributes[GEN_AI_SYSTEM_INSTRUCTIONS_ATTRIBUTE].value).toBe(expectedInstructions); + expect(firstSpan!.attributes[GEN_AI_SYSTEM_INSTRUCTIONS].value).toBe(expectedInstructions); }, }) .start() @@ -764,13 +750,13 @@ describe('Anthropic integration', () => { span: container => { expect(container.items).toHaveLength(2); const conversationSpan = container.items.find( - span => span.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value === expectedAllMessages, + span => span.attributes[GEN_AI_INPUT_MESSAGES]?.value === expectedAllMessages, ); expect(conversationSpan).toBeDefined(); expect(conversationSpan!.attributes[GEN_AI_INPUT_MESSAGES_ORIGINAL_LENGTH_ATTRIBUTE].value).toBe(3); const longStringSpan = container.items.find( - span => span.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value === expectedLongString, + span => span.attributes[GEN_AI_INPUT_MESSAGES]?.value === expectedLongString, ); expect(longStringSpan).toBeDefined(); expect(longStringSpan!.attributes[GEN_AI_INPUT_MESSAGES_ORIGINAL_LENGTH_ATTRIBUTE].value).toBe(1); @@ -792,9 +778,7 @@ describe('Anthropic integration', () => { const spans = container.items; const chatSpan = spans.find(s => - getStringAttributeValue(s.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value)?.includes( - streamingLongContent, - ), + getStringAttributeValue(s.attributes[GEN_AI_INPUT_MESSAGES]?.value)?.includes(streamingLongContent), ); expect(chatSpan).toBeDefined(); }, @@ -818,13 +802,13 @@ describe('Anthropic integration', () => { // With explicit enableTruncation: true, content should be truncated despite streaming. // Find the chat span by matching the start of the truncated content (the 'A' repeated messages). const chatSpan = spans.find(s => - getStringAttributeValue(s.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value)?.startsWith( + getStringAttributeValue(s.attributes[GEN_AI_INPUT_MESSAGES]?.value)?.startsWith( '[{"role":"user","content":"AAAA', ), ); expect(chatSpan).toBeDefined(); expect( - (getStringAttributeValue(chatSpan!.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE].value) ?? '').length, + (getStringAttributeValue(chatSpan!.attributes[GEN_AI_INPUT_MESSAGES].value) ?? '').length, ).toBeLessThan(streamingLongContent.length); }, }) diff --git a/dev-packages/node-integration-tests/suites/tracing/google-genai/test.ts b/dev-packages/node-integration-tests/suites/tracing/google-genai/test.ts index 3332ff1d862c..8097aac2b839 100644 --- a/dev-packages/node-integration-tests/suites/tracing/google-genai/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/google-genai/test.ts @@ -1,26 +1,26 @@ import { afterAll, describe, expect } from 'vitest'; import { - GEN_AI_EMBEDDINGS_INPUT_ATTRIBUTE, - GEN_AI_INPUT_MESSAGES_ATTRIBUTE, - GEN_AI_OPERATION_NAME_ATTRIBUTE, - GEN_AI_INPUT_MESSAGES_ORIGINAL_LENGTH_ATTRIBUTE, - GEN_AI_REQUEST_AVAILABLE_TOOLS_ATTRIBUTE, - GEN_AI_REQUEST_MAX_TOKENS_ATTRIBUTE, - GEN_AI_REQUEST_MODEL_ATTRIBUTE, - GEN_AI_REQUEST_TEMPERATURE_ATTRIBUTE, - GEN_AI_REQUEST_TOP_P_ATTRIBUTE, - GEN_AI_RESPONSE_FINISH_REASONS_ATTRIBUTE, - GEN_AI_RESPONSE_ID_ATTRIBUTE, - GEN_AI_RESPONSE_MODEL_ATTRIBUTE, - GEN_AI_RESPONSE_STREAMING_ATTRIBUTE, - GEN_AI_RESPONSE_TEXT_ATTRIBUTE, - GEN_AI_RESPONSE_TOOL_CALLS_ATTRIBUTE, - GEN_AI_SYSTEM_ATTRIBUTE, - GEN_AI_SYSTEM_INSTRUCTIONS_ATTRIBUTE, - GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE, - GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE, - GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE, -} from '../../../../../packages/core/src/tracing/ai/gen-ai-attributes'; + GEN_AI_EMBEDDINGS_INPUT, + GEN_AI_INPUT_MESSAGES, + GEN_AI_OPERATION_NAME, + GEN_AI_REQUEST_AVAILABLE_TOOLS, + GEN_AI_REQUEST_MAX_TOKENS, + GEN_AI_REQUEST_MODEL, + GEN_AI_REQUEST_TEMPERATURE, + GEN_AI_REQUEST_TOP_P, + GEN_AI_RESPONSE_FINISH_REASONS, + GEN_AI_RESPONSE_ID, + GEN_AI_RESPONSE_MODEL, + GEN_AI_RESPONSE_STREAMING, + GEN_AI_RESPONSE_TEXT, + GEN_AI_RESPONSE_TOOL_CALLS, + GEN_AI_SYSTEM, + GEN_AI_SYSTEM_INSTRUCTIONS, + GEN_AI_USAGE_INPUT_TOKENS, + GEN_AI_USAGE_OUTPUT_TOKENS, + GEN_AI_USAGE_TOTAL_TOKENS, +} from '@sentry/conventions/attributes'; +import { GEN_AI_INPUT_MESSAGES_ORIGINAL_LENGTH_ATTRIBUTE } from '../../../../../packages/core/src/tracing/ai/gen-ai-attributes'; import { cleanupChildProcesses, createEsmAndCjsTests } from '../../../utils/runner'; import { getStringAttributeValue, isOrchestrionEnabled } from '../../../utils'; @@ -49,35 +49,35 @@ describe('Google GenAI integration', () => { expect(chatSpan).toBeDefined(); expect(chatSpan!.status).toBe('ok'); expect(chatSpan!.attributes['sentry.op'].value).toBe('gen_ai.chat'); - expect(chatSpan!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE].value).toBe('chat'); + expect(chatSpan!.attributes[GEN_AI_OPERATION_NAME].value).toBe('chat'); expect(chatSpan!.attributes['sentry.origin'].value).toBe(EXPECTED_ORIGIN); - expect(chatSpan!.attributes[GEN_AI_SYSTEM_ATTRIBUTE].value).toBe('google_genai'); - expect(chatSpan!.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE].value).toBe('gemini-1.5-pro'); - expect(chatSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE].value).toBe(8); - expect(chatSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE].value).toBe(12); - expect(chatSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE].value).toBe(20); + expect(chatSpan!.attributes[GEN_AI_SYSTEM].value).toBe('google_genai'); + expect(chatSpan!.attributes[GEN_AI_REQUEST_MODEL].value).toBe('gemini-1.5-pro'); + expect(chatSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS].value).toBe(8); + expect(chatSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS].value).toBe(12); + expect(chatSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS].value).toBe(20); const generateContentSpan = container.items.find(span => span.name === 'generate_content gemini-1.5-flash'); expect(generateContentSpan).toBeDefined(); expect(generateContentSpan!.status).toBe('ok'); expect(generateContentSpan!.attributes['sentry.op'].value).toBe('gen_ai.generate_content'); - expect(generateContentSpan!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE].value).toBe('generate_content'); - expect(generateContentSpan!.attributes[GEN_AI_SYSTEM_ATTRIBUTE].value).toBe('google_genai'); - expect(generateContentSpan!.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE].value).toBe('gemini-1.5-flash'); - expect(generateContentSpan!.attributes[GEN_AI_REQUEST_TEMPERATURE_ATTRIBUTE].value).toBe(0.7); - expect(generateContentSpan!.attributes[GEN_AI_REQUEST_TOP_P_ATTRIBUTE].value).toBe(0.9); - expect(generateContentSpan!.attributes[GEN_AI_REQUEST_MAX_TOKENS_ATTRIBUTE].value).toBe(100); - expect(generateContentSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE].value).toBe(8); - expect(generateContentSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE].value).toBe(12); - expect(generateContentSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE].value).toBe(20); + expect(generateContentSpan!.attributes[GEN_AI_OPERATION_NAME].value).toBe('generate_content'); + expect(generateContentSpan!.attributes[GEN_AI_SYSTEM].value).toBe('google_genai'); + expect(generateContentSpan!.attributes[GEN_AI_REQUEST_MODEL].value).toBe('gemini-1.5-flash'); + expect(generateContentSpan!.attributes[GEN_AI_REQUEST_TEMPERATURE].value).toBe(0.7); + expect(generateContentSpan!.attributes[GEN_AI_REQUEST_TOP_P].value).toBe(0.9); + expect(generateContentSpan!.attributes[GEN_AI_REQUEST_MAX_TOKENS].value).toBe(100); + expect(generateContentSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS].value).toBe(8); + expect(generateContentSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS].value).toBe(12); + expect(generateContentSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS].value).toBe(20); const errorSpan = container.items.find(span => span.name === 'generate_content error-model'); expect(errorSpan).toBeDefined(); expect(errorSpan!.status).toBe('error'); expect(errorSpan!.attributes['sentry.op'].value).toBe('gen_ai.generate_content'); - expect(errorSpan!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE].value).toBe('generate_content'); - expect(errorSpan!.attributes[GEN_AI_SYSTEM_ATTRIBUTE].value).toBe('google_genai'); - expect(errorSpan!.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE].value).toBe('error-model'); + expect(errorSpan!.attributes[GEN_AI_OPERATION_NAME].value).toBe('generate_content'); + expect(errorSpan!.attributes[GEN_AI_SYSTEM].value).toBe('google_genai'); + expect(errorSpan!.attributes[GEN_AI_REQUEST_MODEL].value).toBe('error-model'); }, }) .start() @@ -103,32 +103,32 @@ describe('Google GenAI integration', () => { expect(chatSpan).toBeDefined(); expect(chatSpan!.status).toBe('ok'); expect(chatSpan!.attributes['sentry.op'].value).toBe('gen_ai.chat'); - expect(chatSpan!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE].value).toBe('chat'); - expect(chatSpan!.attributes[GEN_AI_SYSTEM_ATTRIBUTE].value).toBe('google_genai'); - expect(chatSpan!.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE].value).toBe('gemini-1.5-pro'); - expect(chatSpan!.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]).toBeDefined(); - expect(chatSpan!.attributes[GEN_AI_RESPONSE_TEXT_ATTRIBUTE]).toBeDefined(); - expect(chatSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE].value).toBe(8); - expect(chatSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE].value).toBe(12); - expect(chatSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE].value).toBe(20); + expect(chatSpan!.attributes[GEN_AI_OPERATION_NAME].value).toBe('chat'); + expect(chatSpan!.attributes[GEN_AI_SYSTEM].value).toBe('google_genai'); + expect(chatSpan!.attributes[GEN_AI_REQUEST_MODEL].value).toBe('gemini-1.5-pro'); + expect(chatSpan!.attributes[GEN_AI_INPUT_MESSAGES]).toBeDefined(); + expect(chatSpan!.attributes[GEN_AI_RESPONSE_TEXT]).toBeDefined(); + expect(chatSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS].value).toBe(8); + expect(chatSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS].value).toBe(12); + expect(chatSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS].value).toBe(20); const generateContentSpan = container.items.find(span => span.name === 'generate_content gemini-1.5-flash'); expect(generateContentSpan).toBeDefined(); expect(generateContentSpan!.status).toBe('ok'); expect(generateContentSpan!.attributes['sentry.op'].value).toBe('gen_ai.generate_content'); - expect(generateContentSpan!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE].value).toBe('generate_content'); - expect(generateContentSpan!.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]).toBeDefined(); - expect(generateContentSpan!.attributes[GEN_AI_RESPONSE_TEXT_ATTRIBUTE]).toBeDefined(); - expect(generateContentSpan!.attributes[GEN_AI_REQUEST_TEMPERATURE_ATTRIBUTE].value).toBe(0.7); - expect(generateContentSpan!.attributes[GEN_AI_REQUEST_TOP_P_ATTRIBUTE].value).toBe(0.9); - expect(generateContentSpan!.attributes[GEN_AI_REQUEST_MAX_TOKENS_ATTRIBUTE].value).toBe(100); + expect(generateContentSpan!.attributes[GEN_AI_OPERATION_NAME].value).toBe('generate_content'); + expect(generateContentSpan!.attributes[GEN_AI_INPUT_MESSAGES]).toBeDefined(); + expect(generateContentSpan!.attributes[GEN_AI_RESPONSE_TEXT]).toBeDefined(); + expect(generateContentSpan!.attributes[GEN_AI_REQUEST_TEMPERATURE].value).toBe(0.7); + expect(generateContentSpan!.attributes[GEN_AI_REQUEST_TOP_P].value).toBe(0.9); + expect(generateContentSpan!.attributes[GEN_AI_REQUEST_MAX_TOKENS].value).toBe(100); const errorSpan = container.items.find(span => span.name === 'generate_content error-model'); expect(errorSpan).toBeDefined(); expect(errorSpan!.status).toBe('error'); expect(errorSpan!.attributes['sentry.op'].value).toBe('gen_ai.generate_content'); - expect(errorSpan!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE].value).toBe('generate_content'); - expect(errorSpan!.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]).toBeDefined(); + expect(errorSpan!.attributes[GEN_AI_OPERATION_NAME].value).toBe('generate_content'); + expect(errorSpan!.attributes[GEN_AI_INPUT_MESSAGES]).toBeDefined(); }, }) .start() @@ -152,17 +152,17 @@ describe('Google GenAI integration', () => { const chatSpan = container.items.find(span => span.name === 'chat gemini-1.5-pro'); expect(chatSpan).toBeDefined(); - expect(chatSpan!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE].value).toBe('chat'); - expect(chatSpan!.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]).toBeDefined(); - expect(chatSpan!.attributes[GEN_AI_RESPONSE_TEXT_ATTRIBUTE]).toBeDefined(); + expect(chatSpan!.attributes[GEN_AI_OPERATION_NAME].value).toBe('chat'); + expect(chatSpan!.attributes[GEN_AI_INPUT_MESSAGES]).toBeDefined(); + expect(chatSpan!.attributes[GEN_AI_RESPONSE_TEXT]).toBeDefined(); const generateContentSpan = container.items.find(span => span.name === 'generate_content gemini-1.5-flash'); expect(generateContentSpan).toBeDefined(); - expect(generateContentSpan!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE].value).toBe('generate_content'); + expect(generateContentSpan!.attributes[GEN_AI_OPERATION_NAME].value).toBe('generate_content'); const errorSpan = container.items.find(span => span.name === 'generate_content error-model'); expect(errorSpan).toBeDefined(); - expect(errorSpan!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE].value).toBe('generate_content'); + expect(errorSpan!.attributes[GEN_AI_OPERATION_NAME].value).toBe('generate_content'); }, }) .start() @@ -183,50 +183,50 @@ describe('Google GenAI integration', () => { expect(container.items).toHaveLength(3); const nonStreamingToolsSpan = container.items.find( span => - span.attributes[GEN_AI_REQUEST_AVAILABLE_TOOLS_ATTRIBUTE]?.value === EXPECTED_AVAILABLE_TOOLS_JSON && - span.attributes[GEN_AI_RESPONSE_STREAMING_ATTRIBUTE] === undefined, + span.attributes[GEN_AI_REQUEST_AVAILABLE_TOOLS]?.value === EXPECTED_AVAILABLE_TOOLS_JSON && + span.attributes[GEN_AI_RESPONSE_STREAMING] === undefined, ); expect(nonStreamingToolsSpan).toBeDefined(); expect(nonStreamingToolsSpan!.name).toBe('generate_content gemini-2.0-flash-001'); expect(nonStreamingToolsSpan!.status).toBe('ok'); - expect(nonStreamingToolsSpan!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE].value).toBe('generate_content'); - expect(nonStreamingToolsSpan!.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]).toBeDefined(); - expect(nonStreamingToolsSpan!.attributes[GEN_AI_RESPONSE_TEXT_ATTRIBUTE]).toBeDefined(); - expect(nonStreamingToolsSpan!.attributes[GEN_AI_RESPONSE_TOOL_CALLS_ATTRIBUTE]).toBeDefined(); - expect(nonStreamingToolsSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE].value).toBe(15); - expect(nonStreamingToolsSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE].value).toBe(8); - expect(nonStreamingToolsSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE].value).toBe(23); + expect(nonStreamingToolsSpan!.attributes[GEN_AI_OPERATION_NAME].value).toBe('generate_content'); + expect(nonStreamingToolsSpan!.attributes[GEN_AI_INPUT_MESSAGES]).toBeDefined(); + expect(nonStreamingToolsSpan!.attributes[GEN_AI_RESPONSE_TEXT]).toBeDefined(); + expect(nonStreamingToolsSpan!.attributes[GEN_AI_RESPONSE_TOOL_CALLS]).toBeDefined(); + expect(nonStreamingToolsSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS].value).toBe(15); + expect(nonStreamingToolsSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS].value).toBe(8); + expect(nonStreamingToolsSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS].value).toBe(23); const streamingToolsSpan = container.items.find( span => - span.attributes[GEN_AI_REQUEST_AVAILABLE_TOOLS_ATTRIBUTE]?.value === EXPECTED_AVAILABLE_TOOLS_JSON && - span.attributes[GEN_AI_RESPONSE_STREAMING_ATTRIBUTE]?.value === true, + span.attributes[GEN_AI_REQUEST_AVAILABLE_TOOLS]?.value === EXPECTED_AVAILABLE_TOOLS_JSON && + span.attributes[GEN_AI_RESPONSE_STREAMING]?.value === true, ); expect(streamingToolsSpan).toBeDefined(); expect(streamingToolsSpan!.name).toBe('generate_content gemini-2.0-flash-001'); expect(streamingToolsSpan!.status).toBe('ok'); - expect(streamingToolsSpan!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE].value).toBe('generate_content'); - expect(streamingToolsSpan!.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]).toBeDefined(); - expect(streamingToolsSpan!.attributes[GEN_AI_RESPONSE_TEXT_ATTRIBUTE]).toBeDefined(); - expect(streamingToolsSpan!.attributes[GEN_AI_RESPONSE_TOOL_CALLS_ATTRIBUTE]).toBeDefined(); - expect(streamingToolsSpan!.attributes[GEN_AI_RESPONSE_ID_ATTRIBUTE].value).toBe('mock-response-tools-id'); - expect(streamingToolsSpan!.attributes[GEN_AI_RESPONSE_MODEL_ATTRIBUTE].value).toBe('gemini-2.0-flash-001'); - expect(streamingToolsSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE].value).toBe(12); - expect(streamingToolsSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE].value).toBe(10); - expect(streamingToolsSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE].value).toBe(22); + expect(streamingToolsSpan!.attributes[GEN_AI_OPERATION_NAME].value).toBe('generate_content'); + expect(streamingToolsSpan!.attributes[GEN_AI_INPUT_MESSAGES]).toBeDefined(); + expect(streamingToolsSpan!.attributes[GEN_AI_RESPONSE_TEXT]).toBeDefined(); + expect(streamingToolsSpan!.attributes[GEN_AI_RESPONSE_TOOL_CALLS]).toBeDefined(); + expect(streamingToolsSpan!.attributes[GEN_AI_RESPONSE_ID].value).toBe('mock-response-tools-id'); + expect(streamingToolsSpan!.attributes[GEN_AI_RESPONSE_MODEL].value).toBe('gemini-2.0-flash-001'); + expect(streamingToolsSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS].value).toBe(12); + expect(streamingToolsSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS].value).toBe(10); + expect(streamingToolsSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS].value).toBe(22); const noToolsSpan = container.items.find( - span => span.attributes[GEN_AI_REQUEST_AVAILABLE_TOOLS_ATTRIBUTE] === undefined, + span => span.attributes[GEN_AI_REQUEST_AVAILABLE_TOOLS] === undefined, ); expect(noToolsSpan).toBeDefined(); expect(noToolsSpan!.name).toBe('generate_content gemini-2.0-flash-001'); expect(noToolsSpan!.status).toBe('ok'); - expect(noToolsSpan!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE].value).toBe('generate_content'); - expect(noToolsSpan!.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]).toBeDefined(); - expect(noToolsSpan!.attributes[GEN_AI_RESPONSE_TEXT_ATTRIBUTE]).toBeDefined(); - expect(noToolsSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE].value).toBe(8); - expect(noToolsSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE].value).toBe(12); - expect(noToolsSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE].value).toBe(20); + expect(noToolsSpan!.attributes[GEN_AI_OPERATION_NAME].value).toBe('generate_content'); + expect(noToolsSpan!.attributes[GEN_AI_INPUT_MESSAGES]).toBeDefined(); + expect(noToolsSpan!.attributes[GEN_AI_RESPONSE_TEXT]).toBeDefined(); + expect(noToolsSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS].value).toBe(8); + expect(noToolsSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS].value).toBe(12); + expect(noToolsSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS].value).toBe(20); }, }) .start() @@ -252,39 +252,37 @@ describe('Google GenAI integration', () => { const generateContentSpan = container.items.find(span => span.name === 'generate_content gemini-1.5-flash'); expect(generateContentSpan).toBeDefined(); expect(generateContentSpan!.status).toBe('ok'); - expect(generateContentSpan!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE].value).toBe('generate_content'); - expect(generateContentSpan!.attributes[GEN_AI_RESPONSE_STREAMING_ATTRIBUTE].value).toBe(true); - expect(generateContentSpan!.attributes[GEN_AI_REQUEST_TEMPERATURE_ATTRIBUTE].value).toBe(0.7); - expect(generateContentSpan!.attributes[GEN_AI_REQUEST_TOP_P_ATTRIBUTE].value).toBe(0.9); - expect(generateContentSpan!.attributes[GEN_AI_REQUEST_MAX_TOKENS_ATTRIBUTE].value).toBe(100); - expect(generateContentSpan!.attributes[GEN_AI_RESPONSE_ID_ATTRIBUTE].value).toBe( - 'mock-response-streaming-id', - ); - expect(generateContentSpan!.attributes[GEN_AI_RESPONSE_MODEL_ATTRIBUTE].value).toBe('gemini-1.5-pro'); - expect(generateContentSpan!.attributes[GEN_AI_RESPONSE_FINISH_REASONS_ATTRIBUTE].value).toBe('["STOP"]'); - expect(generateContentSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE].value).toBe(10); - expect(generateContentSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE].value).toBe(12); - expect(generateContentSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE].value).toBe(22); + expect(generateContentSpan!.attributes[GEN_AI_OPERATION_NAME].value).toBe('generate_content'); + expect(generateContentSpan!.attributes[GEN_AI_RESPONSE_STREAMING].value).toBe(true); + expect(generateContentSpan!.attributes[GEN_AI_REQUEST_TEMPERATURE].value).toBe(0.7); + expect(generateContentSpan!.attributes[GEN_AI_REQUEST_TOP_P].value).toBe(0.9); + expect(generateContentSpan!.attributes[GEN_AI_REQUEST_MAX_TOKENS].value).toBe(100); + expect(generateContentSpan!.attributes[GEN_AI_RESPONSE_ID].value).toBe('mock-response-streaming-id'); + expect(generateContentSpan!.attributes[GEN_AI_RESPONSE_MODEL].value).toBe('gemini-1.5-pro'); + expect(generateContentSpan!.attributes[GEN_AI_RESPONSE_FINISH_REASONS].value).toBe('["STOP"]'); + expect(generateContentSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS].value).toBe(10); + expect(generateContentSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS].value).toBe(12); + expect(generateContentSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS].value).toBe(22); const chatSpan = container.items.find(span => span.name === 'chat gemini-1.5-pro'); expect(chatSpan).toBeDefined(); expect(chatSpan!.status).toBe('ok'); - expect(chatSpan!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE].value).toBe('chat'); - expect(chatSpan!.attributes[GEN_AI_RESPONSE_STREAMING_ATTRIBUTE].value).toBe(true); - expect(chatSpan!.attributes[GEN_AI_RESPONSE_ID_ATTRIBUTE].value).toBe('mock-response-streaming-id'); - expect(chatSpan!.attributes[GEN_AI_RESPONSE_MODEL_ATTRIBUTE].value).toBe('gemini-1.5-pro'); + expect(chatSpan!.attributes[GEN_AI_OPERATION_NAME].value).toBe('chat'); + expect(chatSpan!.attributes[GEN_AI_RESPONSE_STREAMING].value).toBe(true); + expect(chatSpan!.attributes[GEN_AI_RESPONSE_ID].value).toBe('mock-response-streaming-id'); + expect(chatSpan!.attributes[GEN_AI_RESPONSE_MODEL].value).toBe('gemini-1.5-pro'); const blockedSpan = container.items.find(span => span.name === 'generate_content blocked-model'); expect(blockedSpan).toBeDefined(); expect(blockedSpan!.status).toBe('error'); expect(blockedSpan!.attributes['sentry.op'].value).toBe('gen_ai.generate_content'); - expect(blockedSpan!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE].value).toBe('generate_content'); + expect(blockedSpan!.attributes[GEN_AI_OPERATION_NAME].value).toBe('generate_content'); const errorSpan = container.items.find(span => span.name === 'generate_content error-model'); expect(errorSpan).toBeDefined(); expect(errorSpan!.status).toBe('error'); expect(errorSpan!.attributes['sentry.op'].value).toBe('gen_ai.generate_content'); - expect(errorSpan!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE].value).toBe('generate_content'); + expect(errorSpan!.attributes[GEN_AI_OPERATION_NAME].value).toBe('generate_content'); }, }) .start() @@ -310,36 +308,36 @@ describe('Google GenAI integration', () => { const generateContentSpan = container.items.find(span => span.name === 'generate_content gemini-1.5-flash'); expect(generateContentSpan).toBeDefined(); expect(generateContentSpan!.status).toBe('ok'); - expect(generateContentSpan!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE].value).toBe('generate_content'); - expect(generateContentSpan!.attributes[GEN_AI_RESPONSE_STREAMING_ATTRIBUTE].value).toBe(true); - expect(generateContentSpan!.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]).toBeDefined(); - expect(generateContentSpan!.attributes[GEN_AI_REQUEST_TEMPERATURE_ATTRIBUTE].value).toBe(0.7); - expect(generateContentSpan!.attributes[GEN_AI_REQUEST_TOP_P_ATTRIBUTE].value).toBe(0.9); - expect(generateContentSpan!.attributes[GEN_AI_REQUEST_MAX_TOKENS_ATTRIBUTE].value).toBe(100); - expect(generateContentSpan!.attributes[GEN_AI_RESPONSE_FINISH_REASONS_ATTRIBUTE].value).toBe('["STOP"]'); + expect(generateContentSpan!.attributes[GEN_AI_OPERATION_NAME].value).toBe('generate_content'); + expect(generateContentSpan!.attributes[GEN_AI_RESPONSE_STREAMING].value).toBe(true); + expect(generateContentSpan!.attributes[GEN_AI_INPUT_MESSAGES]).toBeDefined(); + expect(generateContentSpan!.attributes[GEN_AI_REQUEST_TEMPERATURE].value).toBe(0.7); + expect(generateContentSpan!.attributes[GEN_AI_REQUEST_TOP_P].value).toBe(0.9); + expect(generateContentSpan!.attributes[GEN_AI_REQUEST_MAX_TOKENS].value).toBe(100); + expect(generateContentSpan!.attributes[GEN_AI_RESPONSE_FINISH_REASONS].value).toBe('["STOP"]'); const chatSpan = container.items.find(span => span.name === 'chat gemini-1.5-pro'); expect(chatSpan).toBeDefined(); expect(chatSpan!.status).toBe('ok'); - expect(chatSpan!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE].value).toBe('chat'); - expect(chatSpan!.attributes[GEN_AI_RESPONSE_STREAMING_ATTRIBUTE].value).toBe(true); - expect(chatSpan!.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]).toBeDefined(); - expect(chatSpan!.attributes[GEN_AI_RESPONSE_FINISH_REASONS_ATTRIBUTE].value).toBe('["STOP"]'); + expect(chatSpan!.attributes[GEN_AI_OPERATION_NAME].value).toBe('chat'); + expect(chatSpan!.attributes[GEN_AI_RESPONSE_STREAMING].value).toBe(true); + expect(chatSpan!.attributes[GEN_AI_INPUT_MESSAGES]).toBeDefined(); + expect(chatSpan!.attributes[GEN_AI_RESPONSE_FINISH_REASONS].value).toBe('["STOP"]'); const blockedSpan = container.items.find(span => span.name === 'generate_content blocked-model'); expect(blockedSpan).toBeDefined(); expect(blockedSpan!.status).toBe('error'); - expect(blockedSpan!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE].value).toBe('generate_content'); - expect(blockedSpan!.attributes[GEN_AI_RESPONSE_STREAMING_ATTRIBUTE].value).toBe(true); - expect(blockedSpan!.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]).toBeDefined(); - expect(blockedSpan!.attributes[GEN_AI_REQUEST_TEMPERATURE_ATTRIBUTE].value).toBe(0.7); + expect(blockedSpan!.attributes[GEN_AI_OPERATION_NAME].value).toBe('generate_content'); + expect(blockedSpan!.attributes[GEN_AI_RESPONSE_STREAMING].value).toBe(true); + expect(blockedSpan!.attributes[GEN_AI_INPUT_MESSAGES]).toBeDefined(); + expect(blockedSpan!.attributes[GEN_AI_REQUEST_TEMPERATURE].value).toBe(0.7); const errorSpan = container.items.find(span => span.name === 'generate_content error-model'); expect(errorSpan).toBeDefined(); expect(errorSpan!.status).toBe('error'); - expect(errorSpan!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE].value).toBe('generate_content'); - expect(errorSpan!.attributes[GEN_AI_REQUEST_TEMPERATURE_ATTRIBUTE].value).toBe(0.7); - expect(errorSpan!.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]).toBeDefined(); + expect(errorSpan!.attributes[GEN_AI_OPERATION_NAME].value).toBe('generate_content'); + expect(errorSpan!.attributes[GEN_AI_REQUEST_TEMPERATURE].value).toBe(0.7); + expect(errorSpan!.attributes[GEN_AI_INPUT_MESSAGES]).toBeDefined(); }, }) .start() @@ -360,19 +358,19 @@ describe('Google GenAI integration', () => { span: container => { expect(container.items).toHaveLength(2); const truncatedSpan = container.items.find(span => - getStringAttributeValue(span.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value)?.match( + getStringAttributeValue(span.attributes[GEN_AI_INPUT_MESSAGES]?.value)?.match( /^\[\{"role":"user","parts":\[\{"text":"C+"\}\]\}\]$/, ), ); expect(truncatedSpan).toBeDefined(); expect(truncatedSpan!.name).toBe('generate_content gemini-1.5-flash'); expect(truncatedSpan!.status).toBe('ok'); - expect(truncatedSpan!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE].value).toBe('generate_content'); + expect(truncatedSpan!.attributes[GEN_AI_OPERATION_NAME].value).toBe('generate_content'); expect(truncatedSpan!.attributes[GEN_AI_INPUT_MESSAGES_ORIGINAL_LENGTH_ATTRIBUTE].value).toBe(3); const smallMessageSpan = container.items.find( span => - span.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value === + span.attributes[GEN_AI_INPUT_MESSAGES]?.value === JSON.stringify([ { role: 'user', @@ -383,7 +381,7 @@ describe('Google GenAI integration', () => { expect(smallMessageSpan).toBeDefined(); expect(smallMessageSpan!.name).toBe('generate_content gemini-1.5-flash'); expect(smallMessageSpan!.status).toBe('ok'); - expect(smallMessageSpan!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE].value).toBe('generate_content'); + expect(smallMessageSpan!.attributes[GEN_AI_OPERATION_NAME].value).toBe('generate_content'); expect(smallMessageSpan!.attributes[GEN_AI_INPUT_MESSAGES_ORIGINAL_LENGTH_ATTRIBUTE].value).toBe(3); }, }) @@ -409,8 +407,8 @@ describe('Google GenAI integration', () => { // [0] generate_content with system instructions extracted expect(firstSpan!.name).toBe('generate_content gemini-1.5-flash'); - expect(firstSpan!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE].value).toBe('generate_content'); - expect(firstSpan!.attributes[GEN_AI_SYSTEM_INSTRUCTIONS_ATTRIBUTE].value).toBe( + expect(firstSpan!.attributes[GEN_AI_OPERATION_NAME].value).toBe('generate_content'); + expect(firstSpan!.attributes[GEN_AI_SYSTEM_INSTRUCTIONS].value).toBe( JSON.stringify([{ type: 'text', content: 'You are a helpful assistant' }]), ); }, @@ -441,18 +439,18 @@ describe('Google GenAI integration', () => { expect(successfulSpans).toHaveLength(2); for (const span of successfulSpans) { expect(span.attributes['sentry.op'].value).toBe('gen_ai.embeddings'); - expect(span.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE].value).toBe('embeddings'); + expect(span.attributes[GEN_AI_OPERATION_NAME].value).toBe('embeddings'); expect(span.attributes['sentry.origin'].value).toBe(EXPECTED_ORIGIN); - expect(span.attributes[GEN_AI_SYSTEM_ATTRIBUTE].value).toBe('google_genai'); - expect(span.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE].value).toBe('text-embedding-004'); - expect(span.attributes[GEN_AI_EMBEDDINGS_INPUT_ATTRIBUTE]).toBeUndefined(); + expect(span.attributes[GEN_AI_SYSTEM].value).toBe('google_genai'); + expect(span.attributes[GEN_AI_REQUEST_MODEL].value).toBe('text-embedding-004'); + expect(span.attributes[GEN_AI_EMBEDDINGS_INPUT]).toBeUndefined(); } const errorSpan = container.items.find(span => span.name === 'embeddings error-model'); expect(errorSpan).toBeDefined(); expect(errorSpan!.status).toBe('error'); expect(errorSpan!.attributes['sentry.op'].value).toBe('gen_ai.embeddings'); - expect(errorSpan!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE].value).toBe('embeddings'); + expect(errorSpan!.attributes[GEN_AI_OPERATION_NAME].value).toBe('embeddings'); }, }) .start() @@ -475,31 +473,31 @@ describe('Google GenAI integration', () => { ]); const stringInputSpan = container.items.find( - span => span.attributes[GEN_AI_EMBEDDINGS_INPUT_ATTRIBUTE]?.value === 'What is the capital of France?', + span => span.attributes[GEN_AI_EMBEDDINGS_INPUT]?.value === 'What is the capital of France?', ); expect(stringInputSpan).toBeDefined(); expect(stringInputSpan!.name).toBe('embeddings text-embedding-004'); expect(stringInputSpan!.status).toBe('ok'); - expect(stringInputSpan!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE].value).toBe('embeddings'); - expect(stringInputSpan!.attributes[GEN_AI_SYSTEM_ATTRIBUTE].value).toBe('google_genai'); + expect(stringInputSpan!.attributes[GEN_AI_OPERATION_NAME].value).toBe('embeddings'); + expect(stringInputSpan!.attributes[GEN_AI_SYSTEM].value).toBe('google_genai'); const errorSpan = container.items.find( - span => span.attributes[GEN_AI_EMBEDDINGS_INPUT_ATTRIBUTE]?.value === 'This will fail', + span => span.attributes[GEN_AI_EMBEDDINGS_INPUT]?.value === 'This will fail', ); expect(errorSpan).toBeDefined(); expect(errorSpan!.name).toBe('embeddings error-model'); expect(errorSpan!.status).toBe('error'); - expect(errorSpan!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE].value).toBe('embeddings'); + expect(errorSpan!.attributes[GEN_AI_OPERATION_NAME].value).toBe('embeddings'); const arrayInputSpan = container.items.find( span => - span.attributes[GEN_AI_EMBEDDINGS_INPUT_ATTRIBUTE]?.value === + span.attributes[GEN_AI_EMBEDDINGS_INPUT]?.value === '[{"role":"user","parts":[{"text":"First input text"}]},{"role":"user","parts":[{"text":"Second input text"}]}]', ); expect(arrayInputSpan).toBeDefined(); expect(arrayInputSpan!.name).toBe('embeddings text-embedding-004'); expect(arrayInputSpan!.status).toBe('ok'); - expect(arrayInputSpan!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE].value).toBe('embeddings'); + expect(arrayInputSpan!.attributes[GEN_AI_OPERATION_NAME].value).toBe('embeddings'); }, }) .start() @@ -524,8 +522,8 @@ describe('Google GenAI integration', () => { const [firstSpan] = container.items; // [0] generate_content with full (non-truncated) input messages - expect(firstSpan!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE].value).toBe('generate_content'); - expect(firstSpan!.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE].value).toBe( + expect(firstSpan!.attributes[GEN_AI_OPERATION_NAME].value).toBe('generate_content'); + expect(firstSpan!.attributes[GEN_AI_INPUT_MESSAGES].value).toBe( JSON.stringify([ { role: 'user', parts: [{ text: longContent }] }, { role: 'model', parts: [{ text: 'Some reply' }] }, @@ -551,9 +549,7 @@ describe('Google GenAI integration', () => { const spans = container.items; const chatSpan = spans.find(s => - getStringAttributeValue(s.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value)?.includes( - streamingLongContent, - ), + getStringAttributeValue(s.attributes[GEN_AI_INPUT_MESSAGES]?.value)?.includes(streamingLongContent), ); expect(chatSpan).toBeDefined(); }, @@ -577,13 +573,13 @@ describe('Google GenAI integration', () => { // With explicit enableTruncation: true, content should be truncated despite streaming. // Find the chat span by matching the start of the truncated content (the 'A' repeated messages). const chatSpan = spans.find(s => - getStringAttributeValue(s.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value)?.startsWith( + getStringAttributeValue(s.attributes[GEN_AI_INPUT_MESSAGES]?.value)?.startsWith( '[{"role":"user","parts":[{"text":"AAAA', ), ); expect(chatSpan).toBeDefined(); expect( - (getStringAttributeValue(chatSpan!.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE].value) ?? '').length, + (getStringAttributeValue(chatSpan!.attributes[GEN_AI_INPUT_MESSAGES].value) ?? '').length, ).toBeLessThan(streamingLongContent.length); }, }) diff --git a/dev-packages/node-integration-tests/suites/tracing/langchain/test.ts b/dev-packages/node-integration-tests/suites/tracing/langchain/test.ts index ed342ce9d1a2..cbbbe95ae7d3 100644 --- a/dev-packages/node-integration-tests/suites/tracing/langchain/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/langchain/test.ts @@ -1,25 +1,27 @@ import { afterAll, describe, expect } from 'vitest'; import { - GEN_AI_EMBEDDINGS_INPUT_ATTRIBUTE, + GEN_AI_EMBEDDINGS_INPUT, + GEN_AI_INPUT_MESSAGES, + GEN_AI_OPERATION_NAME, + GEN_AI_REQUEST_MAX_TOKENS, + GEN_AI_REQUEST_MODEL, + GEN_AI_REQUEST_TEMPERATURE, + GEN_AI_REQUEST_TOP_P, + GEN_AI_RESPONSE_ID, + GEN_AI_RESPONSE_MODEL, + GEN_AI_RESPONSE_TEXT, + GEN_AI_RESPONSE_TOOL_CALLS, + GEN_AI_SYSTEM, + GEN_AI_SYSTEM_INSTRUCTIONS, + GEN_AI_USAGE_INPUT_TOKENS, + GEN_AI_USAGE_OUTPUT_TOKENS, + GEN_AI_USAGE_TOTAL_TOKENS, +} from '@sentry/conventions/attributes'; +import { GEN_AI_EMBEDDINGS_OPERATION_ATTRIBUTE, - GEN_AI_INPUT_MESSAGES_ATTRIBUTE, GEN_AI_INPUT_MESSAGES_ORIGINAL_LENGTH_ATTRIBUTE, - GEN_AI_OPERATION_NAME_ATTRIBUTE, GEN_AI_REQUEST_DIMENSIONS_ATTRIBUTE, - GEN_AI_REQUEST_MAX_TOKENS_ATTRIBUTE, - GEN_AI_REQUEST_MODEL_ATTRIBUTE, - GEN_AI_REQUEST_TEMPERATURE_ATTRIBUTE, - GEN_AI_REQUEST_TOP_P_ATTRIBUTE, - GEN_AI_RESPONSE_ID_ATTRIBUTE, - GEN_AI_RESPONSE_MODEL_ATTRIBUTE, GEN_AI_RESPONSE_STOP_REASON_ATTRIBUTE, - GEN_AI_RESPONSE_TEXT_ATTRIBUTE, - GEN_AI_RESPONSE_TOOL_CALLS_ATTRIBUTE, - GEN_AI_SYSTEM_ATTRIBUTE, - GEN_AI_SYSTEM_INSTRUCTIONS_ATTRIBUTE, - GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE, - GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE, - GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE, } from '../../../../../packages/core/src/tracing/ai/gen-ai-attributes'; import { getStringAttributeValue, isOrchestrionEnabled } from '../../../utils'; import { cleanupChildProcesses, createEsmAndCjsTests } from '../../../utils/runner'; @@ -49,16 +51,16 @@ describe('LangChain integration', () => { expect(sonnetSpan!.status).toBe('ok'); expect(sonnetSpan!.attributes['sentry.op'].value).toBe('gen_ai.chat'); expect(sonnetSpan!.attributes['sentry.origin'].value).toBe('auto.ai.langchain'); - expect(sonnetSpan!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE].value).toBe('chat'); - expect(sonnetSpan!.attributes[GEN_AI_SYSTEM_ATTRIBUTE].value).toBe('anthropic'); - expect(sonnetSpan!.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE].value).toBe('claude-3-5-sonnet-20241022'); - expect(sonnetSpan!.attributes[GEN_AI_REQUEST_TEMPERATURE_ATTRIBUTE].value).toBe(0.7); - expect(sonnetSpan!.attributes[GEN_AI_REQUEST_MAX_TOKENS_ATTRIBUTE].value).toBe(100); - expect(sonnetSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE].value).toBe(10); - expect(sonnetSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE].value).toBe(15); - expect(sonnetSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE].value).toBe(25); - expect(sonnetSpan!.attributes[GEN_AI_RESPONSE_ID_ATTRIBUTE]).toBeDefined(); - expect(sonnetSpan!.attributes[GEN_AI_RESPONSE_MODEL_ATTRIBUTE]).toBeDefined(); + expect(sonnetSpan!.attributes[GEN_AI_OPERATION_NAME].value).toBe('chat'); + expect(sonnetSpan!.attributes[GEN_AI_SYSTEM].value).toBe('anthropic'); + expect(sonnetSpan!.attributes[GEN_AI_REQUEST_MODEL].value).toBe('claude-3-5-sonnet-20241022'); + expect(sonnetSpan!.attributes[GEN_AI_REQUEST_TEMPERATURE].value).toBe(0.7); + expect(sonnetSpan!.attributes[GEN_AI_REQUEST_MAX_TOKENS].value).toBe(100); + expect(sonnetSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS].value).toBe(10); + expect(sonnetSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS].value).toBe(15); + expect(sonnetSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS].value).toBe(25); + expect(sonnetSpan!.attributes[GEN_AI_RESPONSE_ID]).toBeDefined(); + expect(sonnetSpan!.attributes[GEN_AI_RESPONSE_MODEL]).toBeDefined(); expect(sonnetSpan!.attributes[GEN_AI_RESPONSE_STOP_REASON_ATTRIBUTE]).toBeDefined(); const opusSpan = container.items.find(span => span.name === 'chat claude-3-opus-20240229'); @@ -66,22 +68,22 @@ describe('LangChain integration', () => { expect(opusSpan!.status).toBe('ok'); expect(opusSpan!.attributes['sentry.op'].value).toBe('gen_ai.chat'); expect(opusSpan!.attributes['sentry.origin'].value).toBe('auto.ai.langchain'); - expect(opusSpan!.attributes[GEN_AI_SYSTEM_ATTRIBUTE].value).toBe('anthropic'); - expect(opusSpan!.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE].value).toBe('claude-3-opus-20240229'); - expect(opusSpan!.attributes[GEN_AI_REQUEST_TEMPERATURE_ATTRIBUTE].value).toBe(0.9); - expect(opusSpan!.attributes[GEN_AI_REQUEST_TOP_P_ATTRIBUTE].value).toBe(0.95); - expect(opusSpan!.attributes[GEN_AI_REQUEST_MAX_TOKENS_ATTRIBUTE].value).toBe(200); - expect(opusSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE].value).toBe(10); - expect(opusSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE].value).toBe(15); - expect(opusSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE].value).toBe(25); + expect(opusSpan!.attributes[GEN_AI_SYSTEM].value).toBe('anthropic'); + expect(opusSpan!.attributes[GEN_AI_REQUEST_MODEL].value).toBe('claude-3-opus-20240229'); + expect(opusSpan!.attributes[GEN_AI_REQUEST_TEMPERATURE].value).toBe(0.9); + expect(opusSpan!.attributes[GEN_AI_REQUEST_TOP_P].value).toBe(0.95); + expect(opusSpan!.attributes[GEN_AI_REQUEST_MAX_TOKENS].value).toBe(200); + expect(opusSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS].value).toBe(10); + expect(opusSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS].value).toBe(15); + expect(opusSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS].value).toBe(25); const errorSpan = container.items.find(span => span.name === 'chat error-model'); expect(errorSpan).toBeDefined(); expect(errorSpan!.status).toBe('error'); expect(errorSpan!.attributes['sentry.op'].value).toBe('gen_ai.chat'); expect(errorSpan!.attributes['sentry.origin'].value).toBe('auto.ai.langchain'); - expect(errorSpan!.attributes[GEN_AI_SYSTEM_ATTRIBUTE].value).toBe('anthropic'); - expect(errorSpan!.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE].value).toBe('error-model'); + expect(errorSpan!.attributes[GEN_AI_SYSTEM].value).toBe('anthropic'); + expect(errorSpan!.attributes[GEN_AI_REQUEST_MODEL].value).toBe('error-model'); }, }) .start() @@ -127,39 +129,39 @@ describe('LangChain integration', () => { expect(sonnetSpan!.status).toBe('ok'); expect(sonnetSpan!.attributes['sentry.op'].value).toBe('gen_ai.chat'); expect(sonnetSpan!.attributes['sentry.origin'].value).toBe('auto.ai.langchain'); - expect(sonnetSpan!.attributes[GEN_AI_SYSTEM_ATTRIBUTE].value).toBe('anthropic'); - expect(sonnetSpan!.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE].value).toBe('claude-3-5-sonnet-20241022'); - expect(sonnetSpan!.attributes[GEN_AI_REQUEST_TEMPERATURE_ATTRIBUTE].value).toBe(0.7); - expect(sonnetSpan!.attributes[GEN_AI_REQUEST_MAX_TOKENS_ATTRIBUTE].value).toBe(100); - expect(sonnetSpan!.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]).toBeDefined(); - expect(sonnetSpan!.attributes[GEN_AI_RESPONSE_TEXT_ATTRIBUTE]).toBeDefined(); - expect(sonnetSpan!.attributes[GEN_AI_RESPONSE_ID_ATTRIBUTE]).toBeDefined(); - expect(sonnetSpan!.attributes[GEN_AI_RESPONSE_MODEL_ATTRIBUTE]).toBeDefined(); + expect(sonnetSpan!.attributes[GEN_AI_SYSTEM].value).toBe('anthropic'); + expect(sonnetSpan!.attributes[GEN_AI_REQUEST_MODEL].value).toBe('claude-3-5-sonnet-20241022'); + expect(sonnetSpan!.attributes[GEN_AI_REQUEST_TEMPERATURE].value).toBe(0.7); + expect(sonnetSpan!.attributes[GEN_AI_REQUEST_MAX_TOKENS].value).toBe(100); + expect(sonnetSpan!.attributes[GEN_AI_INPUT_MESSAGES]).toBeDefined(); + expect(sonnetSpan!.attributes[GEN_AI_RESPONSE_TEXT]).toBeDefined(); + expect(sonnetSpan!.attributes[GEN_AI_RESPONSE_ID]).toBeDefined(); + expect(sonnetSpan!.attributes[GEN_AI_RESPONSE_MODEL]).toBeDefined(); expect(sonnetSpan!.attributes[GEN_AI_RESPONSE_STOP_REASON_ATTRIBUTE]).toBeDefined(); - expect(sonnetSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE].value).toBe(10); - expect(sonnetSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE].value).toBe(15); - expect(sonnetSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE].value).toBe(25); + expect(sonnetSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS].value).toBe(10); + expect(sonnetSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS].value).toBe(15); + expect(sonnetSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS].value).toBe(25); const opusSpan = container.items.find(span => span.name === 'chat claude-3-opus-20240229'); expect(opusSpan).toBeDefined(); expect(opusSpan!.status).toBe('ok'); - expect(opusSpan!.attributes[GEN_AI_SYSTEM_ATTRIBUTE].value).toBe('anthropic'); - expect(opusSpan!.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE].value).toBe('claude-3-opus-20240229'); - expect(opusSpan!.attributes[GEN_AI_REQUEST_TEMPERATURE_ATTRIBUTE].value).toBe(0.9); - expect(opusSpan!.attributes[GEN_AI_REQUEST_TOP_P_ATTRIBUTE].value).toBe(0.95); - expect(opusSpan!.attributes[GEN_AI_REQUEST_MAX_TOKENS_ATTRIBUTE].value).toBe(200); - expect(opusSpan!.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]).toBeDefined(); - expect(opusSpan!.attributes[GEN_AI_RESPONSE_TEXT_ATTRIBUTE]).toBeDefined(); - expect(opusSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE].value).toBe(10); - expect(opusSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE].value).toBe(15); - expect(opusSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE].value).toBe(25); + expect(opusSpan!.attributes[GEN_AI_SYSTEM].value).toBe('anthropic'); + expect(opusSpan!.attributes[GEN_AI_REQUEST_MODEL].value).toBe('claude-3-opus-20240229'); + expect(opusSpan!.attributes[GEN_AI_REQUEST_TEMPERATURE].value).toBe(0.9); + expect(opusSpan!.attributes[GEN_AI_REQUEST_TOP_P].value).toBe(0.95); + expect(opusSpan!.attributes[GEN_AI_REQUEST_MAX_TOKENS].value).toBe(200); + expect(opusSpan!.attributes[GEN_AI_INPUT_MESSAGES]).toBeDefined(); + expect(opusSpan!.attributes[GEN_AI_RESPONSE_TEXT]).toBeDefined(); + expect(opusSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS].value).toBe(10); + expect(opusSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS].value).toBe(15); + expect(opusSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS].value).toBe(25); const errorSpan = container.items.find(span => span.name === 'chat error-model'); expect(errorSpan).toBeDefined(); expect(errorSpan!.status).toBe('error'); - expect(errorSpan!.attributes[GEN_AI_SYSTEM_ATTRIBUTE].value).toBe('anthropic'); - expect(errorSpan!.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE].value).toBe('error-model'); - expect(errorSpan!.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]).toBeDefined(); + expect(errorSpan!.attributes[GEN_AI_SYSTEM].value).toBe('anthropic'); + expect(errorSpan!.attributes[GEN_AI_REQUEST_MODEL].value).toBe('error-model'); + expect(errorSpan!.attributes[GEN_AI_INPUT_MESSAGES]).toBeDefined(); }, }) .start() @@ -182,15 +184,15 @@ describe('LangChain integration', () => { expect(firstSpan!.status).toBe('ok'); expect(firstSpan!.attributes['sentry.op'].value).toBe('gen_ai.chat'); expect(firstSpan!.attributes['sentry.origin'].value).toBe('auto.ai.langchain'); - expect(firstSpan!.attributes[GEN_AI_SYSTEM_ATTRIBUTE].value).toBe('anthropic'); - expect(firstSpan!.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE].value).toBe('claude-3-5-sonnet-20241022'); - expect(firstSpan!.attributes[GEN_AI_REQUEST_TEMPERATURE_ATTRIBUTE].value).toBe(0.7); - expect(firstSpan!.attributes[GEN_AI_REQUEST_MAX_TOKENS_ATTRIBUTE].value).toBe(150); - expect(firstSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE].value).toBe(20); - expect(firstSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE].value).toBe(30); - expect(firstSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE].value).toBe(50); + expect(firstSpan!.attributes[GEN_AI_SYSTEM].value).toBe('anthropic'); + expect(firstSpan!.attributes[GEN_AI_REQUEST_MODEL].value).toBe('claude-3-5-sonnet-20241022'); + expect(firstSpan!.attributes[GEN_AI_REQUEST_TEMPERATURE].value).toBe(0.7); + expect(firstSpan!.attributes[GEN_AI_REQUEST_MAX_TOKENS].value).toBe(150); + expect(firstSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS].value).toBe(20); + expect(firstSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS].value).toBe(30); + expect(firstSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS].value).toBe(50); expect(firstSpan!.attributes[GEN_AI_RESPONSE_STOP_REASON_ATTRIBUTE].value).toBe('tool_use'); - expect(firstSpan!.attributes[GEN_AI_RESPONSE_TOOL_CALLS_ATTRIBUTE]).toBeDefined(); + expect(firstSpan!.attributes[GEN_AI_RESPONSE_TOOL_CALLS]).toBeDefined(); }, }) .start() @@ -215,30 +217,30 @@ describe('LangChain integration', () => { ); expect(stringInputSpan).toBeDefined(); expect(stringInputSpan!.name).toBe('chat claude-3-5-sonnet-20241022'); - expect(stringInputSpan!.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE].value).toMatch( + expect(stringInputSpan!.attributes[GEN_AI_INPUT_MESSAGES].value).toMatch( /^\[\{"role":"user","content":"C+"\}\]$/, ); const arrayInputSpan = container.items.find( span => span.attributes[GEN_AI_INPUT_MESSAGES_ORIGINAL_LENGTH_ATTRIBUTE]?.value === 2 && - getStringAttributeValue(span.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value)?.match( + getStringAttributeValue(span.attributes[GEN_AI_INPUT_MESSAGES]?.value)?.match( /^\[\{"role":"user","content":"C+"\}\]$/, ), ); expect(arrayInputSpan).toBeDefined(); expect(arrayInputSpan!.name).toBe('chat claude-3-5-sonnet-20241022'); - expect(arrayInputSpan!.attributes[GEN_AI_SYSTEM_INSTRUCTIONS_ATTRIBUTE]).toBeDefined(); + expect(arrayInputSpan!.attributes[GEN_AI_SYSTEM_INSTRUCTIONS]).toBeDefined(); const smallMessageSpan = container.items.find( span => - span.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value === + span.attributes[GEN_AI_INPUT_MESSAGES]?.value === JSON.stringify([{ role: 'user', content: 'This is a small message that fits within the limit' }]), ); expect(smallMessageSpan).toBeDefined(); expect(smallMessageSpan!.name).toBe('chat claude-3-5-sonnet-20241022'); expect(smallMessageSpan!.attributes[GEN_AI_INPUT_MESSAGES_ORIGINAL_LENGTH_ATTRIBUTE].value).toBe(2); - expect(smallMessageSpan!.attributes[GEN_AI_SYSTEM_INSTRUCTIONS_ATTRIBUTE]).toBeDefined(); + expect(smallMessageSpan!.attributes[GEN_AI_SYSTEM_INSTRUCTIONS]).toBeDefined(); }, }) .start() @@ -297,7 +299,7 @@ describe('LangChain integration', () => { // [0] chat with extracted system instructions expect(firstSpan!.name).toBe('chat claude-3-5-sonnet-20241022'); expect(firstSpan!.attributes['sentry.op'].value).toBe('gen_ai.chat'); - expect(firstSpan!.attributes[GEN_AI_SYSTEM_INSTRUCTIONS_ATTRIBUTE].value).toBe( + expect(firstSpan!.attributes[GEN_AI_SYSTEM_INSTRUCTIONS].value).toBe( JSON.stringify([{ type: 'text', content: 'You are a helpful assistant' }]), ); }, @@ -375,9 +377,9 @@ describe('LangChain integration', () => { for (const span of successfulSpans) { expect(span.attributes['sentry.op'].value).toBe(GEN_AI_EMBEDDINGS_OPERATION_ATTRIBUTE); expect(span.attributes['sentry.origin'].value).toBe('auto.ai.langchain'); - expect(span.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE].value).toBe('embeddings'); - expect(span.attributes[GEN_AI_SYSTEM_ATTRIBUTE].value).toBe('openai'); - expect(span.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE].value).toBe('text-embedding-3-small'); + expect(span.attributes[GEN_AI_OPERATION_NAME].value).toBe('embeddings'); + expect(span.attributes[GEN_AI_SYSTEM].value).toBe('openai'); + expect(span.attributes[GEN_AI_REQUEST_MODEL].value).toBe('text-embedding-3-small'); expect(span.attributes[GEN_AI_REQUEST_DIMENSIONS_ATTRIBUTE].value).toBe(1536); } @@ -385,7 +387,7 @@ describe('LangChain integration', () => { expect(errorSpan).toBeDefined(); expect(errorSpan!.status).toBe('error'); expect(errorSpan!.attributes['sentry.op'].value).toBe(GEN_AI_EMBEDDINGS_OPERATION_ATTRIBUTE); - expect(errorSpan!.attributes[GEN_AI_SYSTEM_ATTRIBUTE].value).toBe('openai'); + expect(errorSpan!.attributes[GEN_AI_SYSTEM].value).toBe('openai'); }, }) .start() @@ -425,7 +427,7 @@ describe('LangChain integration', () => { ]); const querySpan = container.items.find( - span => span.attributes[GEN_AI_EMBEDDINGS_INPUT_ATTRIBUTE]?.value === 'Hello world', + span => span.attributes[GEN_AI_EMBEDDINGS_INPUT]?.value === 'Hello world', ); expect(querySpan).toBeDefined(); expect(querySpan!.name).toBe('embeddings text-embedding-3-small'); @@ -434,7 +436,7 @@ describe('LangChain integration', () => { const documentsSpan = container.items.find( span => - span.attributes[GEN_AI_EMBEDDINGS_INPUT_ATTRIBUTE]?.value === + span.attributes[GEN_AI_EMBEDDINGS_INPUT]?.value === JSON.stringify(['First document', 'Second document']), ); expect(documentsSpan).toBeDefined(); @@ -469,7 +471,7 @@ describe('LangChain integration', () => { // [0] chat with full (untruncated) input messages expect(firstSpan!.name).toBe('chat claude-3-5-sonnet-20241022'); - expect(firstSpan!.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE].value).toBe( + expect(firstSpan!.attributes[GEN_AI_INPUT_MESSAGES].value).toBe( JSON.stringify([ { role: 'user', content: longContent }, { role: 'assistant', content: 'Some reply' }, @@ -495,9 +497,7 @@ describe('LangChain integration', () => { const spans = container.items; const chatSpan = spans.find(s => - getStringAttributeValue(s.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value)?.includes( - streamingLongContent, - ), + getStringAttributeValue(s.attributes[GEN_AI_INPUT_MESSAGES]?.value)?.includes(streamingLongContent), ); expect(chatSpan).toBeDefined(); }, @@ -520,13 +520,13 @@ describe('LangChain integration', () => { // With explicit enableTruncation: true, content should be truncated despite streaming. const chatSpan = spans.find(s => - getStringAttributeValue(s.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value)?.startsWith( + getStringAttributeValue(s.attributes[GEN_AI_INPUT_MESSAGES]?.value)?.startsWith( '[{"role":"user","content":"AAAA', ), ); expect(chatSpan).toBeDefined(); expect( - (getStringAttributeValue(chatSpan!.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE].value) ?? '').length, + (getStringAttributeValue(chatSpan!.attributes[GEN_AI_INPUT_MESSAGES].value) ?? '').length, ).toBeLessThan(streamingLongContent.length); }, }) diff --git a/dev-packages/node-integration-tests/suites/tracing/langchain/v1/test.ts b/dev-packages/node-integration-tests/suites/tracing/langchain/v1/test.ts index b555e48229e4..579ef83bcbc3 100644 --- a/dev-packages/node-integration-tests/suites/tracing/langchain/v1/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/langchain/v1/test.ts @@ -1,22 +1,24 @@ import { afterAll, expect } from 'vitest'; import { - GEN_AI_INPUT_MESSAGES_ATTRIBUTE, + GEN_AI_INPUT_MESSAGES, + GEN_AI_OPERATION_NAME, + GEN_AI_REQUEST_MAX_TOKENS, + GEN_AI_REQUEST_MODEL, + GEN_AI_REQUEST_TEMPERATURE, + GEN_AI_REQUEST_TOP_P, + GEN_AI_RESPONSE_ID, + GEN_AI_RESPONSE_MODEL, + GEN_AI_RESPONSE_TEXT, + GEN_AI_RESPONSE_TOOL_CALLS, + GEN_AI_SYSTEM, + GEN_AI_SYSTEM_INSTRUCTIONS, + GEN_AI_USAGE_INPUT_TOKENS, + GEN_AI_USAGE_OUTPUT_TOKENS, + GEN_AI_USAGE_TOTAL_TOKENS, +} from '@sentry/conventions/attributes'; +import { GEN_AI_INPUT_MESSAGES_ORIGINAL_LENGTH_ATTRIBUTE, - GEN_AI_OPERATION_NAME_ATTRIBUTE, - GEN_AI_REQUEST_MAX_TOKENS_ATTRIBUTE, - GEN_AI_REQUEST_MODEL_ATTRIBUTE, - GEN_AI_REQUEST_TEMPERATURE_ATTRIBUTE, - GEN_AI_REQUEST_TOP_P_ATTRIBUTE, - GEN_AI_RESPONSE_ID_ATTRIBUTE, - GEN_AI_RESPONSE_MODEL_ATTRIBUTE, GEN_AI_RESPONSE_STOP_REASON_ATTRIBUTE, - GEN_AI_RESPONSE_TEXT_ATTRIBUTE, - GEN_AI_RESPONSE_TOOL_CALLS_ATTRIBUTE, - GEN_AI_SYSTEM_ATTRIBUTE, - GEN_AI_SYSTEM_INSTRUCTIONS_ATTRIBUTE, - GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE, - GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE, - GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE, } from '../../../../../../packages/core/src/tracing/ai/gen-ai-attributes'; import { conditionalTest, getStringAttributeValue, isOrchestrionEnabled } from '../../../../utils'; import { cleanupChildProcesses, createEsmAndCjsTests } from '../../../../utils/runner'; @@ -52,16 +54,16 @@ conditionalTest({ min: 20 })('LangChain integration (v1)', () => { expect(sonnetSpan!.status).toBe('ok'); expect(sonnetSpan!.attributes['sentry.op'].value).toBe('gen_ai.chat'); expect(sonnetSpan!.attributes['sentry.origin'].value).toBe('auto.ai.langchain'); - expect(sonnetSpan!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE].value).toBe('chat'); - expect(sonnetSpan!.attributes[GEN_AI_SYSTEM_ATTRIBUTE].value).toBe('anthropic'); - expect(sonnetSpan!.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE].value).toBe('claude-3-5-sonnet-20241022'); - expect(sonnetSpan!.attributes[GEN_AI_REQUEST_TEMPERATURE_ATTRIBUTE].value).toBe(0.7); - expect(sonnetSpan!.attributes[GEN_AI_REQUEST_MAX_TOKENS_ATTRIBUTE].value).toBe(100); - expect(sonnetSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE].value).toBe(10); - expect(sonnetSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE].value).toBe(15); - expect(sonnetSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE].value).toBe(25); - expect(sonnetSpan!.attributes[GEN_AI_RESPONSE_ID_ATTRIBUTE]).toBeDefined(); - expect(sonnetSpan!.attributes[GEN_AI_RESPONSE_MODEL_ATTRIBUTE]).toBeDefined(); + expect(sonnetSpan!.attributes[GEN_AI_OPERATION_NAME].value).toBe('chat'); + expect(sonnetSpan!.attributes[GEN_AI_SYSTEM].value).toBe('anthropic'); + expect(sonnetSpan!.attributes[GEN_AI_REQUEST_MODEL].value).toBe('claude-3-5-sonnet-20241022'); + expect(sonnetSpan!.attributes[GEN_AI_REQUEST_TEMPERATURE].value).toBe(0.7); + expect(sonnetSpan!.attributes[GEN_AI_REQUEST_MAX_TOKENS].value).toBe(100); + expect(sonnetSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS].value).toBe(10); + expect(sonnetSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS].value).toBe(15); + expect(sonnetSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS].value).toBe(25); + expect(sonnetSpan!.attributes[GEN_AI_RESPONSE_ID]).toBeDefined(); + expect(sonnetSpan!.attributes[GEN_AI_RESPONSE_MODEL]).toBeDefined(); expect(sonnetSpan!.attributes[GEN_AI_RESPONSE_STOP_REASON_ATTRIBUTE]).toBeDefined(); const opusSpan = container.items.find(span => span.name === 'chat claude-3-opus-20240229'); @@ -69,22 +71,22 @@ conditionalTest({ min: 20 })('LangChain integration (v1)', () => { expect(opusSpan!.status).toBe('ok'); expect(opusSpan!.attributes['sentry.op'].value).toBe('gen_ai.chat'); expect(opusSpan!.attributes['sentry.origin'].value).toBe('auto.ai.langchain'); - expect(opusSpan!.attributes[GEN_AI_SYSTEM_ATTRIBUTE].value).toBe('anthropic'); - expect(opusSpan!.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE].value).toBe('claude-3-opus-20240229'); - expect(opusSpan!.attributes[GEN_AI_REQUEST_TEMPERATURE_ATTRIBUTE].value).toBe(0.9); - expect(opusSpan!.attributes[GEN_AI_REQUEST_TOP_P_ATTRIBUTE].value).toBe(0.95); - expect(opusSpan!.attributes[GEN_AI_REQUEST_MAX_TOKENS_ATTRIBUTE].value).toBe(200); - expect(opusSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE].value).toBe(10); - expect(opusSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE].value).toBe(15); - expect(opusSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE].value).toBe(25); + expect(opusSpan!.attributes[GEN_AI_SYSTEM].value).toBe('anthropic'); + expect(opusSpan!.attributes[GEN_AI_REQUEST_MODEL].value).toBe('claude-3-opus-20240229'); + expect(opusSpan!.attributes[GEN_AI_REQUEST_TEMPERATURE].value).toBe(0.9); + expect(opusSpan!.attributes[GEN_AI_REQUEST_TOP_P].value).toBe(0.95); + expect(opusSpan!.attributes[GEN_AI_REQUEST_MAX_TOKENS].value).toBe(200); + expect(opusSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS].value).toBe(10); + expect(opusSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS].value).toBe(15); + expect(opusSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS].value).toBe(25); const errorSpan = container.items.find(span => span.name === 'chat error-model'); expect(errorSpan).toBeDefined(); expect(errorSpan!.status).toBe('error'); expect(errorSpan!.attributes['sentry.op'].value).toBe('gen_ai.chat'); expect(errorSpan!.attributes['sentry.origin'].value).toBe('auto.ai.langchain'); - expect(errorSpan!.attributes[GEN_AI_SYSTEM_ATTRIBUTE].value).toBe('anthropic'); - expect(errorSpan!.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE].value).toBe('error-model'); + expect(errorSpan!.attributes[GEN_AI_SYSTEM].value).toBe('anthropic'); + expect(errorSpan!.attributes[GEN_AI_REQUEST_MODEL].value).toBe('error-model'); }, }) .start() @@ -123,39 +125,39 @@ conditionalTest({ min: 20 })('LangChain integration (v1)', () => { expect(sonnetSpan!.status).toBe('ok'); expect(sonnetSpan!.attributes['sentry.op'].value).toBe('gen_ai.chat'); expect(sonnetSpan!.attributes['sentry.origin'].value).toBe('auto.ai.langchain'); - expect(sonnetSpan!.attributes[GEN_AI_SYSTEM_ATTRIBUTE].value).toBe('anthropic'); - expect(sonnetSpan!.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE].value).toBe('claude-3-5-sonnet-20241022'); - expect(sonnetSpan!.attributes[GEN_AI_REQUEST_TEMPERATURE_ATTRIBUTE].value).toBe(0.7); - expect(sonnetSpan!.attributes[GEN_AI_REQUEST_MAX_TOKENS_ATTRIBUTE].value).toBe(100); - expect(sonnetSpan!.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]).toBeDefined(); - expect(sonnetSpan!.attributes[GEN_AI_RESPONSE_TEXT_ATTRIBUTE]).toBeDefined(); - expect(sonnetSpan!.attributes[GEN_AI_RESPONSE_ID_ATTRIBUTE]).toBeDefined(); - expect(sonnetSpan!.attributes[GEN_AI_RESPONSE_MODEL_ATTRIBUTE]).toBeDefined(); + expect(sonnetSpan!.attributes[GEN_AI_SYSTEM].value).toBe('anthropic'); + expect(sonnetSpan!.attributes[GEN_AI_REQUEST_MODEL].value).toBe('claude-3-5-sonnet-20241022'); + expect(sonnetSpan!.attributes[GEN_AI_REQUEST_TEMPERATURE].value).toBe(0.7); + expect(sonnetSpan!.attributes[GEN_AI_REQUEST_MAX_TOKENS].value).toBe(100); + expect(sonnetSpan!.attributes[GEN_AI_INPUT_MESSAGES]).toBeDefined(); + expect(sonnetSpan!.attributes[GEN_AI_RESPONSE_TEXT]).toBeDefined(); + expect(sonnetSpan!.attributes[GEN_AI_RESPONSE_ID]).toBeDefined(); + expect(sonnetSpan!.attributes[GEN_AI_RESPONSE_MODEL]).toBeDefined(); expect(sonnetSpan!.attributes[GEN_AI_RESPONSE_STOP_REASON_ATTRIBUTE]).toBeDefined(); - expect(sonnetSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE].value).toBe(10); - expect(sonnetSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE].value).toBe(15); - expect(sonnetSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE].value).toBe(25); + expect(sonnetSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS].value).toBe(10); + expect(sonnetSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS].value).toBe(15); + expect(sonnetSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS].value).toBe(25); const opusSpan = container.items.find(span => span.name === 'chat claude-3-opus-20240229'); expect(opusSpan).toBeDefined(); expect(opusSpan!.status).toBe('ok'); - expect(opusSpan!.attributes[GEN_AI_SYSTEM_ATTRIBUTE].value).toBe('anthropic'); - expect(opusSpan!.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE].value).toBe('claude-3-opus-20240229'); - expect(opusSpan!.attributes[GEN_AI_REQUEST_TEMPERATURE_ATTRIBUTE].value).toBe(0.9); - expect(opusSpan!.attributes[GEN_AI_REQUEST_TOP_P_ATTRIBUTE].value).toBe(0.95); - expect(opusSpan!.attributes[GEN_AI_REQUEST_MAX_TOKENS_ATTRIBUTE].value).toBe(200); - expect(opusSpan!.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]).toBeDefined(); - expect(opusSpan!.attributes[GEN_AI_RESPONSE_TEXT_ATTRIBUTE]).toBeDefined(); - expect(opusSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE].value).toBe(10); - expect(opusSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE].value).toBe(15); - expect(opusSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE].value).toBe(25); + expect(opusSpan!.attributes[GEN_AI_SYSTEM].value).toBe('anthropic'); + expect(opusSpan!.attributes[GEN_AI_REQUEST_MODEL].value).toBe('claude-3-opus-20240229'); + expect(opusSpan!.attributes[GEN_AI_REQUEST_TEMPERATURE].value).toBe(0.9); + expect(opusSpan!.attributes[GEN_AI_REQUEST_TOP_P].value).toBe(0.95); + expect(opusSpan!.attributes[GEN_AI_REQUEST_MAX_TOKENS].value).toBe(200); + expect(opusSpan!.attributes[GEN_AI_INPUT_MESSAGES]).toBeDefined(); + expect(opusSpan!.attributes[GEN_AI_RESPONSE_TEXT]).toBeDefined(); + expect(opusSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS].value).toBe(10); + expect(opusSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS].value).toBe(15); + expect(opusSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS].value).toBe(25); const errorSpan = container.items.find(span => span.name === 'chat error-model'); expect(errorSpan).toBeDefined(); expect(errorSpan!.status).toBe('error'); - expect(errorSpan!.attributes[GEN_AI_SYSTEM_ATTRIBUTE].value).toBe('anthropic'); - expect(errorSpan!.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE].value).toBe('error-model'); - expect(errorSpan!.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]).toBeDefined(); + expect(errorSpan!.attributes[GEN_AI_SYSTEM].value).toBe('anthropic'); + expect(errorSpan!.attributes[GEN_AI_REQUEST_MODEL].value).toBe('error-model'); + expect(errorSpan!.attributes[GEN_AI_INPUT_MESSAGES]).toBeDefined(); }, }) .start() @@ -190,15 +192,15 @@ conditionalTest({ min: 20 })('LangChain integration (v1)', () => { expect(firstSpan!.status).toBe('ok'); expect(firstSpan!.attributes['sentry.op'].value).toBe('gen_ai.chat'); expect(firstSpan!.attributes['sentry.origin'].value).toBe('auto.ai.langchain'); - expect(firstSpan!.attributes[GEN_AI_SYSTEM_ATTRIBUTE].value).toBe('anthropic'); - expect(firstSpan!.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE].value).toBe('claude-3-5-sonnet-20241022'); - expect(firstSpan!.attributes[GEN_AI_REQUEST_TEMPERATURE_ATTRIBUTE].value).toBe(0.7); - expect(firstSpan!.attributes[GEN_AI_REQUEST_MAX_TOKENS_ATTRIBUTE].value).toBe(150); - expect(firstSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE].value).toBe(20); - expect(firstSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE].value).toBe(30); - expect(firstSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE].value).toBe(50); + expect(firstSpan!.attributes[GEN_AI_SYSTEM].value).toBe('anthropic'); + expect(firstSpan!.attributes[GEN_AI_REQUEST_MODEL].value).toBe('claude-3-5-sonnet-20241022'); + expect(firstSpan!.attributes[GEN_AI_REQUEST_TEMPERATURE].value).toBe(0.7); + expect(firstSpan!.attributes[GEN_AI_REQUEST_MAX_TOKENS].value).toBe(150); + expect(firstSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS].value).toBe(20); + expect(firstSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS].value).toBe(30); + expect(firstSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS].value).toBe(50); expect(firstSpan!.attributes[GEN_AI_RESPONSE_STOP_REASON_ATTRIBUTE].value).toBe('tool_use'); - expect(firstSpan!.attributes[GEN_AI_RESPONSE_TOOL_CALLS_ATTRIBUTE]).toBeDefined(); + expect(firstSpan!.attributes[GEN_AI_RESPONSE_TOOL_CALLS]).toBeDefined(); }, }) .start() @@ -231,32 +233,32 @@ conditionalTest({ min: 20 })('LangChain integration (v1)', () => { ); expect(stringInputSpan).toBeDefined(); expect(stringInputSpan!.name).toBe('chat claude-3-5-sonnet-20241022'); - expect(stringInputSpan!.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE].value).toMatch( + expect(stringInputSpan!.attributes[GEN_AI_INPUT_MESSAGES].value).toMatch( /^\[\{"role":"user","content":"C+"\}\]$/, ); const arrayInputSpan = container.items.find( span => span.attributes[GEN_AI_INPUT_MESSAGES_ORIGINAL_LENGTH_ATTRIBUTE]?.value === 2 && - getStringAttributeValue(span.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value)?.match( + getStringAttributeValue(span.attributes[GEN_AI_INPUT_MESSAGES]?.value)?.match( /^\[\{"role":"user","content":"C+"\}\]$/, ), ); expect(arrayInputSpan).toBeDefined(); expect(arrayInputSpan!.name).toBe('chat claude-3-5-sonnet-20241022'); - expect(arrayInputSpan!.attributes[GEN_AI_SYSTEM_INSTRUCTIONS_ATTRIBUTE].value).toMatch( + expect(arrayInputSpan!.attributes[GEN_AI_SYSTEM_INSTRUCTIONS].value).toMatch( /^\[\{"type":"text","content":"A+"\}\]$/, ); const smallMessageSpan = container.items.find( span => - span.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value === + span.attributes[GEN_AI_INPUT_MESSAGES]?.value === JSON.stringify([{ role: 'user', content: 'This is a small message that fits within the limit' }]), ); expect(smallMessageSpan).toBeDefined(); expect(smallMessageSpan!.name).toBe('chat claude-3-5-sonnet-20241022'); expect(smallMessageSpan!.attributes[GEN_AI_INPUT_MESSAGES_ORIGINAL_LENGTH_ATTRIBUTE].value).toBe(2); - expect(smallMessageSpan!.attributes[GEN_AI_SYSTEM_INSTRUCTIONS_ATTRIBUTE].value).toMatch( + expect(smallMessageSpan!.attributes[GEN_AI_SYSTEM_INSTRUCTIONS].value).toMatch( /^\[\{"type":"text","content":"A+"\}\]$/, ); }, @@ -340,16 +342,16 @@ conditionalTest({ min: 20 })('LangChain integration (v1)', () => { expect(gpt4oSpan!.status).toBe('ok'); expect(gpt4oSpan!.attributes['sentry.op'].value).toBe('gen_ai.chat'); expect(gpt4oSpan!.attributes['sentry.origin'].value).toBe('auto.ai.langchain'); - expect(gpt4oSpan!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE].value).toBe('chat'); - expect(gpt4oSpan!.attributes[GEN_AI_SYSTEM_ATTRIBUTE].value).toBe('openai'); - expect(gpt4oSpan!.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE].value).toBe('gpt-4o'); - expect(gpt4oSpan!.attributes[GEN_AI_REQUEST_TEMPERATURE_ATTRIBUTE].value).toBe(0.7); - expect(gpt4oSpan!.attributes[GEN_AI_REQUEST_MAX_TOKENS_ATTRIBUTE].value).toBe(100); - expect(gpt4oSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE].value).toBe(8); - expect(gpt4oSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE].value).toBe(12); - expect(gpt4oSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE].value).toBe(20); - expect(gpt4oSpan!.attributes[GEN_AI_RESPONSE_ID_ATTRIBUTE]).toBeDefined(); - expect(gpt4oSpan!.attributes[GEN_AI_RESPONSE_MODEL_ATTRIBUTE].value).toBe('gpt-4o'); + expect(gpt4oSpan!.attributes[GEN_AI_OPERATION_NAME].value).toBe('chat'); + expect(gpt4oSpan!.attributes[GEN_AI_SYSTEM].value).toBe('openai'); + expect(gpt4oSpan!.attributes[GEN_AI_REQUEST_MODEL].value).toBe('gpt-4o'); + expect(gpt4oSpan!.attributes[GEN_AI_REQUEST_TEMPERATURE].value).toBe(0.7); + expect(gpt4oSpan!.attributes[GEN_AI_REQUEST_MAX_TOKENS].value).toBe(100); + expect(gpt4oSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS].value).toBe(8); + expect(gpt4oSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS].value).toBe(12); + expect(gpt4oSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS].value).toBe(20); + expect(gpt4oSpan!.attributes[GEN_AI_RESPONSE_ID]).toBeDefined(); + expect(gpt4oSpan!.attributes[GEN_AI_RESPONSE_MODEL].value).toBe('gpt-4o'); expect(gpt4oSpan!.attributes[GEN_AI_RESPONSE_STOP_REASON_ATTRIBUTE].value).toBe('stop'); const gpt35Span = container.items.find(span => span.name === 'chat gpt-3.5-turbo'); @@ -357,13 +359,13 @@ conditionalTest({ min: 20 })('LangChain integration (v1)', () => { expect(gpt35Span!.status).toBe('ok'); expect(gpt35Span!.attributes['sentry.op'].value).toBe('gen_ai.chat'); expect(gpt35Span!.attributes['sentry.origin'].value).toBe('auto.ai.langchain'); - expect(gpt35Span!.attributes[GEN_AI_SYSTEM_ATTRIBUTE].value).toBe('openai'); - expect(gpt35Span!.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE].value).toBe('gpt-3.5-turbo'); - expect(gpt35Span!.attributes[GEN_AI_REQUEST_TEMPERATURE_ATTRIBUTE].value).toBe(0.5); - expect(gpt35Span!.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE].value).toBe(8); - expect(gpt35Span!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE].value).toBe(12); - expect(gpt35Span!.attributes[GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE].value).toBe(20); - expect(gpt35Span!.attributes[GEN_AI_RESPONSE_MODEL_ATTRIBUTE].value).toBe('gpt-3.5-turbo'); + expect(gpt35Span!.attributes[GEN_AI_SYSTEM].value).toBe('openai'); + expect(gpt35Span!.attributes[GEN_AI_REQUEST_MODEL].value).toBe('gpt-3.5-turbo'); + expect(gpt35Span!.attributes[GEN_AI_REQUEST_TEMPERATURE].value).toBe(0.5); + expect(gpt35Span!.attributes[GEN_AI_USAGE_INPUT_TOKENS].value).toBe(8); + expect(gpt35Span!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS].value).toBe(12); + expect(gpt35Span!.attributes[GEN_AI_USAGE_TOTAL_TOKENS].value).toBe(20); + expect(gpt35Span!.attributes[GEN_AI_RESPONSE_MODEL].value).toBe('gpt-3.5-turbo'); expect(gpt35Span!.attributes[GEN_AI_RESPONSE_STOP_REASON_ATTRIBUTE].value).toBe('stop'); const errorSpan = container.items.find(span => span.name === 'chat error-model'); @@ -371,8 +373,8 @@ conditionalTest({ min: 20 })('LangChain integration (v1)', () => { expect(errorSpan!.status).toBe('error'); expect(errorSpan!.attributes['sentry.op'].value).toBe('gen_ai.chat'); expect(errorSpan!.attributes['sentry.origin'].value).toBe('auto.ai.langchain'); - expect(errorSpan!.attributes[GEN_AI_SYSTEM_ATTRIBUTE].value).toBe('openai'); - expect(errorSpan!.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE].value).toBe('error-model'); + expect(errorSpan!.attributes[GEN_AI_SYSTEM].value).toBe('openai'); + expect(errorSpan!.attributes[GEN_AI_REQUEST_MODEL].value).toBe('error-model'); }, }) .start() diff --git a/dev-packages/node-integration-tests/suites/tracing/langgraph/test.ts b/dev-packages/node-integration-tests/suites/tracing/langgraph/test.ts index 817aef2923ca..2174b33e406e 100644 --- a/dev-packages/node-integration-tests/suites/tracing/langgraph/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/langgraph/test.ts @@ -1,22 +1,22 @@ import { SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '@sentry/core'; import { afterAll, describe, expect } from 'vitest'; import { - GEN_AI_AGENT_NAME_ATTRIBUTE, - GEN_AI_CONVERSATION_ID_ATTRIBUTE, - GEN_AI_INPUT_MESSAGES_ATTRIBUTE, - GEN_AI_INPUT_MESSAGES_ORIGINAL_LENGTH_ATTRIBUTE, - GEN_AI_OPERATION_NAME_ATTRIBUTE, - GEN_AI_PIPELINE_NAME_ATTRIBUTE, - GEN_AI_REQUEST_AVAILABLE_TOOLS_ATTRIBUTE, - GEN_AI_RESPONSE_MODEL_ATTRIBUTE, - GEN_AI_RESPONSE_TEXT_ATTRIBUTE, - GEN_AI_RESPONSE_TOOL_CALLS_ATTRIBUTE, - GEN_AI_SYSTEM_INSTRUCTIONS_ATTRIBUTE, - GEN_AI_TOOL_NAME_ATTRIBUTE, - GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE, - GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE, - GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE, -} from '../../../../../packages/core/src/tracing/ai/gen-ai-attributes'; + GEN_AI_AGENT_NAME, + GEN_AI_CONVERSATION_ID, + GEN_AI_INPUT_MESSAGES, + GEN_AI_OPERATION_NAME, + GEN_AI_PIPELINE_NAME, + GEN_AI_REQUEST_AVAILABLE_TOOLS, + GEN_AI_RESPONSE_MODEL, + GEN_AI_RESPONSE_TEXT, + GEN_AI_RESPONSE_TOOL_CALLS, + GEN_AI_SYSTEM_INSTRUCTIONS, + GEN_AI_TOOL_NAME, + GEN_AI_USAGE_INPUT_TOKENS, + GEN_AI_USAGE_OUTPUT_TOKENS, + GEN_AI_USAGE_TOTAL_TOKENS, +} from '@sentry/conventions/attributes'; +import { GEN_AI_INPUT_MESSAGES_ORIGINAL_LENGTH_ATTRIBUTE } from '../../../../../packages/core/src/tracing/ai/gen-ai-attributes'; import { getStringAttributeValue } from '../../../utils'; import { cleanupChildProcesses, createEsmAndCjsTests } from '../../../utils/runner'; @@ -44,8 +44,8 @@ describe('LangGraph integration', () => { expect(createAgentSpan!.status).toBe('ok'); expect(createAgentSpan!.attributes['sentry.op'].value).toBe('gen_ai.create_agent'); expect(createAgentSpan!.attributes['sentry.origin'].value).toBe('auto.ai.langgraph'); - expect(createAgentSpan!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE].value).toBe('create_agent'); - expect(createAgentSpan!.attributes[GEN_AI_AGENT_NAME_ATTRIBUTE].value).toBe('weather_assistant'); + expect(createAgentSpan!.attributes[GEN_AI_OPERATION_NAME].value).toBe('create_agent'); + expect(createAgentSpan!.attributes[GEN_AI_AGENT_NAME].value).toBe('weather_assistant'); const invokeAgentSpans = container.items.filter(span => span.name === 'invoke_agent weather_assistant'); expect(invokeAgentSpans).toHaveLength(2); @@ -53,9 +53,9 @@ describe('LangGraph integration', () => { expect(span.status).toBe('ok'); expect(span.attributes['sentry.op'].value).toBe('gen_ai.invoke_agent'); expect(span.attributes['sentry.origin'].value).toBe('auto.ai.langgraph'); - expect(span.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE].value).toBe('invoke_agent'); - expect(span.attributes[GEN_AI_AGENT_NAME_ATTRIBUTE].value).toBe('weather_assistant'); - expect(span.attributes[GEN_AI_PIPELINE_NAME_ATTRIBUTE].value).toBe('weather_assistant'); + expect(span.attributes[GEN_AI_OPERATION_NAME].value).toBe('invoke_agent'); + expect(span.attributes[GEN_AI_AGENT_NAME].value).toBe('weather_assistant'); + expect(span.attributes[GEN_AI_PIPELINE_NAME].value).toBe('weather_assistant'); } }, }) @@ -78,7 +78,7 @@ describe('LangGraph integration', () => { expect(createAgentSpan!.attributes['sentry.op'].value).toBe('gen_ai.create_agent'); const weatherTodaySpan = container.items.find(span => - getStringAttributeValue(span.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value)?.includes( + getStringAttributeValue(span.attributes[GEN_AI_INPUT_MESSAGES]?.value)?.includes( 'What is the weather today?', ), ); @@ -89,7 +89,7 @@ describe('LangGraph integration', () => { expect(weatherTodaySpan!.attributes['sentry.origin'].value).toBe('auto.ai.langgraph'); const weatherDetailsSpan = container.items.find(span => - getStringAttributeValue(span.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value)?.includes( + getStringAttributeValue(span.attributes[GEN_AI_INPUT_MESSAGES]?.value)?.includes( 'Tell me about the weather', ), ); @@ -123,45 +123,39 @@ describe('LangGraph integration', () => { expect(toolAgentSpan).toBeDefined(); expect(toolAgentSpan!.status).toBe('ok'); expect(toolAgentSpan!.attributes['sentry.op'].value).toBe('gen_ai.create_agent'); - expect(toolAgentSpan!.attributes[GEN_AI_AGENT_NAME_ATTRIBUTE].value).toBe('tool_agent'); + expect(toolAgentSpan!.attributes[GEN_AI_AGENT_NAME].value).toBe('tool_agent'); const toolAgentInvokeSpan = container.items.find(span => span.name === 'invoke_agent tool_agent'); expect(toolAgentInvokeSpan).toBeDefined(); expect(toolAgentInvokeSpan!.status).toBe('ok'); expect(toolAgentInvokeSpan!.attributes['sentry.op'].value).toBe('gen_ai.invoke_agent'); - expect(toolAgentInvokeSpan!.attributes[GEN_AI_REQUEST_AVAILABLE_TOOLS_ATTRIBUTE].value).toContain( - 'get_weather', - ); - expect(toolAgentInvokeSpan!.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE].value).toContain( - 'What is the weather?', - ); - expect(toolAgentInvokeSpan!.attributes[GEN_AI_RESPONSE_MODEL_ATTRIBUTE].value).toBe('gpt-4-0613'); - expect(toolAgentInvokeSpan!.attributes[GEN_AI_RESPONSE_TEXT_ATTRIBUTE].value).toContain( + expect(toolAgentInvokeSpan!.attributes[GEN_AI_REQUEST_AVAILABLE_TOOLS].value).toContain('get_weather'); + expect(toolAgentInvokeSpan!.attributes[GEN_AI_INPUT_MESSAGES].value).toContain('What is the weather?'); + expect(toolAgentInvokeSpan!.attributes[GEN_AI_RESPONSE_MODEL].value).toBe('gpt-4-0613'); + expect(toolAgentInvokeSpan!.attributes[GEN_AI_RESPONSE_TEXT].value).toContain( 'Response without calling tools', ); - expect(toolAgentInvokeSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE].value).toBe(25); - expect(toolAgentInvokeSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE].value).toBe(15); - expect(toolAgentInvokeSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE].value).toBe(40); + expect(toolAgentInvokeSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS].value).toBe(25); + expect(toolAgentInvokeSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS].value).toBe(15); + expect(toolAgentInvokeSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS].value).toBe(40); const toolCallingAgentSpan = container.items.find(span => span.name === 'create_agent tool_calling_agent'); expect(toolCallingAgentSpan).toBeDefined(); expect(toolCallingAgentSpan!.status).toBe('ok'); expect(toolCallingAgentSpan!.attributes['sentry.op'].value).toBe('gen_ai.create_agent'); - expect(toolCallingAgentSpan!.attributes[GEN_AI_AGENT_NAME_ATTRIBUTE].value).toBe('tool_calling_agent'); + expect(toolCallingAgentSpan!.attributes[GEN_AI_AGENT_NAME].value).toBe('tool_calling_agent'); const toolCallingInvokeSpan = container.items.find(span => span.name === 'invoke_agent tool_calling_agent'); expect(toolCallingInvokeSpan).toBeDefined(); expect(toolCallingInvokeSpan!.status).toBe('ok'); expect(toolCallingInvokeSpan!.attributes['sentry.op'].value).toBe('gen_ai.invoke_agent'); - expect(toolCallingInvokeSpan!.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE].value).toContain('San Francisco'); - expect(toolCallingInvokeSpan!.attributes[GEN_AI_RESPONSE_MODEL_ATTRIBUTE].value).toBe('gpt-4-0613'); - expect(toolCallingInvokeSpan!.attributes[GEN_AI_RESPONSE_TEXT_ATTRIBUTE].value).toMatch(/"role":"tool"/); - expect(toolCallingInvokeSpan!.attributes[GEN_AI_RESPONSE_TOOL_CALLS_ATTRIBUTE].value).toContain( - 'get_weather', - ); - expect(toolCallingInvokeSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE].value).toBe(80); - expect(toolCallingInvokeSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE].value).toBe(40); - expect(toolCallingInvokeSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE].value).toBe(120); + expect(toolCallingInvokeSpan!.attributes[GEN_AI_INPUT_MESSAGES].value).toContain('San Francisco'); + expect(toolCallingInvokeSpan!.attributes[GEN_AI_RESPONSE_MODEL].value).toBe('gpt-4-0613'); + expect(toolCallingInvokeSpan!.attributes[GEN_AI_RESPONSE_TEXT].value).toMatch(/"role":"tool"/); + expect(toolCallingInvokeSpan!.attributes[GEN_AI_RESPONSE_TOOL_CALLS].value).toContain('get_weather'); + expect(toolCallingInvokeSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS].value).toBe(80); + expect(toolCallingInvokeSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS].value).toBe(40); + expect(toolCallingInvokeSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS].value).toBe(120); }, }) .start() @@ -184,7 +178,7 @@ describe('LangGraph integration', () => { expect(createAgentSpan!.attributes['sentry.op'].value).toBe('gen_ai.create_agent'); const firstThreadSpan = container.items.find( - span => span.attributes[GEN_AI_CONVERSATION_ID_ATTRIBUTE]?.value === 'thread_abc123_session_1', + span => span.attributes[GEN_AI_CONVERSATION_ID]?.value === 'thread_abc123_session_1', ); expect(firstThreadSpan).toBeDefined(); expect(firstThreadSpan!.name).toBe('invoke_agent thread_test_agent'); @@ -192,7 +186,7 @@ describe('LangGraph integration', () => { expect(firstThreadSpan!.attributes['sentry.op'].value).toBe('gen_ai.invoke_agent'); const secondThreadSpan = container.items.find( - span => span.attributes[GEN_AI_CONVERSATION_ID_ATTRIBUTE]?.value === 'thread_xyz789_session_2', + span => span.attributes[GEN_AI_CONVERSATION_ID]?.value === 'thread_xyz789_session_2', ); expect(secondThreadSpan).toBeDefined(); expect(secondThreadSpan!.name).toBe('invoke_agent thread_test_agent'); @@ -200,8 +194,7 @@ describe('LangGraph integration', () => { const noThreadSpan = container.items.find( span => - span.name === 'invoke_agent thread_test_agent' && - span.attributes[GEN_AI_CONVERSATION_ID_ATTRIBUTE] === undefined, + span.name === 'invoke_agent thread_test_agent' && span.attributes[GEN_AI_CONVERSATION_ID] === undefined, ); expect(noThreadSpan).toBeDefined(); expect(noThreadSpan!.status).toBe('ok'); @@ -227,7 +220,7 @@ describe('LangGraph integration', () => { const invokeAgentSpan = container.items.find(span => span.name === 'invoke_agent test-agent'); expect(invokeAgentSpan).toBeDefined(); - expect(invokeAgentSpan!.attributes[GEN_AI_SYSTEM_INSTRUCTIONS_ATTRIBUTE].value).toBe( + expect(invokeAgentSpan!.attributes[GEN_AI_SYSTEM_INSTRUCTIONS].value).toBe( JSON.stringify([{ type: 'text', content: 'You are a helpful assistant' }]), ); }, @@ -260,18 +253,18 @@ describe('LangGraph integration', () => { expect(createAgentSpan).toBeDefined(); expect(createAgentSpan!.status).toBe('ok'); expect(createAgentSpan!.attributes['sentry.op'].value).toBe('gen_ai.create_agent'); - expect(createAgentSpan!.attributes[GEN_AI_AGENT_NAME_ATTRIBUTE].value).toBe('resume_agent'); + expect(createAgentSpan!.attributes[GEN_AI_AGENT_NAME].value).toBe('resume_agent'); const invokeAgentSpan = container.items.find( - span => span.attributes[GEN_AI_CONVERSATION_ID_ATTRIBUTE]?.value === 'resume-thread-1', + span => span.attributes[GEN_AI_CONVERSATION_ID]?.value === 'resume-thread-1', ); expect(invokeAgentSpan).toBeDefined(); expect(invokeAgentSpan!.name).toBe('invoke_agent resume_agent'); expect(invokeAgentSpan!.status).toBe('ok'); expect(invokeAgentSpan!.attributes['sentry.op'].value).toBe('gen_ai.invoke_agent'); expect(invokeAgentSpan!.attributes['sentry.origin'].value).toBe('auto.ai.langgraph'); - expect(invokeAgentSpan!.attributes[GEN_AI_AGENT_NAME_ATTRIBUTE].value).toBe('resume_agent'); - expect(invokeAgentSpan!.attributes[GEN_AI_PIPELINE_NAME_ATTRIBUTE].value).toBe('resume_agent'); + expect(invokeAgentSpan!.attributes[GEN_AI_AGENT_NAME].value).toBe('resume_agent'); + expect(invokeAgentSpan!.attributes[GEN_AI_PIPELINE_NAME].value).toBe('resume_agent'); }, }) .start() @@ -300,7 +293,7 @@ describe('LangGraph integration', () => { expect(container.items).toHaveLength(2); const invokeAgentSpan = container.items.find( - span => span.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value === expectedMessages, + span => span.attributes[GEN_AI_INPUT_MESSAGES]?.value === expectedMessages, ); expect(invokeAgentSpan).toBeDefined(); @@ -324,9 +317,7 @@ describe('LangGraph integration', () => { const spans = container.items; const chatSpan = spans.find(s => - getStringAttributeValue(s.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value)?.includes( - streamingLongContent, - ), + getStringAttributeValue(s.attributes[GEN_AI_INPUT_MESSAGES]?.value)?.includes(streamingLongContent), ); expect(chatSpan).toBeDefined(); }, @@ -349,13 +340,13 @@ describe('LangGraph integration', () => { // With explicit enableTruncation: true, content should be truncated despite streaming. const chatSpan = spans.find(s => - getStringAttributeValue(s.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value)?.startsWith( + getStringAttributeValue(s.attributes[GEN_AI_INPUT_MESSAGES]?.value)?.startsWith( '[{"role":"user","content":"AAAA', ), ); expect(chatSpan).toBeDefined(); expect( - (getStringAttributeValue(chatSpan!.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE].value) ?? '').length, + (getStringAttributeValue(chatSpan!.attributes[GEN_AI_INPUT_MESSAGES].value) ?? '').length, ).toBeLessThan(streamingLongContent.length); }, }) @@ -381,11 +372,11 @@ describe('LangGraph integration', () => { expect(spans).toContainEqual( expect.objectContaining({ data: expect.objectContaining({ - [GEN_AI_OPERATION_NAME_ATTRIBUTE]: 'invoke_agent', + [GEN_AI_OPERATION_NAME]: 'invoke_agent', [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'gen_ai.invoke_agent', [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ai.langgraph', - [GEN_AI_AGENT_NAME_ATTRIBUTE]: 'helpful_assistant', - [GEN_AI_PIPELINE_NAME_ATTRIBUTE]: 'helpful_assistant', + [GEN_AI_AGENT_NAME]: 'helpful_assistant', + [GEN_AI_PIPELINE_NAME]: 'helpful_assistant', }), description: 'invoke_agent helpful_assistant', op: 'gen_ai.invoke_agent', @@ -396,7 +387,7 @@ describe('LangGraph integration', () => { expect(spans).toContainEqual(expect.objectContaining({ op: 'http.client' })); expect(spans).toContainEqual( expect.objectContaining({ - data: expect.objectContaining({ [GEN_AI_AGENT_NAME_ATTRIBUTE]: 'helpful_assistant' }), + data: expect.objectContaining({ [GEN_AI_AGENT_NAME]: 'helpful_assistant' }), op: 'gen_ai.chat', }), ); @@ -420,8 +411,8 @@ describe('LangGraph integration', () => { expect(spans).toContainEqual( expect.objectContaining({ data: expect.objectContaining({ - [GEN_AI_OPERATION_NAME_ATTRIBUTE]: 'invoke_agent', - [GEN_AI_AGENT_NAME_ATTRIBUTE]: 'math_assistant', + [GEN_AI_OPERATION_NAME]: 'invoke_agent', + [GEN_AI_AGENT_NAME]: 'math_assistant', }), op: 'gen_ai.invoke_agent', status: 'ok', @@ -430,8 +421,8 @@ describe('LangGraph integration', () => { expect(spans).toContainEqual( expect.objectContaining({ data: expect.objectContaining({ - [GEN_AI_OPERATION_NAME_ATTRIBUTE]: 'execute_tool', - [GEN_AI_TOOL_NAME_ATTRIBUTE]: 'add', + [GEN_AI_OPERATION_NAME]: 'execute_tool', + [GEN_AI_TOOL_NAME]: 'add', 'gen_ai.tool.type': 'function', }), description: 'execute_tool add', @@ -442,8 +433,8 @@ describe('LangGraph integration', () => { expect(spans).toContainEqual( expect.objectContaining({ data: expect.objectContaining({ - [GEN_AI_OPERATION_NAME_ATTRIBUTE]: 'execute_tool', - [GEN_AI_TOOL_NAME_ATTRIBUTE]: 'multiply', + [GEN_AI_OPERATION_NAME]: 'execute_tool', + [GEN_AI_TOOL_NAME]: 'multiply', 'gen_ai.tool.type': 'function', }), description: 'execute_tool multiply', @@ -470,7 +461,7 @@ describe('LangGraph integration', () => { const chatSpans = spans.filter(s => s.op === 'gen_ai.chat'); expect(chatSpans).toHaveLength(1); expect(chatSpans[0]?.data).toMatchObject({ - [GEN_AI_AGENT_NAME_ATTRIBUTE]: 'plain_assistant', + [GEN_AI_AGENT_NAME]: 'plain_assistant', }); }, }) diff --git a/dev-packages/node-integration-tests/suites/tracing/openai/openai-tool-calls/test.ts b/dev-packages/node-integration-tests/suites/tracing/openai/openai-tool-calls/test.ts index 0895e6dffd19..f2c2c94aca92 100644 --- a/dev-packages/node-integration-tests/suites/tracing/openai/openai-tool-calls/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/openai/openai-tool-calls/test.ts @@ -1,22 +1,24 @@ import { SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '@sentry/core'; import { afterAll, describe, expect } from 'vitest'; import { - GEN_AI_INPUT_MESSAGES_ATTRIBUTE, + GEN_AI_INPUT_MESSAGES, + GEN_AI_OPERATION_NAME, + GEN_AI_REQUEST_AVAILABLE_TOOLS, + GEN_AI_REQUEST_MODEL, + GEN_AI_RESPONSE_FINISH_REASONS, + GEN_AI_RESPONSE_ID, + GEN_AI_RESPONSE_MODEL, + GEN_AI_RESPONSE_STREAMING, + GEN_AI_RESPONSE_TEXT, + GEN_AI_RESPONSE_TOOL_CALLS, + GEN_AI_SYSTEM, + GEN_AI_USAGE_INPUT_TOKENS, + GEN_AI_USAGE_OUTPUT_TOKENS, + GEN_AI_USAGE_TOTAL_TOKENS, +} from '@sentry/conventions/attributes'; +import { GEN_AI_INPUT_MESSAGES_ORIGINAL_LENGTH_ATTRIBUTE, - GEN_AI_OPERATION_NAME_ATTRIBUTE, - GEN_AI_REQUEST_AVAILABLE_TOOLS_ATTRIBUTE, - GEN_AI_REQUEST_MODEL_ATTRIBUTE, GEN_AI_REQUEST_STREAM_ATTRIBUTE, - GEN_AI_RESPONSE_FINISH_REASONS_ATTRIBUTE, - GEN_AI_RESPONSE_ID_ATTRIBUTE, - GEN_AI_RESPONSE_MODEL_ATTRIBUTE, - GEN_AI_RESPONSE_STREAMING_ATTRIBUTE, - GEN_AI_RESPONSE_TEXT_ATTRIBUTE, - GEN_AI_RESPONSE_TOOL_CALLS_ATTRIBUTE, - GEN_AI_SYSTEM_ATTRIBUTE, - GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE, - GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE, - GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE, } from '../../../../../../packages/core/src/tracing/ai/gen-ai-attributes'; import { cleanupChildProcesses, createEsmAndCjsTests } from '../../../../utils/runner'; @@ -85,12 +87,12 @@ describe('OpenAI Tool Calls integration', () => { span: container => { expect(container.items).toHaveLength(4); const chatToolsSpan = container.items.find( - span => span.attributes[GEN_AI_RESPONSE_ID_ATTRIBUTE]?.value === 'chatcmpl-tools-123', + span => span.attributes[GEN_AI_RESPONSE_ID]?.value === 'chatcmpl-tools-123', ); expect(chatToolsSpan).toBeDefined(); expect(chatToolsSpan!.name).toBe('chat gpt-4'); expect(chatToolsSpan!.status).toBe('ok'); - expect(chatToolsSpan!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE]).toEqual({ + expect(chatToolsSpan!.attributes[GEN_AI_OPERATION_NAME]).toEqual({ type: 'string', value: 'chat', }); @@ -102,47 +104,47 @@ describe('OpenAI Tool Calls integration', () => { type: 'string', value: 'auto.ai.openai', }); - expect(chatToolsSpan!.attributes[GEN_AI_SYSTEM_ATTRIBUTE]).toEqual({ type: 'string', value: 'openai' }); - expect(chatToolsSpan!.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE]).toEqual({ + expect(chatToolsSpan!.attributes[GEN_AI_SYSTEM]).toEqual({ type: 'string', value: 'openai' }); + expect(chatToolsSpan!.attributes[GEN_AI_REQUEST_MODEL]).toEqual({ type: 'string', value: 'gpt-4', }); - expect(chatToolsSpan!.attributes[GEN_AI_REQUEST_AVAILABLE_TOOLS_ATTRIBUTE]).toEqual({ + expect(chatToolsSpan!.attributes[GEN_AI_REQUEST_AVAILABLE_TOOLS]).toEqual({ type: 'string', value: WEATHER_TOOL_DEFINITION, }); - expect(chatToolsSpan!.attributes[GEN_AI_RESPONSE_MODEL_ATTRIBUTE]).toEqual({ + expect(chatToolsSpan!.attributes[GEN_AI_RESPONSE_MODEL]).toEqual({ type: 'string', value: 'gpt-4', }); - expect(chatToolsSpan!.attributes[GEN_AI_RESPONSE_ID_ATTRIBUTE]).toEqual({ + expect(chatToolsSpan!.attributes[GEN_AI_RESPONSE_ID]).toEqual({ type: 'string', value: 'chatcmpl-tools-123', }); - expect(chatToolsSpan!.attributes[GEN_AI_RESPONSE_FINISH_REASONS_ATTRIBUTE]).toEqual({ + expect(chatToolsSpan!.attributes[GEN_AI_RESPONSE_FINISH_REASONS]).toEqual({ type: 'string', value: '["tool_calls"]', }); - expect(chatToolsSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]).toEqual({ + expect(chatToolsSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS]).toEqual({ type: 'integer', value: 15, }); - expect(chatToolsSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE]).toEqual({ + expect(chatToolsSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS]).toEqual({ type: 'integer', value: 25, }); - expect(chatToolsSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE]).toEqual({ + expect(chatToolsSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS]).toEqual({ type: 'integer', value: 40, }); const streamingChatToolsSpan = container.items.find( - span => span.attributes[GEN_AI_RESPONSE_ID_ATTRIBUTE]?.value === 'chatcmpl-stream-tools-123', + span => span.attributes[GEN_AI_RESPONSE_ID]?.value === 'chatcmpl-stream-tools-123', ); expect(streamingChatToolsSpan).toBeDefined(); expect(streamingChatToolsSpan!.name).toBe('chat gpt-4'); expect(streamingChatToolsSpan!.status).toBe('ok'); - expect(streamingChatToolsSpan!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE]).toEqual({ + expect(streamingChatToolsSpan!.attributes[GEN_AI_OPERATION_NAME]).toEqual({ type: 'string', value: 'chat', }); @@ -154,11 +156,11 @@ describe('OpenAI Tool Calls integration', () => { type: 'string', value: 'auto.ai.openai', }); - expect(streamingChatToolsSpan!.attributes[GEN_AI_SYSTEM_ATTRIBUTE]).toEqual({ + expect(streamingChatToolsSpan!.attributes[GEN_AI_SYSTEM]).toEqual({ type: 'string', value: 'openai', }); - expect(streamingChatToolsSpan!.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE]).toEqual({ + expect(streamingChatToolsSpan!.attributes[GEN_AI_REQUEST_MODEL]).toEqual({ type: 'string', value: 'gpt-4', }); @@ -166,46 +168,46 @@ describe('OpenAI Tool Calls integration', () => { type: 'boolean', value: true, }); - expect(streamingChatToolsSpan!.attributes[GEN_AI_REQUEST_AVAILABLE_TOOLS_ATTRIBUTE]).toEqual({ + expect(streamingChatToolsSpan!.attributes[GEN_AI_REQUEST_AVAILABLE_TOOLS]).toEqual({ type: 'string', value: WEATHER_TOOL_DEFINITION, }); - expect(streamingChatToolsSpan!.attributes[GEN_AI_RESPONSE_MODEL_ATTRIBUTE]).toEqual({ + expect(streamingChatToolsSpan!.attributes[GEN_AI_RESPONSE_MODEL]).toEqual({ type: 'string', value: 'gpt-4', }); - expect(streamingChatToolsSpan!.attributes[GEN_AI_RESPONSE_ID_ATTRIBUTE]).toEqual({ + expect(streamingChatToolsSpan!.attributes[GEN_AI_RESPONSE_ID]).toEqual({ type: 'string', value: 'chatcmpl-stream-tools-123', }); - expect(streamingChatToolsSpan!.attributes[GEN_AI_RESPONSE_FINISH_REASONS_ATTRIBUTE]).toEqual({ + expect(streamingChatToolsSpan!.attributes[GEN_AI_RESPONSE_FINISH_REASONS]).toEqual({ type: 'string', value: '["tool_calls"]', }); - expect(streamingChatToolsSpan!.attributes[GEN_AI_RESPONSE_STREAMING_ATTRIBUTE]).toEqual({ + expect(streamingChatToolsSpan!.attributes[GEN_AI_RESPONSE_STREAMING]).toEqual({ type: 'boolean', value: true, }); - expect(streamingChatToolsSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]).toEqual({ + expect(streamingChatToolsSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS]).toEqual({ type: 'integer', value: 15, }); - expect(streamingChatToolsSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE]).toEqual({ + expect(streamingChatToolsSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS]).toEqual({ type: 'integer', value: 25, }); - expect(streamingChatToolsSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE]).toEqual({ + expect(streamingChatToolsSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS]).toEqual({ type: 'integer', value: 40, }); const responsesToolsSpan = container.items.find( - span => span.attributes[GEN_AI_RESPONSE_ID_ATTRIBUTE]?.value === 'resp_tools_789', + span => span.attributes[GEN_AI_RESPONSE_ID]?.value === 'resp_tools_789', ); expect(responsesToolsSpan).toBeDefined(); expect(responsesToolsSpan!.name).toBe('chat gpt-4'); expect(responsesToolsSpan!.status).toBe('ok'); - expect(responsesToolsSpan!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE]).toEqual({ + expect(responsesToolsSpan!.attributes[GEN_AI_OPERATION_NAME]).toEqual({ type: 'string', value: 'chat', }); @@ -217,51 +219,51 @@ describe('OpenAI Tool Calls integration', () => { type: 'string', value: 'auto.ai.openai', }); - expect(responsesToolsSpan!.attributes[GEN_AI_SYSTEM_ATTRIBUTE]).toEqual({ + expect(responsesToolsSpan!.attributes[GEN_AI_SYSTEM]).toEqual({ type: 'string', value: 'openai', }); - expect(responsesToolsSpan!.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE]).toEqual({ + expect(responsesToolsSpan!.attributes[GEN_AI_REQUEST_MODEL]).toEqual({ type: 'string', value: 'gpt-4', }); expect(responsesToolsSpan!.attributes[GEN_AI_REQUEST_STREAM_ATTRIBUTE]).toBeUndefined(); - expect(responsesToolsSpan!.attributes[GEN_AI_REQUEST_AVAILABLE_TOOLS_ATTRIBUTE]).toEqual({ + expect(responsesToolsSpan!.attributes[GEN_AI_REQUEST_AVAILABLE_TOOLS]).toEqual({ type: 'string', value: WEATHER_TOOL_DEFINITION, }); - expect(responsesToolsSpan!.attributes[GEN_AI_RESPONSE_MODEL_ATTRIBUTE]).toEqual({ + expect(responsesToolsSpan!.attributes[GEN_AI_RESPONSE_MODEL]).toEqual({ type: 'string', value: 'gpt-4', }); - expect(responsesToolsSpan!.attributes[GEN_AI_RESPONSE_ID_ATTRIBUTE]).toEqual({ + expect(responsesToolsSpan!.attributes[GEN_AI_RESPONSE_ID]).toEqual({ type: 'string', value: 'resp_tools_789', }); - expect(responsesToolsSpan!.attributes[GEN_AI_RESPONSE_FINISH_REASONS_ATTRIBUTE]).toEqual({ + expect(responsesToolsSpan!.attributes[GEN_AI_RESPONSE_FINISH_REASONS]).toEqual({ type: 'string', value: '["completed"]', }); - expect(responsesToolsSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]).toEqual({ + expect(responsesToolsSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS]).toEqual({ type: 'integer', value: 8, }); - expect(responsesToolsSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE]).toEqual({ + expect(responsesToolsSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS]).toEqual({ type: 'integer', value: 12, }); - expect(responsesToolsSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE]).toEqual({ + expect(responsesToolsSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS]).toEqual({ type: 'integer', value: 20, }); const streamingResponsesToolsSpan = container.items.find( - span => span.attributes[GEN_AI_RESPONSE_ID_ATTRIBUTE]?.value === 'resp_stream_tools_789', + span => span.attributes[GEN_AI_RESPONSE_ID]?.value === 'resp_stream_tools_789', ); expect(streamingResponsesToolsSpan).toBeDefined(); expect(streamingResponsesToolsSpan!.name).toBe('chat gpt-4'); expect(streamingResponsesToolsSpan!.status).toBe('ok'); - expect(streamingResponsesToolsSpan!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE]).toEqual({ + expect(streamingResponsesToolsSpan!.attributes[GEN_AI_OPERATION_NAME]).toEqual({ type: 'string', value: 'chat', }); @@ -273,11 +275,11 @@ describe('OpenAI Tool Calls integration', () => { type: 'string', value: 'auto.ai.openai', }); - expect(streamingResponsesToolsSpan!.attributes[GEN_AI_SYSTEM_ATTRIBUTE]).toEqual({ + expect(streamingResponsesToolsSpan!.attributes[GEN_AI_SYSTEM]).toEqual({ type: 'string', value: 'openai', }); - expect(streamingResponsesToolsSpan!.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE]).toEqual({ + expect(streamingResponsesToolsSpan!.attributes[GEN_AI_REQUEST_MODEL]).toEqual({ type: 'string', value: 'gpt-4', }); @@ -285,35 +287,35 @@ describe('OpenAI Tool Calls integration', () => { type: 'boolean', value: true, }); - expect(streamingResponsesToolsSpan!.attributes[GEN_AI_REQUEST_AVAILABLE_TOOLS_ATTRIBUTE]).toEqual({ + expect(streamingResponsesToolsSpan!.attributes[GEN_AI_REQUEST_AVAILABLE_TOOLS]).toEqual({ type: 'string', value: WEATHER_TOOL_DEFINITION, }); - expect(streamingResponsesToolsSpan!.attributes[GEN_AI_RESPONSE_MODEL_ATTRIBUTE]).toEqual({ + expect(streamingResponsesToolsSpan!.attributes[GEN_AI_RESPONSE_MODEL]).toEqual({ type: 'string', value: 'gpt-4', }); - expect(streamingResponsesToolsSpan!.attributes[GEN_AI_RESPONSE_ID_ATTRIBUTE]).toEqual({ + expect(streamingResponsesToolsSpan!.attributes[GEN_AI_RESPONSE_ID]).toEqual({ type: 'string', value: 'resp_stream_tools_789', }); - expect(streamingResponsesToolsSpan!.attributes[GEN_AI_RESPONSE_FINISH_REASONS_ATTRIBUTE]).toEqual({ + expect(streamingResponsesToolsSpan!.attributes[GEN_AI_RESPONSE_FINISH_REASONS]).toEqual({ type: 'string', value: '["in_progress","completed"]', }); - expect(streamingResponsesToolsSpan!.attributes[GEN_AI_RESPONSE_STREAMING_ATTRIBUTE]).toEqual({ + expect(streamingResponsesToolsSpan!.attributes[GEN_AI_RESPONSE_STREAMING]).toEqual({ type: 'boolean', value: true, }); - expect(streamingResponsesToolsSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]).toEqual({ + expect(streamingResponsesToolsSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS]).toEqual({ type: 'integer', value: 8, }); - expect(streamingResponsesToolsSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE]).toEqual({ + expect(streamingResponsesToolsSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS]).toEqual({ type: 'integer', value: 12, }); - expect(streamingResponsesToolsSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE]).toEqual({ + expect(streamingResponsesToolsSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS]).toEqual({ type: 'integer', value: 20, }); @@ -333,12 +335,12 @@ describe('OpenAI Tool Calls integration', () => { span: container => { expect(container.items).toHaveLength(4); const chatToolsSpan = container.items.find( - span => span.attributes[GEN_AI_RESPONSE_ID_ATTRIBUTE]?.value === 'chatcmpl-tools-123', + span => span.attributes[GEN_AI_RESPONSE_ID]?.value === 'chatcmpl-tools-123', ); expect(chatToolsSpan).toBeDefined(); expect(chatToolsSpan!.name).toBe('chat gpt-4'); expect(chatToolsSpan!.status).toBe('ok'); - expect(chatToolsSpan!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE]).toEqual({ + expect(chatToolsSpan!.attributes[GEN_AI_OPERATION_NAME]).toEqual({ type: 'string', value: 'chat', }); @@ -350,8 +352,8 @@ describe('OpenAI Tool Calls integration', () => { type: 'string', value: 'auto.ai.openai', }); - expect(chatToolsSpan!.attributes[GEN_AI_SYSTEM_ATTRIBUTE]).toEqual({ type: 'string', value: 'openai' }); - expect(chatToolsSpan!.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE]).toEqual({ + expect(chatToolsSpan!.attributes[GEN_AI_SYSTEM]).toEqual({ type: 'string', value: 'openai' }); + expect(chatToolsSpan!.attributes[GEN_AI_REQUEST_MODEL]).toEqual({ type: 'string', value: 'gpt-4', }); @@ -359,54 +361,54 @@ describe('OpenAI Tool Calls integration', () => { type: 'integer', value: 1, }); - expect(chatToolsSpan!.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]).toEqual({ + expect(chatToolsSpan!.attributes[GEN_AI_INPUT_MESSAGES]).toEqual({ type: 'string', value: '[{"role":"user","content":"What is the weather like in Paris today?"}]', }); - expect(chatToolsSpan!.attributes[GEN_AI_REQUEST_AVAILABLE_TOOLS_ATTRIBUTE]).toEqual({ + expect(chatToolsSpan!.attributes[GEN_AI_REQUEST_AVAILABLE_TOOLS]).toEqual({ type: 'string', value: WEATHER_TOOL_DEFINITION, }); - expect(chatToolsSpan!.attributes[GEN_AI_RESPONSE_MODEL_ATTRIBUTE]).toEqual({ + expect(chatToolsSpan!.attributes[GEN_AI_RESPONSE_MODEL]).toEqual({ type: 'string', value: 'gpt-4', }); - expect(chatToolsSpan!.attributes[GEN_AI_RESPONSE_ID_ATTRIBUTE]).toEqual({ + expect(chatToolsSpan!.attributes[GEN_AI_RESPONSE_ID]).toEqual({ type: 'string', value: 'chatcmpl-tools-123', }); - expect(chatToolsSpan!.attributes[GEN_AI_RESPONSE_FINISH_REASONS_ATTRIBUTE]).toEqual({ + expect(chatToolsSpan!.attributes[GEN_AI_RESPONSE_FINISH_REASONS]).toEqual({ type: 'string', value: '["tool_calls"]', }); - expect(chatToolsSpan!.attributes[GEN_AI_RESPONSE_TEXT_ATTRIBUTE]).toEqual({ + expect(chatToolsSpan!.attributes[GEN_AI_RESPONSE_TEXT]).toEqual({ type: 'string', value: '[""]', }); - expect(chatToolsSpan!.attributes[GEN_AI_RESPONSE_TOOL_CALLS_ATTRIBUTE]).toEqual({ + expect(chatToolsSpan!.attributes[GEN_AI_RESPONSE_TOOL_CALLS]).toEqual({ type: 'string', value: CHAT_TOOL_CALLS, }); - expect(chatToolsSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]).toEqual({ + expect(chatToolsSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS]).toEqual({ type: 'integer', value: 15, }); - expect(chatToolsSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE]).toEqual({ + expect(chatToolsSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS]).toEqual({ type: 'integer', value: 25, }); - expect(chatToolsSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE]).toEqual({ + expect(chatToolsSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS]).toEqual({ type: 'integer', value: 40, }); const streamingChatToolsSpan = container.items.find( - span => span.attributes[GEN_AI_RESPONSE_ID_ATTRIBUTE]?.value === 'chatcmpl-stream-tools-123', + span => span.attributes[GEN_AI_RESPONSE_ID]?.value === 'chatcmpl-stream-tools-123', ); expect(streamingChatToolsSpan).toBeDefined(); expect(streamingChatToolsSpan!.name).toBe('chat gpt-4'); expect(streamingChatToolsSpan!.status).toBe('ok'); - expect(streamingChatToolsSpan!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE]).toEqual({ + expect(streamingChatToolsSpan!.attributes[GEN_AI_OPERATION_NAME]).toEqual({ type: 'string', value: 'chat', }); @@ -418,11 +420,11 @@ describe('OpenAI Tool Calls integration', () => { type: 'string', value: 'auto.ai.openai', }); - expect(streamingChatToolsSpan!.attributes[GEN_AI_SYSTEM_ATTRIBUTE]).toEqual({ + expect(streamingChatToolsSpan!.attributes[GEN_AI_SYSTEM]).toEqual({ type: 'string', value: 'openai', }); - expect(streamingChatToolsSpan!.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE]).toEqual({ + expect(streamingChatToolsSpan!.attributes[GEN_AI_REQUEST_MODEL]).toEqual({ type: 'string', value: 'gpt-4', }); @@ -434,54 +436,54 @@ describe('OpenAI Tool Calls integration', () => { type: 'integer', value: 1, }); - expect(streamingChatToolsSpan!.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]).toEqual({ + expect(streamingChatToolsSpan!.attributes[GEN_AI_INPUT_MESSAGES]).toEqual({ type: 'string', value: '[{"role":"user","content":"What is the weather like in Paris today?"}]', }); - expect(streamingChatToolsSpan!.attributes[GEN_AI_REQUEST_AVAILABLE_TOOLS_ATTRIBUTE]).toEqual({ + expect(streamingChatToolsSpan!.attributes[GEN_AI_REQUEST_AVAILABLE_TOOLS]).toEqual({ type: 'string', value: WEATHER_TOOL_DEFINITION, }); - expect(streamingChatToolsSpan!.attributes[GEN_AI_RESPONSE_MODEL_ATTRIBUTE]).toEqual({ + expect(streamingChatToolsSpan!.attributes[GEN_AI_RESPONSE_MODEL]).toEqual({ type: 'string', value: 'gpt-4', }); - expect(streamingChatToolsSpan!.attributes[GEN_AI_RESPONSE_ID_ATTRIBUTE]).toEqual({ + expect(streamingChatToolsSpan!.attributes[GEN_AI_RESPONSE_ID]).toEqual({ type: 'string', value: 'chatcmpl-stream-tools-123', }); - expect(streamingChatToolsSpan!.attributes[GEN_AI_RESPONSE_FINISH_REASONS_ATTRIBUTE]).toEqual({ + expect(streamingChatToolsSpan!.attributes[GEN_AI_RESPONSE_FINISH_REASONS]).toEqual({ type: 'string', value: '["tool_calls"]', }); - expect(streamingChatToolsSpan!.attributes[GEN_AI_RESPONSE_STREAMING_ATTRIBUTE]).toEqual({ + expect(streamingChatToolsSpan!.attributes[GEN_AI_RESPONSE_STREAMING]).toEqual({ type: 'boolean', value: true, }); - expect(streamingChatToolsSpan!.attributes[GEN_AI_RESPONSE_TOOL_CALLS_ATTRIBUTE]).toEqual({ + expect(streamingChatToolsSpan!.attributes[GEN_AI_RESPONSE_TOOL_CALLS]).toEqual({ type: 'string', value: CHAT_STREAM_TOOL_CALLS, }); - expect(streamingChatToolsSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]).toEqual({ + expect(streamingChatToolsSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS]).toEqual({ type: 'integer', value: 15, }); - expect(streamingChatToolsSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE]).toEqual({ + expect(streamingChatToolsSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS]).toEqual({ type: 'integer', value: 25, }); - expect(streamingChatToolsSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE]).toEqual({ + expect(streamingChatToolsSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS]).toEqual({ type: 'integer', value: 40, }); const responsesToolsSpan = container.items.find( - span => span.attributes[GEN_AI_RESPONSE_ID_ATTRIBUTE]?.value === 'resp_tools_789', + span => span.attributes[GEN_AI_RESPONSE_ID]?.value === 'resp_tools_789', ); expect(responsesToolsSpan).toBeDefined(); expect(responsesToolsSpan!.name).toBe('chat gpt-4'); expect(responsesToolsSpan!.status).toBe('ok'); - expect(responsesToolsSpan!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE]).toEqual({ + expect(responsesToolsSpan!.attributes[GEN_AI_OPERATION_NAME]).toEqual({ type: 'string', value: 'chat', }); @@ -493,11 +495,11 @@ describe('OpenAI Tool Calls integration', () => { type: 'string', value: 'auto.ai.openai', }); - expect(responsesToolsSpan!.attributes[GEN_AI_SYSTEM_ATTRIBUTE]).toEqual({ + expect(responsesToolsSpan!.attributes[GEN_AI_SYSTEM]).toEqual({ type: 'string', value: 'openai', }); - expect(responsesToolsSpan!.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE]).toEqual({ + expect(responsesToolsSpan!.attributes[GEN_AI_REQUEST_MODEL]).toEqual({ type: 'string', value: 'gpt-4', }); @@ -506,50 +508,50 @@ describe('OpenAI Tool Calls integration', () => { type: 'integer', value: 1, }); - expect(responsesToolsSpan!.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]).toEqual({ + expect(responsesToolsSpan!.attributes[GEN_AI_INPUT_MESSAGES]).toEqual({ type: 'string', value: '[{"role":"user","content":"What is the weather like in Paris today?"}]', }); - expect(responsesToolsSpan!.attributes[GEN_AI_REQUEST_AVAILABLE_TOOLS_ATTRIBUTE]).toEqual({ + expect(responsesToolsSpan!.attributes[GEN_AI_REQUEST_AVAILABLE_TOOLS]).toEqual({ type: 'string', value: WEATHER_TOOL_DEFINITION, }); - expect(responsesToolsSpan!.attributes[GEN_AI_RESPONSE_MODEL_ATTRIBUTE]).toEqual({ + expect(responsesToolsSpan!.attributes[GEN_AI_RESPONSE_MODEL]).toEqual({ type: 'string', value: 'gpt-4', }); - expect(responsesToolsSpan!.attributes[GEN_AI_RESPONSE_ID_ATTRIBUTE]).toEqual({ + expect(responsesToolsSpan!.attributes[GEN_AI_RESPONSE_ID]).toEqual({ type: 'string', value: 'resp_tools_789', }); - expect(responsesToolsSpan!.attributes[GEN_AI_RESPONSE_FINISH_REASONS_ATTRIBUTE]).toEqual({ + expect(responsesToolsSpan!.attributes[GEN_AI_RESPONSE_FINISH_REASONS]).toEqual({ type: 'string', value: '["completed"]', }); - expect(responsesToolsSpan!.attributes[GEN_AI_RESPONSE_TOOL_CALLS_ATTRIBUTE]).toEqual({ + expect(responsesToolsSpan!.attributes[GEN_AI_RESPONSE_TOOL_CALLS]).toEqual({ type: 'string', value: RESPONSES_TOOL_CALLS, }); - expect(responsesToolsSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]).toEqual({ + expect(responsesToolsSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS]).toEqual({ type: 'integer', value: 8, }); - expect(responsesToolsSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE]).toEqual({ + expect(responsesToolsSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS]).toEqual({ type: 'integer', value: 12, }); - expect(responsesToolsSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE]).toEqual({ + expect(responsesToolsSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS]).toEqual({ type: 'integer', value: 20, }); const streamingResponsesToolsSpan = container.items.find( - span => span.attributes[GEN_AI_RESPONSE_ID_ATTRIBUTE]?.value === 'resp_stream_tools_789', + span => span.attributes[GEN_AI_RESPONSE_ID]?.value === 'resp_stream_tools_789', ); expect(streamingResponsesToolsSpan).toBeDefined(); expect(streamingResponsesToolsSpan!.name).toBe('chat gpt-4'); expect(streamingResponsesToolsSpan!.status).toBe('ok'); - expect(streamingResponsesToolsSpan!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE]).toEqual({ + expect(streamingResponsesToolsSpan!.attributes[GEN_AI_OPERATION_NAME]).toEqual({ type: 'string', value: 'chat', }); @@ -561,11 +563,11 @@ describe('OpenAI Tool Calls integration', () => { type: 'string', value: 'auto.ai.openai', }); - expect(streamingResponsesToolsSpan!.attributes[GEN_AI_SYSTEM_ATTRIBUTE]).toEqual({ + expect(streamingResponsesToolsSpan!.attributes[GEN_AI_SYSTEM]).toEqual({ type: 'string', value: 'openai', }); - expect(streamingResponsesToolsSpan!.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE]).toEqual({ + expect(streamingResponsesToolsSpan!.attributes[GEN_AI_REQUEST_MODEL]).toEqual({ type: 'string', value: 'gpt-4', }); @@ -577,43 +579,43 @@ describe('OpenAI Tool Calls integration', () => { type: 'integer', value: 1, }); - expect(streamingResponsesToolsSpan!.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]).toEqual({ + expect(streamingResponsesToolsSpan!.attributes[GEN_AI_INPUT_MESSAGES]).toEqual({ type: 'string', value: '[{"role":"user","content":"What is the weather like in Paris today?"}]', }); - expect(streamingResponsesToolsSpan!.attributes[GEN_AI_REQUEST_AVAILABLE_TOOLS_ATTRIBUTE]).toEqual({ + expect(streamingResponsesToolsSpan!.attributes[GEN_AI_REQUEST_AVAILABLE_TOOLS]).toEqual({ type: 'string', value: WEATHER_TOOL_DEFINITION, }); - expect(streamingResponsesToolsSpan!.attributes[GEN_AI_RESPONSE_MODEL_ATTRIBUTE]).toEqual({ + expect(streamingResponsesToolsSpan!.attributes[GEN_AI_RESPONSE_MODEL]).toEqual({ type: 'string', value: 'gpt-4', }); - expect(streamingResponsesToolsSpan!.attributes[GEN_AI_RESPONSE_ID_ATTRIBUTE]).toEqual({ + expect(streamingResponsesToolsSpan!.attributes[GEN_AI_RESPONSE_ID]).toEqual({ type: 'string', value: 'resp_stream_tools_789', }); - expect(streamingResponsesToolsSpan!.attributes[GEN_AI_RESPONSE_FINISH_REASONS_ATTRIBUTE]).toEqual({ + expect(streamingResponsesToolsSpan!.attributes[GEN_AI_RESPONSE_FINISH_REASONS]).toEqual({ type: 'string', value: '["in_progress","completed"]', }); - expect(streamingResponsesToolsSpan!.attributes[GEN_AI_RESPONSE_STREAMING_ATTRIBUTE]).toEqual({ + expect(streamingResponsesToolsSpan!.attributes[GEN_AI_RESPONSE_STREAMING]).toEqual({ type: 'boolean', value: true, }); - expect(streamingResponsesToolsSpan!.attributes[GEN_AI_RESPONSE_TOOL_CALLS_ATTRIBUTE]).toEqual({ + expect(streamingResponsesToolsSpan!.attributes[GEN_AI_RESPONSE_TOOL_CALLS]).toEqual({ type: 'string', value: RESPONSES_TOOL_CALLS, }); - expect(streamingResponsesToolsSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]).toEqual({ + expect(streamingResponsesToolsSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS]).toEqual({ type: 'integer', value: 8, }); - expect(streamingResponsesToolsSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE]).toEqual({ + expect(streamingResponsesToolsSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS]).toEqual({ type: 'integer', value: 12, }); - expect(streamingResponsesToolsSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE]).toEqual({ + expect(streamingResponsesToolsSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS]).toEqual({ type: 'integer', value: 20, }); diff --git a/dev-packages/node-integration-tests/suites/tracing/openai/test.ts b/dev-packages/node-integration-tests/suites/tracing/openai/test.ts index 3f102d360e40..8f4c8c631bf1 100644 --- a/dev-packages/node-integration-tests/suites/tracing/openai/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/openai/test.ts @@ -1,26 +1,28 @@ import { SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '@sentry/core'; import { afterAll, describe, expect } from 'vitest'; import { - GEN_AI_CONVERSATION_ID_ATTRIBUTE, - GEN_AI_EMBEDDINGS_INPUT_ATTRIBUTE, - GEN_AI_INPUT_MESSAGES_ATTRIBUTE, + GEN_AI_CONVERSATION_ID, + GEN_AI_EMBEDDINGS_INPUT, + GEN_AI_INPUT_MESSAGES, + GEN_AI_OPERATION_NAME, + GEN_AI_REQUEST_MODEL, + GEN_AI_REQUEST_TEMPERATURE, + GEN_AI_RESPONSE_FINISH_REASONS, + GEN_AI_RESPONSE_ID, + GEN_AI_RESPONSE_MODEL, + GEN_AI_RESPONSE_STREAMING, + GEN_AI_RESPONSE_TEXT, + GEN_AI_SYSTEM, + GEN_AI_SYSTEM_INSTRUCTIONS, + GEN_AI_USAGE_INPUT_TOKENS, + GEN_AI_USAGE_OUTPUT_TOKENS, + GEN_AI_USAGE_TOTAL_TOKENS, +} from '@sentry/conventions/attributes'; +import { GEN_AI_INPUT_MESSAGES_ORIGINAL_LENGTH_ATTRIBUTE, - GEN_AI_OPERATION_NAME_ATTRIBUTE, GEN_AI_REQUEST_DIMENSIONS_ATTRIBUTE, GEN_AI_REQUEST_ENCODING_FORMAT_ATTRIBUTE, - GEN_AI_REQUEST_MODEL_ATTRIBUTE, GEN_AI_REQUEST_STREAM_ATTRIBUTE, - GEN_AI_REQUEST_TEMPERATURE_ATTRIBUTE, - GEN_AI_RESPONSE_FINISH_REASONS_ATTRIBUTE, - GEN_AI_RESPONSE_ID_ATTRIBUTE, - GEN_AI_RESPONSE_MODEL_ATTRIBUTE, - GEN_AI_RESPONSE_STREAMING_ATTRIBUTE, - GEN_AI_RESPONSE_TEXT_ATTRIBUTE, - GEN_AI_SYSTEM_ATTRIBUTE, - GEN_AI_SYSTEM_INSTRUCTIONS_ATTRIBUTE, - GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE, - GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE, - GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE, } from '../../../../../packages/core/src/tracing/ai/gen-ai-attributes'; import { getStringAttributeValue, isOrchestrionEnabled } from '../../../utils'; import { cleanupChildProcesses, createEsmAndCjsTests } from '../../../utils/runner'; @@ -39,12 +41,12 @@ describe('OpenAI integration', () => { span: container => { expect(container.items).toHaveLength(6); const chatCompletionSpan = container.items.find( - span => span.attributes[GEN_AI_RESPONSE_ID_ATTRIBUTE]?.value === 'chatcmpl-mock123', + span => span.attributes[GEN_AI_RESPONSE_ID]?.value === 'chatcmpl-mock123', ); expect(chatCompletionSpan).toBeDefined(); expect(chatCompletionSpan!.name).toBe('chat gpt-3.5-turbo'); expect(chatCompletionSpan!.status).toBe('ok'); - expect(chatCompletionSpan!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE]).toEqual({ + expect(chatCompletionSpan!.attributes[GEN_AI_OPERATION_NAME]).toEqual({ type: 'string', value: 'chat', }); @@ -56,50 +58,50 @@ describe('OpenAI integration', () => { type: 'string', value: isOrchestrionEnabled() ? 'auto.ai.orchestrion.openai' : 'auto.ai.openai', }); - expect(chatCompletionSpan!.attributes[GEN_AI_SYSTEM_ATTRIBUTE]).toEqual({ + expect(chatCompletionSpan!.attributes[GEN_AI_SYSTEM]).toEqual({ type: 'string', value: 'openai', }); - expect(chatCompletionSpan!.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE]).toEqual({ + expect(chatCompletionSpan!.attributes[GEN_AI_REQUEST_MODEL]).toEqual({ type: 'string', value: 'gpt-3.5-turbo', }); - expect(chatCompletionSpan!.attributes[GEN_AI_REQUEST_TEMPERATURE_ATTRIBUTE]).toEqual({ + expect(chatCompletionSpan!.attributes[GEN_AI_REQUEST_TEMPERATURE]).toEqual({ type: 'double', value: 0.7, }); - expect(chatCompletionSpan!.attributes[GEN_AI_RESPONSE_MODEL_ATTRIBUTE]).toEqual({ + expect(chatCompletionSpan!.attributes[GEN_AI_RESPONSE_MODEL]).toEqual({ type: 'string', value: 'gpt-3.5-turbo', }); - expect(chatCompletionSpan!.attributes[GEN_AI_RESPONSE_ID_ATTRIBUTE]).toEqual({ + expect(chatCompletionSpan!.attributes[GEN_AI_RESPONSE_ID]).toEqual({ type: 'string', value: 'chatcmpl-mock123', }); - expect(chatCompletionSpan!.attributes[GEN_AI_RESPONSE_FINISH_REASONS_ATTRIBUTE]).toEqual({ + expect(chatCompletionSpan!.attributes[GEN_AI_RESPONSE_FINISH_REASONS]).toEqual({ type: 'string', value: '["stop"]', }); - expect(chatCompletionSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]).toEqual({ + expect(chatCompletionSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS]).toEqual({ type: 'integer', value: 10, }); - expect(chatCompletionSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE]).toEqual({ + expect(chatCompletionSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS]).toEqual({ type: 'integer', value: 15, }); - expect(chatCompletionSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE]).toEqual({ + expect(chatCompletionSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS]).toEqual({ type: 'integer', value: 25, }); const responsesSpan = container.items.find( - span => span.attributes[GEN_AI_RESPONSE_ID_ATTRIBUTE]?.value === 'resp_mock456', + span => span.attributes[GEN_AI_RESPONSE_ID]?.value === 'resp_mock456', ); expect(responsesSpan).toBeDefined(); expect(responsesSpan!.name).toBe('chat gpt-3.5-turbo'); expect(responsesSpan!.status).toBe('ok'); - expect(responsesSpan!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE]).toEqual({ + expect(responsesSpan!.attributes[GEN_AI_OPERATION_NAME]).toEqual({ type: 'string', value: 'chat', }); @@ -111,32 +113,32 @@ describe('OpenAI integration', () => { type: 'string', value: isOrchestrionEnabled() ? 'auto.ai.orchestrion.openai' : 'auto.ai.openai', }); - expect(responsesSpan!.attributes[GEN_AI_SYSTEM_ATTRIBUTE]).toEqual({ type: 'string', value: 'openai' }); - expect(responsesSpan!.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE]).toEqual({ + expect(responsesSpan!.attributes[GEN_AI_SYSTEM]).toEqual({ type: 'string', value: 'openai' }); + expect(responsesSpan!.attributes[GEN_AI_REQUEST_MODEL]).toEqual({ type: 'string', value: 'gpt-3.5-turbo', }); - expect(responsesSpan!.attributes[GEN_AI_RESPONSE_MODEL_ATTRIBUTE]).toEqual({ + expect(responsesSpan!.attributes[GEN_AI_RESPONSE_MODEL]).toEqual({ type: 'string', value: 'gpt-3.5-turbo', }); - expect(responsesSpan!.attributes[GEN_AI_RESPONSE_ID_ATTRIBUTE]).toEqual({ + expect(responsesSpan!.attributes[GEN_AI_RESPONSE_ID]).toEqual({ type: 'string', value: 'resp_mock456', }); - expect(responsesSpan!.attributes[GEN_AI_RESPONSE_FINISH_REASONS_ATTRIBUTE]).toEqual({ + expect(responsesSpan!.attributes[GEN_AI_RESPONSE_FINISH_REASONS]).toEqual({ type: 'string', value: '["completed"]', }); - expect(responsesSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]).toEqual({ + expect(responsesSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS]).toEqual({ type: 'integer', value: 5, }); - expect(responsesSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE]).toEqual({ + expect(responsesSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS]).toEqual({ type: 'integer', value: 8, }); - expect(responsesSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE]).toEqual({ + expect(responsesSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS]).toEqual({ type: 'integer', value: 13, }); @@ -149,7 +151,7 @@ describe('OpenAI integration', () => { expect(nonStreamingErrorSpan!.name).toBe('chat error-model'); expect(nonStreamingErrorSpan!.status).toBe('error'); expect(nonStreamingErrorSpan!.attributes[GEN_AI_REQUEST_STREAM_ATTRIBUTE]).toBeUndefined(); - expect(nonStreamingErrorSpan!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE]).toEqual({ + expect(nonStreamingErrorSpan!.attributes[GEN_AI_OPERATION_NAME]).toEqual({ type: 'string', value: 'chat', }); @@ -161,22 +163,22 @@ describe('OpenAI integration', () => { type: 'string', value: isOrchestrionEnabled() ? 'auto.ai.orchestrion.openai' : 'auto.ai.openai', }); - expect(nonStreamingErrorSpan!.attributes[GEN_AI_SYSTEM_ATTRIBUTE]).toEqual({ + expect(nonStreamingErrorSpan!.attributes[GEN_AI_SYSTEM]).toEqual({ type: 'string', value: 'openai', }); - expect(nonStreamingErrorSpan!.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE]).toEqual({ + expect(nonStreamingErrorSpan!.attributes[GEN_AI_REQUEST_MODEL]).toEqual({ type: 'string', value: 'error-model', }); const streamingChatCompletionSpan = container.items.find( - span => span.attributes[GEN_AI_RESPONSE_ID_ATTRIBUTE]?.value === 'chatcmpl-stream-123', + span => span.attributes[GEN_AI_RESPONSE_ID]?.value === 'chatcmpl-stream-123', ); expect(streamingChatCompletionSpan).toBeDefined(); expect(streamingChatCompletionSpan!.name).toBe('chat gpt-4'); expect(streamingChatCompletionSpan!.status).toBe('ok'); - expect(streamingChatCompletionSpan!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE]).toEqual({ + expect(streamingChatCompletionSpan!.attributes[GEN_AI_OPERATION_NAME]).toEqual({ type: 'string', value: 'chat', }); @@ -188,15 +190,15 @@ describe('OpenAI integration', () => { type: 'string', value: isOrchestrionEnabled() ? 'auto.ai.orchestrion.openai' : 'auto.ai.openai', }); - expect(streamingChatCompletionSpan!.attributes[GEN_AI_SYSTEM_ATTRIBUTE]).toEqual({ + expect(streamingChatCompletionSpan!.attributes[GEN_AI_SYSTEM]).toEqual({ type: 'string', value: 'openai', }); - expect(streamingChatCompletionSpan!.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE]).toEqual({ + expect(streamingChatCompletionSpan!.attributes[GEN_AI_REQUEST_MODEL]).toEqual({ type: 'string', value: 'gpt-4', }); - expect(streamingChatCompletionSpan!.attributes[GEN_AI_REQUEST_TEMPERATURE_ATTRIBUTE]).toEqual({ + expect(streamingChatCompletionSpan!.attributes[GEN_AI_REQUEST_TEMPERATURE]).toEqual({ type: 'double', value: 0.8, }); @@ -204,42 +206,42 @@ describe('OpenAI integration', () => { type: 'boolean', value: true, }); - expect(streamingChatCompletionSpan!.attributes[GEN_AI_RESPONSE_MODEL_ATTRIBUTE]).toEqual({ + expect(streamingChatCompletionSpan!.attributes[GEN_AI_RESPONSE_MODEL]).toEqual({ type: 'string', value: 'gpt-4', }); - expect(streamingChatCompletionSpan!.attributes[GEN_AI_RESPONSE_ID_ATTRIBUTE]).toEqual({ + expect(streamingChatCompletionSpan!.attributes[GEN_AI_RESPONSE_ID]).toEqual({ type: 'string', value: 'chatcmpl-stream-123', }); - expect(streamingChatCompletionSpan!.attributes[GEN_AI_RESPONSE_FINISH_REASONS_ATTRIBUTE]).toEqual({ + expect(streamingChatCompletionSpan!.attributes[GEN_AI_RESPONSE_FINISH_REASONS]).toEqual({ type: 'string', value: '["stop"]', }); - expect(streamingChatCompletionSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]).toEqual({ + expect(streamingChatCompletionSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS]).toEqual({ type: 'integer', value: 12, }); - expect(streamingChatCompletionSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE]).toEqual({ + expect(streamingChatCompletionSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS]).toEqual({ type: 'integer', value: 18, }); - expect(streamingChatCompletionSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE]).toEqual({ + expect(streamingChatCompletionSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS]).toEqual({ type: 'integer', value: 30, }); - expect(streamingChatCompletionSpan!.attributes[GEN_AI_RESPONSE_STREAMING_ATTRIBUTE]).toEqual({ + expect(streamingChatCompletionSpan!.attributes[GEN_AI_RESPONSE_STREAMING]).toEqual({ type: 'boolean', value: true, }); const streamingResponsesSpan = container.items.find( - span => span.attributes[GEN_AI_RESPONSE_ID_ATTRIBUTE]?.value === 'resp_stream_456', + span => span.attributes[GEN_AI_RESPONSE_ID]?.value === 'resp_stream_456', ); expect(streamingResponsesSpan).toBeDefined(); expect(streamingResponsesSpan!.name).toBe('chat gpt-4'); expect(streamingResponsesSpan!.status).toBe('ok'); - expect(streamingResponsesSpan!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE]).toEqual({ + expect(streamingResponsesSpan!.attributes[GEN_AI_OPERATION_NAME]).toEqual({ type: 'string', value: 'chat', }); @@ -251,11 +253,11 @@ describe('OpenAI integration', () => { type: 'string', value: isOrchestrionEnabled() ? 'auto.ai.orchestrion.openai' : 'auto.ai.openai', }); - expect(streamingResponsesSpan!.attributes[GEN_AI_SYSTEM_ATTRIBUTE]).toEqual({ + expect(streamingResponsesSpan!.attributes[GEN_AI_SYSTEM]).toEqual({ type: 'string', value: 'openai', }); - expect(streamingResponsesSpan!.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE]).toEqual({ + expect(streamingResponsesSpan!.attributes[GEN_AI_REQUEST_MODEL]).toEqual({ type: 'string', value: 'gpt-4', }); @@ -263,31 +265,31 @@ describe('OpenAI integration', () => { type: 'boolean', value: true, }); - expect(streamingResponsesSpan!.attributes[GEN_AI_RESPONSE_MODEL_ATTRIBUTE]).toEqual({ + expect(streamingResponsesSpan!.attributes[GEN_AI_RESPONSE_MODEL]).toEqual({ type: 'string', value: 'gpt-4', }); - expect(streamingResponsesSpan!.attributes[GEN_AI_RESPONSE_ID_ATTRIBUTE]).toEqual({ + expect(streamingResponsesSpan!.attributes[GEN_AI_RESPONSE_ID]).toEqual({ type: 'string', value: 'resp_stream_456', }); - expect(streamingResponsesSpan!.attributes[GEN_AI_RESPONSE_FINISH_REASONS_ATTRIBUTE]).toEqual({ + expect(streamingResponsesSpan!.attributes[GEN_AI_RESPONSE_FINISH_REASONS]).toEqual({ type: 'string', value: '["in_progress","completed"]', }); - expect(streamingResponsesSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]).toEqual({ + expect(streamingResponsesSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS]).toEqual({ type: 'integer', value: 6, }); - expect(streamingResponsesSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE]).toEqual({ + expect(streamingResponsesSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS]).toEqual({ type: 'integer', value: 10, }); - expect(streamingResponsesSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE]).toEqual({ + expect(streamingResponsesSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS]).toEqual({ type: 'integer', value: 16, }); - expect(streamingResponsesSpan!.attributes[GEN_AI_RESPONSE_STREAMING_ATTRIBUTE]).toEqual({ + expect(streamingResponsesSpan!.attributes[GEN_AI_RESPONSE_STREAMING]).toEqual({ type: 'boolean', value: true, }); @@ -299,11 +301,11 @@ describe('OpenAI integration', () => { expect(streamingErrorSpan).toBeDefined(); expect(streamingErrorSpan!.name).toBe('chat error-model'); expect(streamingErrorSpan!.status).toBe('error'); - expect(streamingErrorSpan!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE]).toEqual({ + expect(streamingErrorSpan!.attributes[GEN_AI_OPERATION_NAME]).toEqual({ type: 'string', value: 'chat', }); - expect(streamingErrorSpan!.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE]).toEqual({ + expect(streamingErrorSpan!.attributes[GEN_AI_REQUEST_MODEL]).toEqual({ type: 'string', value: 'error-model', }); @@ -311,7 +313,7 @@ describe('OpenAI integration', () => { type: 'boolean', value: true, }); - expect(streamingErrorSpan!.attributes[GEN_AI_SYSTEM_ATTRIBUTE]).toEqual({ + expect(streamingErrorSpan!.attributes[GEN_AI_SYSTEM]).toEqual({ type: 'string', value: 'openai', }); @@ -339,12 +341,12 @@ describe('OpenAI integration', () => { span: container => { expect(container.items).toHaveLength(6); const chatCompletionSpan = container.items.find( - span => span.attributes[GEN_AI_RESPONSE_ID_ATTRIBUTE]?.value === 'chatcmpl-mock123', + span => span.attributes[GEN_AI_RESPONSE_ID]?.value === 'chatcmpl-mock123', ); expect(chatCompletionSpan).toBeDefined(); expect(chatCompletionSpan!.name).toBe('chat gpt-3.5-turbo'); expect(chatCompletionSpan!.status).toBe('ok'); - expect(chatCompletionSpan!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE]).toEqual({ + expect(chatCompletionSpan!.attributes[GEN_AI_OPERATION_NAME]).toEqual({ type: 'string', value: 'chat', }); @@ -356,15 +358,15 @@ describe('OpenAI integration', () => { type: 'string', value: isOrchestrionEnabled() ? 'auto.ai.orchestrion.openai' : 'auto.ai.openai', }); - expect(chatCompletionSpan!.attributes[GEN_AI_SYSTEM_ATTRIBUTE]).toEqual({ + expect(chatCompletionSpan!.attributes[GEN_AI_SYSTEM]).toEqual({ type: 'string', value: 'openai', }); - expect(chatCompletionSpan!.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE]).toEqual({ + expect(chatCompletionSpan!.attributes[GEN_AI_REQUEST_MODEL]).toEqual({ type: 'string', value: 'gpt-3.5-turbo', }); - expect(chatCompletionSpan!.attributes[GEN_AI_REQUEST_TEMPERATURE_ATTRIBUTE]).toEqual({ + expect(chatCompletionSpan!.attributes[GEN_AI_REQUEST_TEMPERATURE]).toEqual({ type: 'double', value: 0.7, }); @@ -372,50 +374,50 @@ describe('OpenAI integration', () => { type: 'integer', value: 1, }); - expect(chatCompletionSpan!.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]).toEqual({ + expect(chatCompletionSpan!.attributes[GEN_AI_INPUT_MESSAGES]).toEqual({ type: 'string', value: '[{"role":"user","content":"What is the capital of France?"}]', }); - expect(chatCompletionSpan!.attributes[GEN_AI_SYSTEM_INSTRUCTIONS_ATTRIBUTE]).toEqual({ + expect(chatCompletionSpan!.attributes[GEN_AI_SYSTEM_INSTRUCTIONS]).toEqual({ type: 'string', value: JSON.stringify([{ type: 'text', content: 'You are a helpful assistant.' }]), }); - expect(chatCompletionSpan!.attributes[GEN_AI_RESPONSE_MODEL_ATTRIBUTE]).toEqual({ + expect(chatCompletionSpan!.attributes[GEN_AI_RESPONSE_MODEL]).toEqual({ type: 'string', value: 'gpt-3.5-turbo', }); - expect(chatCompletionSpan!.attributes[GEN_AI_RESPONSE_ID_ATTRIBUTE]).toEqual({ + expect(chatCompletionSpan!.attributes[GEN_AI_RESPONSE_ID]).toEqual({ type: 'string', value: 'chatcmpl-mock123', }); - expect(chatCompletionSpan!.attributes[GEN_AI_RESPONSE_FINISH_REASONS_ATTRIBUTE]).toEqual({ + expect(chatCompletionSpan!.attributes[GEN_AI_RESPONSE_FINISH_REASONS]).toEqual({ type: 'string', value: '["stop"]', }); - expect(chatCompletionSpan!.attributes[GEN_AI_RESPONSE_TEXT_ATTRIBUTE]).toEqual({ + expect(chatCompletionSpan!.attributes[GEN_AI_RESPONSE_TEXT]).toEqual({ type: 'string', value: '["Hello from OpenAI mock!"]', }); - expect(chatCompletionSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]).toEqual({ + expect(chatCompletionSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS]).toEqual({ type: 'integer', value: 10, }); - expect(chatCompletionSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE]).toEqual({ + expect(chatCompletionSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS]).toEqual({ type: 'integer', value: 15, }); - expect(chatCompletionSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE]).toEqual({ + expect(chatCompletionSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS]).toEqual({ type: 'integer', value: 25, }); const responsesSpan = container.items.find( - span => span.attributes[GEN_AI_RESPONSE_ID_ATTRIBUTE]?.value === 'resp_mock456', + span => span.attributes[GEN_AI_RESPONSE_ID]?.value === 'resp_mock456', ); expect(responsesSpan).toBeDefined(); expect(responsesSpan!.name).toBe('chat gpt-3.5-turbo'); expect(responsesSpan!.status).toBe('ok'); - expect(responsesSpan!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE]).toEqual({ + expect(responsesSpan!.attributes[GEN_AI_OPERATION_NAME]).toEqual({ type: 'string', value: 'chat', }); @@ -427,8 +429,8 @@ describe('OpenAI integration', () => { type: 'string', value: isOrchestrionEnabled() ? 'auto.ai.orchestrion.openai' : 'auto.ai.openai', }); - expect(responsesSpan!.attributes[GEN_AI_SYSTEM_ATTRIBUTE]).toEqual({ type: 'string', value: 'openai' }); - expect(responsesSpan!.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE]).toEqual({ + expect(responsesSpan!.attributes[GEN_AI_SYSTEM]).toEqual({ type: 'string', value: 'openai' }); + expect(responsesSpan!.attributes[GEN_AI_REQUEST_MODEL]).toEqual({ type: 'string', value: 'gpt-3.5-turbo', }); @@ -436,35 +438,35 @@ describe('OpenAI integration', () => { type: 'integer', value: 1, }); - expect(responsesSpan!.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]).toEqual({ + expect(responsesSpan!.attributes[GEN_AI_INPUT_MESSAGES]).toEqual({ type: 'string', value: 'Translate this to French: Hello', }); - expect(responsesSpan!.attributes[GEN_AI_RESPONSE_TEXT_ATTRIBUTE]).toEqual({ + expect(responsesSpan!.attributes[GEN_AI_RESPONSE_TEXT]).toEqual({ type: 'string', value: 'Response to: Translate this to French: Hello', }); - expect(responsesSpan!.attributes[GEN_AI_RESPONSE_FINISH_REASONS_ATTRIBUTE]).toEqual({ + expect(responsesSpan!.attributes[GEN_AI_RESPONSE_FINISH_REASONS]).toEqual({ type: 'string', value: '["completed"]', }); - expect(responsesSpan!.attributes[GEN_AI_RESPONSE_MODEL_ATTRIBUTE]).toEqual({ + expect(responsesSpan!.attributes[GEN_AI_RESPONSE_MODEL]).toEqual({ type: 'string', value: 'gpt-3.5-turbo', }); - expect(responsesSpan!.attributes[GEN_AI_RESPONSE_ID_ATTRIBUTE]).toEqual({ + expect(responsesSpan!.attributes[GEN_AI_RESPONSE_ID]).toEqual({ type: 'string', value: 'resp_mock456', }); - expect(responsesSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]).toEqual({ + expect(responsesSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS]).toEqual({ type: 'integer', value: 5, }); - expect(responsesSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE]).toEqual({ + expect(responsesSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS]).toEqual({ type: 'integer', value: 8, }); - expect(responsesSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE]).toEqual({ + expect(responsesSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS]).toEqual({ type: 'integer', value: 13, }); @@ -477,7 +479,7 @@ describe('OpenAI integration', () => { expect(nonStreamingErrorSpan!.name).toBe('chat error-model'); expect(nonStreamingErrorSpan!.status).toBe('error'); expect(nonStreamingErrorSpan!.attributes[GEN_AI_REQUEST_STREAM_ATTRIBUTE]).toBeUndefined(); - expect(nonStreamingErrorSpan!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE]).toEqual({ + expect(nonStreamingErrorSpan!.attributes[GEN_AI_OPERATION_NAME]).toEqual({ type: 'string', value: 'chat', }); @@ -489,11 +491,11 @@ describe('OpenAI integration', () => { type: 'string', value: isOrchestrionEnabled() ? 'auto.ai.orchestrion.openai' : 'auto.ai.openai', }); - expect(nonStreamingErrorSpan!.attributes[GEN_AI_SYSTEM_ATTRIBUTE]).toEqual({ + expect(nonStreamingErrorSpan!.attributes[GEN_AI_SYSTEM]).toEqual({ type: 'string', value: 'openai', }); - expect(nonStreamingErrorSpan!.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE]).toEqual({ + expect(nonStreamingErrorSpan!.attributes[GEN_AI_REQUEST_MODEL]).toEqual({ type: 'string', value: 'error-model', }); @@ -501,18 +503,18 @@ describe('OpenAI integration', () => { type: 'integer', value: 1, }); - expect(nonStreamingErrorSpan!.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]).toEqual({ + expect(nonStreamingErrorSpan!.attributes[GEN_AI_INPUT_MESSAGES]).toEqual({ type: 'string', value: '[{"role":"user","content":"This will fail"}]', }); const streamingChatCompletionSpan = container.items.find( - span => span.attributes[GEN_AI_RESPONSE_ID_ATTRIBUTE]?.value === 'chatcmpl-stream-123', + span => span.attributes[GEN_AI_RESPONSE_ID]?.value === 'chatcmpl-stream-123', ); expect(streamingChatCompletionSpan).toBeDefined(); expect(streamingChatCompletionSpan!.name).toBe('chat gpt-4'); expect(streamingChatCompletionSpan!.status).toBe('ok'); - expect(streamingChatCompletionSpan!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE]).toEqual({ + expect(streamingChatCompletionSpan!.attributes[GEN_AI_OPERATION_NAME]).toEqual({ type: 'string', value: 'chat', }); @@ -524,15 +526,15 @@ describe('OpenAI integration', () => { type: 'string', value: isOrchestrionEnabled() ? 'auto.ai.orchestrion.openai' : 'auto.ai.openai', }); - expect(streamingChatCompletionSpan!.attributes[GEN_AI_SYSTEM_ATTRIBUTE]).toEqual({ + expect(streamingChatCompletionSpan!.attributes[GEN_AI_SYSTEM]).toEqual({ type: 'string', value: 'openai', }); - expect(streamingChatCompletionSpan!.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE]).toEqual({ + expect(streamingChatCompletionSpan!.attributes[GEN_AI_REQUEST_MODEL]).toEqual({ type: 'string', value: 'gpt-4', }); - expect(streamingChatCompletionSpan!.attributes[GEN_AI_REQUEST_TEMPERATURE_ATTRIBUTE]).toEqual({ + expect(streamingChatCompletionSpan!.attributes[GEN_AI_REQUEST_TEMPERATURE]).toEqual({ type: 'double', value: 0.8, }); @@ -544,54 +546,54 @@ describe('OpenAI integration', () => { type: 'integer', value: 1, }); - expect(streamingChatCompletionSpan!.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]).toEqual({ + expect(streamingChatCompletionSpan!.attributes[GEN_AI_INPUT_MESSAGES]).toEqual({ type: 'string', value: '[{"role":"user","content":"Tell me about streaming"}]', }); - expect(streamingChatCompletionSpan!.attributes[GEN_AI_SYSTEM_INSTRUCTIONS_ATTRIBUTE]).toEqual({ + expect(streamingChatCompletionSpan!.attributes[GEN_AI_SYSTEM_INSTRUCTIONS]).toEqual({ type: 'string', value: JSON.stringify([{ type: 'text', content: 'You are a helpful assistant.' }]), }); - expect(streamingChatCompletionSpan!.attributes[GEN_AI_RESPONSE_TEXT_ATTRIBUTE]).toEqual({ + expect(streamingChatCompletionSpan!.attributes[GEN_AI_RESPONSE_TEXT]).toEqual({ type: 'string', value: 'Hello from OpenAI streaming!', }); - expect(streamingChatCompletionSpan!.attributes[GEN_AI_RESPONSE_FINISH_REASONS_ATTRIBUTE]).toEqual({ + expect(streamingChatCompletionSpan!.attributes[GEN_AI_RESPONSE_FINISH_REASONS]).toEqual({ type: 'string', value: '["stop"]', }); - expect(streamingChatCompletionSpan!.attributes[GEN_AI_RESPONSE_ID_ATTRIBUTE]).toEqual({ + expect(streamingChatCompletionSpan!.attributes[GEN_AI_RESPONSE_ID]).toEqual({ type: 'string', value: 'chatcmpl-stream-123', }); - expect(streamingChatCompletionSpan!.attributes[GEN_AI_RESPONSE_MODEL_ATTRIBUTE]).toEqual({ + expect(streamingChatCompletionSpan!.attributes[GEN_AI_RESPONSE_MODEL]).toEqual({ type: 'string', value: 'gpt-4', }); - expect(streamingChatCompletionSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]).toEqual({ + expect(streamingChatCompletionSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS]).toEqual({ type: 'integer', value: 12, }); - expect(streamingChatCompletionSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE]).toEqual({ + expect(streamingChatCompletionSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS]).toEqual({ type: 'integer', value: 18, }); - expect(streamingChatCompletionSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE]).toEqual({ + expect(streamingChatCompletionSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS]).toEqual({ type: 'integer', value: 30, }); - expect(streamingChatCompletionSpan!.attributes[GEN_AI_RESPONSE_STREAMING_ATTRIBUTE]).toEqual({ + expect(streamingChatCompletionSpan!.attributes[GEN_AI_RESPONSE_STREAMING]).toEqual({ type: 'boolean', value: true, }); const streamingResponsesSpan = container.items.find( - span => span.attributes[GEN_AI_RESPONSE_ID_ATTRIBUTE]?.value === 'resp_stream_456', + span => span.attributes[GEN_AI_RESPONSE_ID]?.value === 'resp_stream_456', ); expect(streamingResponsesSpan).toBeDefined(); expect(streamingResponsesSpan!.name).toBe('chat gpt-4'); expect(streamingResponsesSpan!.status).toBe('ok'); - expect(streamingResponsesSpan!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE]).toEqual({ + expect(streamingResponsesSpan!.attributes[GEN_AI_OPERATION_NAME]).toEqual({ type: 'string', value: 'chat', }); @@ -603,11 +605,11 @@ describe('OpenAI integration', () => { type: 'string', value: isOrchestrionEnabled() ? 'auto.ai.orchestrion.openai' : 'auto.ai.openai', }); - expect(streamingResponsesSpan!.attributes[GEN_AI_SYSTEM_ATTRIBUTE]).toEqual({ + expect(streamingResponsesSpan!.attributes[GEN_AI_SYSTEM]).toEqual({ type: 'string', value: 'openai', }); - expect(streamingResponsesSpan!.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE]).toEqual({ + expect(streamingResponsesSpan!.attributes[GEN_AI_REQUEST_MODEL]).toEqual({ type: 'string', value: 'gpt-4', }); @@ -619,39 +621,39 @@ describe('OpenAI integration', () => { type: 'integer', value: 1, }); - expect(streamingResponsesSpan!.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]).toEqual({ + expect(streamingResponsesSpan!.attributes[GEN_AI_INPUT_MESSAGES]).toEqual({ type: 'string', value: 'Test streaming responses API', }); - expect(streamingResponsesSpan!.attributes[GEN_AI_RESPONSE_TEXT_ATTRIBUTE]).toEqual({ + expect(streamingResponsesSpan!.attributes[GEN_AI_RESPONSE_TEXT]).toEqual({ type: 'string', value: 'Streaming response to: Test streaming responses APITest streaming responses API', }); - expect(streamingResponsesSpan!.attributes[GEN_AI_RESPONSE_FINISH_REASONS_ATTRIBUTE]).toEqual({ + expect(streamingResponsesSpan!.attributes[GEN_AI_RESPONSE_FINISH_REASONS]).toEqual({ type: 'string', value: '["in_progress","completed"]', }); - expect(streamingResponsesSpan!.attributes[GEN_AI_RESPONSE_ID_ATTRIBUTE]).toEqual({ + expect(streamingResponsesSpan!.attributes[GEN_AI_RESPONSE_ID]).toEqual({ type: 'string', value: 'resp_stream_456', }); - expect(streamingResponsesSpan!.attributes[GEN_AI_RESPONSE_MODEL_ATTRIBUTE]).toEqual({ + expect(streamingResponsesSpan!.attributes[GEN_AI_RESPONSE_MODEL]).toEqual({ type: 'string', value: 'gpt-4', }); - expect(streamingResponsesSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]).toEqual({ + expect(streamingResponsesSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS]).toEqual({ type: 'integer', value: 6, }); - expect(streamingResponsesSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE]).toEqual({ + expect(streamingResponsesSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS]).toEqual({ type: 'integer', value: 10, }); - expect(streamingResponsesSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE]).toEqual({ + expect(streamingResponsesSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS]).toEqual({ type: 'integer', value: 16, }); - expect(streamingResponsesSpan!.attributes[GEN_AI_RESPONSE_STREAMING_ATTRIBUTE]).toEqual({ + expect(streamingResponsesSpan!.attributes[GEN_AI_RESPONSE_STREAMING]).toEqual({ type: 'boolean', value: true, }); @@ -663,11 +665,11 @@ describe('OpenAI integration', () => { expect(streamingErrorSpan).toBeDefined(); expect(streamingErrorSpan!.name).toBe('chat error-model'); expect(streamingErrorSpan!.status).toBe('error'); - expect(streamingErrorSpan!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE]).toEqual({ + expect(streamingErrorSpan!.attributes[GEN_AI_OPERATION_NAME]).toEqual({ type: 'string', value: 'chat', }); - expect(streamingErrorSpan!.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE]).toEqual({ + expect(streamingErrorSpan!.attributes[GEN_AI_REQUEST_MODEL]).toEqual({ type: 'string', value: 'error-model', }); @@ -679,11 +681,11 @@ describe('OpenAI integration', () => { type: 'integer', value: 1, }); - expect(streamingErrorSpan!.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]).toEqual({ + expect(streamingErrorSpan!.attributes[GEN_AI_INPUT_MESSAGES]).toEqual({ type: 'string', value: '[{"role":"user","content":"This will fail"}]', }); - expect(streamingErrorSpan!.attributes[GEN_AI_SYSTEM_ATTRIBUTE]).toEqual({ + expect(streamingErrorSpan!.attributes[GEN_AI_SYSTEM]).toEqual({ type: 'string', value: 'openai', }); @@ -711,32 +713,32 @@ describe('OpenAI integration', () => { span: container => { expect(container.items).toHaveLength(6); const chatCompletionSpan = container.items.find( - span => span.attributes[GEN_AI_RESPONSE_ID_ATTRIBUTE]?.value === 'chatcmpl-mock123', + span => span.attributes[GEN_AI_RESPONSE_ID]?.value === 'chatcmpl-mock123', ); expect(chatCompletionSpan).toBeDefined(); expect(chatCompletionSpan!.attributes[GEN_AI_REQUEST_STREAM_ATTRIBUTE]).toBeUndefined(); - expect(chatCompletionSpan!.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]).toMatchObject({ + expect(chatCompletionSpan!.attributes[GEN_AI_INPUT_MESSAGES]).toMatchObject({ type: 'string', value: expect.any(String), }); - expect(chatCompletionSpan!.attributes[GEN_AI_RESPONSE_TEXT_ATTRIBUTE]).toMatchObject({ + expect(chatCompletionSpan!.attributes[GEN_AI_RESPONSE_TEXT]).toMatchObject({ type: 'string', value: expect.any(String), }); const streamingChatCompletionSpan = container.items.find( - span => span.attributes[GEN_AI_RESPONSE_ID_ATTRIBUTE]?.value === 'chatcmpl-stream-123', + span => span.attributes[GEN_AI_RESPONSE_ID]?.value === 'chatcmpl-stream-123', ); expect(streamingChatCompletionSpan).toBeDefined(); expect(streamingChatCompletionSpan!.attributes[GEN_AI_REQUEST_STREAM_ATTRIBUTE]).toEqual({ type: 'boolean', value: true, }); - expect(streamingChatCompletionSpan!.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]).toMatchObject({ + expect(streamingChatCompletionSpan!.attributes[GEN_AI_INPUT_MESSAGES]).toMatchObject({ type: 'string', value: expect.any(String), }); - expect(streamingChatCompletionSpan!.attributes[GEN_AI_RESPONSE_TEXT_ATTRIBUTE]).toMatchObject({ + expect(streamingChatCompletionSpan!.attributes[GEN_AI_RESPONSE_TEXT]).toMatchObject({ type: 'string', value: expect.any(String), }); @@ -766,14 +768,14 @@ describe('OpenAI integration', () => { span: container => { expect(container.items).toHaveLength(2); const chatCompletionSpan = container.items.find( - span => span.attributes[GEN_AI_RESPONSE_ID_ATTRIBUTE]?.value === 'chatcmpl-mock123', + span => span.attributes[GEN_AI_RESPONSE_ID]?.value === 'chatcmpl-mock123', ); expect(chatCompletionSpan).toBeDefined(); - expect(chatCompletionSpan!.attributes[GEN_AI_RESPONSE_ID_ATTRIBUTE]).toEqual({ + expect(chatCompletionSpan!.attributes[GEN_AI_RESPONSE_ID]).toEqual({ type: 'string', value: 'chatcmpl-mock123', }); - expect(chatCompletionSpan!.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]).toMatchObject({ + expect(chatCompletionSpan!.attributes[GEN_AI_INPUT_MESSAGES]).toMatchObject({ type: 'string', value: JSON.stringify([ { role: 'user', content: longContent }, @@ -787,14 +789,14 @@ describe('OpenAI integration', () => { }); const responsesSpan = container.items.find( - span => span.attributes[GEN_AI_RESPONSE_ID_ATTRIBUTE]?.value === 'resp_mock456', + span => span.attributes[GEN_AI_RESPONSE_ID]?.value === 'resp_mock456', ); expect(responsesSpan).toBeDefined(); - expect(responsesSpan!.attributes[GEN_AI_RESPONSE_ID_ATTRIBUTE]).toEqual({ + expect(responsesSpan!.attributes[GEN_AI_RESPONSE_ID]).toEqual({ type: 'string', value: 'resp_mock456', }); - expect(responsesSpan!.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]).toMatchObject({ + expect(responsesSpan!.attributes[GEN_AI_INPUT_MESSAGES]).toMatchObject({ type: 'string', value: 'B'.repeat(50_000), }); @@ -830,7 +832,7 @@ describe('OpenAI integration', () => { expect(singleEmbeddingSpan).toBeDefined(); expect(singleEmbeddingSpan!.name).toBe('embeddings text-embedding-3-small'); expect(singleEmbeddingSpan!.status).toBe('ok'); - expect(singleEmbeddingSpan!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE]).toEqual({ + expect(singleEmbeddingSpan!.attributes[GEN_AI_OPERATION_NAME]).toEqual({ type: 'string', value: 'embeddings', }); @@ -842,11 +844,11 @@ describe('OpenAI integration', () => { type: 'string', value: isOrchestrionEnabled() ? 'auto.ai.orchestrion.openai' : 'auto.ai.openai', }); - expect(singleEmbeddingSpan!.attributes[GEN_AI_SYSTEM_ATTRIBUTE]).toEqual({ + expect(singleEmbeddingSpan!.attributes[GEN_AI_SYSTEM]).toEqual({ type: 'string', value: 'openai', }); - expect(singleEmbeddingSpan!.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE]).toEqual({ + expect(singleEmbeddingSpan!.attributes[GEN_AI_REQUEST_MODEL]).toEqual({ type: 'string', value: 'text-embedding-3-small', }); @@ -858,15 +860,15 @@ describe('OpenAI integration', () => { type: 'integer', value: 1536, }); - expect(singleEmbeddingSpan!.attributes[GEN_AI_RESPONSE_MODEL_ATTRIBUTE]).toEqual({ + expect(singleEmbeddingSpan!.attributes[GEN_AI_RESPONSE_MODEL]).toEqual({ type: 'string', value: 'text-embedding-3-small', }); - expect(singleEmbeddingSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]).toEqual({ + expect(singleEmbeddingSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS]).toEqual({ type: 'integer', value: 10, }); - expect(singleEmbeddingSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE]).toEqual({ + expect(singleEmbeddingSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS]).toEqual({ type: 'integer', value: 10, }); @@ -875,7 +877,7 @@ describe('OpenAI integration', () => { expect(errorEmbeddingSpan).toBeDefined(); expect(errorEmbeddingSpan!.name).toBe('embeddings error-model'); expect(errorEmbeddingSpan!.status).toBe('error'); - expect(errorEmbeddingSpan!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE]).toEqual({ + expect(errorEmbeddingSpan!.attributes[GEN_AI_OPERATION_NAME]).toEqual({ type: 'string', value: 'embeddings', }); @@ -887,11 +889,11 @@ describe('OpenAI integration', () => { type: 'string', value: isOrchestrionEnabled() ? 'auto.ai.orchestrion.openai' : 'auto.ai.openai', }); - expect(errorEmbeddingSpan!.attributes[GEN_AI_SYSTEM_ATTRIBUTE]).toEqual({ + expect(errorEmbeddingSpan!.attributes[GEN_AI_SYSTEM]).toEqual({ type: 'string', value: 'openai', }); - expect(errorEmbeddingSpan!.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE]).toEqual({ + expect(errorEmbeddingSpan!.attributes[GEN_AI_REQUEST_MODEL]).toEqual({ type: 'string', value: 'error-model', }); @@ -904,7 +906,7 @@ describe('OpenAI integration', () => { expect(multiEmbeddingSpan).toBeDefined(); expect(multiEmbeddingSpan!.name).toBe('embeddings text-embedding-3-small'); expect(multiEmbeddingSpan!.status).toBe('ok'); - expect(multiEmbeddingSpan!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE]).toEqual({ + expect(multiEmbeddingSpan!.attributes[GEN_AI_OPERATION_NAME]).toEqual({ type: 'string', value: 'embeddings', }); @@ -916,23 +918,23 @@ describe('OpenAI integration', () => { type: 'string', value: isOrchestrionEnabled() ? 'auto.ai.orchestrion.openai' : 'auto.ai.openai', }); - expect(multiEmbeddingSpan!.attributes[GEN_AI_SYSTEM_ATTRIBUTE]).toEqual({ + expect(multiEmbeddingSpan!.attributes[GEN_AI_SYSTEM]).toEqual({ type: 'string', value: 'openai', }); - expect(multiEmbeddingSpan!.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE]).toEqual({ + expect(multiEmbeddingSpan!.attributes[GEN_AI_REQUEST_MODEL]).toEqual({ type: 'string', value: 'text-embedding-3-small', }); - expect(multiEmbeddingSpan!.attributes[GEN_AI_RESPONSE_MODEL_ATTRIBUTE]).toEqual({ + expect(multiEmbeddingSpan!.attributes[GEN_AI_RESPONSE_MODEL]).toEqual({ type: 'string', value: 'text-embedding-3-small', }); - expect(multiEmbeddingSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]).toEqual({ + expect(multiEmbeddingSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS]).toEqual({ type: 'integer', value: 10, }); - expect(multiEmbeddingSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE]).toEqual({ + expect(multiEmbeddingSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS]).toEqual({ type: 'integer', value: 10, }); @@ -956,12 +958,12 @@ describe('OpenAI integration', () => { span: container => { expect(container.items).toHaveLength(3); const singleEmbeddingSpan = container.items.find( - span => span.attributes[GEN_AI_EMBEDDINGS_INPUT_ATTRIBUTE]?.value === 'Embedding test!', + span => span.attributes[GEN_AI_EMBEDDINGS_INPUT]?.value === 'Embedding test!', ); expect(singleEmbeddingSpan).toBeDefined(); expect(singleEmbeddingSpan!.name).toBe('embeddings text-embedding-3-small'); expect(singleEmbeddingSpan!.status).toBe('ok'); - expect(singleEmbeddingSpan!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE]).toEqual({ + expect(singleEmbeddingSpan!.attributes[GEN_AI_OPERATION_NAME]).toEqual({ type: 'string', value: 'embeddings', }); @@ -973,11 +975,11 @@ describe('OpenAI integration', () => { type: 'string', value: isOrchestrionEnabled() ? 'auto.ai.orchestrion.openai' : 'auto.ai.openai', }); - expect(singleEmbeddingSpan!.attributes[GEN_AI_SYSTEM_ATTRIBUTE]).toEqual({ + expect(singleEmbeddingSpan!.attributes[GEN_AI_SYSTEM]).toEqual({ type: 'string', value: 'openai', }); - expect(singleEmbeddingSpan!.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE]).toEqual({ + expect(singleEmbeddingSpan!.attributes[GEN_AI_REQUEST_MODEL]).toEqual({ type: 'string', value: 'text-embedding-3-small', }); @@ -989,30 +991,30 @@ describe('OpenAI integration', () => { type: 'integer', value: 1536, }); - expect(singleEmbeddingSpan!.attributes[GEN_AI_EMBEDDINGS_INPUT_ATTRIBUTE]).toEqual({ + expect(singleEmbeddingSpan!.attributes[GEN_AI_EMBEDDINGS_INPUT]).toEqual({ type: 'string', value: 'Embedding test!', }); - expect(singleEmbeddingSpan!.attributes[GEN_AI_RESPONSE_MODEL_ATTRIBUTE]).toEqual({ + expect(singleEmbeddingSpan!.attributes[GEN_AI_RESPONSE_MODEL]).toEqual({ type: 'string', value: 'text-embedding-3-small', }); - expect(singleEmbeddingSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]).toEqual({ + expect(singleEmbeddingSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS]).toEqual({ type: 'integer', value: 10, }); - expect(singleEmbeddingSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE]).toEqual({ + expect(singleEmbeddingSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS]).toEqual({ type: 'integer', value: 10, }); const errorEmbeddingSpan = container.items.find( - span => span.attributes[GEN_AI_EMBEDDINGS_INPUT_ATTRIBUTE]?.value === 'Error embedding test!', + span => span.attributes[GEN_AI_EMBEDDINGS_INPUT]?.value === 'Error embedding test!', ); expect(errorEmbeddingSpan).toBeDefined(); expect(errorEmbeddingSpan!.name).toBe('embeddings error-model'); expect(errorEmbeddingSpan!.status).toBe('error'); - expect(errorEmbeddingSpan!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE]).toEqual({ + expect(errorEmbeddingSpan!.attributes[GEN_AI_OPERATION_NAME]).toEqual({ type: 'string', value: 'embeddings', }); @@ -1024,28 +1026,28 @@ describe('OpenAI integration', () => { type: 'string', value: isOrchestrionEnabled() ? 'auto.ai.orchestrion.openai' : 'auto.ai.openai', }); - expect(errorEmbeddingSpan!.attributes[GEN_AI_SYSTEM_ATTRIBUTE]).toEqual({ + expect(errorEmbeddingSpan!.attributes[GEN_AI_SYSTEM]).toEqual({ type: 'string', value: 'openai', }); - expect(errorEmbeddingSpan!.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE]).toEqual({ + expect(errorEmbeddingSpan!.attributes[GEN_AI_REQUEST_MODEL]).toEqual({ type: 'string', value: 'error-model', }); - expect(errorEmbeddingSpan!.attributes[GEN_AI_EMBEDDINGS_INPUT_ATTRIBUTE]).toEqual({ + expect(errorEmbeddingSpan!.attributes[GEN_AI_EMBEDDINGS_INPUT]).toEqual({ type: 'string', value: 'Error embedding test!', }); const multiEmbeddingSpan = container.items.find( span => - span.attributes[GEN_AI_EMBEDDINGS_INPUT_ATTRIBUTE]?.value === + span.attributes[GEN_AI_EMBEDDINGS_INPUT]?.value === '["First input text","Second input text","Third input text"]', ); expect(multiEmbeddingSpan).toBeDefined(); expect(multiEmbeddingSpan!.name).toBe('embeddings text-embedding-3-small'); expect(multiEmbeddingSpan!.status).toBe('ok'); - expect(multiEmbeddingSpan!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE]).toEqual({ + expect(multiEmbeddingSpan!.attributes[GEN_AI_OPERATION_NAME]).toEqual({ type: 'string', value: 'embeddings', }); @@ -1057,27 +1059,27 @@ describe('OpenAI integration', () => { type: 'string', value: isOrchestrionEnabled() ? 'auto.ai.orchestrion.openai' : 'auto.ai.openai', }); - expect(multiEmbeddingSpan!.attributes[GEN_AI_SYSTEM_ATTRIBUTE]).toEqual({ + expect(multiEmbeddingSpan!.attributes[GEN_AI_SYSTEM]).toEqual({ type: 'string', value: 'openai', }); - expect(multiEmbeddingSpan!.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE]).toEqual({ + expect(multiEmbeddingSpan!.attributes[GEN_AI_REQUEST_MODEL]).toEqual({ type: 'string', value: 'text-embedding-3-small', }); - expect(multiEmbeddingSpan!.attributes[GEN_AI_EMBEDDINGS_INPUT_ATTRIBUTE]).toEqual({ + expect(multiEmbeddingSpan!.attributes[GEN_AI_EMBEDDINGS_INPUT]).toEqual({ type: 'string', value: '["First input text","Second input text","Third input text"]', }); - expect(multiEmbeddingSpan!.attributes[GEN_AI_RESPONSE_MODEL_ATTRIBUTE]).toEqual({ + expect(multiEmbeddingSpan!.attributes[GEN_AI_RESPONSE_MODEL]).toEqual({ type: 'string', value: 'text-embedding-3-small', }); - expect(multiEmbeddingSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]).toEqual({ + expect(multiEmbeddingSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS]).toEqual({ type: 'integer', value: 10, }); - expect(multiEmbeddingSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE]).toEqual({ + expect(multiEmbeddingSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS]).toEqual({ type: 'integer', value: 10, }); @@ -1105,20 +1107,20 @@ describe('OpenAI integration', () => { span_id: expect.any(String), trace_id: expect.any(String), data: { - [GEN_AI_OPERATION_NAME_ATTRIBUTE]: 'chat', + [GEN_AI_OPERATION_NAME]: 'chat', [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'gen_ai.chat', [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: isOrchestrionEnabled() ? 'auto.ai.orchestrion.openai' : 'auto.ai.openai', - [GEN_AI_SYSTEM_ATTRIBUTE]: 'openai', - [GEN_AI_REQUEST_MODEL_ATTRIBUTE]: 'gpt-3.5-turbo', - [GEN_AI_REQUEST_TEMPERATURE_ATTRIBUTE]: 0.7, - [GEN_AI_RESPONSE_MODEL_ATTRIBUTE]: 'gpt-3.5-turbo', - [GEN_AI_RESPONSE_ID_ATTRIBUTE]: 'chatcmpl-mock123', - [GEN_AI_RESPONSE_FINISH_REASONS_ATTRIBUTE]: '["stop"]', - [GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]: 10, - [GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE]: 15, - [GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE]: 25, + [GEN_AI_SYSTEM]: 'openai', + [GEN_AI_REQUEST_MODEL]: 'gpt-3.5-turbo', + [GEN_AI_REQUEST_TEMPERATURE]: 0.7, + [GEN_AI_RESPONSE_MODEL]: 'gpt-3.5-turbo', + [GEN_AI_RESPONSE_ID]: 'chatcmpl-mock123', + [GEN_AI_RESPONSE_FINISH_REASONS]: '["stop"]', + [GEN_AI_USAGE_INPUT_TOKENS]: 10, + [GEN_AI_USAGE_OUTPUT_TOKENS]: 15, + [GEN_AI_USAGE_TOTAL_TOKENS]: 25, }, op: 'gen_ai.chat', origin: isOrchestrionEnabled() ? 'auto.ai.orchestrion.openai' : 'auto.ai.openai', @@ -1149,20 +1151,20 @@ describe('OpenAI integration', () => { span_id: expect.any(String), trace_id: expect.any(String), data: { - [GEN_AI_OPERATION_NAME_ATTRIBUTE]: 'chat', + [GEN_AI_OPERATION_NAME]: 'chat', [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'gen_ai.chat', [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: isOrchestrionEnabled() ? 'auto.ai.orchestrion.openai' : 'auto.ai.openai', - [GEN_AI_SYSTEM_ATTRIBUTE]: 'openai', - [GEN_AI_REQUEST_MODEL_ATTRIBUTE]: 'gpt-3.5-turbo', - [GEN_AI_REQUEST_TEMPERATURE_ATTRIBUTE]: 0.7, - [GEN_AI_RESPONSE_MODEL_ATTRIBUTE]: 'gpt-3.5-turbo', - [GEN_AI_RESPONSE_ID_ATTRIBUTE]: 'chatcmpl-mock123', - [GEN_AI_RESPONSE_FINISH_REASONS_ATTRIBUTE]: '["stop"]', - [GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]: 10, - [GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE]: 15, - [GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE]: 25, + [GEN_AI_SYSTEM]: 'openai', + [GEN_AI_REQUEST_MODEL]: 'gpt-3.5-turbo', + [GEN_AI_REQUEST_TEMPERATURE]: 0.7, + [GEN_AI_RESPONSE_MODEL]: 'gpt-3.5-turbo', + [GEN_AI_RESPONSE_ID]: 'chatcmpl-mock123', + [GEN_AI_RESPONSE_FINISH_REASONS]: '["stop"]', + [GEN_AI_USAGE_INPUT_TOKENS]: 10, + [GEN_AI_USAGE_OUTPUT_TOKENS]: 15, + [GEN_AI_USAGE_TOTAL_TOKENS]: 25, }, op: 'gen_ai.chat', origin: isOrchestrionEnabled() ? 'auto.ai.orchestrion.openai' : 'auto.ai.openai', @@ -1193,14 +1195,14 @@ describe('OpenAI integration', () => { span: container => { expect(container.items).toHaveLength(2); const truncatedMessageSpan = container.items.find(span => - getStringAttributeValue(span.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value)?.match( + getStringAttributeValue(span.attributes[GEN_AI_INPUT_MESSAGES]?.value)?.match( /^\[\{"role":"user","content":"C+"\}\]$/, ), ); expect(truncatedMessageSpan).toBeDefined(); expect(truncatedMessageSpan!.name).toBe('chat gpt-3.5-turbo'); expect(truncatedMessageSpan!.status).toBe('ok'); - expect(truncatedMessageSpan!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE]).toEqual({ + expect(truncatedMessageSpan!.attributes[GEN_AI_OPERATION_NAME]).toEqual({ type: 'string', value: 'chat', }); @@ -1212,11 +1214,11 @@ describe('OpenAI integration', () => { type: 'string', value: 'auto.ai.openai', }); - expect(truncatedMessageSpan!.attributes[GEN_AI_SYSTEM_ATTRIBUTE]).toEqual({ + expect(truncatedMessageSpan!.attributes[GEN_AI_SYSTEM]).toEqual({ type: 'string', value: 'openai', }); - expect(truncatedMessageSpan!.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE]).toEqual({ + expect(truncatedMessageSpan!.attributes[GEN_AI_REQUEST_MODEL]).toEqual({ type: 'string', value: 'gpt-3.5-turbo', }); @@ -1224,22 +1226,22 @@ describe('OpenAI integration', () => { type: 'integer', value: 2, }); - expect(truncatedMessageSpan!.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE].value).toMatch( + expect(truncatedMessageSpan!.attributes[GEN_AI_INPUT_MESSAGES].value).toMatch( /^\[\{"role":"user","content":"C+"\}\]$/, ); - expect(truncatedMessageSpan!.attributes[GEN_AI_SYSTEM_INSTRUCTIONS_ATTRIBUTE].value).toMatch( + expect(truncatedMessageSpan!.attributes[GEN_AI_SYSTEM_INSTRUCTIONS].value).toMatch( /^\[\{"type":"text","content":"A+"\}\]$/, ); const smallMessageSpan = container.items.find( span => - span.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value === + span.attributes[GEN_AI_INPUT_MESSAGES]?.value === JSON.stringify([{ role: 'user', content: 'This is a small message that fits within the limit' }]), ); expect(smallMessageSpan).toBeDefined(); expect(smallMessageSpan!.name).toBe('chat gpt-3.5-turbo'); expect(smallMessageSpan!.status).toBe('ok'); - expect(smallMessageSpan!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE]).toEqual({ + expect(smallMessageSpan!.attributes[GEN_AI_OPERATION_NAME]).toEqual({ type: 'string', value: 'chat', }); @@ -1251,15 +1253,15 @@ describe('OpenAI integration', () => { type: 'string', value: 'auto.ai.openai', }); - expect(smallMessageSpan!.attributes[GEN_AI_SYSTEM_ATTRIBUTE]).toEqual({ + expect(smallMessageSpan!.attributes[GEN_AI_SYSTEM]).toEqual({ type: 'string', value: 'openai', }); - expect(smallMessageSpan!.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE]).toEqual({ + expect(smallMessageSpan!.attributes[GEN_AI_REQUEST_MODEL]).toEqual({ type: 'string', value: 'gpt-3.5-turbo', }); - expect(smallMessageSpan!.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]).toEqual({ + expect(smallMessageSpan!.attributes[GEN_AI_INPUT_MESSAGES]).toEqual({ type: 'string', value: JSON.stringify([ { role: 'user', content: 'This is a small message that fits within the limit' }, @@ -1269,7 +1271,7 @@ describe('OpenAI integration', () => { type: 'integer', value: 2, }); - expect(smallMessageSpan!.attributes[GEN_AI_SYSTEM_INSTRUCTIONS_ATTRIBUTE].value).toMatch( + expect(smallMessageSpan!.attributes[GEN_AI_SYSTEM_INSTRUCTIONS].value).toMatch( /^\[\{"type":"text","content":"A+"\}\]$/, ); }, @@ -1301,7 +1303,7 @@ describe('OpenAI integration', () => { // [0] long A-string input is truncated expect(firstSpan!.name).toBe('chat gpt-3.5-turbo'); expect(firstSpan!.status).toBe('ok'); - expect(firstSpan!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE]).toEqual({ type: 'string', value: 'chat' }); + expect(firstSpan!.attributes[GEN_AI_OPERATION_NAME]).toEqual({ type: 'string', value: 'chat' }); expect(firstSpan!.attributes[SEMANTIC_ATTRIBUTE_SENTRY_OP]).toEqual({ type: 'string', value: 'gen_ai.chat', @@ -1310,8 +1312,8 @@ describe('OpenAI integration', () => { type: 'string', value: 'auto.ai.openai', }); - expect(firstSpan!.attributes[GEN_AI_SYSTEM_ATTRIBUTE]).toEqual({ type: 'string', value: 'openai' }); - expect(firstSpan!.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE]).toEqual({ + expect(firstSpan!.attributes[GEN_AI_SYSTEM]).toEqual({ type: 'string', value: 'openai' }); + expect(firstSpan!.attributes[GEN_AI_REQUEST_MODEL]).toEqual({ type: 'string', value: 'gpt-3.5-turbo', }); @@ -1319,7 +1321,7 @@ describe('OpenAI integration', () => { type: 'integer', value: 1, }); - expect(firstSpan!.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE].value).toMatch(/^A+$/); + expect(firstSpan!.attributes[GEN_AI_INPUT_MESSAGES].value).toMatch(/^A+$/); }, }) .start() @@ -1345,7 +1347,7 @@ describe('OpenAI integration', () => { expect(conversationCreateSpan).toBeDefined(); expect(conversationCreateSpan!.name).toBe('chat unknown'); expect(conversationCreateSpan!.status).toBe('ok'); - expect(conversationCreateSpan!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE]).toEqual({ + expect(conversationCreateSpan!.attributes[GEN_AI_OPERATION_NAME]).toEqual({ type: 'string', value: 'chat', }); @@ -1357,24 +1359,24 @@ describe('OpenAI integration', () => { type: 'string', value: isOrchestrionEnabled() ? 'auto.ai.orchestrion.openai' : 'auto.ai.openai', }); - expect(conversationCreateSpan!.attributes[GEN_AI_SYSTEM_ATTRIBUTE]).toEqual({ + expect(conversationCreateSpan!.attributes[GEN_AI_SYSTEM]).toEqual({ type: 'string', value: 'openai', }); - expect(conversationCreateSpan!.attributes[GEN_AI_CONVERSATION_ID_ATTRIBUTE]).toEqual({ + expect(conversationCreateSpan!.attributes[GEN_AI_CONVERSATION_ID]).toEqual({ type: 'string', value: 'conv_689667905b048191b4740501625afd940c7533ace33a2dab', }); const conversationResponseSpan = container.items.find( span => - span.attributes[GEN_AI_CONVERSATION_ID_ATTRIBUTE]?.value === + span.attributes[GEN_AI_CONVERSATION_ID]?.value === 'conv_689667905b048191b4740501625afd940c7533ace33a2dab' && - span.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE]?.value === 'gpt-4', + span.attributes[GEN_AI_REQUEST_MODEL]?.value === 'gpt-4', ); expect(conversationResponseSpan).toBeDefined(); expect(conversationResponseSpan!.status).toBe('ok'); - expect(conversationResponseSpan!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE]).toEqual({ + expect(conversationResponseSpan!.attributes[GEN_AI_OPERATION_NAME]).toEqual({ type: 'string', value: 'chat', }); @@ -1386,15 +1388,15 @@ describe('OpenAI integration', () => { type: 'string', value: isOrchestrionEnabled() ? 'auto.ai.orchestrion.openai' : 'auto.ai.openai', }); - expect(conversationResponseSpan!.attributes[GEN_AI_SYSTEM_ATTRIBUTE]).toEqual({ + expect(conversationResponseSpan!.attributes[GEN_AI_SYSTEM]).toEqual({ type: 'string', value: 'openai', }); - expect(conversationResponseSpan!.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE]).toEqual({ + expect(conversationResponseSpan!.attributes[GEN_AI_REQUEST_MODEL]).toEqual({ type: 'string', value: 'gpt-4', }); - expect(conversationResponseSpan!.attributes[GEN_AI_CONVERSATION_ID_ATTRIBUTE]).toEqual({ + expect(conversationResponseSpan!.attributes[GEN_AI_CONVERSATION_ID]).toEqual({ type: 'string', value: 'conv_689667905b048191b4740501625afd940c7533ace33a2dab', }); @@ -1402,7 +1404,7 @@ describe('OpenAI integration', () => { const unlinkedResponseSpan = container.items.find( span => span.attributes[SEMANTIC_ATTRIBUTE_SENTRY_OP]?.value === 'gen_ai.chat' && - span.attributes[GEN_AI_CONVERSATION_ID_ATTRIBUTE] === undefined, + span.attributes[GEN_AI_CONVERSATION_ID] === undefined, ); expect(unlinkedResponseSpan).toBeDefined(); expect(unlinkedResponseSpan!.status).toBe('ok'); @@ -1410,14 +1412,14 @@ describe('OpenAI integration', () => { type: 'string', value: 'gen_ai.chat', }); - expect(unlinkedResponseSpan!.attributes[GEN_AI_CONVERSATION_ID_ATTRIBUTE]).toBeUndefined(); + expect(unlinkedResponseSpan!.attributes[GEN_AI_CONVERSATION_ID]).toBeUndefined(); const previousResponseSpan = container.items.find( - span => span.attributes[GEN_AI_CONVERSATION_ID_ATTRIBUTE]?.value === 'resp_mock_conv_123', + span => span.attributes[GEN_AI_CONVERSATION_ID]?.value === 'resp_mock_conv_123', ); expect(previousResponseSpan).toBeDefined(); expect(previousResponseSpan!.status).toBe('ok'); - expect(previousResponseSpan!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE]).toEqual({ + expect(previousResponseSpan!.attributes[GEN_AI_OPERATION_NAME]).toEqual({ type: 'string', value: 'chat', }); @@ -1429,15 +1431,15 @@ describe('OpenAI integration', () => { type: 'string', value: isOrchestrionEnabled() ? 'auto.ai.orchestrion.openai' : 'auto.ai.openai', }); - expect(previousResponseSpan!.attributes[GEN_AI_SYSTEM_ATTRIBUTE]).toEqual({ + expect(previousResponseSpan!.attributes[GEN_AI_SYSTEM]).toEqual({ type: 'string', value: 'openai', }); - expect(previousResponseSpan!.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE]).toEqual({ + expect(previousResponseSpan!.attributes[GEN_AI_REQUEST_MODEL]).toEqual({ type: 'string', value: 'gpt-4', }); - expect(previousResponseSpan!.attributes[GEN_AI_CONVERSATION_ID_ATTRIBUTE]).toEqual({ + expect(previousResponseSpan!.attributes[GEN_AI_CONVERSATION_ID]).toEqual({ type: 'string', value: 'resp_mock_conv_123', }); @@ -1565,7 +1567,7 @@ describe('OpenAI integration', () => { const [firstSpan] = container.items; // [0] chat completion with system instructions extracted from messages - expect(firstSpan!.attributes[GEN_AI_SYSTEM_INSTRUCTIONS_ATTRIBUTE]).toEqual({ + expect(firstSpan!.attributes[GEN_AI_SYSTEM_INSTRUCTIONS]).toEqual({ type: 'string', value: JSON.stringify([{ type: 'text', content: 'You are a helpful assistant' }]), }); @@ -1594,9 +1596,9 @@ describe('OpenAI integration', () => { for (const span of container.items) { expect(span!.name).toBe('chat gpt-4'); expect(span!.status).toBe('ok'); - expect(span!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE]).toEqual({ type: 'string', value: 'chat' }); - expect(span!.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE]).toEqual({ type: 'string', value: 'gpt-4' }); - expect(span!.attributes[GEN_AI_RESPONSE_ID_ATTRIBUTE]).toEqual({ + expect(span!.attributes[GEN_AI_OPERATION_NAME]).toEqual({ type: 'string', value: 'chat' }); + expect(span!.attributes[GEN_AI_REQUEST_MODEL]).toEqual({ type: 'string', value: 'gpt-4' }); + expect(span!.attributes[GEN_AI_RESPONSE_ID]).toEqual({ type: 'string', value: 'chatcmpl-withresponse', }); @@ -1625,13 +1627,13 @@ describe('OpenAI integration', () => { for (const span of container.items) { expect(span!.name).toBe('chat gpt-4o'); expect(span!.status).toBe('ok'); - expect(span!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE]).toEqual({ type: 'string', value: 'chat' }); - expect(span!.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE]).toEqual({ type: 'string', value: 'gpt-4o' }); + expect(span!.attributes[GEN_AI_OPERATION_NAME]).toEqual({ type: 'string', value: 'chat' }); + expect(span!.attributes[GEN_AI_REQUEST_MODEL]).toEqual({ type: 'string', value: 'gpt-4o' }); expect(span!.attributes[GEN_AI_INPUT_MESSAGES_ORIGINAL_LENGTH_ATTRIBUTE]).toEqual({ type: 'integer', value: 1, }); - expect(span!.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE].value).toContain('[Blob substitute]'); + expect(span!.attributes[GEN_AI_INPUT_MESSAGES].value).toContain('[Blob substitute]'); } }, }) @@ -1651,12 +1653,12 @@ describe('OpenAI integration', () => { span: container => { expect(container.items).toHaveLength(2); const multipleImagesSpan = container.items.find(span => - getStringAttributeValue(span.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value)?.includes( + getStringAttributeValue(span.attributes[GEN_AI_INPUT_MESSAGES]?.value)?.includes( 'https://example.com/image.png', ), ); expect(multipleImagesSpan).toBeDefined(); - expect(multipleImagesSpan!.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE].value).toContain( + expect(multipleImagesSpan!.attributes[GEN_AI_INPUT_MESSAGES].value).toContain( 'https://example.com/image.png', ); }, @@ -1677,16 +1679,12 @@ describe('OpenAI integration', () => { const spans = container.items; const chatSpan = spans.find(s => - getStringAttributeValue(s.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value)?.includes( - streamingLongContent, - ), + getStringAttributeValue(s.attributes[GEN_AI_INPUT_MESSAGES]?.value)?.includes(streamingLongContent), ); expect(chatSpan).toBeDefined(); const responsesSpan = spans.find(s => - getStringAttributeValue(s.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value)?.includes( - streamingLongString, - ), + getStringAttributeValue(s.attributes[GEN_AI_INPUT_MESSAGES]?.value)?.includes(streamingLongString), ); expect(responsesSpan).toBeDefined(); }, @@ -1710,23 +1708,22 @@ describe('OpenAI integration', () => { // With explicit enableTruncation: true, content should be truncated despite streaming. // Truncation keeps only the last message (50k 'A's) and crops it to the byte limit. const chatSpan = spans.find(s => - getStringAttributeValue(s.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value)?.startsWith( + getStringAttributeValue(s.attributes[GEN_AI_INPUT_MESSAGES]?.value)?.startsWith( '[{"role":"user","content":"AAAA', ), ); expect(chatSpan).toBeDefined(); expect( - (getStringAttributeValue(chatSpan!.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE].value) ?? '').length, + (getStringAttributeValue(chatSpan!.attributes[GEN_AI_INPUT_MESSAGES].value) ?? '').length, ).toBeLessThan(streamingLongContent.length); // The responses API string input (50k 'B's) should also be truncated. const responsesSpan = spans.find(s => - getStringAttributeValue(s.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value)?.startsWith('BBB'), + getStringAttributeValue(s.attributes[GEN_AI_INPUT_MESSAGES]?.value)?.startsWith('BBB'), ); expect(responsesSpan).toBeDefined(); expect( - (getStringAttributeValue(responsesSpan!.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE].value) ?? '') - .length, + (getStringAttributeValue(responsesSpan!.attributes[GEN_AI_INPUT_MESSAGES].value) ?? '').length, ).toBeLessThan(streamingLongString.length); }, }) diff --git a/dev-packages/node-integration-tests/suites/tracing/openai/v6/test.ts b/dev-packages/node-integration-tests/suites/tracing/openai/v6/test.ts index 0530d1575845..855b614108eb 100644 --- a/dev-packages/node-integration-tests/suites/tracing/openai/v6/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/openai/v6/test.ts @@ -1,25 +1,27 @@ import { SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '@sentry/core'; import { afterAll, describe, expect } from 'vitest'; import { - GEN_AI_EMBEDDINGS_INPUT_ATTRIBUTE, - GEN_AI_INPUT_MESSAGES_ATTRIBUTE, + GEN_AI_EMBEDDINGS_INPUT, + GEN_AI_INPUT_MESSAGES, + GEN_AI_OPERATION_NAME, + GEN_AI_REQUEST_MODEL, + GEN_AI_REQUEST_TEMPERATURE, + GEN_AI_RESPONSE_FINISH_REASONS, + GEN_AI_RESPONSE_ID, + GEN_AI_RESPONSE_MODEL, + GEN_AI_RESPONSE_STREAMING, + GEN_AI_RESPONSE_TEXT, + GEN_AI_SYSTEM, + GEN_AI_SYSTEM_INSTRUCTIONS, + GEN_AI_USAGE_INPUT_TOKENS, + GEN_AI_USAGE_OUTPUT_TOKENS, + GEN_AI_USAGE_TOTAL_TOKENS, +} from '@sentry/conventions/attributes'; +import { GEN_AI_INPUT_MESSAGES_ORIGINAL_LENGTH_ATTRIBUTE, - GEN_AI_OPERATION_NAME_ATTRIBUTE, GEN_AI_REQUEST_DIMENSIONS_ATTRIBUTE, GEN_AI_REQUEST_ENCODING_FORMAT_ATTRIBUTE, - GEN_AI_REQUEST_MODEL_ATTRIBUTE, GEN_AI_REQUEST_STREAM_ATTRIBUTE, - GEN_AI_REQUEST_TEMPERATURE_ATTRIBUTE, - GEN_AI_RESPONSE_FINISH_REASONS_ATTRIBUTE, - GEN_AI_RESPONSE_ID_ATTRIBUTE, - GEN_AI_RESPONSE_MODEL_ATTRIBUTE, - GEN_AI_RESPONSE_STREAMING_ATTRIBUTE, - GEN_AI_RESPONSE_TEXT_ATTRIBUTE, - GEN_AI_SYSTEM_ATTRIBUTE, - GEN_AI_SYSTEM_INSTRUCTIONS_ATTRIBUTE, - GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE, - GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE, - GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE, } from '../../../../../../packages/core/src/tracing/ai/gen-ai-attributes'; import { isOrchestrionEnabled } from '../../../../utils'; import { cleanupChildProcesses, createEsmAndCjsTests } from '../../../../utils/runner'; @@ -42,12 +44,12 @@ describe('OpenAI integration (V6)', () => { span: container => { expect(container.items).toHaveLength(6); const chatCompletionSpan = container.items.find( - span => span.attributes[GEN_AI_RESPONSE_ID_ATTRIBUTE]?.value === 'chatcmpl-mock123', + span => span.attributes[GEN_AI_RESPONSE_ID]?.value === 'chatcmpl-mock123', ); expect(chatCompletionSpan).toBeDefined(); expect(chatCompletionSpan!.name).toBe('chat gpt-3.5-turbo'); expect(chatCompletionSpan!.status).toBe('ok'); - expect(chatCompletionSpan!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE]).toEqual({ + expect(chatCompletionSpan!.attributes[GEN_AI_OPERATION_NAME]).toEqual({ type: 'string', value: 'chat', }); @@ -59,50 +61,50 @@ describe('OpenAI integration (V6)', () => { type: 'string', value: isOrchestrionEnabled() ? 'auto.ai.orchestrion.openai' : 'auto.ai.openai', }); - expect(chatCompletionSpan!.attributes[GEN_AI_SYSTEM_ATTRIBUTE]).toEqual({ + expect(chatCompletionSpan!.attributes[GEN_AI_SYSTEM]).toEqual({ type: 'string', value: 'openai', }); - expect(chatCompletionSpan!.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE]).toEqual({ + expect(chatCompletionSpan!.attributes[GEN_AI_REQUEST_MODEL]).toEqual({ type: 'string', value: 'gpt-3.5-turbo', }); - expect(chatCompletionSpan!.attributes[GEN_AI_REQUEST_TEMPERATURE_ATTRIBUTE]).toEqual({ + expect(chatCompletionSpan!.attributes[GEN_AI_REQUEST_TEMPERATURE]).toEqual({ type: 'double', value: 0.7, }); - expect(chatCompletionSpan!.attributes[GEN_AI_RESPONSE_MODEL_ATTRIBUTE]).toEqual({ + expect(chatCompletionSpan!.attributes[GEN_AI_RESPONSE_MODEL]).toEqual({ type: 'string', value: 'gpt-3.5-turbo', }); - expect(chatCompletionSpan!.attributes[GEN_AI_RESPONSE_ID_ATTRIBUTE]).toEqual({ + expect(chatCompletionSpan!.attributes[GEN_AI_RESPONSE_ID]).toEqual({ type: 'string', value: 'chatcmpl-mock123', }); - expect(chatCompletionSpan!.attributes[GEN_AI_RESPONSE_FINISH_REASONS_ATTRIBUTE]).toEqual({ + expect(chatCompletionSpan!.attributes[GEN_AI_RESPONSE_FINISH_REASONS]).toEqual({ type: 'string', value: '["stop"]', }); - expect(chatCompletionSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]).toEqual({ + expect(chatCompletionSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS]).toEqual({ type: 'integer', value: 10, }); - expect(chatCompletionSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE]).toEqual({ + expect(chatCompletionSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS]).toEqual({ type: 'integer', value: 15, }); - expect(chatCompletionSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE]).toEqual({ + expect(chatCompletionSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS]).toEqual({ type: 'integer', value: 25, }); const responsesSpan = container.items.find( - span => span.attributes[GEN_AI_RESPONSE_ID_ATTRIBUTE]?.value === 'resp_mock456', + span => span.attributes[GEN_AI_RESPONSE_ID]?.value === 'resp_mock456', ); expect(responsesSpan).toBeDefined(); expect(responsesSpan!.name).toBe('chat gpt-3.5-turbo'); expect(responsesSpan!.status).toBe('ok'); - expect(responsesSpan!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE]).toEqual({ + expect(responsesSpan!.attributes[GEN_AI_OPERATION_NAME]).toEqual({ type: 'string', value: 'chat', }); @@ -114,32 +116,32 @@ describe('OpenAI integration (V6)', () => { type: 'string', value: isOrchestrionEnabled() ? 'auto.ai.orchestrion.openai' : 'auto.ai.openai', }); - expect(responsesSpan!.attributes[GEN_AI_SYSTEM_ATTRIBUTE]).toEqual({ type: 'string', value: 'openai' }); - expect(responsesSpan!.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE]).toEqual({ + expect(responsesSpan!.attributes[GEN_AI_SYSTEM]).toEqual({ type: 'string', value: 'openai' }); + expect(responsesSpan!.attributes[GEN_AI_REQUEST_MODEL]).toEqual({ type: 'string', value: 'gpt-3.5-turbo', }); - expect(responsesSpan!.attributes[GEN_AI_RESPONSE_MODEL_ATTRIBUTE]).toEqual({ + expect(responsesSpan!.attributes[GEN_AI_RESPONSE_MODEL]).toEqual({ type: 'string', value: 'gpt-3.5-turbo', }); - expect(responsesSpan!.attributes[GEN_AI_RESPONSE_ID_ATTRIBUTE]).toEqual({ + expect(responsesSpan!.attributes[GEN_AI_RESPONSE_ID]).toEqual({ type: 'string', value: 'resp_mock456', }); - expect(responsesSpan!.attributes[GEN_AI_RESPONSE_FINISH_REASONS_ATTRIBUTE]).toEqual({ + expect(responsesSpan!.attributes[GEN_AI_RESPONSE_FINISH_REASONS]).toEqual({ type: 'string', value: '["completed"]', }); - expect(responsesSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]).toEqual({ + expect(responsesSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS]).toEqual({ type: 'integer', value: 5, }); - expect(responsesSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE]).toEqual({ + expect(responsesSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS]).toEqual({ type: 'integer', value: 8, }); - expect(responsesSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE]).toEqual({ + expect(responsesSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS]).toEqual({ type: 'integer', value: 13, }); @@ -152,7 +154,7 @@ describe('OpenAI integration (V6)', () => { expect(nonStreamingErrorSpan!.name).toBe('chat error-model'); expect(nonStreamingErrorSpan!.status).toBe('error'); expect(nonStreamingErrorSpan!.attributes[GEN_AI_REQUEST_STREAM_ATTRIBUTE]).toBeUndefined(); - expect(nonStreamingErrorSpan!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE]).toEqual({ + expect(nonStreamingErrorSpan!.attributes[GEN_AI_OPERATION_NAME]).toEqual({ type: 'string', value: 'chat', }); @@ -164,22 +166,22 @@ describe('OpenAI integration (V6)', () => { type: 'string', value: isOrchestrionEnabled() ? 'auto.ai.orchestrion.openai' : 'auto.ai.openai', }); - expect(nonStreamingErrorSpan!.attributes[GEN_AI_SYSTEM_ATTRIBUTE]).toEqual({ + expect(nonStreamingErrorSpan!.attributes[GEN_AI_SYSTEM]).toEqual({ type: 'string', value: 'openai', }); - expect(nonStreamingErrorSpan!.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE]).toEqual({ + expect(nonStreamingErrorSpan!.attributes[GEN_AI_REQUEST_MODEL]).toEqual({ type: 'string', value: 'error-model', }); const streamingChatCompletionSpan = container.items.find( - span => span.attributes[GEN_AI_RESPONSE_ID_ATTRIBUTE]?.value === 'chatcmpl-stream-123', + span => span.attributes[GEN_AI_RESPONSE_ID]?.value === 'chatcmpl-stream-123', ); expect(streamingChatCompletionSpan).toBeDefined(); expect(streamingChatCompletionSpan!.name).toBe('chat gpt-4'); expect(streamingChatCompletionSpan!.status).toBe('ok'); - expect(streamingChatCompletionSpan!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE]).toEqual({ + expect(streamingChatCompletionSpan!.attributes[GEN_AI_OPERATION_NAME]).toEqual({ type: 'string', value: 'chat', }); @@ -191,15 +193,15 @@ describe('OpenAI integration (V6)', () => { type: 'string', value: isOrchestrionEnabled() ? 'auto.ai.orchestrion.openai' : 'auto.ai.openai', }); - expect(streamingChatCompletionSpan!.attributes[GEN_AI_SYSTEM_ATTRIBUTE]).toEqual({ + expect(streamingChatCompletionSpan!.attributes[GEN_AI_SYSTEM]).toEqual({ type: 'string', value: 'openai', }); - expect(streamingChatCompletionSpan!.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE]).toEqual({ + expect(streamingChatCompletionSpan!.attributes[GEN_AI_REQUEST_MODEL]).toEqual({ type: 'string', value: 'gpt-4', }); - expect(streamingChatCompletionSpan!.attributes[GEN_AI_REQUEST_TEMPERATURE_ATTRIBUTE]).toEqual({ + expect(streamingChatCompletionSpan!.attributes[GEN_AI_REQUEST_TEMPERATURE]).toEqual({ type: 'double', value: 0.8, }); @@ -207,42 +209,42 @@ describe('OpenAI integration (V6)', () => { type: 'boolean', value: true, }); - expect(streamingChatCompletionSpan!.attributes[GEN_AI_RESPONSE_MODEL_ATTRIBUTE]).toEqual({ + expect(streamingChatCompletionSpan!.attributes[GEN_AI_RESPONSE_MODEL]).toEqual({ type: 'string', value: 'gpt-4', }); - expect(streamingChatCompletionSpan!.attributes[GEN_AI_RESPONSE_ID_ATTRIBUTE]).toEqual({ + expect(streamingChatCompletionSpan!.attributes[GEN_AI_RESPONSE_ID]).toEqual({ type: 'string', value: 'chatcmpl-stream-123', }); - expect(streamingChatCompletionSpan!.attributes[GEN_AI_RESPONSE_FINISH_REASONS_ATTRIBUTE]).toEqual({ + expect(streamingChatCompletionSpan!.attributes[GEN_AI_RESPONSE_FINISH_REASONS]).toEqual({ type: 'string', value: '["stop"]', }); - expect(streamingChatCompletionSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]).toEqual({ + expect(streamingChatCompletionSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS]).toEqual({ type: 'integer', value: 12, }); - expect(streamingChatCompletionSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE]).toEqual({ + expect(streamingChatCompletionSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS]).toEqual({ type: 'integer', value: 18, }); - expect(streamingChatCompletionSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE]).toEqual({ + expect(streamingChatCompletionSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS]).toEqual({ type: 'integer', value: 30, }); - expect(streamingChatCompletionSpan!.attributes[GEN_AI_RESPONSE_STREAMING_ATTRIBUTE]).toEqual({ + expect(streamingChatCompletionSpan!.attributes[GEN_AI_RESPONSE_STREAMING]).toEqual({ type: 'boolean', value: true, }); const streamingResponsesSpan = container.items.find( - span => span.attributes[GEN_AI_RESPONSE_ID_ATTRIBUTE]?.value === 'resp_stream_456', + span => span.attributes[GEN_AI_RESPONSE_ID]?.value === 'resp_stream_456', ); expect(streamingResponsesSpan).toBeDefined(); expect(streamingResponsesSpan!.name).toBe('chat gpt-4'); expect(streamingResponsesSpan!.status).toBe('ok'); - expect(streamingResponsesSpan!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE]).toEqual({ + expect(streamingResponsesSpan!.attributes[GEN_AI_OPERATION_NAME]).toEqual({ type: 'string', value: 'chat', }); @@ -254,11 +256,11 @@ describe('OpenAI integration (V6)', () => { type: 'string', value: isOrchestrionEnabled() ? 'auto.ai.orchestrion.openai' : 'auto.ai.openai', }); - expect(streamingResponsesSpan!.attributes[GEN_AI_SYSTEM_ATTRIBUTE]).toEqual({ + expect(streamingResponsesSpan!.attributes[GEN_AI_SYSTEM]).toEqual({ type: 'string', value: 'openai', }); - expect(streamingResponsesSpan!.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE]).toEqual({ + expect(streamingResponsesSpan!.attributes[GEN_AI_REQUEST_MODEL]).toEqual({ type: 'string', value: 'gpt-4', }); @@ -266,31 +268,31 @@ describe('OpenAI integration (V6)', () => { type: 'boolean', value: true, }); - expect(streamingResponsesSpan!.attributes[GEN_AI_RESPONSE_MODEL_ATTRIBUTE]).toEqual({ + expect(streamingResponsesSpan!.attributes[GEN_AI_RESPONSE_MODEL]).toEqual({ type: 'string', value: 'gpt-4', }); - expect(streamingResponsesSpan!.attributes[GEN_AI_RESPONSE_ID_ATTRIBUTE]).toEqual({ + expect(streamingResponsesSpan!.attributes[GEN_AI_RESPONSE_ID]).toEqual({ type: 'string', value: 'resp_stream_456', }); - expect(streamingResponsesSpan!.attributes[GEN_AI_RESPONSE_FINISH_REASONS_ATTRIBUTE]).toEqual({ + expect(streamingResponsesSpan!.attributes[GEN_AI_RESPONSE_FINISH_REASONS]).toEqual({ type: 'string', value: '["in_progress","completed"]', }); - expect(streamingResponsesSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]).toEqual({ + expect(streamingResponsesSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS]).toEqual({ type: 'integer', value: 6, }); - expect(streamingResponsesSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE]).toEqual({ + expect(streamingResponsesSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS]).toEqual({ type: 'integer', value: 10, }); - expect(streamingResponsesSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE]).toEqual({ + expect(streamingResponsesSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS]).toEqual({ type: 'integer', value: 16, }); - expect(streamingResponsesSpan!.attributes[GEN_AI_RESPONSE_STREAMING_ATTRIBUTE]).toEqual({ + expect(streamingResponsesSpan!.attributes[GEN_AI_RESPONSE_STREAMING]).toEqual({ type: 'boolean', value: true, }); @@ -302,11 +304,11 @@ describe('OpenAI integration (V6)', () => { expect(streamingErrorSpan).toBeDefined(); expect(streamingErrorSpan!.name).toBe('chat error-model'); expect(streamingErrorSpan!.status).toBe('error'); - expect(streamingErrorSpan!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE]).toEqual({ + expect(streamingErrorSpan!.attributes[GEN_AI_OPERATION_NAME]).toEqual({ type: 'string', value: 'chat', }); - expect(streamingErrorSpan!.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE]).toEqual({ + expect(streamingErrorSpan!.attributes[GEN_AI_REQUEST_MODEL]).toEqual({ type: 'string', value: 'error-model', }); @@ -314,7 +316,7 @@ describe('OpenAI integration (V6)', () => { type: 'boolean', value: true, }); - expect(streamingErrorSpan!.attributes[GEN_AI_SYSTEM_ATTRIBUTE]).toEqual({ + expect(streamingErrorSpan!.attributes[GEN_AI_SYSTEM]).toEqual({ type: 'string', value: 'openai', }); @@ -352,12 +354,12 @@ describe('OpenAI integration (V6)', () => { span: container => { expect(container.items).toHaveLength(6); const chatCompletionSpan = container.items.find( - span => span.attributes[GEN_AI_RESPONSE_ID_ATTRIBUTE]?.value === 'chatcmpl-mock123', + span => span.attributes[GEN_AI_RESPONSE_ID]?.value === 'chatcmpl-mock123', ); expect(chatCompletionSpan).toBeDefined(); expect(chatCompletionSpan!.name).toBe('chat gpt-3.5-turbo'); expect(chatCompletionSpan!.status).toBe('ok'); - expect(chatCompletionSpan!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE]).toEqual({ + expect(chatCompletionSpan!.attributes[GEN_AI_OPERATION_NAME]).toEqual({ type: 'string', value: 'chat', }); @@ -369,15 +371,15 @@ describe('OpenAI integration (V6)', () => { type: 'string', value: isOrchestrionEnabled() ? 'auto.ai.orchestrion.openai' : 'auto.ai.openai', }); - expect(chatCompletionSpan!.attributes[GEN_AI_SYSTEM_ATTRIBUTE]).toEqual({ + expect(chatCompletionSpan!.attributes[GEN_AI_SYSTEM]).toEqual({ type: 'string', value: 'openai', }); - expect(chatCompletionSpan!.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE]).toEqual({ + expect(chatCompletionSpan!.attributes[GEN_AI_REQUEST_MODEL]).toEqual({ type: 'string', value: 'gpt-3.5-turbo', }); - expect(chatCompletionSpan!.attributes[GEN_AI_REQUEST_TEMPERATURE_ATTRIBUTE]).toEqual({ + expect(chatCompletionSpan!.attributes[GEN_AI_REQUEST_TEMPERATURE]).toEqual({ type: 'double', value: 0.7, }); @@ -385,50 +387,50 @@ describe('OpenAI integration (V6)', () => { type: 'integer', value: 1, }); - expect(chatCompletionSpan!.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]).toEqual({ + expect(chatCompletionSpan!.attributes[GEN_AI_INPUT_MESSAGES]).toEqual({ type: 'string', value: '[{"role":"user","content":"What is the capital of France?"}]', }); - expect(chatCompletionSpan!.attributes[GEN_AI_SYSTEM_INSTRUCTIONS_ATTRIBUTE]).toEqual({ + expect(chatCompletionSpan!.attributes[GEN_AI_SYSTEM_INSTRUCTIONS]).toEqual({ type: 'string', value: '[{"type":"text","content":"You are a helpful assistant."}]', }); - expect(chatCompletionSpan!.attributes[GEN_AI_RESPONSE_MODEL_ATTRIBUTE]).toEqual({ + expect(chatCompletionSpan!.attributes[GEN_AI_RESPONSE_MODEL]).toEqual({ type: 'string', value: 'gpt-3.5-turbo', }); - expect(chatCompletionSpan!.attributes[GEN_AI_RESPONSE_ID_ATTRIBUTE]).toEqual({ + expect(chatCompletionSpan!.attributes[GEN_AI_RESPONSE_ID]).toEqual({ type: 'string', value: 'chatcmpl-mock123', }); - expect(chatCompletionSpan!.attributes[GEN_AI_RESPONSE_FINISH_REASONS_ATTRIBUTE]).toEqual({ + expect(chatCompletionSpan!.attributes[GEN_AI_RESPONSE_FINISH_REASONS]).toEqual({ type: 'string', value: '["stop"]', }); - expect(chatCompletionSpan!.attributes[GEN_AI_RESPONSE_TEXT_ATTRIBUTE]).toEqual({ + expect(chatCompletionSpan!.attributes[GEN_AI_RESPONSE_TEXT]).toEqual({ type: 'string', value: '["Hello from OpenAI mock!"]', }); - expect(chatCompletionSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]).toEqual({ + expect(chatCompletionSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS]).toEqual({ type: 'integer', value: 10, }); - expect(chatCompletionSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE]).toEqual({ + expect(chatCompletionSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS]).toEqual({ type: 'integer', value: 15, }); - expect(chatCompletionSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE]).toEqual({ + expect(chatCompletionSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS]).toEqual({ type: 'integer', value: 25, }); const responsesSpan = container.items.find( - span => span.attributes[GEN_AI_RESPONSE_ID_ATTRIBUTE]?.value === 'resp_mock456', + span => span.attributes[GEN_AI_RESPONSE_ID]?.value === 'resp_mock456', ); expect(responsesSpan).toBeDefined(); expect(responsesSpan!.name).toBe('chat gpt-3.5-turbo'); expect(responsesSpan!.status).toBe('ok'); - expect(responsesSpan!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE]).toEqual({ + expect(responsesSpan!.attributes[GEN_AI_OPERATION_NAME]).toEqual({ type: 'string', value: 'chat', }); @@ -440,8 +442,8 @@ describe('OpenAI integration (V6)', () => { type: 'string', value: isOrchestrionEnabled() ? 'auto.ai.orchestrion.openai' : 'auto.ai.openai', }); - expect(responsesSpan!.attributes[GEN_AI_SYSTEM_ATTRIBUTE]).toEqual({ type: 'string', value: 'openai' }); - expect(responsesSpan!.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE]).toEqual({ + expect(responsesSpan!.attributes[GEN_AI_SYSTEM]).toEqual({ type: 'string', value: 'openai' }); + expect(responsesSpan!.attributes[GEN_AI_REQUEST_MODEL]).toEqual({ type: 'string', value: 'gpt-3.5-turbo', }); @@ -449,35 +451,35 @@ describe('OpenAI integration (V6)', () => { type: 'integer', value: 1, }); - expect(responsesSpan!.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]).toEqual({ + expect(responsesSpan!.attributes[GEN_AI_INPUT_MESSAGES]).toEqual({ type: 'string', value: 'Translate this to French: Hello', }); - expect(responsesSpan!.attributes[GEN_AI_RESPONSE_TEXT_ATTRIBUTE]).toEqual({ + expect(responsesSpan!.attributes[GEN_AI_RESPONSE_TEXT]).toEqual({ type: 'string', value: 'Response to: Translate this to French: Hello', }); - expect(responsesSpan!.attributes[GEN_AI_RESPONSE_FINISH_REASONS_ATTRIBUTE]).toEqual({ + expect(responsesSpan!.attributes[GEN_AI_RESPONSE_FINISH_REASONS]).toEqual({ type: 'string', value: '["completed"]', }); - expect(responsesSpan!.attributes[GEN_AI_RESPONSE_MODEL_ATTRIBUTE]).toEqual({ + expect(responsesSpan!.attributes[GEN_AI_RESPONSE_MODEL]).toEqual({ type: 'string', value: 'gpt-3.5-turbo', }); - expect(responsesSpan!.attributes[GEN_AI_RESPONSE_ID_ATTRIBUTE]).toEqual({ + expect(responsesSpan!.attributes[GEN_AI_RESPONSE_ID]).toEqual({ type: 'string', value: 'resp_mock456', }); - expect(responsesSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]).toEqual({ + expect(responsesSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS]).toEqual({ type: 'integer', value: 5, }); - expect(responsesSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE]).toEqual({ + expect(responsesSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS]).toEqual({ type: 'integer', value: 8, }); - expect(responsesSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE]).toEqual({ + expect(responsesSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS]).toEqual({ type: 'integer', value: 13, }); @@ -490,7 +492,7 @@ describe('OpenAI integration (V6)', () => { expect(nonStreamingErrorSpan!.name).toBe('chat error-model'); expect(nonStreamingErrorSpan!.status).toBe('error'); expect(nonStreamingErrorSpan!.attributes[GEN_AI_REQUEST_STREAM_ATTRIBUTE]).toBeUndefined(); - expect(nonStreamingErrorSpan!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE]).toEqual({ + expect(nonStreamingErrorSpan!.attributes[GEN_AI_OPERATION_NAME]).toEqual({ type: 'string', value: 'chat', }); @@ -502,11 +504,11 @@ describe('OpenAI integration (V6)', () => { type: 'string', value: isOrchestrionEnabled() ? 'auto.ai.orchestrion.openai' : 'auto.ai.openai', }); - expect(nonStreamingErrorSpan!.attributes[GEN_AI_SYSTEM_ATTRIBUTE]).toEqual({ + expect(nonStreamingErrorSpan!.attributes[GEN_AI_SYSTEM]).toEqual({ type: 'string', value: 'openai', }); - expect(nonStreamingErrorSpan!.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE]).toEqual({ + expect(nonStreamingErrorSpan!.attributes[GEN_AI_REQUEST_MODEL]).toEqual({ type: 'string', value: 'error-model', }); @@ -514,18 +516,18 @@ describe('OpenAI integration (V6)', () => { type: 'integer', value: 1, }); - expect(nonStreamingErrorSpan!.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]).toEqual({ + expect(nonStreamingErrorSpan!.attributes[GEN_AI_INPUT_MESSAGES]).toEqual({ type: 'string', value: '[{"role":"user","content":"This will fail"}]', }); const streamingChatCompletionSpan = container.items.find( - span => span.attributes[GEN_AI_RESPONSE_ID_ATTRIBUTE]?.value === 'chatcmpl-stream-123', + span => span.attributes[GEN_AI_RESPONSE_ID]?.value === 'chatcmpl-stream-123', ); expect(streamingChatCompletionSpan).toBeDefined(); expect(streamingChatCompletionSpan!.name).toBe('chat gpt-4'); expect(streamingChatCompletionSpan!.status).toBe('ok'); - expect(streamingChatCompletionSpan!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE]).toEqual({ + expect(streamingChatCompletionSpan!.attributes[GEN_AI_OPERATION_NAME]).toEqual({ type: 'string', value: 'chat', }); @@ -537,15 +539,15 @@ describe('OpenAI integration (V6)', () => { type: 'string', value: isOrchestrionEnabled() ? 'auto.ai.orchestrion.openai' : 'auto.ai.openai', }); - expect(streamingChatCompletionSpan!.attributes[GEN_AI_SYSTEM_ATTRIBUTE]).toEqual({ + expect(streamingChatCompletionSpan!.attributes[GEN_AI_SYSTEM]).toEqual({ type: 'string', value: 'openai', }); - expect(streamingChatCompletionSpan!.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE]).toEqual({ + expect(streamingChatCompletionSpan!.attributes[GEN_AI_REQUEST_MODEL]).toEqual({ type: 'string', value: 'gpt-4', }); - expect(streamingChatCompletionSpan!.attributes[GEN_AI_REQUEST_TEMPERATURE_ATTRIBUTE]).toEqual({ + expect(streamingChatCompletionSpan!.attributes[GEN_AI_REQUEST_TEMPERATURE]).toEqual({ type: 'double', value: 0.8, }); @@ -557,54 +559,54 @@ describe('OpenAI integration (V6)', () => { type: 'integer', value: 1, }); - expect(streamingChatCompletionSpan!.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]).toEqual({ + expect(streamingChatCompletionSpan!.attributes[GEN_AI_INPUT_MESSAGES]).toEqual({ type: 'string', value: '[{"role":"user","content":"Tell me about streaming"}]', }); - expect(streamingChatCompletionSpan!.attributes[GEN_AI_SYSTEM_INSTRUCTIONS_ATTRIBUTE]).toEqual({ + expect(streamingChatCompletionSpan!.attributes[GEN_AI_SYSTEM_INSTRUCTIONS]).toEqual({ type: 'string', value: '[{"type":"text","content":"You are a helpful assistant."}]', }); - expect(streamingChatCompletionSpan!.attributes[GEN_AI_RESPONSE_TEXT_ATTRIBUTE]).toEqual({ + expect(streamingChatCompletionSpan!.attributes[GEN_AI_RESPONSE_TEXT]).toEqual({ type: 'string', value: 'Hello from OpenAI streaming!', }); - expect(streamingChatCompletionSpan!.attributes[GEN_AI_RESPONSE_FINISH_REASONS_ATTRIBUTE]).toEqual({ + expect(streamingChatCompletionSpan!.attributes[GEN_AI_RESPONSE_FINISH_REASONS]).toEqual({ type: 'string', value: '["stop"]', }); - expect(streamingChatCompletionSpan!.attributes[GEN_AI_RESPONSE_ID_ATTRIBUTE]).toEqual({ + expect(streamingChatCompletionSpan!.attributes[GEN_AI_RESPONSE_ID]).toEqual({ type: 'string', value: 'chatcmpl-stream-123', }); - expect(streamingChatCompletionSpan!.attributes[GEN_AI_RESPONSE_MODEL_ATTRIBUTE]).toEqual({ + expect(streamingChatCompletionSpan!.attributes[GEN_AI_RESPONSE_MODEL]).toEqual({ type: 'string', value: 'gpt-4', }); - expect(streamingChatCompletionSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]).toEqual({ + expect(streamingChatCompletionSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS]).toEqual({ type: 'integer', value: 12, }); - expect(streamingChatCompletionSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE]).toEqual({ + expect(streamingChatCompletionSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS]).toEqual({ type: 'integer', value: 18, }); - expect(streamingChatCompletionSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE]).toEqual({ + expect(streamingChatCompletionSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS]).toEqual({ type: 'integer', value: 30, }); - expect(streamingChatCompletionSpan!.attributes[GEN_AI_RESPONSE_STREAMING_ATTRIBUTE]).toEqual({ + expect(streamingChatCompletionSpan!.attributes[GEN_AI_RESPONSE_STREAMING]).toEqual({ type: 'boolean', value: true, }); const streamingResponsesSpan = container.items.find( - span => span.attributes[GEN_AI_RESPONSE_ID_ATTRIBUTE]?.value === 'resp_stream_456', + span => span.attributes[GEN_AI_RESPONSE_ID]?.value === 'resp_stream_456', ); expect(streamingResponsesSpan).toBeDefined(); expect(streamingResponsesSpan!.name).toBe('chat gpt-4'); expect(streamingResponsesSpan!.status).toBe('ok'); - expect(streamingResponsesSpan!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE]).toEqual({ + expect(streamingResponsesSpan!.attributes[GEN_AI_OPERATION_NAME]).toEqual({ type: 'string', value: 'chat', }); @@ -616,11 +618,11 @@ describe('OpenAI integration (V6)', () => { type: 'string', value: isOrchestrionEnabled() ? 'auto.ai.orchestrion.openai' : 'auto.ai.openai', }); - expect(streamingResponsesSpan!.attributes[GEN_AI_SYSTEM_ATTRIBUTE]).toEqual({ + expect(streamingResponsesSpan!.attributes[GEN_AI_SYSTEM]).toEqual({ type: 'string', value: 'openai', }); - expect(streamingResponsesSpan!.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE]).toEqual({ + expect(streamingResponsesSpan!.attributes[GEN_AI_REQUEST_MODEL]).toEqual({ type: 'string', value: 'gpt-4', }); @@ -632,39 +634,39 @@ describe('OpenAI integration (V6)', () => { type: 'integer', value: 1, }); - expect(streamingResponsesSpan!.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]).toEqual({ + expect(streamingResponsesSpan!.attributes[GEN_AI_INPUT_MESSAGES]).toEqual({ type: 'string', value: 'Test streaming responses API', }); - expect(streamingResponsesSpan!.attributes[GEN_AI_RESPONSE_TEXT_ATTRIBUTE]).toEqual({ + expect(streamingResponsesSpan!.attributes[GEN_AI_RESPONSE_TEXT]).toEqual({ type: 'string', value: 'Streaming response to: Test streaming responses APITest streaming responses API', }); - expect(streamingResponsesSpan!.attributes[GEN_AI_RESPONSE_FINISH_REASONS_ATTRIBUTE]).toEqual({ + expect(streamingResponsesSpan!.attributes[GEN_AI_RESPONSE_FINISH_REASONS]).toEqual({ type: 'string', value: '["in_progress","completed"]', }); - expect(streamingResponsesSpan!.attributes[GEN_AI_RESPONSE_ID_ATTRIBUTE]).toEqual({ + expect(streamingResponsesSpan!.attributes[GEN_AI_RESPONSE_ID]).toEqual({ type: 'string', value: 'resp_stream_456', }); - expect(streamingResponsesSpan!.attributes[GEN_AI_RESPONSE_MODEL_ATTRIBUTE]).toEqual({ + expect(streamingResponsesSpan!.attributes[GEN_AI_RESPONSE_MODEL]).toEqual({ type: 'string', value: 'gpt-4', }); - expect(streamingResponsesSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]).toEqual({ + expect(streamingResponsesSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS]).toEqual({ type: 'integer', value: 6, }); - expect(streamingResponsesSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE]).toEqual({ + expect(streamingResponsesSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS]).toEqual({ type: 'integer', value: 10, }); - expect(streamingResponsesSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE]).toEqual({ + expect(streamingResponsesSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS]).toEqual({ type: 'integer', value: 16, }); - expect(streamingResponsesSpan!.attributes[GEN_AI_RESPONSE_STREAMING_ATTRIBUTE]).toEqual({ + expect(streamingResponsesSpan!.attributes[GEN_AI_RESPONSE_STREAMING]).toEqual({ type: 'boolean', value: true, }); @@ -676,11 +678,11 @@ describe('OpenAI integration (V6)', () => { expect(streamingErrorSpan).toBeDefined(); expect(streamingErrorSpan!.name).toBe('chat error-model'); expect(streamingErrorSpan!.status).toBe('error'); - expect(streamingErrorSpan!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE]).toEqual({ + expect(streamingErrorSpan!.attributes[GEN_AI_OPERATION_NAME]).toEqual({ type: 'string', value: 'chat', }); - expect(streamingErrorSpan!.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE]).toEqual({ + expect(streamingErrorSpan!.attributes[GEN_AI_REQUEST_MODEL]).toEqual({ type: 'string', value: 'error-model', }); @@ -692,11 +694,11 @@ describe('OpenAI integration (V6)', () => { type: 'integer', value: 1, }); - expect(streamingErrorSpan!.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]).toEqual({ + expect(streamingErrorSpan!.attributes[GEN_AI_INPUT_MESSAGES]).toEqual({ type: 'string', value: '[{"role":"user","content":"This will fail"}]', }); - expect(streamingErrorSpan!.attributes[GEN_AI_SYSTEM_ATTRIBUTE]).toEqual({ + expect(streamingErrorSpan!.attributes[GEN_AI_SYSTEM]).toEqual({ type: 'string', value: 'openai', }); @@ -734,7 +736,7 @@ describe('OpenAI integration (V6)', () => { span: container => { expect(container.items).toHaveLength(6); const chatCompletionSpan = container.items.find( - span => span.attributes[GEN_AI_RESPONSE_ID_ATTRIBUTE]?.value === 'chatcmpl-mock123', + span => span.attributes[GEN_AI_RESPONSE_ID]?.value === 'chatcmpl-mock123', ); expect(chatCompletionSpan).toBeDefined(); expect(chatCompletionSpan!.attributes[GEN_AI_REQUEST_STREAM_ATTRIBUTE]).toBeUndefined(); @@ -742,21 +744,21 @@ describe('OpenAI integration (V6)', () => { type: 'integer', value: 1, }); - expect(chatCompletionSpan!.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]).toMatchObject({ + expect(chatCompletionSpan!.attributes[GEN_AI_INPUT_MESSAGES]).toMatchObject({ type: 'string', value: expect.any(String), }); - expect(chatCompletionSpan!.attributes[GEN_AI_SYSTEM_INSTRUCTIONS_ATTRIBUTE]).toMatchObject({ + expect(chatCompletionSpan!.attributes[GEN_AI_SYSTEM_INSTRUCTIONS]).toMatchObject({ type: 'string', value: expect.any(String), }); - expect(chatCompletionSpan!.attributes[GEN_AI_RESPONSE_TEXT_ATTRIBUTE]).toMatchObject({ + expect(chatCompletionSpan!.attributes[GEN_AI_RESPONSE_TEXT]).toMatchObject({ type: 'string', value: expect.any(String), }); const streamingChatCompletionSpan = container.items.find( - span => span.attributes[GEN_AI_RESPONSE_ID_ATTRIBUTE]?.value === 'chatcmpl-stream-123', + span => span.attributes[GEN_AI_RESPONSE_ID]?.value === 'chatcmpl-stream-123', ); expect(streamingChatCompletionSpan).toBeDefined(); expect(streamingChatCompletionSpan!.attributes[GEN_AI_REQUEST_STREAM_ATTRIBUTE]).toEqual({ @@ -769,15 +771,15 @@ describe('OpenAI integration (V6)', () => { type: 'integer', value: 1, }); - expect(streamingChatCompletionSpan!.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]).toMatchObject({ + expect(streamingChatCompletionSpan!.attributes[GEN_AI_INPUT_MESSAGES]).toMatchObject({ type: 'string', value: expect.any(String), }); - expect(streamingChatCompletionSpan!.attributes[GEN_AI_SYSTEM_INSTRUCTIONS_ATTRIBUTE]).toMatchObject({ + expect(streamingChatCompletionSpan!.attributes[GEN_AI_SYSTEM_INSTRUCTIONS]).toMatchObject({ type: 'string', value: expect.any(String), }); - expect(streamingChatCompletionSpan!.attributes[GEN_AI_RESPONSE_TEXT_ATTRIBUTE]).toMatchObject({ + expect(streamingChatCompletionSpan!.attributes[GEN_AI_RESPONSE_TEXT]).toMatchObject({ type: 'string', value: expect.any(String), }); @@ -818,7 +820,7 @@ describe('OpenAI integration (V6)', () => { expect(singleEmbeddingSpan).toBeDefined(); expect(singleEmbeddingSpan!.name).toBe('embeddings text-embedding-3-small'); expect(singleEmbeddingSpan!.status).toBe('ok'); - expect(singleEmbeddingSpan!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE]).toEqual({ + expect(singleEmbeddingSpan!.attributes[GEN_AI_OPERATION_NAME]).toEqual({ type: 'string', value: 'embeddings', }); @@ -830,11 +832,11 @@ describe('OpenAI integration (V6)', () => { type: 'string', value: isOrchestrionEnabled() ? 'auto.ai.orchestrion.openai' : 'auto.ai.openai', }); - expect(singleEmbeddingSpan!.attributes[GEN_AI_SYSTEM_ATTRIBUTE]).toEqual({ + expect(singleEmbeddingSpan!.attributes[GEN_AI_SYSTEM]).toEqual({ type: 'string', value: 'openai', }); - expect(singleEmbeddingSpan!.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE]).toEqual({ + expect(singleEmbeddingSpan!.attributes[GEN_AI_REQUEST_MODEL]).toEqual({ type: 'string', value: 'text-embedding-3-small', }); @@ -846,15 +848,15 @@ describe('OpenAI integration (V6)', () => { type: 'integer', value: 1536, }); - expect(singleEmbeddingSpan!.attributes[GEN_AI_RESPONSE_MODEL_ATTRIBUTE]).toEqual({ + expect(singleEmbeddingSpan!.attributes[GEN_AI_RESPONSE_MODEL]).toEqual({ type: 'string', value: 'text-embedding-3-small', }); - expect(singleEmbeddingSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]).toEqual({ + expect(singleEmbeddingSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS]).toEqual({ type: 'integer', value: 10, }); - expect(singleEmbeddingSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE]).toEqual({ + expect(singleEmbeddingSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS]).toEqual({ type: 'integer', value: 10, }); @@ -863,7 +865,7 @@ describe('OpenAI integration (V6)', () => { expect(errorEmbeddingSpan).toBeDefined(); expect(errorEmbeddingSpan!.name).toBe('embeddings error-model'); expect(errorEmbeddingSpan!.status).toBe('error'); - expect(errorEmbeddingSpan!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE]).toEqual({ + expect(errorEmbeddingSpan!.attributes[GEN_AI_OPERATION_NAME]).toEqual({ type: 'string', value: 'embeddings', }); @@ -875,11 +877,11 @@ describe('OpenAI integration (V6)', () => { type: 'string', value: isOrchestrionEnabled() ? 'auto.ai.orchestrion.openai' : 'auto.ai.openai', }); - expect(errorEmbeddingSpan!.attributes[GEN_AI_SYSTEM_ATTRIBUTE]).toEqual({ + expect(errorEmbeddingSpan!.attributes[GEN_AI_SYSTEM]).toEqual({ type: 'string', value: 'openai', }); - expect(errorEmbeddingSpan!.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE]).toEqual({ + expect(errorEmbeddingSpan!.attributes[GEN_AI_REQUEST_MODEL]).toEqual({ type: 'string', value: 'error-model', }); @@ -892,7 +894,7 @@ describe('OpenAI integration (V6)', () => { expect(multiEmbeddingSpan).toBeDefined(); expect(multiEmbeddingSpan!.name).toBe('embeddings text-embedding-3-small'); expect(multiEmbeddingSpan!.status).toBe('ok'); - expect(multiEmbeddingSpan!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE]).toEqual({ + expect(multiEmbeddingSpan!.attributes[GEN_AI_OPERATION_NAME]).toEqual({ type: 'string', value: 'embeddings', }); @@ -904,23 +906,23 @@ describe('OpenAI integration (V6)', () => { type: 'string', value: isOrchestrionEnabled() ? 'auto.ai.orchestrion.openai' : 'auto.ai.openai', }); - expect(multiEmbeddingSpan!.attributes[GEN_AI_SYSTEM_ATTRIBUTE]).toEqual({ + expect(multiEmbeddingSpan!.attributes[GEN_AI_SYSTEM]).toEqual({ type: 'string', value: 'openai', }); - expect(multiEmbeddingSpan!.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE]).toEqual({ + expect(multiEmbeddingSpan!.attributes[GEN_AI_REQUEST_MODEL]).toEqual({ type: 'string', value: 'text-embedding-3-small', }); - expect(multiEmbeddingSpan!.attributes[GEN_AI_RESPONSE_MODEL_ATTRIBUTE]).toEqual({ + expect(multiEmbeddingSpan!.attributes[GEN_AI_RESPONSE_MODEL]).toEqual({ type: 'string', value: 'text-embedding-3-small', }); - expect(multiEmbeddingSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]).toEqual({ + expect(multiEmbeddingSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS]).toEqual({ type: 'integer', value: 10, }); - expect(multiEmbeddingSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE]).toEqual({ + expect(multiEmbeddingSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS]).toEqual({ type: 'integer', value: 10, }); @@ -954,12 +956,12 @@ describe('OpenAI integration (V6)', () => { span: container => { expect(container.items).toHaveLength(3); const singleEmbeddingSpan = container.items.find( - span => span.attributes[GEN_AI_EMBEDDINGS_INPUT_ATTRIBUTE]?.value === 'Embedding test!', + span => span.attributes[GEN_AI_EMBEDDINGS_INPUT]?.value === 'Embedding test!', ); expect(singleEmbeddingSpan).toBeDefined(); expect(singleEmbeddingSpan!.name).toBe('embeddings text-embedding-3-small'); expect(singleEmbeddingSpan!.status).toBe('ok'); - expect(singleEmbeddingSpan!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE]).toEqual({ + expect(singleEmbeddingSpan!.attributes[GEN_AI_OPERATION_NAME]).toEqual({ type: 'string', value: 'embeddings', }); @@ -971,11 +973,11 @@ describe('OpenAI integration (V6)', () => { type: 'string', value: isOrchestrionEnabled() ? 'auto.ai.orchestrion.openai' : 'auto.ai.openai', }); - expect(singleEmbeddingSpan!.attributes[GEN_AI_SYSTEM_ATTRIBUTE]).toEqual({ + expect(singleEmbeddingSpan!.attributes[GEN_AI_SYSTEM]).toEqual({ type: 'string', value: 'openai', }); - expect(singleEmbeddingSpan!.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE]).toEqual({ + expect(singleEmbeddingSpan!.attributes[GEN_AI_REQUEST_MODEL]).toEqual({ type: 'string', value: 'text-embedding-3-small', }); @@ -987,30 +989,30 @@ describe('OpenAI integration (V6)', () => { type: 'integer', value: 1536, }); - expect(singleEmbeddingSpan!.attributes[GEN_AI_EMBEDDINGS_INPUT_ATTRIBUTE]).toEqual({ + expect(singleEmbeddingSpan!.attributes[GEN_AI_EMBEDDINGS_INPUT]).toEqual({ type: 'string', value: 'Embedding test!', }); - expect(singleEmbeddingSpan!.attributes[GEN_AI_RESPONSE_MODEL_ATTRIBUTE]).toEqual({ + expect(singleEmbeddingSpan!.attributes[GEN_AI_RESPONSE_MODEL]).toEqual({ type: 'string', value: 'text-embedding-3-small', }); - expect(singleEmbeddingSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]).toEqual({ + expect(singleEmbeddingSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS]).toEqual({ type: 'integer', value: 10, }); - expect(singleEmbeddingSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE]).toEqual({ + expect(singleEmbeddingSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS]).toEqual({ type: 'integer', value: 10, }); const errorEmbeddingSpan = container.items.find( - span => span.attributes[GEN_AI_EMBEDDINGS_INPUT_ATTRIBUTE]?.value === 'Error embedding test!', + span => span.attributes[GEN_AI_EMBEDDINGS_INPUT]?.value === 'Error embedding test!', ); expect(errorEmbeddingSpan).toBeDefined(); expect(errorEmbeddingSpan!.name).toBe('embeddings error-model'); expect(errorEmbeddingSpan!.status).toBe('error'); - expect(errorEmbeddingSpan!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE]).toEqual({ + expect(errorEmbeddingSpan!.attributes[GEN_AI_OPERATION_NAME]).toEqual({ type: 'string', value: 'embeddings', }); @@ -1022,28 +1024,28 @@ describe('OpenAI integration (V6)', () => { type: 'string', value: isOrchestrionEnabled() ? 'auto.ai.orchestrion.openai' : 'auto.ai.openai', }); - expect(errorEmbeddingSpan!.attributes[GEN_AI_SYSTEM_ATTRIBUTE]).toEqual({ + expect(errorEmbeddingSpan!.attributes[GEN_AI_SYSTEM]).toEqual({ type: 'string', value: 'openai', }); - expect(errorEmbeddingSpan!.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE]).toEqual({ + expect(errorEmbeddingSpan!.attributes[GEN_AI_REQUEST_MODEL]).toEqual({ type: 'string', value: 'error-model', }); - expect(errorEmbeddingSpan!.attributes[GEN_AI_EMBEDDINGS_INPUT_ATTRIBUTE]).toEqual({ + expect(errorEmbeddingSpan!.attributes[GEN_AI_EMBEDDINGS_INPUT]).toEqual({ type: 'string', value: 'Error embedding test!', }); const multiEmbeddingSpan = container.items.find( span => - span.attributes[GEN_AI_EMBEDDINGS_INPUT_ATTRIBUTE]?.value === + span.attributes[GEN_AI_EMBEDDINGS_INPUT]?.value === '["First input text","Second input text","Third input text"]', ); expect(multiEmbeddingSpan).toBeDefined(); expect(multiEmbeddingSpan!.name).toBe('embeddings text-embedding-3-small'); expect(multiEmbeddingSpan!.status).toBe('ok'); - expect(multiEmbeddingSpan!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE]).toEqual({ + expect(multiEmbeddingSpan!.attributes[GEN_AI_OPERATION_NAME]).toEqual({ type: 'string', value: 'embeddings', }); @@ -1055,27 +1057,27 @@ describe('OpenAI integration (V6)', () => { type: 'string', value: isOrchestrionEnabled() ? 'auto.ai.orchestrion.openai' : 'auto.ai.openai', }); - expect(multiEmbeddingSpan!.attributes[GEN_AI_SYSTEM_ATTRIBUTE]).toEqual({ + expect(multiEmbeddingSpan!.attributes[GEN_AI_SYSTEM]).toEqual({ type: 'string', value: 'openai', }); - expect(multiEmbeddingSpan!.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE]).toEqual({ + expect(multiEmbeddingSpan!.attributes[GEN_AI_REQUEST_MODEL]).toEqual({ type: 'string', value: 'text-embedding-3-small', }); - expect(multiEmbeddingSpan!.attributes[GEN_AI_EMBEDDINGS_INPUT_ATTRIBUTE]).toEqual({ + expect(multiEmbeddingSpan!.attributes[GEN_AI_EMBEDDINGS_INPUT]).toEqual({ type: 'string', value: '["First input text","Second input text","Third input text"]', }); - expect(multiEmbeddingSpan!.attributes[GEN_AI_RESPONSE_MODEL_ATTRIBUTE]).toEqual({ + expect(multiEmbeddingSpan!.attributes[GEN_AI_RESPONSE_MODEL]).toEqual({ type: 'string', value: 'text-embedding-3-small', }); - expect(multiEmbeddingSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]).toEqual({ + expect(multiEmbeddingSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS]).toEqual({ type: 'integer', value: 10, }); - expect(multiEmbeddingSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE]).toEqual({ + expect(multiEmbeddingSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS]).toEqual({ type: 'integer', value: 10, }); @@ -1113,20 +1115,20 @@ describe('OpenAI integration (V6)', () => { span_id: expect.any(String), trace_id: expect.any(String), data: { - [GEN_AI_OPERATION_NAME_ATTRIBUTE]: 'chat', + [GEN_AI_OPERATION_NAME]: 'chat', [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'gen_ai.chat', [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: isOrchestrionEnabled() ? 'auto.ai.orchestrion.openai' : 'auto.ai.openai', - [GEN_AI_SYSTEM_ATTRIBUTE]: 'openai', - [GEN_AI_REQUEST_MODEL_ATTRIBUTE]: 'gpt-3.5-turbo', - [GEN_AI_REQUEST_TEMPERATURE_ATTRIBUTE]: 0.7, - [GEN_AI_RESPONSE_MODEL_ATTRIBUTE]: 'gpt-3.5-turbo', - [GEN_AI_RESPONSE_ID_ATTRIBUTE]: 'chatcmpl-mock123', - [GEN_AI_RESPONSE_FINISH_REASONS_ATTRIBUTE]: '["stop"]', - [GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]: 10, - [GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE]: 15, - [GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE]: 25, + [GEN_AI_SYSTEM]: 'openai', + [GEN_AI_REQUEST_MODEL]: 'gpt-3.5-turbo', + [GEN_AI_REQUEST_TEMPERATURE]: 0.7, + [GEN_AI_RESPONSE_MODEL]: 'gpt-3.5-turbo', + [GEN_AI_RESPONSE_ID]: 'chatcmpl-mock123', + [GEN_AI_RESPONSE_FINISH_REASONS]: '["stop"]', + [GEN_AI_USAGE_INPUT_TOKENS]: 10, + [GEN_AI_USAGE_OUTPUT_TOKENS]: 15, + [GEN_AI_USAGE_TOTAL_TOKENS]: 25, }, op: 'gen_ai.chat', origin: isOrchestrionEnabled() ? 'auto.ai.orchestrion.openai' : 'auto.ai.openai', @@ -1168,20 +1170,20 @@ describe('OpenAI integration (V6)', () => { span_id: expect.any(String), trace_id: expect.any(String), data: { - [GEN_AI_OPERATION_NAME_ATTRIBUTE]: 'chat', + [GEN_AI_OPERATION_NAME]: 'chat', [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'gen_ai.chat', [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: isOrchestrionEnabled() ? 'auto.ai.orchestrion.openai' : 'auto.ai.openai', - [GEN_AI_SYSTEM_ATTRIBUTE]: 'openai', - [GEN_AI_REQUEST_MODEL_ATTRIBUTE]: 'gpt-3.5-turbo', - [GEN_AI_REQUEST_TEMPERATURE_ATTRIBUTE]: 0.7, - [GEN_AI_RESPONSE_MODEL_ATTRIBUTE]: 'gpt-3.5-turbo', - [GEN_AI_RESPONSE_ID_ATTRIBUTE]: 'chatcmpl-mock123', - [GEN_AI_RESPONSE_FINISH_REASONS_ATTRIBUTE]: '["stop"]', - [GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]: 10, - [GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE]: 15, - [GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE]: 25, + [GEN_AI_SYSTEM]: 'openai', + [GEN_AI_REQUEST_MODEL]: 'gpt-3.5-turbo', + [GEN_AI_REQUEST_TEMPERATURE]: 0.7, + [GEN_AI_RESPONSE_MODEL]: 'gpt-3.5-turbo', + [GEN_AI_RESPONSE_ID]: 'chatcmpl-mock123', + [GEN_AI_RESPONSE_FINISH_REASONS]: '["stop"]', + [GEN_AI_USAGE_INPUT_TOKENS]: 10, + [GEN_AI_USAGE_OUTPUT_TOKENS]: 15, + [GEN_AI_USAGE_TOTAL_TOKENS]: 25, }, op: 'gen_ai.chat', origin: isOrchestrionEnabled() ? 'auto.ai.orchestrion.openai' : 'auto.ai.openai', diff --git a/dev-packages/node-integration-tests/suites/tracing/vercelai/span-streaming-v4/test.ts b/dev-packages/node-integration-tests/suites/tracing/vercelai/span-streaming-v4/test.ts index f28559c4b9e9..faebe38f1d30 100644 --- a/dev-packages/node-integration-tests/suites/tracing/vercelai/span-streaming-v4/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/vercelai/span-streaming-v4/test.ts @@ -1,22 +1,24 @@ import { SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '@sentry/core'; import { afterAll, describe, expect } from 'vitest'; import { - GEN_AI_INPUT_MESSAGES_ATTRIBUTE, + GEN_AI_INPUT_MESSAGES, + GEN_AI_OPERATION_NAME, + GEN_AI_OUTPUT_MESSAGES, + GEN_AI_REQUEST_AVAILABLE_TOOLS, + GEN_AI_REQUEST_MODEL, + GEN_AI_RESPONSE_MODEL, + GEN_AI_TOOL_DESCRIPTION, + GEN_AI_TOOL_INPUT, + GEN_AI_TOOL_NAME, + GEN_AI_TOOL_OUTPUT, + GEN_AI_TOOL_TYPE, + GEN_AI_USAGE_INPUT_TOKENS, + GEN_AI_USAGE_OUTPUT_TOKENS, + GEN_AI_USAGE_TOTAL_TOKENS, +} from '@sentry/conventions/attributes'; +import { GEN_AI_INPUT_MESSAGES_ORIGINAL_LENGTH_ATTRIBUTE, - GEN_AI_OPERATION_NAME_ATTRIBUTE, - GEN_AI_OUTPUT_MESSAGES_ATTRIBUTE, - GEN_AI_REQUEST_AVAILABLE_TOOLS_ATTRIBUTE, - GEN_AI_REQUEST_MODEL_ATTRIBUTE, - GEN_AI_RESPONSE_MODEL_ATTRIBUTE, GEN_AI_TOOL_CALL_ID_ATTRIBUTE, - GEN_AI_TOOL_DESCRIPTION_ATTRIBUTE, - GEN_AI_TOOL_INPUT_ATTRIBUTE, - GEN_AI_TOOL_NAME_ATTRIBUTE, - GEN_AI_TOOL_OUTPUT_ATTRIBUTE, - GEN_AI_TOOL_TYPE_ATTRIBUTE, - GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE, - GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE, - GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE, } from '../../../../../../packages/core/src/tracing/ai/gen-ai-attributes'; import { cleanupChildProcesses, createEsmAndCjsTests } from '../../../../utils/runner'; import { getStringAttributeValue, isOrchestrionEnabled } from '../../../../utils'; @@ -50,12 +52,12 @@ describe('Vercel AI integration (streaming v4)', () => { name: 'invoke_agent', status: 'ok', attributes: expect.objectContaining({ - [GEN_AI_REQUEST_MODEL_ATTRIBUTE]: attr('mock-model-id'), - [GEN_AI_RESPONSE_MODEL_ATTRIBUTE]: attr('mock-model-id'), - [GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]: attr(10), - [GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE]: attr(20), - [GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE]: attr(30), - [GEN_AI_OPERATION_NAME_ATTRIBUTE]: attr('invoke_agent'), + [GEN_AI_REQUEST_MODEL]: attr('mock-model-id'), + [GEN_AI_RESPONSE_MODEL]: attr('mock-model-id'), + [GEN_AI_USAGE_INPUT_TOKENS]: attr(10), + [GEN_AI_USAGE_OUTPUT_TOKENS]: attr(20), + [GEN_AI_USAGE_TOTAL_TOKENS]: attr(30), + [GEN_AI_OPERATION_NAME]: attr('invoke_agent'), [SEMANTIC_ATTRIBUTE_SENTRY_OP]: attr('gen_ai.invoke_agent'), [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: attr(expectedOrigin), }), @@ -65,12 +67,12 @@ describe('Vercel AI integration (streaming v4)', () => { name: 'generate_content mock-model-id', status: 'ok', attributes: expect.objectContaining({ - [GEN_AI_REQUEST_MODEL_ATTRIBUTE]: attr('mock-model-id'), - [GEN_AI_RESPONSE_MODEL_ATTRIBUTE]: attr('mock-model-id'), - [GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]: attr(10), - [GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE]: attr(20), - [GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE]: attr(30), - [GEN_AI_OPERATION_NAME_ATTRIBUTE]: attr('generate_content'), + [GEN_AI_REQUEST_MODEL]: attr('mock-model-id'), + [GEN_AI_RESPONSE_MODEL]: attr('mock-model-id'), + [GEN_AI_USAGE_INPUT_TOKENS]: attr(10), + [GEN_AI_USAGE_OUTPUT_TOKENS]: attr(20), + [GEN_AI_USAGE_TOTAL_TOKENS]: attr(30), + [GEN_AI_OPERATION_NAME]: attr('generate_content'), [SEMANTIC_ATTRIBUTE_SENTRY_OP]: attr('gen_ai.generate_content'), [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: attr(expectedOrigin), }), @@ -80,11 +82,11 @@ describe('Vercel AI integration (streaming v4)', () => { name: 'invoke_agent', status: 'ok', attributes: expect.objectContaining({ - [GEN_AI_REQUEST_MODEL_ATTRIBUTE]: attr('mock-model-id'), - [GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]: attr(10), - [GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE]: attr(20), - [GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE]: attr(30), - [GEN_AI_OPERATION_NAME_ATTRIBUTE]: attr('invoke_agent'), + [GEN_AI_REQUEST_MODEL]: attr('mock-model-id'), + [GEN_AI_USAGE_INPUT_TOKENS]: attr(10), + [GEN_AI_USAGE_OUTPUT_TOKENS]: attr(20), + [GEN_AI_USAGE_TOTAL_TOKENS]: attr(30), + [GEN_AI_OPERATION_NAME]: attr('invoke_agent'), [SEMANTIC_ATTRIBUTE_SENTRY_OP]: attr('gen_ai.invoke_agent'), [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: attr(expectedOrigin), }), @@ -94,11 +96,11 @@ describe('Vercel AI integration (streaming v4)', () => { name: 'invoke_agent', status: 'ok', attributes: expect.objectContaining({ - [GEN_AI_REQUEST_MODEL_ATTRIBUTE]: attr('mock-model-id'), - [GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]: attr(15), - [GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE]: attr(25), - [GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE]: attr(40), - [GEN_AI_OPERATION_NAME_ATTRIBUTE]: attr('invoke_agent'), + [GEN_AI_REQUEST_MODEL]: attr('mock-model-id'), + [GEN_AI_USAGE_INPUT_TOKENS]: attr(15), + [GEN_AI_USAGE_OUTPUT_TOKENS]: attr(25), + [GEN_AI_USAGE_TOTAL_TOKENS]: attr(40), + [GEN_AI_OPERATION_NAME]: attr('invoke_agent'), [SEMANTIC_ATTRIBUTE_SENTRY_OP]: attr('gen_ai.invoke_agent'), [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: attr(expectedOrigin), }), @@ -108,11 +110,11 @@ describe('Vercel AI integration (streaming v4)', () => { name: 'generate_content mock-model-id', status: 'ok', attributes: expect.objectContaining({ - [GEN_AI_REQUEST_MODEL_ATTRIBUTE]: attr('mock-model-id'), - [GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]: attr(15), - [GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE]: attr(25), - [GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE]: attr(40), - [GEN_AI_OPERATION_NAME_ATTRIBUTE]: attr('generate_content'), + [GEN_AI_REQUEST_MODEL]: attr('mock-model-id'), + [GEN_AI_USAGE_INPUT_TOKENS]: attr(15), + [GEN_AI_USAGE_OUTPUT_TOKENS]: attr(25), + [GEN_AI_USAGE_TOTAL_TOKENS]: attr(40), + [GEN_AI_OPERATION_NAME]: attr('generate_content'), [SEMANTIC_ATTRIBUTE_SENTRY_OP]: attr('gen_ai.generate_content'), [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: attr(expectedOrigin), }), @@ -124,9 +126,9 @@ describe('Vercel AI integration (streaming v4)', () => { status: 'ok', attributes: expect.objectContaining({ [GEN_AI_TOOL_CALL_ID_ATTRIBUTE]: attr('call-1'), - [GEN_AI_TOOL_NAME_ATTRIBUTE]: attr('getWeather'), - [GEN_AI_TOOL_TYPE_ATTRIBUTE]: attr('function'), - [GEN_AI_OPERATION_NAME_ATTRIBUTE]: attr('execute_tool'), + [GEN_AI_TOOL_NAME]: attr('getWeather'), + [GEN_AI_TOOL_TYPE]: attr('function'), + [GEN_AI_OPERATION_NAME]: attr('execute_tool'), [SEMANTIC_ATTRIBUTE_SENTRY_OP]: attr('gen_ai.execute_tool'), [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: attr(expectedOrigin), }), @@ -142,16 +144,16 @@ describe('Vercel AI integration (streaming v4)', () => { status: 'ok', attributes: expect.objectContaining({ [GEN_AI_INPUT_MESSAGES_ORIGINAL_LENGTH_ATTRIBUTE]: attr(1), - [GEN_AI_INPUT_MESSAGES_ATTRIBUTE]: attr('[{"role":"user","content":"Where is the first span?"}]'), - [GEN_AI_OUTPUT_MESSAGES_ATTRIBUTE]: attr( + [GEN_AI_INPUT_MESSAGES]: attr('[{"role":"user","content":"Where is the first span?"}]'), + [GEN_AI_OUTPUT_MESSAGES]: attr( '[{"role":"assistant","parts":[{"type":"text","content":"First span here!"}],"finish_reason":"stop"}]', ), - [GEN_AI_REQUEST_MODEL_ATTRIBUTE]: attr('mock-model-id'), - [GEN_AI_RESPONSE_MODEL_ATTRIBUTE]: attr('mock-model-id'), - [GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]: attr(10), - [GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE]: attr(20), - [GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE]: attr(30), - [GEN_AI_OPERATION_NAME_ATTRIBUTE]: attr('invoke_agent'), + [GEN_AI_REQUEST_MODEL]: attr('mock-model-id'), + [GEN_AI_RESPONSE_MODEL]: attr('mock-model-id'), + [GEN_AI_USAGE_INPUT_TOKENS]: attr(10), + [GEN_AI_USAGE_OUTPUT_TOKENS]: attr(20), + [GEN_AI_USAGE_TOTAL_TOKENS]: attr(30), + [GEN_AI_OPERATION_NAME]: attr('invoke_agent'), [SEMANTIC_ATTRIBUTE_SENTRY_OP]: attr('gen_ai.invoke_agent'), [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: attr(expectedOrigin), }), @@ -161,16 +163,16 @@ describe('Vercel AI integration (streaming v4)', () => { name: 'generate_content mock-model-id', status: 'ok', attributes: expect.objectContaining({ - [GEN_AI_INPUT_MESSAGES_ATTRIBUTE]: expect.objectContaining({ value: expect.any(String) }), - [GEN_AI_OUTPUT_MESSAGES_ATTRIBUTE]: attr( + [GEN_AI_INPUT_MESSAGES]: expect.objectContaining({ value: expect.any(String) }), + [GEN_AI_OUTPUT_MESSAGES]: attr( '[{"role":"assistant","parts":[{"type":"text","content":"First span here!"}],"finish_reason":"stop"}]', ), - [GEN_AI_REQUEST_MODEL_ATTRIBUTE]: attr('mock-model-id'), - [GEN_AI_RESPONSE_MODEL_ATTRIBUTE]: attr('mock-model-id'), - [GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]: attr(10), - [GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE]: attr(20), - [GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE]: attr(30), - [GEN_AI_OPERATION_NAME_ATTRIBUTE]: attr('generate_content'), + [GEN_AI_REQUEST_MODEL]: attr('mock-model-id'), + [GEN_AI_RESPONSE_MODEL]: attr('mock-model-id'), + [GEN_AI_USAGE_INPUT_TOKENS]: attr(10), + [GEN_AI_USAGE_OUTPUT_TOKENS]: attr(20), + [GEN_AI_USAGE_TOTAL_TOKENS]: attr(30), + [GEN_AI_OPERATION_NAME]: attr('generate_content'), [SEMANTIC_ATTRIBUTE_SENTRY_OP]: attr('gen_ai.generate_content'), [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: attr(expectedOrigin), }), @@ -180,15 +182,15 @@ describe('Vercel AI integration (streaming v4)', () => { name: 'invoke_agent', status: 'ok', attributes: expect.objectContaining({ - [GEN_AI_INPUT_MESSAGES_ATTRIBUTE]: attr('[{"role":"user","content":"Where is the second span?"}]'), - [GEN_AI_OUTPUT_MESSAGES_ATTRIBUTE]: attr( + [GEN_AI_INPUT_MESSAGES]: attr('[{"role":"user","content":"Where is the second span?"}]'), + [GEN_AI_OUTPUT_MESSAGES]: attr( '[{"role":"assistant","parts":[{"type":"text","content":"Second span here!"}],"finish_reason":"stop"}]', ), - [GEN_AI_REQUEST_MODEL_ATTRIBUTE]: attr('mock-model-id'), - [GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]: attr(10), - [GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE]: attr(20), - [GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE]: attr(30), - [GEN_AI_OPERATION_NAME_ATTRIBUTE]: attr('invoke_agent'), + [GEN_AI_REQUEST_MODEL]: attr('mock-model-id'), + [GEN_AI_USAGE_INPUT_TOKENS]: attr(10), + [GEN_AI_USAGE_OUTPUT_TOKENS]: attr(20), + [GEN_AI_USAGE_TOTAL_TOKENS]: attr(30), + [GEN_AI_OPERATION_NAME]: attr('invoke_agent'), [SEMANTIC_ATTRIBUTE_SENTRY_OP]: attr('gen_ai.invoke_agent'), [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: attr(expectedOrigin), }), @@ -198,17 +200,15 @@ describe('Vercel AI integration (streaming v4)', () => { name: 'invoke_agent', status: 'ok', attributes: expect.objectContaining({ - [GEN_AI_INPUT_MESSAGES_ATTRIBUTE]: attr( - '[{"role":"user","content":"What is the weather in San Francisco?"}]', - ), - [GEN_AI_OUTPUT_MESSAGES_ATTRIBUTE]: attr( + [GEN_AI_INPUT_MESSAGES]: attr('[{"role":"user","content":"What is the weather in San Francisco?"}]'), + [GEN_AI_OUTPUT_MESSAGES]: attr( `[{"role":"assistant","parts":[{"type":"text","content":"Tool call completed!"},{"type":"tool_call","id":"call-1","name":"getWeather","arguments":"${toolCallArgs}"}],"finish_reason":"tool_call"}]`, ), - [GEN_AI_REQUEST_MODEL_ATTRIBUTE]: attr('mock-model-id'), - [GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]: attr(15), - [GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE]: attr(25), - [GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE]: attr(40), - [GEN_AI_OPERATION_NAME_ATTRIBUTE]: attr('invoke_agent'), + [GEN_AI_REQUEST_MODEL]: attr('mock-model-id'), + [GEN_AI_USAGE_INPUT_TOKENS]: attr(15), + [GEN_AI_USAGE_OUTPUT_TOKENS]: attr(25), + [GEN_AI_USAGE_TOTAL_TOKENS]: attr(40), + [GEN_AI_OPERATION_NAME]: attr('invoke_agent'), [SEMANTIC_ATTRIBUTE_SENTRY_OP]: attr('gen_ai.invoke_agent'), [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: attr(expectedOrigin), }), @@ -218,14 +218,14 @@ describe('Vercel AI integration (streaming v4)', () => { name: 'generate_content mock-model-id', status: 'ok', attributes: expect.objectContaining({ - [GEN_AI_REQUEST_AVAILABLE_TOOLS_ATTRIBUTE]: expect.objectContaining({ + [GEN_AI_REQUEST_AVAILABLE_TOOLS]: expect.objectContaining({ value: expect.stringContaining('getWeather'), }), - [GEN_AI_REQUEST_MODEL_ATTRIBUTE]: attr('mock-model-id'), - [GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]: attr(15), - [GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE]: attr(25), - [GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE]: attr(40), - [GEN_AI_OPERATION_NAME_ATTRIBUTE]: attr('generate_content'), + [GEN_AI_REQUEST_MODEL]: attr('mock-model-id'), + [GEN_AI_USAGE_INPUT_TOKENS]: attr(15), + [GEN_AI_USAGE_OUTPUT_TOKENS]: attr(25), + [GEN_AI_USAGE_TOTAL_TOKENS]: attr(40), + [GEN_AI_OPERATION_NAME]: attr('generate_content'), [SEMANTIC_ATTRIBUTE_SENTRY_OP]: attr('gen_ai.generate_content'), [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: attr(expectedOrigin), }), @@ -236,12 +236,12 @@ describe('Vercel AI integration (streaming v4)', () => { status: 'ok', attributes: expect.objectContaining({ [GEN_AI_TOOL_CALL_ID_ATTRIBUTE]: attr('call-1'), - [GEN_AI_TOOL_DESCRIPTION_ATTRIBUTE]: attr('Get the current weather for a location'), - [GEN_AI_TOOL_INPUT_ATTRIBUTE]: expect.objectContaining({ value: expect.any(String) }), - [GEN_AI_TOOL_NAME_ATTRIBUTE]: attr('getWeather'), - [GEN_AI_TOOL_OUTPUT_ATTRIBUTE]: expect.objectContaining({ value: expect.any(String) }), - [GEN_AI_TOOL_TYPE_ATTRIBUTE]: attr('function'), - [GEN_AI_OPERATION_NAME_ATTRIBUTE]: attr('execute_tool'), + [GEN_AI_TOOL_DESCRIPTION]: attr('Get the current weather for a location'), + [GEN_AI_TOOL_INPUT]: expect.objectContaining({ value: expect.any(String) }), + [GEN_AI_TOOL_NAME]: attr('getWeather'), + [GEN_AI_TOOL_OUTPUT]: expect.objectContaining({ value: expect.any(String) }), + [GEN_AI_TOOL_TYPE]: attr('function'), + [GEN_AI_OPERATION_NAME]: attr('execute_tool'), [SEMANTIC_ATTRIBUTE_SENTRY_OP]: attr('gen_ai.execute_tool'), [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: attr(expectedOrigin), }), @@ -255,7 +255,7 @@ describe('Vercel AI integration (streaming v4)', () => { name: 'invoke_agent', status: 'error', attributes: expect.objectContaining({ - [GEN_AI_OPERATION_NAME_ATTRIBUTE]: attr('invoke_agent'), + [GEN_AI_OPERATION_NAME]: attr('invoke_agent'), [SEMANTIC_ATTRIBUTE_SENTRY_OP]: attr('gen_ai.invoke_agent'), [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: attr(expectedOrigin), }), @@ -264,11 +264,11 @@ describe('Vercel AI integration (streaming v4)', () => { name: 'generate_content mock-model-id', status: 'ok', attributes: expect.objectContaining({ - [GEN_AI_REQUEST_MODEL_ATTRIBUTE]: attr('mock-model-id'), - [GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]: attr(15), - [GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE]: attr(25), - [GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE]: attr(40), - [GEN_AI_OPERATION_NAME_ATTRIBUTE]: attr('generate_content'), + [GEN_AI_REQUEST_MODEL]: attr('mock-model-id'), + [GEN_AI_USAGE_INPUT_TOKENS]: attr(15), + [GEN_AI_USAGE_OUTPUT_TOKENS]: attr(25), + [GEN_AI_USAGE_TOTAL_TOKENS]: attr(40), + [GEN_AI_OPERATION_NAME]: attr('generate_content'), [SEMANTIC_ATTRIBUTE_SENTRY_OP]: attr('gen_ai.generate_content'), [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: attr(expectedOrigin), }), @@ -278,9 +278,9 @@ describe('Vercel AI integration (streaming v4)', () => { status: 'error', attributes: expect.objectContaining({ [GEN_AI_TOOL_CALL_ID_ATTRIBUTE]: attr('call-1'), - [GEN_AI_TOOL_NAME_ATTRIBUTE]: attr('getWeather'), - [GEN_AI_TOOL_TYPE_ATTRIBUTE]: attr('function'), - [GEN_AI_OPERATION_NAME_ATTRIBUTE]: attr('execute_tool'), + [GEN_AI_TOOL_NAME]: attr('getWeather'), + [GEN_AI_TOOL_TYPE]: attr('function'), + [GEN_AI_OPERATION_NAME]: attr('execute_tool'), [SEMANTIC_ATTRIBUTE_SENTRY_OP]: attr('gen_ai.execute_tool'), [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: attr(expectedOrigin), }), @@ -316,9 +316,7 @@ describe('Vercel AI integration (streaming v4)', () => { const spans = container.items; const chatSpan = spans.find(s => - getStringAttributeValue(s.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value)?.includes( - streamingLongContent, - ), + getStringAttributeValue(s.attributes[GEN_AI_INPUT_MESSAGES]?.value)?.includes(streamingLongContent), ); expect(chatSpan).toBeDefined(); }, @@ -337,13 +335,13 @@ describe('Vercel AI integration (streaming v4)', () => { // With explicit enableTruncation: true, content should be truncated despite streaming. const chatSpan = spans.find(s => - getStringAttributeValue(s.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value)?.startsWith( + getStringAttributeValue(s.attributes[GEN_AI_INPUT_MESSAGES]?.value)?.startsWith( '[{"role":"user","content":"AAAA', ), ); expect(chatSpan).toBeDefined(); expect( - (getStringAttributeValue(chatSpan!.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE].value) ?? '').length, + (getStringAttributeValue(chatSpan!.attributes[GEN_AI_INPUT_MESSAGES].value) ?? '').length, ).toBeLessThan(streamingLongContent.length); }, }) diff --git a/dev-packages/node-integration-tests/suites/tracing/vercelai/span-streaming-v6/test.ts b/dev-packages/node-integration-tests/suites/tracing/vercelai/span-streaming-v6/test.ts index 2e8ccc0c1602..38d4b0873d59 100644 --- a/dev-packages/node-integration-tests/suites/tracing/vercelai/span-streaming-v6/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/vercelai/span-streaming-v6/test.ts @@ -1,22 +1,24 @@ import { SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '@sentry/core'; import { afterAll, describe, expect } from 'vitest'; import { - GEN_AI_INPUT_MESSAGES_ATTRIBUTE, + GEN_AI_INPUT_MESSAGES, + GEN_AI_OPERATION_NAME, + GEN_AI_OUTPUT_MESSAGES, + GEN_AI_REQUEST_AVAILABLE_TOOLS, + GEN_AI_REQUEST_MODEL, + GEN_AI_RESPONSE_MODEL, + GEN_AI_TOOL_DESCRIPTION, + GEN_AI_TOOL_INPUT, + GEN_AI_TOOL_NAME, + GEN_AI_TOOL_OUTPUT, + GEN_AI_TOOL_TYPE, + GEN_AI_USAGE_INPUT_TOKENS, + GEN_AI_USAGE_OUTPUT_TOKENS, + GEN_AI_USAGE_TOTAL_TOKENS, +} from '@sentry/conventions/attributes'; +import { GEN_AI_INPUT_MESSAGES_ORIGINAL_LENGTH_ATTRIBUTE, - GEN_AI_OPERATION_NAME_ATTRIBUTE, - GEN_AI_OUTPUT_MESSAGES_ATTRIBUTE, - GEN_AI_REQUEST_AVAILABLE_TOOLS_ATTRIBUTE, - GEN_AI_REQUEST_MODEL_ATTRIBUTE, - GEN_AI_RESPONSE_MODEL_ATTRIBUTE, GEN_AI_TOOL_CALL_ID_ATTRIBUTE, - GEN_AI_TOOL_DESCRIPTION_ATTRIBUTE, - GEN_AI_TOOL_INPUT_ATTRIBUTE, - GEN_AI_TOOL_NAME_ATTRIBUTE, - GEN_AI_TOOL_OUTPUT_ATTRIBUTE, - GEN_AI_TOOL_TYPE_ATTRIBUTE, - GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE, - GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE, - GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE, } from '../../../../../../packages/core/src/tracing/ai/gen-ai-attributes'; import { cleanupChildProcesses, createEsmAndCjsTests } from '../../../../utils/runner'; import { isOrchestrionEnabled } from '../../../../utils'; @@ -44,12 +46,12 @@ describe('Vercel AI integration (streaming, v6)', () => { name: 'invoke_agent', status: 'ok', attributes: expect.objectContaining({ - [GEN_AI_REQUEST_MODEL_ATTRIBUTE]: attr('mock-model-id'), - [GEN_AI_RESPONSE_MODEL_ATTRIBUTE]: attr('mock-model-id'), - [GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]: attr(10), - [GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE]: attr(20), - [GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE]: attr(30), - [GEN_AI_OPERATION_NAME_ATTRIBUTE]: attr('invoke_agent'), + [GEN_AI_REQUEST_MODEL]: attr('mock-model-id'), + [GEN_AI_RESPONSE_MODEL]: attr('mock-model-id'), + [GEN_AI_USAGE_INPUT_TOKENS]: attr(10), + [GEN_AI_USAGE_OUTPUT_TOKENS]: attr(20), + [GEN_AI_USAGE_TOTAL_TOKENS]: attr(30), + [GEN_AI_OPERATION_NAME]: attr('invoke_agent'), [SEMANTIC_ATTRIBUTE_SENTRY_OP]: attr('gen_ai.invoke_agent'), [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: attr(origin), }), @@ -59,12 +61,12 @@ describe('Vercel AI integration (streaming, v6)', () => { name: 'generate_content mock-model-id', status: 'ok', attributes: expect.objectContaining({ - [GEN_AI_REQUEST_MODEL_ATTRIBUTE]: attr('mock-model-id'), - [GEN_AI_RESPONSE_MODEL_ATTRIBUTE]: attr('mock-model-id'), - [GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]: attr(10), - [GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE]: attr(20), - [GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE]: attr(30), - [GEN_AI_OPERATION_NAME_ATTRIBUTE]: attr('generate_content'), + [GEN_AI_REQUEST_MODEL]: attr('mock-model-id'), + [GEN_AI_RESPONSE_MODEL]: attr('mock-model-id'), + [GEN_AI_USAGE_INPUT_TOKENS]: attr(10), + [GEN_AI_USAGE_OUTPUT_TOKENS]: attr(20), + [GEN_AI_USAGE_TOTAL_TOKENS]: attr(30), + [GEN_AI_OPERATION_NAME]: attr('generate_content'), [SEMANTIC_ATTRIBUTE_SENTRY_OP]: attr('gen_ai.generate_content'), [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: attr(origin), }), @@ -74,11 +76,11 @@ describe('Vercel AI integration (streaming, v6)', () => { name: 'invoke_agent', status: 'ok', attributes: expect.objectContaining({ - [GEN_AI_REQUEST_MODEL_ATTRIBUTE]: attr('mock-model-id'), - [GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]: attr(10), - [GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE]: attr(20), - [GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE]: attr(30), - [GEN_AI_OPERATION_NAME_ATTRIBUTE]: attr('invoke_agent'), + [GEN_AI_REQUEST_MODEL]: attr('mock-model-id'), + [GEN_AI_USAGE_INPUT_TOKENS]: attr(10), + [GEN_AI_USAGE_OUTPUT_TOKENS]: attr(20), + [GEN_AI_USAGE_TOTAL_TOKENS]: attr(30), + [GEN_AI_OPERATION_NAME]: attr('invoke_agent'), [SEMANTIC_ATTRIBUTE_SENTRY_OP]: attr('gen_ai.invoke_agent'), [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: attr(origin), }), @@ -88,11 +90,11 @@ describe('Vercel AI integration (streaming, v6)', () => { name: 'invoke_agent', status: 'ok', attributes: expect.objectContaining({ - [GEN_AI_REQUEST_MODEL_ATTRIBUTE]: attr('mock-model-id'), - [GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]: attr(15), - [GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE]: attr(25), - [GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE]: attr(40), - [GEN_AI_OPERATION_NAME_ATTRIBUTE]: attr('invoke_agent'), + [GEN_AI_REQUEST_MODEL]: attr('mock-model-id'), + [GEN_AI_USAGE_INPUT_TOKENS]: attr(15), + [GEN_AI_USAGE_OUTPUT_TOKENS]: attr(25), + [GEN_AI_USAGE_TOTAL_TOKENS]: attr(40), + [GEN_AI_OPERATION_NAME]: attr('invoke_agent'), [SEMANTIC_ATTRIBUTE_SENTRY_OP]: attr('gen_ai.invoke_agent'), [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: attr(origin), }), @@ -102,11 +104,11 @@ describe('Vercel AI integration (streaming, v6)', () => { name: 'generate_content mock-model-id', status: 'ok', attributes: expect.objectContaining({ - [GEN_AI_REQUEST_MODEL_ATTRIBUTE]: attr('mock-model-id'), - [GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]: attr(15), - [GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE]: attr(25), - [GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE]: attr(40), - [GEN_AI_OPERATION_NAME_ATTRIBUTE]: attr('generate_content'), + [GEN_AI_REQUEST_MODEL]: attr('mock-model-id'), + [GEN_AI_USAGE_INPUT_TOKENS]: attr(15), + [GEN_AI_USAGE_OUTPUT_TOKENS]: attr(25), + [GEN_AI_USAGE_TOTAL_TOKENS]: attr(40), + [GEN_AI_OPERATION_NAME]: attr('generate_content'), [SEMANTIC_ATTRIBUTE_SENTRY_OP]: attr('gen_ai.generate_content'), [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: attr(origin), }), @@ -118,9 +120,9 @@ describe('Vercel AI integration (streaming, v6)', () => { status: 'ok', attributes: expect.objectContaining({ [GEN_AI_TOOL_CALL_ID_ATTRIBUTE]: attr('call-1'), - [GEN_AI_TOOL_NAME_ATTRIBUTE]: attr('getWeather'), - [GEN_AI_TOOL_TYPE_ATTRIBUTE]: attr('function'), - [GEN_AI_OPERATION_NAME_ATTRIBUTE]: attr('execute_tool'), + [GEN_AI_TOOL_NAME]: attr('getWeather'), + [GEN_AI_TOOL_TYPE]: attr('function'), + [GEN_AI_OPERATION_NAME]: attr('execute_tool'), [SEMANTIC_ATTRIBUTE_SENTRY_OP]: attr('gen_ai.execute_tool'), [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: attr(origin), }), @@ -136,16 +138,16 @@ describe('Vercel AI integration (streaming, v6)', () => { status: 'ok', attributes: expect.objectContaining({ [GEN_AI_INPUT_MESSAGES_ORIGINAL_LENGTH_ATTRIBUTE]: attr(1), - [GEN_AI_INPUT_MESSAGES_ATTRIBUTE]: attr('[{"role":"user","content":"Where is the first span?"}]'), - [GEN_AI_OUTPUT_MESSAGES_ATTRIBUTE]: attr( + [GEN_AI_INPUT_MESSAGES]: attr('[{"role":"user","content":"Where is the first span?"}]'), + [GEN_AI_OUTPUT_MESSAGES]: attr( '[{"role":"assistant","parts":[{"type":"text","content":"First span here!"}],"finish_reason":"stop"}]', ), - [GEN_AI_REQUEST_MODEL_ATTRIBUTE]: attr('mock-model-id'), - [GEN_AI_RESPONSE_MODEL_ATTRIBUTE]: attr('mock-model-id'), - [GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]: attr(10), - [GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE]: attr(20), - [GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE]: attr(30), - [GEN_AI_OPERATION_NAME_ATTRIBUTE]: attr('invoke_agent'), + [GEN_AI_REQUEST_MODEL]: attr('mock-model-id'), + [GEN_AI_RESPONSE_MODEL]: attr('mock-model-id'), + [GEN_AI_USAGE_INPUT_TOKENS]: attr(10), + [GEN_AI_USAGE_OUTPUT_TOKENS]: attr(20), + [GEN_AI_USAGE_TOTAL_TOKENS]: attr(30), + [GEN_AI_OPERATION_NAME]: attr('invoke_agent'), [SEMANTIC_ATTRIBUTE_SENTRY_OP]: attr('gen_ai.invoke_agent'), [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: attr(origin), }), @@ -155,16 +157,16 @@ describe('Vercel AI integration (streaming, v6)', () => { name: 'generate_content mock-model-id', status: 'ok', attributes: expect.objectContaining({ - [GEN_AI_INPUT_MESSAGES_ATTRIBUTE]: expect.objectContaining({ value: expect.any(String) }), - [GEN_AI_OUTPUT_MESSAGES_ATTRIBUTE]: attr( + [GEN_AI_INPUT_MESSAGES]: expect.objectContaining({ value: expect.any(String) }), + [GEN_AI_OUTPUT_MESSAGES]: attr( '[{"role":"assistant","parts":[{"type":"text","content":"First span here!"}],"finish_reason":"stop"}]', ), - [GEN_AI_REQUEST_MODEL_ATTRIBUTE]: attr('mock-model-id'), - [GEN_AI_RESPONSE_MODEL_ATTRIBUTE]: attr('mock-model-id'), - [GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]: attr(10), - [GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE]: attr(20), - [GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE]: attr(30), - [GEN_AI_OPERATION_NAME_ATTRIBUTE]: attr('generate_content'), + [GEN_AI_REQUEST_MODEL]: attr('mock-model-id'), + [GEN_AI_RESPONSE_MODEL]: attr('mock-model-id'), + [GEN_AI_USAGE_INPUT_TOKENS]: attr(10), + [GEN_AI_USAGE_OUTPUT_TOKENS]: attr(20), + [GEN_AI_USAGE_TOTAL_TOKENS]: attr(30), + [GEN_AI_OPERATION_NAME]: attr('generate_content'), [SEMANTIC_ATTRIBUTE_SENTRY_OP]: attr('gen_ai.generate_content'), [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: attr(origin), }), @@ -174,15 +176,15 @@ describe('Vercel AI integration (streaming, v6)', () => { name: 'invoke_agent', status: 'ok', attributes: expect.objectContaining({ - [GEN_AI_INPUT_MESSAGES_ATTRIBUTE]: attr('[{"role":"user","content":"Where is the second span?"}]'), - [GEN_AI_OUTPUT_MESSAGES_ATTRIBUTE]: attr( + [GEN_AI_INPUT_MESSAGES]: attr('[{"role":"user","content":"Where is the second span?"}]'), + [GEN_AI_OUTPUT_MESSAGES]: attr( '[{"role":"assistant","parts":[{"type":"text","content":"Second span here!"}],"finish_reason":"stop"}]', ), - [GEN_AI_REQUEST_MODEL_ATTRIBUTE]: attr('mock-model-id'), - [GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]: attr(10), - [GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE]: attr(20), - [GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE]: attr(30), - [GEN_AI_OPERATION_NAME_ATTRIBUTE]: attr('invoke_agent'), + [GEN_AI_REQUEST_MODEL]: attr('mock-model-id'), + [GEN_AI_USAGE_INPUT_TOKENS]: attr(10), + [GEN_AI_USAGE_OUTPUT_TOKENS]: attr(20), + [GEN_AI_USAGE_TOTAL_TOKENS]: attr(30), + [GEN_AI_OPERATION_NAME]: attr('invoke_agent'), [SEMANTIC_ATTRIBUTE_SENTRY_OP]: attr('gen_ai.invoke_agent'), [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: attr(origin), }), @@ -192,17 +194,15 @@ describe('Vercel AI integration (streaming, v6)', () => { name: 'invoke_agent', status: 'ok', attributes: expect.objectContaining({ - [GEN_AI_INPUT_MESSAGES_ATTRIBUTE]: attr( - '[{"role":"user","content":"What is the weather in San Francisco?"}]', - ), - [GEN_AI_OUTPUT_MESSAGES_ATTRIBUTE]: attr( + [GEN_AI_INPUT_MESSAGES]: attr('[{"role":"user","content":"What is the weather in San Francisco?"}]'), + [GEN_AI_OUTPUT_MESSAGES]: attr( '[{"role":"assistant","parts":[{"type":"tool_call","id":"call-1","name":"getWeather","arguments":"{\\"location\\":\\"San Francisco\\"}"}],"finish_reason":"tool_call"}]', ), - [GEN_AI_REQUEST_MODEL_ATTRIBUTE]: attr('mock-model-id'), - [GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]: attr(15), - [GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE]: attr(25), - [GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE]: attr(40), - [GEN_AI_OPERATION_NAME_ATTRIBUTE]: attr('invoke_agent'), + [GEN_AI_REQUEST_MODEL]: attr('mock-model-id'), + [GEN_AI_USAGE_INPUT_TOKENS]: attr(15), + [GEN_AI_USAGE_OUTPUT_TOKENS]: attr(25), + [GEN_AI_USAGE_TOTAL_TOKENS]: attr(40), + [GEN_AI_OPERATION_NAME]: attr('invoke_agent'), [SEMANTIC_ATTRIBUTE_SENTRY_OP]: attr('gen_ai.invoke_agent'), [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: attr(origin), }), @@ -212,14 +212,14 @@ describe('Vercel AI integration (streaming, v6)', () => { name: 'generate_content mock-model-id', status: 'ok', attributes: expect.objectContaining({ - [GEN_AI_REQUEST_AVAILABLE_TOOLS_ATTRIBUTE]: expect.objectContaining({ + [GEN_AI_REQUEST_AVAILABLE_TOOLS]: expect.objectContaining({ value: expect.stringContaining('getWeather'), }), - [GEN_AI_REQUEST_MODEL_ATTRIBUTE]: attr('mock-model-id'), - [GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]: attr(15), - [GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE]: attr(25), - [GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE]: attr(40), - [GEN_AI_OPERATION_NAME_ATTRIBUTE]: attr('generate_content'), + [GEN_AI_REQUEST_MODEL]: attr('mock-model-id'), + [GEN_AI_USAGE_INPUT_TOKENS]: attr(15), + [GEN_AI_USAGE_OUTPUT_TOKENS]: attr(25), + [GEN_AI_USAGE_TOTAL_TOKENS]: attr(40), + [GEN_AI_OPERATION_NAME]: attr('generate_content'), [SEMANTIC_ATTRIBUTE_SENTRY_OP]: attr('gen_ai.generate_content'), [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: attr(origin), }), @@ -230,12 +230,12 @@ describe('Vercel AI integration (streaming, v6)', () => { status: 'ok', attributes: expect.objectContaining({ [GEN_AI_TOOL_CALL_ID_ATTRIBUTE]: attr('call-1'), - [GEN_AI_TOOL_DESCRIPTION_ATTRIBUTE]: attr('Get the current weather for a location'), - [GEN_AI_TOOL_INPUT_ATTRIBUTE]: expect.objectContaining({ value: expect.any(String) }), - [GEN_AI_TOOL_NAME_ATTRIBUTE]: attr('getWeather'), - [GEN_AI_TOOL_OUTPUT_ATTRIBUTE]: expect.objectContaining({ value: expect.any(String) }), - [GEN_AI_TOOL_TYPE_ATTRIBUTE]: attr('function'), - [GEN_AI_OPERATION_NAME_ATTRIBUTE]: attr('execute_tool'), + [GEN_AI_TOOL_DESCRIPTION]: attr('Get the current weather for a location'), + [GEN_AI_TOOL_INPUT]: expect.objectContaining({ value: expect.any(String) }), + [GEN_AI_TOOL_NAME]: attr('getWeather'), + [GEN_AI_TOOL_OUTPUT]: expect.objectContaining({ value: expect.any(String) }), + [GEN_AI_TOOL_TYPE]: attr('function'), + [GEN_AI_OPERATION_NAME]: attr('execute_tool'), [SEMANTIC_ATTRIBUTE_SENTRY_OP]: attr('gen_ai.execute_tool'), [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: attr(origin), }), @@ -248,7 +248,7 @@ describe('Vercel AI integration (streaming, v6)', () => { expect.objectContaining({ name: 'invoke_agent', attributes: expect.objectContaining({ - [GEN_AI_OPERATION_NAME_ATTRIBUTE]: attr('invoke_agent'), + [GEN_AI_OPERATION_NAME]: attr('invoke_agent'), [SEMANTIC_ATTRIBUTE_SENTRY_OP]: attr('gen_ai.invoke_agent'), [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: attr(origin), }), @@ -257,11 +257,11 @@ describe('Vercel AI integration (streaming, v6)', () => { name: 'generate_content mock-model-id', status: 'ok', attributes: expect.objectContaining({ - [GEN_AI_REQUEST_MODEL_ATTRIBUTE]: attr('mock-model-id'), - [GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]: attr(15), - [GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE]: attr(25), - [GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE]: attr(40), - [GEN_AI_OPERATION_NAME_ATTRIBUTE]: attr('generate_content'), + [GEN_AI_REQUEST_MODEL]: attr('mock-model-id'), + [GEN_AI_USAGE_INPUT_TOKENS]: attr(15), + [GEN_AI_USAGE_OUTPUT_TOKENS]: attr(25), + [GEN_AI_USAGE_TOTAL_TOKENS]: attr(40), + [GEN_AI_OPERATION_NAME]: attr('generate_content'), [SEMANTIC_ATTRIBUTE_SENTRY_OP]: attr('gen_ai.generate_content'), [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: attr(origin), }), @@ -271,9 +271,9 @@ describe('Vercel AI integration (streaming, v6)', () => { status: 'error', attributes: expect.objectContaining({ [GEN_AI_TOOL_CALL_ID_ATTRIBUTE]: attr('call-1'), - [GEN_AI_TOOL_NAME_ATTRIBUTE]: attr('getWeather'), - [GEN_AI_TOOL_TYPE_ATTRIBUTE]: attr('function'), - [GEN_AI_OPERATION_NAME_ATTRIBUTE]: attr('execute_tool'), + [GEN_AI_TOOL_NAME]: attr('getWeather'), + [GEN_AI_TOOL_TYPE]: attr('function'), + [GEN_AI_OPERATION_NAME]: attr('execute_tool'), [SEMANTIC_ATTRIBUTE_SENTRY_OP]: attr('gen_ai.execute_tool'), [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: attr(origin), }), diff --git a/dev-packages/node-integration-tests/suites/tracing/vercelai/test.ts b/dev-packages/node-integration-tests/suites/tracing/vercelai/test.ts index b759495545cc..e948e2706ec5 100644 --- a/dev-packages/node-integration-tests/suites/tracing/vercelai/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/vercelai/test.ts @@ -1,25 +1,27 @@ import type { Event } from '@sentry/node'; import { afterAll, describe, expect } from 'vitest'; import { - GEN_AI_EMBEDDINGS_INPUT_ATTRIBUTE, - GEN_AI_INPUT_MESSAGES_ATTRIBUTE, + GEN_AI_EMBEDDINGS_INPUT, + GEN_AI_INPUT_MESSAGES, + GEN_AI_OPERATION_NAME, + GEN_AI_OUTPUT_MESSAGES, + GEN_AI_REQUEST_AVAILABLE_TOOLS, + GEN_AI_REQUEST_MODEL, + GEN_AI_RESPONSE_MODEL, + GEN_AI_SYSTEM, + GEN_AI_SYSTEM_INSTRUCTIONS, + GEN_AI_TOOL_DESCRIPTION, + GEN_AI_TOOL_INPUT, + GEN_AI_TOOL_NAME, + GEN_AI_TOOL_OUTPUT, + GEN_AI_TOOL_TYPE, + GEN_AI_USAGE_INPUT_TOKENS, + GEN_AI_USAGE_OUTPUT_TOKENS, + GEN_AI_USAGE_TOTAL_TOKENS, +} from '@sentry/conventions/attributes'; +import { GEN_AI_INPUT_MESSAGES_ORIGINAL_LENGTH_ATTRIBUTE, - GEN_AI_OPERATION_NAME_ATTRIBUTE, - GEN_AI_OUTPUT_MESSAGES_ATTRIBUTE, - GEN_AI_REQUEST_AVAILABLE_TOOLS_ATTRIBUTE, - GEN_AI_REQUEST_MODEL_ATTRIBUTE, - GEN_AI_RESPONSE_MODEL_ATTRIBUTE, - GEN_AI_SYSTEM_ATTRIBUTE, - GEN_AI_SYSTEM_INSTRUCTIONS_ATTRIBUTE, GEN_AI_TOOL_CALL_ID_ATTRIBUTE, - GEN_AI_TOOL_DESCRIPTION_ATTRIBUTE, - GEN_AI_TOOL_INPUT_ATTRIBUTE, - GEN_AI_TOOL_NAME_ATTRIBUTE, - GEN_AI_TOOL_OUTPUT_ATTRIBUTE, - GEN_AI_TOOL_TYPE_ATTRIBUTE, - GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE, - GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE, - GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE, } from '../../../../../packages/core/src/tracing/ai/gen-ai-attributes'; import { cleanupChildProcesses, createEsmAndCjsTests } from '../../../utils/runner'; import { getStringAttributeValue, isOrchestrionEnabled } from '../../../utils'; @@ -43,27 +45,27 @@ describe('Vercel AI integration (v4)', () => { span => span.name === 'invoke_agent' && span.attributes['vercel.ai.operationId'].value === 'ai.generateText' && - span.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE] === undefined && - span.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE].value === 10, + span.attributes[GEN_AI_INPUT_MESSAGES] === undefined && + span.attributes[GEN_AI_USAGE_INPUT_TOKENS].value === 10, ); expect(firstInvokeAgentSpan).toBeDefined(); expect(firstInvokeAgentSpan!.name).toBe('invoke_agent'); expect(firstInvokeAgentSpan!.status).toBe('ok'); expect(firstInvokeAgentSpan!.attributes['sentry.op'].value).toBe('gen_ai.invoke_agent'); expect(firstInvokeAgentSpan!.attributes['vercel.ai.operationId'].value).toBe('ai.generateText'); - expect(firstInvokeAgentSpan!.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE].value).toBe('mock-model-id'); - expect(firstInvokeAgentSpan!.attributes[GEN_AI_RESPONSE_MODEL_ATTRIBUTE].value).toBe('mock-model-id'); - expect(firstInvokeAgentSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE].value).toBe(10); - expect(firstInvokeAgentSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE].value).toBe(20); - expect(firstInvokeAgentSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE].value).toBe(30); - expect(firstInvokeAgentSpan!.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]).toBeUndefined(); + expect(firstInvokeAgentSpan!.attributes[GEN_AI_REQUEST_MODEL].value).toBe('mock-model-id'); + expect(firstInvokeAgentSpan!.attributes[GEN_AI_RESPONSE_MODEL].value).toBe('mock-model-id'); + expect(firstInvokeAgentSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS].value).toBe(10); + expect(firstInvokeAgentSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS].value).toBe(20); + expect(firstInvokeAgentSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS].value).toBe(30); + expect(firstInvokeAgentSpan!.attributes[GEN_AI_INPUT_MESSAGES]).toBeUndefined(); const firstGenerateContentSpan = container.items.find( span => span.name === 'generate_content mock-model-id' && span.attributes['vercel.ai.operationId'].value === 'ai.generateText.doGenerate' && - span.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE] === undefined && - span.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE].value === 10, + span.attributes[GEN_AI_INPUT_MESSAGES] === undefined && + span.attributes[GEN_AI_USAGE_INPUT_TOKENS].value === 10, ); expect(firstGenerateContentSpan).toBeDefined(); expect(firstGenerateContentSpan!.name).toBe('generate_content mock-model-id'); @@ -72,74 +74,69 @@ describe('Vercel AI integration (v4)', () => { expect(firstGenerateContentSpan!.attributes['vercel.ai.operationId'].value).toBe( 'ai.generateText.doGenerate', ); - expect(firstGenerateContentSpan!.attributes[GEN_AI_SYSTEM_ATTRIBUTE].value).toBe('mock-provider'); - expect(firstGenerateContentSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE].value).toBe(10); - expect(firstGenerateContentSpan!.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]).toBeUndefined(); + expect(firstGenerateContentSpan!.attributes[GEN_AI_SYSTEM].value).toBe('mock-provider'); + expect(firstGenerateContentSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS].value).toBe(10); + expect(firstGenerateContentSpan!.attributes[GEN_AI_INPUT_MESSAGES]).toBeUndefined(); const secondInvokeAgentSpan = container.items.find( span => span.name === 'invoke_agent' && - span.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value === + span.attributes[GEN_AI_INPUT_MESSAGES]?.value === '[{"role":"user","content":"Where is the second span?"}]', ); expect(secondInvokeAgentSpan).toBeDefined(); expect(secondInvokeAgentSpan!.name).toBe('invoke_agent'); expect(secondInvokeAgentSpan!.status).toBe('ok'); expect(secondInvokeAgentSpan!.attributes['sentry.op'].value).toBe('gen_ai.invoke_agent'); - expect(secondInvokeAgentSpan!.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE].value).toBe( + expect(secondInvokeAgentSpan!.attributes[GEN_AI_INPUT_MESSAGES].value).toBe( '[{"role":"user","content":"Where is the second span?"}]', ); - expect(secondInvokeAgentSpan!.attributes[GEN_AI_OUTPUT_MESSAGES_ATTRIBUTE].value).toBe( + expect(secondInvokeAgentSpan!.attributes[GEN_AI_OUTPUT_MESSAGES].value).toBe( '[{"role":"assistant","parts":[{"type":"text","content":"Second span here!"}],"finish_reason":"stop"}]', ); const secondGenerateContentSpan = container.items.find( span => span.name === 'generate_content mock-model-id' && - getStringAttributeValue(span.attributes[GEN_AI_OUTPUT_MESSAGES_ATTRIBUTE]?.value)?.includes( - 'Second span here!', - ), + getStringAttributeValue(span.attributes[GEN_AI_OUTPUT_MESSAGES]?.value)?.includes('Second span here!'), ); expect(secondGenerateContentSpan).toBeDefined(); expect(secondGenerateContentSpan!.name).toBe('generate_content mock-model-id'); expect(secondGenerateContentSpan!.status).toBe('ok'); expect(secondGenerateContentSpan!.attributes['sentry.op'].value).toBe('gen_ai.generate_content'); - expect(secondGenerateContentSpan!.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]).toBeDefined(); - expect(secondGenerateContentSpan!.attributes[GEN_AI_OUTPUT_MESSAGES_ATTRIBUTE].value).toContain( - 'Second span here!', - ); + expect(secondGenerateContentSpan!.attributes[GEN_AI_INPUT_MESSAGES]).toBeDefined(); + expect(secondGenerateContentSpan!.attributes[GEN_AI_OUTPUT_MESSAGES].value).toContain('Second span here!'); const toolInvokeAgentSpan = container.items.find( - span => - span.name === 'invoke_agent' && span.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]?.value === 15, + span => span.name === 'invoke_agent' && span.attributes[GEN_AI_USAGE_INPUT_TOKENS]?.value === 15, ); expect(toolInvokeAgentSpan).toBeDefined(); expect(toolInvokeAgentSpan!.name).toBe('invoke_agent'); expect(toolInvokeAgentSpan!.status).toBe('ok'); expect(toolInvokeAgentSpan!.attributes['sentry.op'].value).toBe('gen_ai.invoke_agent'); - expect(toolInvokeAgentSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE].value).toBe(15); - expect(toolInvokeAgentSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE].value).toBe(25); - expect(toolInvokeAgentSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE].value).toBe(40); + expect(toolInvokeAgentSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS].value).toBe(15); + expect(toolInvokeAgentSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS].value).toBe(25); + expect(toolInvokeAgentSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS].value).toBe(40); const toolGenerateContentSpan = container.items.find( span => span.name === 'generate_content mock-model-id' && - span.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]?.value === 15, + span.attributes[GEN_AI_USAGE_INPUT_TOKENS]?.value === 15, ); expect(toolGenerateContentSpan).toBeDefined(); expect(toolGenerateContentSpan!.name).toBe('generate_content mock-model-id'); expect(toolGenerateContentSpan!.status).toBe('ok'); expect(toolGenerateContentSpan!.attributes['sentry.op'].value).toBe('gen_ai.generate_content'); - expect(toolGenerateContentSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE].value).toBe(15); + expect(toolGenerateContentSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS].value).toBe(15); const toolExecutionSpan = container.items.find(span => span.name === 'execute_tool getWeather'); expect(toolExecutionSpan).toBeDefined(); expect(toolExecutionSpan!.name).toBe('execute_tool getWeather'); expect(toolExecutionSpan!.status).toBe('ok'); expect(toolExecutionSpan!.attributes['sentry.op'].value).toBe('gen_ai.execute_tool'); - expect(toolExecutionSpan!.attributes[GEN_AI_TOOL_NAME_ATTRIBUTE].value).toBe('getWeather'); + expect(toolExecutionSpan!.attributes[GEN_AI_TOOL_NAME].value).toBe('getWeather'); expect(toolExecutionSpan!.attributes[GEN_AI_TOOL_CALL_ID_ATTRIBUTE].value).toBe('call-1'); - expect(toolExecutionSpan!.attributes[GEN_AI_TOOL_TYPE_ATTRIBUTE].value).toBe('function'); + expect(toolExecutionSpan!.attributes[GEN_AI_TOOL_TYPE].value).toBe('function'); }, }) .start() @@ -157,7 +154,7 @@ describe('Vercel AI integration (v4)', () => { const firstInvokeAgentSpan = container.items.find( span => span.name === 'invoke_agent' && - span.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value === + span.attributes[GEN_AI_INPUT_MESSAGES]?.value === '[{"role":"user","content":"Where is the first span?"}]', ); expect(firstInvokeAgentSpan).toBeDefined(); @@ -165,19 +162,17 @@ describe('Vercel AI integration (v4)', () => { expect(firstInvokeAgentSpan!.status).toBe('ok'); expect(firstInvokeAgentSpan!.attributes['sentry.op'].value).toBe('gen_ai.invoke_agent'); expect(firstInvokeAgentSpan!.attributes['vercel.ai.operationId'].value).toBe('ai.generateText'); - expect(firstInvokeAgentSpan!.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE].value).toBe( + expect(firstInvokeAgentSpan!.attributes[GEN_AI_INPUT_MESSAGES].value).toBe( '[{"role":"user","content":"Where is the first span?"}]', ); - expect(firstInvokeAgentSpan!.attributes[GEN_AI_OUTPUT_MESSAGES_ATTRIBUTE].value).toBe( + expect(firstInvokeAgentSpan!.attributes[GEN_AI_OUTPUT_MESSAGES].value).toBe( '[{"role":"assistant","parts":[{"type":"text","content":"First span here!"}],"finish_reason":"stop"}]', ); const firstGenerateContentSpan = container.items.find( span => span.name === 'generate_content mock-model-id' && - getStringAttributeValue(span.attributes[GEN_AI_OUTPUT_MESSAGES_ATTRIBUTE]?.value)?.includes( - 'First span here!', - ), + getStringAttributeValue(span.attributes[GEN_AI_OUTPUT_MESSAGES]?.value)?.includes('First span here!'), ); expect(firstGenerateContentSpan).toBeDefined(); expect(firstGenerateContentSpan!.name).toBe('generate_content mock-model-id'); @@ -186,31 +181,27 @@ describe('Vercel AI integration (v4)', () => { expect(firstGenerateContentSpan!.attributes['vercel.ai.operationId'].value).toBe( 'ai.generateText.doGenerate', ); - expect(firstGenerateContentSpan!.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]).toBeDefined(); - expect(firstGenerateContentSpan!.attributes[GEN_AI_OUTPUT_MESSAGES_ATTRIBUTE].value).toContain( - 'First span here!', - ); + expect(firstGenerateContentSpan!.attributes[GEN_AI_INPUT_MESSAGES]).toBeDefined(); + expect(firstGenerateContentSpan!.attributes[GEN_AI_OUTPUT_MESSAGES].value).toContain('First span here!'); const secondInvokeAgentSpan = container.items.find( span => span.name === 'invoke_agent' && - span.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value === + span.attributes[GEN_AI_INPUT_MESSAGES]?.value === '[{"role":"user","content":"Where is the second span?"}]', ); expect(secondInvokeAgentSpan).toBeDefined(); expect(secondInvokeAgentSpan!.name).toBe('invoke_agent'); expect(secondInvokeAgentSpan!.status).toBe('ok'); expect(secondInvokeAgentSpan!.attributes['sentry.op'].value).toBe('gen_ai.invoke_agent'); - expect(secondInvokeAgentSpan!.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE].value).toBe( + expect(secondInvokeAgentSpan!.attributes[GEN_AI_INPUT_MESSAGES].value).toBe( '[{"role":"user","content":"Where is the second span?"}]', ); const secondGenerateContentSpan = container.items.find( span => span.name === 'generate_content mock-model-id' && - getStringAttributeValue(span.attributes[GEN_AI_OUTPUT_MESSAGES_ATTRIBUTE]?.value)?.includes( - 'Second span here!', - ), + getStringAttributeValue(span.attributes[GEN_AI_OUTPUT_MESSAGES]?.value)?.includes('Second span here!'), ); expect(secondGenerateContentSpan).toBeDefined(); expect(secondGenerateContentSpan!.name).toBe('generate_content mock-model-id'); @@ -220,45 +211,41 @@ describe('Vercel AI integration (v4)', () => { const toolInvokeAgentSpan = container.items.find( span => span.name === 'invoke_agent' && - span.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value === + span.attributes[GEN_AI_INPUT_MESSAGES]?.value === '[{"role":"user","content":"What is the weather in San Francisco?"}]', ); expect(toolInvokeAgentSpan).toBeDefined(); expect(toolInvokeAgentSpan!.name).toBe('invoke_agent'); expect(toolInvokeAgentSpan!.status).toBe('ok'); expect(toolInvokeAgentSpan!.attributes['sentry.op'].value).toBe('gen_ai.invoke_agent'); - expect(toolInvokeAgentSpan!.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE].value).toBe( + expect(toolInvokeAgentSpan!.attributes[GEN_AI_INPUT_MESSAGES].value).toBe( '[{"role":"user","content":"What is the weather in San Francisco?"}]', ); - expect(toolInvokeAgentSpan!.attributes[GEN_AI_OUTPUT_MESSAGES_ATTRIBUTE]).toBeDefined(); + expect(toolInvokeAgentSpan!.attributes[GEN_AI_OUTPUT_MESSAGES]).toBeDefined(); const toolGenerateContentSpan = container.items.find( span => span.name === 'generate_content mock-model-id' && - getStringAttributeValue(span.attributes[GEN_AI_REQUEST_AVAILABLE_TOOLS_ATTRIBUTE]?.value)?.includes( - 'getWeather', - ), + getStringAttributeValue(span.attributes[GEN_AI_REQUEST_AVAILABLE_TOOLS]?.value)?.includes('getWeather'), ); expect(toolGenerateContentSpan).toBeDefined(); expect(toolGenerateContentSpan!.name).toBe('generate_content mock-model-id'); expect(toolGenerateContentSpan!.status).toBe('ok'); expect(toolGenerateContentSpan!.attributes['sentry.op'].value).toBe('gen_ai.generate_content'); - expect(toolGenerateContentSpan!.attributes[GEN_AI_REQUEST_AVAILABLE_TOOLS_ATTRIBUTE].value).toContain( - 'getWeather', - ); - expect(toolGenerateContentSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE].value).toBe(15); + expect(toolGenerateContentSpan!.attributes[GEN_AI_REQUEST_AVAILABLE_TOOLS].value).toContain('getWeather'); + expect(toolGenerateContentSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS].value).toBe(15); const toolExecutionSpan = container.items.find(span => span.name === 'execute_tool getWeather'); expect(toolExecutionSpan).toBeDefined(); expect(toolExecutionSpan!.name).toBe('execute_tool getWeather'); expect(toolExecutionSpan!.status).toBe('ok'); expect(toolExecutionSpan!.attributes['sentry.op'].value).toBe('gen_ai.execute_tool'); - expect(toolExecutionSpan!.attributes[GEN_AI_TOOL_NAME_ATTRIBUTE].value).toBe('getWeather'); - expect(toolExecutionSpan!.attributes[GEN_AI_TOOL_DESCRIPTION_ATTRIBUTE].value).toBe( + expect(toolExecutionSpan!.attributes[GEN_AI_TOOL_NAME].value).toBe('getWeather'); + expect(toolExecutionSpan!.attributes[GEN_AI_TOOL_DESCRIPTION].value).toBe( 'Get the current weather for a location', ); - expect(toolExecutionSpan!.attributes[GEN_AI_TOOL_INPUT_ATTRIBUTE]).toBeDefined(); - expect(toolExecutionSpan!.attributes[GEN_AI_TOOL_OUTPUT_ATTRIBUTE]).toBeDefined(); + expect(toolExecutionSpan!.attributes[GEN_AI_TOOL_INPUT]).toBeDefined(); + expect(toolExecutionSpan!.attributes[GEN_AI_TOOL_OUTPUT]).toBeDefined(); }, }) .start() @@ -305,7 +292,7 @@ describe('Vercel AI integration (v4)', () => { expect(toolSpan!.status).toBe('error'); expect(toolSpan!.attributes['sentry.op'].value).toBe('gen_ai.execute_tool'); expect(toolSpan!.attributes['sentry.origin'].value).toBe(expectedOrigin); - expect(toolSpan!.attributes[GEN_AI_TOOL_NAME_ATTRIBUTE].value).toBe('getWeather'); + expect(toolSpan!.attributes[GEN_AI_TOOL_NAME].value).toBe('getWeather'); }, }) .expect({ @@ -376,7 +363,7 @@ describe('Vercel AI integration (v4)', () => { expect(toolSpan!.name).toBe('execute_tool getWeather'); expect(toolSpan!.status).toBe('error'); expect(toolSpan!.attributes['sentry.op'].value).toBe('gen_ai.execute_tool'); - expect(toolSpan!.attributes[GEN_AI_TOOL_NAME_ATTRIBUTE].value).toBe('getWeather'); + expect(toolSpan!.attributes[GEN_AI_TOOL_NAME].value).toBe('getWeather'); }, }) .expect({ @@ -431,7 +418,7 @@ describe('Vercel AI integration (v4)', () => { expect(invokeAgentSpan!.status).toBe('ok'); expect(invokeAgentSpan!.attributes['sentry.op'].value).toBe('gen_ai.invoke_agent'); expect(invokeAgentSpan!.attributes['sentry.origin'].value).toBe('auto.vercelai.otel'); - expect(invokeAgentSpan!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE].value).toBe('invoke_agent'); + expect(invokeAgentSpan!.attributes[GEN_AI_OPERATION_NAME].value).toBe('invoke_agent'); const generateContentSpan = container.items.find(span => span.name === 'generateText.doGenerate'); expect(generateContentSpan).toBeDefined(); @@ -439,7 +426,7 @@ describe('Vercel AI integration (v4)', () => { expect(generateContentSpan!.status).toBe('ok'); expect(generateContentSpan!.attributes['sentry.op'].value).toBe('gen_ai.generate_content'); expect(generateContentSpan!.attributes['sentry.origin'].value).toBe('auto.vercelai.otel'); - expect(generateContentSpan!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE].value).toBe('generate_content'); + expect(generateContentSpan!.attributes[GEN_AI_OPERATION_NAME].value).toBe('generate_content'); }, }) .start() @@ -463,7 +450,7 @@ describe('Vercel AI integration (v4)', () => { expect(invokeAgentSpan).toBeDefined(); expect(invokeAgentSpan!.name).toBe('invoke_agent'); expect(invokeAgentSpan!.attributes['sentry.op'].value).toBe('gen_ai.invoke_agent'); - expect(invokeAgentSpan!.attributes[GEN_AI_SYSTEM_INSTRUCTIONS_ATTRIBUTE].value).toBe( + expect(invokeAgentSpan!.attributes[GEN_AI_SYSTEM_INSTRUCTIONS].value).toBe( JSON.stringify([{ type: 'text', content: 'You are a helpful assistant' }]), ); @@ -494,7 +481,7 @@ describe('Vercel AI integration (v4)', () => { const truncatedInvokeAgentSpan = container.items.find( span => span.name === 'invoke_agent' && - getStringAttributeValue(span.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value)?.match( + getStringAttributeValue(span.attributes[GEN_AI_INPUT_MESSAGES]?.value)?.match( /^\[.*"(?:text|content)":"C+".*\]$/, ), ); @@ -504,14 +491,14 @@ describe('Vercel AI integration (v4)', () => { expect(truncatedInvokeAgentSpan!.attributes[GEN_AI_INPUT_MESSAGES_ORIGINAL_LENGTH_ATTRIBUTE].value).toBe( 3, ); - expect(truncatedInvokeAgentSpan!.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE].value).toMatch( + expect(truncatedInvokeAgentSpan!.attributes[GEN_AI_INPUT_MESSAGES].value).toMatch( /^\[.*"(?:text|content)":"C+".*\]$/, ); const smallMessageInvokeAgentSpan = container.items.find( span => span.name === 'invoke_agent' && - getStringAttributeValue(span.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value)?.includes( + getStringAttributeValue(span.attributes[GEN_AI_INPUT_MESSAGES]?.value)?.includes( 'This is a small message that fits within the limit', ), ); @@ -521,7 +508,7 @@ describe('Vercel AI integration (v4)', () => { expect( smallMessageInvokeAgentSpan!.attributes[GEN_AI_INPUT_MESSAGES_ORIGINAL_LENGTH_ATTRIBUTE].value, ).toBe(3); - expect(smallMessageInvokeAgentSpan!.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE].value).toContain( + expect(smallMessageInvokeAgentSpan!.attributes[GEN_AI_INPUT_MESSAGES].value).toContain( 'This is a small message that fits within the limit', ); }, @@ -539,24 +526,22 @@ describe('Vercel AI integration (v4)', () => { .expect({ span: container => { expect(container.items).toHaveLength(2); - const embedSpan = container.items.find( - span => span.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]?.value === 10, - ); + const embedSpan = container.items.find(span => span.attributes[GEN_AI_USAGE_INPUT_TOKENS]?.value === 10); expect(embedSpan).toBeDefined(); expect(embedSpan!.name).toBe('embeddings mock-model-id'); expect(embedSpan!.status).toBe('ok'); expect(embedSpan!.attributes['sentry.op'].value).toBe('gen_ai.embeddings'); - expect(embedSpan!.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE].value).toBe('mock-model-id'); - expect(embedSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE].value).toBe(10); + expect(embedSpan!.attributes[GEN_AI_REQUEST_MODEL].value).toBe('mock-model-id'); + expect(embedSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS].value).toBe(10); const embedManySpan = container.items.find( - span => span.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]?.value === 20, + span => span.attributes[GEN_AI_USAGE_INPUT_TOKENS]?.value === 20, ); expect(embedManySpan).toBeDefined(); expect(embedManySpan!.name).toBe('embeddings mock-model-id'); expect(embedManySpan!.status).toBe('ok'); expect(embedManySpan!.attributes['sentry.op'].value).toBe('gen_ai.embeddings'); - expect(embedManySpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE].value).toBe(20); + expect(embedManySpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS].value).toBe(20); }, }) .start() @@ -572,24 +557,22 @@ describe('Vercel AI integration (v4)', () => { span: container => { expect(container.items).toHaveLength(2); const embedSpan = container.items.find( - span => span.attributes[GEN_AI_EMBEDDINGS_INPUT_ATTRIBUTE]?.value === 'Embedding test!', + span => span.attributes[GEN_AI_EMBEDDINGS_INPUT]?.value === 'Embedding test!', ); expect(embedSpan).toBeDefined(); expect(embedSpan!.name).toBe('embeddings mock-model-id'); expect(embedSpan!.status).toBe('ok'); expect(embedSpan!.attributes['sentry.op'].value).toBe('gen_ai.embeddings'); - expect(embedSpan!.attributes[GEN_AI_EMBEDDINGS_INPUT_ATTRIBUTE].value).toBe('Embedding test!'); + expect(embedSpan!.attributes[GEN_AI_EMBEDDINGS_INPUT].value).toBe('Embedding test!'); const embedManySpan = container.items.find( - span => span.attributes[GEN_AI_EMBEDDINGS_INPUT_ATTRIBUTE]?.value === '["First input","Second input"]', + span => span.attributes[GEN_AI_EMBEDDINGS_INPUT]?.value === '["First input","Second input"]', ); expect(embedManySpan).toBeDefined(); expect(embedManySpan!.name).toBe('embeddings mock-model-id'); expect(embedManySpan!.status).toBe('ok'); expect(embedManySpan!.attributes['sentry.op'].value).toBe('gen_ai.embeddings'); - expect(embedManySpan!.attributes[GEN_AI_EMBEDDINGS_INPUT_ATTRIBUTE].value).toBe( - '["First input","Second input"]', - ); + expect(embedManySpan!.attributes[GEN_AI_EMBEDDINGS_INPUT].value).toBe('["First input","Second input"]'); }, }) .start() @@ -638,7 +621,7 @@ describe('Vercel AI integration (v4)', () => { const invokeAgentSpan = container.items.find( span => span.name === 'invoke_agent' && - span.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value === + span.attributes[GEN_AI_INPUT_MESSAGES]?.value === JSON.stringify([ { role: 'user', content: longContent }, { role: 'assistant', content: 'Some reply' }, @@ -648,7 +631,7 @@ describe('Vercel AI integration (v4)', () => { expect(invokeAgentSpan).toBeDefined(); expect(invokeAgentSpan!.name).toBe('invoke_agent'); expect(invokeAgentSpan!.attributes['sentry.op'].value).toBe('gen_ai.invoke_agent'); - expect(invokeAgentSpan!.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE].value).toBe( + expect(invokeAgentSpan!.attributes[GEN_AI_INPUT_MESSAGES].value).toBe( JSON.stringify([ { role: 'user', content: longContent }, { role: 'assistant', content: 'Some reply' }, @@ -683,13 +666,13 @@ describe('Vercel AI integration (v4)', () => { expect(invokeAgentSpan!.attributes['sentry.op'].value).toBe('gen_ai.invoke_agent'); expect(invokeAgentSpan!.attributes['sentry.origin'].value).toBe(expectedOrigin); expect(invokeAgentSpan!.attributes['vercel.ai.operationId'].value).toBe('ai.streamText'); - expect(invokeAgentSpan!.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE].value).toBe('mock-model-id'); + expect(invokeAgentSpan!.attributes[GEN_AI_REQUEST_MODEL].value).toBe('mock-model-id'); // Aggregated over the drained stream: v4 reports `promptTokens`/`completionTokens`, which the // subscriber normalizes to input/output token attributes. - expect(invokeAgentSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE].value).toBe(10); - expect(invokeAgentSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE].value).toBe(20); - expect(invokeAgentSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE].value).toBe(30); - expect(invokeAgentSpan!.attributes[GEN_AI_OUTPUT_MESSAGES_ATTRIBUTE].value).toContain('Stream response!'); + expect(invokeAgentSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS].value).toBe(10); + expect(invokeAgentSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS].value).toBe(20); + expect(invokeAgentSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS].value).toBe(30); + expect(invokeAgentSpan!.attributes[GEN_AI_OUTPUT_MESSAGES].value).toContain('Stream response!'); const generateContentSpan = container.items.find(span => span.name === 'generate_content mock-model-id'); expect(generateContentSpan).toBeDefined(); diff --git a/dev-packages/node-integration-tests/suites/tracing/vercelai/v5/test.ts b/dev-packages/node-integration-tests/suites/tracing/vercelai/v5/test.ts index 87ca0c5861f3..37809bc4eeef 100644 --- a/dev-packages/node-integration-tests/suites/tracing/vercelai/v5/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/vercelai/v5/test.ts @@ -1,22 +1,22 @@ import type { Event } from '@sentry/node'; import { afterAll, describe, expect } from 'vitest'; import { - GEN_AI_INPUT_MESSAGES_ATTRIBUTE, - GEN_AI_OUTPUT_MESSAGES_ATTRIBUTE, - GEN_AI_REQUEST_AVAILABLE_TOOLS_ATTRIBUTE, - GEN_AI_REQUEST_MODEL_ATTRIBUTE, - GEN_AI_RESPONSE_MODEL_ATTRIBUTE, - GEN_AI_SYSTEM_ATTRIBUTE, - GEN_AI_TOOL_CALL_ID_ATTRIBUTE, - GEN_AI_TOOL_DESCRIPTION_ATTRIBUTE, - GEN_AI_TOOL_INPUT_ATTRIBUTE, - GEN_AI_TOOL_NAME_ATTRIBUTE, - GEN_AI_TOOL_OUTPUT_ATTRIBUTE, - GEN_AI_TOOL_TYPE_ATTRIBUTE, - GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE, - GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE, - GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE, -} from '../../../../../../packages/core/src/tracing/ai/gen-ai-attributes'; + GEN_AI_INPUT_MESSAGES, + GEN_AI_OUTPUT_MESSAGES, + GEN_AI_REQUEST_AVAILABLE_TOOLS, + GEN_AI_REQUEST_MODEL, + GEN_AI_RESPONSE_MODEL, + GEN_AI_SYSTEM, + GEN_AI_TOOL_DESCRIPTION, + GEN_AI_TOOL_INPUT, + GEN_AI_TOOL_NAME, + GEN_AI_TOOL_OUTPUT, + GEN_AI_TOOL_TYPE, + GEN_AI_USAGE_INPUT_TOKENS, + GEN_AI_USAGE_OUTPUT_TOKENS, + GEN_AI_USAGE_TOTAL_TOKENS, +} from '@sentry/conventions/attributes'; +import { GEN_AI_TOOL_CALL_ID_ATTRIBUTE } from '../../../../../../packages/core/src/tracing/ai/gen-ai-attributes'; import { cleanupChildProcesses, createEsmAndCjsTests } from '../../../../utils/runner'; import { getStringAttributeValue, isOrchestrionEnabled } from '../../../../utils'; @@ -42,8 +42,8 @@ describe('Vercel AI integration (v5)', () => { span => span.name === 'invoke_agent' && span.attributes['vercel.ai.operationId'].value === 'ai.generateText' && - span.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE] === undefined && - span.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE].value === 10, + span.attributes[GEN_AI_INPUT_MESSAGES] === undefined && + span.attributes[GEN_AI_USAGE_INPUT_TOKENS].value === 10, ); expect(firstInvokeAgentSpan).toBeDefined(); expect(firstInvokeAgentSpan!.name).toBe('invoke_agent'); @@ -51,19 +51,19 @@ describe('Vercel AI integration (v5)', () => { expect(firstInvokeAgentSpan!.attributes['sentry.op'].value).toBe('gen_ai.invoke_agent'); expect(firstInvokeAgentSpan!.attributes['sentry.origin'].value).toBe(expectedOrigin); expect(firstInvokeAgentSpan!.attributes['vercel.ai.operationId'].value).toBe('ai.generateText'); - expect(firstInvokeAgentSpan!.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE].value).toBe('mock-model-id'); - expect(firstInvokeAgentSpan!.attributes[GEN_AI_RESPONSE_MODEL_ATTRIBUTE].value).toBe('mock-model-id'); - expect(firstInvokeAgentSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE].value).toBe(10); - expect(firstInvokeAgentSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE].value).toBe(20); - expect(firstInvokeAgentSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE].value).toBe(30); - expect(firstInvokeAgentSpan!.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]).toBeUndefined(); + expect(firstInvokeAgentSpan!.attributes[GEN_AI_REQUEST_MODEL].value).toBe('mock-model-id'); + expect(firstInvokeAgentSpan!.attributes[GEN_AI_RESPONSE_MODEL].value).toBe('mock-model-id'); + expect(firstInvokeAgentSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS].value).toBe(10); + expect(firstInvokeAgentSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS].value).toBe(20); + expect(firstInvokeAgentSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS].value).toBe(30); + expect(firstInvokeAgentSpan!.attributes[GEN_AI_INPUT_MESSAGES]).toBeUndefined(); const firstGenerateContentSpan = container.items.find( span => span.name === 'generate_content mock-model-id' && span.attributes['vercel.ai.operationId'].value === 'ai.generateText.doGenerate' && - span.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE] === undefined && - span.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE].value === 10, + span.attributes[GEN_AI_INPUT_MESSAGES] === undefined && + span.attributes[GEN_AI_USAGE_INPUT_TOKENS].value === 10, ); expect(firstGenerateContentSpan).toBeDefined(); expect(firstGenerateContentSpan!.name).toBe('generate_content mock-model-id'); @@ -72,31 +72,31 @@ describe('Vercel AI integration (v5)', () => { expect(firstGenerateContentSpan!.attributes['vercel.ai.operationId'].value).toBe( 'ai.generateText.doGenerate', ); - expect(firstGenerateContentSpan!.attributes[GEN_AI_SYSTEM_ATTRIBUTE].value).toBe('mock-provider'); - expect(firstGenerateContentSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE].value).toBe(10); - expect(firstGenerateContentSpan!.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]).toBeUndefined(); + expect(firstGenerateContentSpan!.attributes[GEN_AI_SYSTEM].value).toBe('mock-provider'); + expect(firstGenerateContentSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS].value).toBe(10); + expect(firstGenerateContentSpan!.attributes[GEN_AI_INPUT_MESSAGES]).toBeUndefined(); const secondInvokeAgentSpan = container.items.find( span => span.name === 'invoke_agent' && - span.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value === + span.attributes[GEN_AI_INPUT_MESSAGES]?.value === '[{"role":"user","content":"Where is the second span?"}]', ); expect(secondInvokeAgentSpan).toBeDefined(); expect(secondInvokeAgentSpan!.name).toBe('invoke_agent'); expect(secondInvokeAgentSpan!.status).toBe('ok'); expect(secondInvokeAgentSpan!.attributes['sentry.op'].value).toBe('gen_ai.invoke_agent'); - expect(secondInvokeAgentSpan!.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE].value).toBe( + expect(secondInvokeAgentSpan!.attributes[GEN_AI_INPUT_MESSAGES].value).toBe( '[{"role":"user","content":"Where is the second span?"}]', ); - expect(secondInvokeAgentSpan!.attributes[GEN_AI_OUTPUT_MESSAGES_ATTRIBUTE].value).toBe( + expect(secondInvokeAgentSpan!.attributes[GEN_AI_OUTPUT_MESSAGES].value).toBe( '[{"role":"assistant","parts":[{"type":"text","content":"Second span here!"}],"finish_reason":"stop"}]', ); const secondGenerateContentSpan = container.items.find( span => span.name === 'generate_content mock-model-id' && - getStringAttributeValue(span.attributes[GEN_AI_OUTPUT_MESSAGES_ATTRIBUTE]?.value)?.includes( + getStringAttributeValue(span.attributes[GEN_AI_OUTPUT_MESSAGES]?.value)?.includes( 'Second span here!', ), ); @@ -106,8 +106,7 @@ describe('Vercel AI integration (v5)', () => { expect(secondGenerateContentSpan!.attributes['sentry.op'].value).toBe('gen_ai.generate_content'); const toolInvokeAgentSpan = container.items.find( - span => - span.name === 'invoke_agent' && span.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]?.value === 15, + span => span.name === 'invoke_agent' && span.attributes[GEN_AI_USAGE_INPUT_TOKENS]?.value === 15, ); expect(toolInvokeAgentSpan).toBeDefined(); expect(toolInvokeAgentSpan!.name).toBe('invoke_agent'); @@ -116,7 +115,7 @@ describe('Vercel AI integration (v5)', () => { const toolGenerateContentSpan = container.items.find( span => span.name === 'generate_content mock-model-id' && - span.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]?.value === 15, + span.attributes[GEN_AI_USAGE_INPUT_TOKENS]?.value === 15, ); expect(toolGenerateContentSpan).toBeDefined(); expect(toolGenerateContentSpan!.name).toBe('generate_content mock-model-id'); @@ -128,9 +127,9 @@ describe('Vercel AI integration (v5)', () => { expect(toolExecutionSpan!.status).toBe('ok'); expect(toolExecutionSpan!.attributes['sentry.op'].value).toBe('gen_ai.execute_tool'); expect(toolExecutionSpan!.attributes['sentry.origin'].value).toBe(expectedOrigin); - expect(toolExecutionSpan!.attributes[GEN_AI_TOOL_NAME_ATTRIBUTE].value).toBe('getWeather'); + expect(toolExecutionSpan!.attributes[GEN_AI_TOOL_NAME].value).toBe('getWeather'); expect(toolExecutionSpan!.attributes[GEN_AI_TOOL_CALL_ID_ATTRIBUTE].value).toBe('call-1'); - expect(toolExecutionSpan!.attributes[GEN_AI_TOOL_TYPE_ATTRIBUTE].value).toBe('function'); + expect(toolExecutionSpan!.attributes[GEN_AI_TOOL_TYPE].value).toBe('function'); }, }) .start() @@ -158,7 +157,7 @@ describe('Vercel AI integration (v5)', () => { const firstInvokeAgentSpan = container.items.find( span => span.name === 'invoke_agent' && - span.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value === + span.attributes[GEN_AI_INPUT_MESSAGES]?.value === '[{"role":"user","content":"Where is the first span?"}]', ); expect(firstInvokeAgentSpan).toBeDefined(); @@ -166,19 +165,17 @@ describe('Vercel AI integration (v5)', () => { expect(firstInvokeAgentSpan!.status).toBe('ok'); expect(firstInvokeAgentSpan!.attributes['sentry.op'].value).toBe('gen_ai.invoke_agent'); expect(firstInvokeAgentSpan!.attributes['vercel.ai.operationId'].value).toBe('ai.generateText'); - expect(firstInvokeAgentSpan!.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE].value).toBe( + expect(firstInvokeAgentSpan!.attributes[GEN_AI_INPUT_MESSAGES].value).toBe( '[{"role":"user","content":"Where is the first span?"}]', ); - expect(firstInvokeAgentSpan!.attributes[GEN_AI_OUTPUT_MESSAGES_ATTRIBUTE].value).toBe( + expect(firstInvokeAgentSpan!.attributes[GEN_AI_OUTPUT_MESSAGES].value).toBe( '[{"role":"assistant","parts":[{"type":"text","content":"First span here!"}],"finish_reason":"stop"}]', ); const firstGenerateContentSpan = container.items.find( span => span.name === 'generate_content mock-model-id' && - getStringAttributeValue(span.attributes[GEN_AI_OUTPUT_MESSAGES_ATTRIBUTE]?.value)?.includes( - 'First span here!', - ), + getStringAttributeValue(span.attributes[GEN_AI_OUTPUT_MESSAGES]?.value)?.includes('First span here!'), ); expect(firstGenerateContentSpan).toBeDefined(); expect(firstGenerateContentSpan!.name).toBe('generate_content mock-model-id'); @@ -187,29 +184,27 @@ describe('Vercel AI integration (v5)', () => { expect(firstGenerateContentSpan!.attributes['vercel.ai.operationId'].value).toBe( 'ai.generateText.doGenerate', ); - expect(firstGenerateContentSpan!.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]).toBeDefined(); - expect(firstGenerateContentSpan!.attributes[GEN_AI_OUTPUT_MESSAGES_ATTRIBUTE].value).toContain( - 'First span here!', - ); + expect(firstGenerateContentSpan!.attributes[GEN_AI_INPUT_MESSAGES]).toBeDefined(); + expect(firstGenerateContentSpan!.attributes[GEN_AI_OUTPUT_MESSAGES].value).toContain('First span here!'); const secondInvokeAgentSpan = container.items.find( span => span.name === 'invoke_agent' && - span.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value === + span.attributes[GEN_AI_INPUT_MESSAGES]?.value === '[{"role":"user","content":"Where is the second span?"}]', ); expect(secondInvokeAgentSpan).toBeDefined(); expect(secondInvokeAgentSpan!.name).toBe('invoke_agent'); expect(secondInvokeAgentSpan!.status).toBe('ok'); expect(secondInvokeAgentSpan!.attributes['sentry.op'].value).toBe('gen_ai.invoke_agent'); - expect(secondInvokeAgentSpan!.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE].value).toBe( + expect(secondInvokeAgentSpan!.attributes[GEN_AI_INPUT_MESSAGES].value).toBe( '[{"role":"user","content":"Where is the second span?"}]', ); const secondGenerateContentSpan = container.items.find( span => span.name === 'generate_content mock-model-id' && - getStringAttributeValue(span.attributes[GEN_AI_OUTPUT_MESSAGES_ATTRIBUTE]?.value)?.includes( + getStringAttributeValue(span.attributes[GEN_AI_OUTPUT_MESSAGES]?.value)?.includes( 'Second span here!', ), ); @@ -221,39 +216,39 @@ describe('Vercel AI integration (v5)', () => { const toolInvokeAgentSpan = container.items.find( span => span.name === 'invoke_agent' && - span.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value === + span.attributes[GEN_AI_INPUT_MESSAGES]?.value === '[{"role":"user","content":"What is the weather in San Francisco?"}]', ); expect(toolInvokeAgentSpan).toBeDefined(); expect(toolInvokeAgentSpan!.name).toBe('invoke_agent'); expect(toolInvokeAgentSpan!.status).toBe('ok'); - expect(toolInvokeAgentSpan!.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE].value).toBe( + expect(toolInvokeAgentSpan!.attributes[GEN_AI_INPUT_MESSAGES].value).toBe( '[{"role":"user","content":"What is the weather in San Francisco?"}]', ); const toolGenerateContentSpan = container.items.find( span => span.name === 'generate_content mock-model-id' && - span.attributes[GEN_AI_REQUEST_AVAILABLE_TOOLS_ATTRIBUTE] !== undefined, + span.attributes[GEN_AI_REQUEST_AVAILABLE_TOOLS] !== undefined, ); expect(toolGenerateContentSpan).toBeDefined(); expect(toolGenerateContentSpan!.name).toBe('generate_content mock-model-id'); expect(toolGenerateContentSpan!.status).toBe('ok'); expect(toolGenerateContentSpan!.attributes['sentry.op'].value).toBe('gen_ai.generate_content'); - expect(toolGenerateContentSpan!.attributes[GEN_AI_REQUEST_AVAILABLE_TOOLS_ATTRIBUTE]).toBeDefined(); - expect(toolGenerateContentSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE].value).toBe(15); + expect(toolGenerateContentSpan!.attributes[GEN_AI_REQUEST_AVAILABLE_TOOLS]).toBeDefined(); + expect(toolGenerateContentSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS].value).toBe(15); const toolExecutionSpan = container.items.find(span => span.name === 'execute_tool getWeather'); expect(toolExecutionSpan).toBeDefined(); expect(toolExecutionSpan!.name).toBe('execute_tool getWeather'); expect(toolExecutionSpan!.status).toBe('ok'); expect(toolExecutionSpan!.attributes['sentry.op'].value).toBe('gen_ai.execute_tool'); - expect(toolExecutionSpan!.attributes[GEN_AI_TOOL_NAME_ATTRIBUTE].value).toBe('getWeather'); - expect(toolExecutionSpan!.attributes[GEN_AI_TOOL_DESCRIPTION_ATTRIBUTE].value).toBe( + expect(toolExecutionSpan!.attributes[GEN_AI_TOOL_NAME].value).toBe('getWeather'); + expect(toolExecutionSpan!.attributes[GEN_AI_TOOL_DESCRIPTION].value).toBe( 'Get the current weather for a location', ); - expect(toolExecutionSpan!.attributes[GEN_AI_TOOL_INPUT_ATTRIBUTE]).toBeDefined(); - expect(toolExecutionSpan!.attributes[GEN_AI_TOOL_OUTPUT_ATTRIBUTE]).toBeDefined(); + expect(toolExecutionSpan!.attributes[GEN_AI_TOOL_INPUT]).toBeDefined(); + expect(toolExecutionSpan!.attributes[GEN_AI_TOOL_OUTPUT]).toBeDefined(); }, }) .start() @@ -305,7 +300,7 @@ describe('Vercel AI integration (v5)', () => { expect(toolSpan!.status).toBe('error'); expect(toolSpan!.attributes['sentry.op'].value).toBe('gen_ai.execute_tool'); expect(toolSpan!.attributes['sentry.origin'].value).toBe(expectedOrigin); - expect(toolSpan!.attributes[GEN_AI_TOOL_NAME_ATTRIBUTE].value).toBe('getWeather'); + expect(toolSpan!.attributes[GEN_AI_TOOL_NAME].value).toBe('getWeather'); }, }) .expect({ diff --git a/dev-packages/node-integration-tests/suites/tracing/vercelai/v6_v7/test.ts b/dev-packages/node-integration-tests/suites/tracing/vercelai/v6_v7/test.ts index 6963de59bcc6..4803ce795370 100644 --- a/dev-packages/node-integration-tests/suites/tracing/vercelai/v6_v7/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/vercelai/v6_v7/test.ts @@ -1,27 +1,27 @@ import { NODE_VERSION, type Event } from '@sentry/node'; import { afterAll, describe, expect } from 'vitest'; import { - GEN_AI_CONVERSATION_ID_ATTRIBUTE, - GEN_AI_EMBEDDINGS_INPUT_ATTRIBUTE, - GEN_AI_INPUT_MESSAGES_ATTRIBUTE, - GEN_AI_OUTPUT_MESSAGES_ATTRIBUTE, - GEN_AI_REQUEST_AVAILABLE_TOOLS_ATTRIBUTE, - GEN_AI_REQUEST_MODEL_ATTRIBUTE, - GEN_AI_RESPONSE_FINISH_REASONS_ATTRIBUTE, - GEN_AI_RESPONSE_MODEL_ATTRIBUTE, - GEN_AI_SYSTEM_ATTRIBUTE, - GEN_AI_SYSTEM_INSTRUCTIONS_ATTRIBUTE, - GEN_AI_TOOL_CALL_ID_ATTRIBUTE, - GEN_AI_TOOL_DESCRIPTION_ATTRIBUTE, - GEN_AI_TOOL_INPUT_ATTRIBUTE, - GEN_AI_TOOL_NAME_ATTRIBUTE, - GEN_AI_TOOL_OUTPUT_ATTRIBUTE, - GEN_AI_TOOL_TYPE_ATTRIBUTE, - GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE, - GEN_AI_USAGE_INPUT_TOKENS_CACHED_ATTRIBUTE, - GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE, - GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE, -} from '../../../../../../packages/core/src/tracing/ai/gen-ai-attributes'; + GEN_AI_CONVERSATION_ID, + GEN_AI_EMBEDDINGS_INPUT, + GEN_AI_INPUT_MESSAGES, + GEN_AI_OUTPUT_MESSAGES, + GEN_AI_REQUEST_AVAILABLE_TOOLS, + GEN_AI_REQUEST_MODEL, + GEN_AI_RESPONSE_FINISH_REASONS, + GEN_AI_RESPONSE_MODEL, + GEN_AI_SYSTEM, + GEN_AI_SYSTEM_INSTRUCTIONS, + GEN_AI_TOOL_DESCRIPTION, + GEN_AI_TOOL_INPUT, + GEN_AI_TOOL_NAME, + GEN_AI_TOOL_OUTPUT, + GEN_AI_TOOL_TYPE, + GEN_AI_USAGE_INPUT_TOKENS, + GEN_AI_USAGE_INPUT_TOKENS_CACHED, + GEN_AI_USAGE_OUTPUT_TOKENS, + GEN_AI_USAGE_TOTAL_TOKENS, +} from '@sentry/conventions/attributes'; +import { GEN_AI_TOOL_CALL_ID_ATTRIBUTE } from '../../../../../../packages/core/src/tracing/ai/gen-ai-attributes'; import { cleanupChildProcesses, createEsmAndCjsTests, createEsmTests } from '../../../../utils/runner'; import { isOrchestrionEnabled } from '../../../../utils'; @@ -65,7 +65,7 @@ describe.each(matrix)('Vercel AI integration (version %s)', (version, vercelAiVe const firstInvokeAgentSpan = container.items.find( span => span.name === 'invoke_agent' && - span.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value === + span.attributes[GEN_AI_INPUT_MESSAGES]?.value === '[{"role":"user","content":"Where is the first span?"}]', )!; expect(firstInvokeAgentSpan).toBeDefined(); @@ -73,19 +73,17 @@ describe.each(matrix)('Vercel AI integration (version %s)', (version, vercelAiVe expect(firstInvokeAgentSpan.status).toBe('ok'); expect(firstInvokeAgentSpan.attributes['sentry.op']?.value).toBe('gen_ai.invoke_agent'); expect(firstInvokeAgentSpan.attributes['vercel.ai.operationId']?.value).toBe('ai.generateText'); - expect(firstInvokeAgentSpan.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value).toBe( + expect(firstInvokeAgentSpan.attributes[GEN_AI_INPUT_MESSAGES]?.value).toBe( '[{"role":"user","content":"Where is the first span?"}]', ); - expect(firstInvokeAgentSpan.attributes[GEN_AI_OUTPUT_MESSAGES_ATTRIBUTE]?.value).toBe( + expect(firstInvokeAgentSpan.attributes[GEN_AI_OUTPUT_MESSAGES]?.value).toBe( '[{"role":"assistant","parts":[{"type":"text","content":"First span here!"}],"finish_reason":"stop"}]', ); const firstGenerateContentSpan = container.items.find( span => span.name === 'generate_content mock-model-id' && - (span.attributes[GEN_AI_OUTPUT_MESSAGES_ATTRIBUTE]?.value as string | undefined)?.includes( - 'First span here!', - ), + (span.attributes[GEN_AI_OUTPUT_MESSAGES]?.value as string | undefined)?.includes('First span here!'), )!; expect(firstGenerateContentSpan).toBeDefined(); expect(firstGenerateContentSpan.name).toBe('generate_content mock-model-id'); @@ -94,15 +92,13 @@ describe.each(matrix)('Vercel AI integration (version %s)', (version, vercelAiVe expect(firstGenerateContentSpan.attributes['vercel.ai.operationId']?.value).toBe( 'ai.generateText.doGenerate', ); - expect(firstGenerateContentSpan.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]).toBeDefined(); - expect(firstGenerateContentSpan.attributes[GEN_AI_OUTPUT_MESSAGES_ATTRIBUTE]?.value).toContain( - 'First span here!', - ); + expect(firstGenerateContentSpan.attributes[GEN_AI_INPUT_MESSAGES]).toBeDefined(); + expect(firstGenerateContentSpan.attributes[GEN_AI_OUTPUT_MESSAGES]?.value).toContain('First span here!'); const secondInvokeAgentSpan = container.items.find( span => span.name === 'invoke_agent' && - span.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value === + span.attributes[GEN_AI_INPUT_MESSAGES]?.value === '[{"role":"user","content":"Where is the second span?"}]', )!; expect(secondInvokeAgentSpan).toBeDefined(); @@ -113,9 +109,7 @@ describe.each(matrix)('Vercel AI integration (version %s)', (version, vercelAiVe const secondGenerateContentSpan = container.items.find( span => span.name === 'generate_content mock-model-id' && - (span.attributes[GEN_AI_OUTPUT_MESSAGES_ATTRIBUTE]?.value as string | undefined)?.includes( - 'Second span here!', - ), + (span.attributes[GEN_AI_OUTPUT_MESSAGES]?.value as string | undefined)?.includes('Second span here!'), )!; expect(secondGenerateContentSpan).toBeDefined(); expect(secondGenerateContentSpan.name).toBe('generate_content mock-model-id'); @@ -125,39 +119,39 @@ describe.each(matrix)('Vercel AI integration (version %s)', (version, vercelAiVe const toolInvokeAgentSpan = container.items.find( span => span.name === 'invoke_agent' && - span.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value === + span.attributes[GEN_AI_INPUT_MESSAGES]?.value === '[{"role":"user","content":"What is the weather in San Francisco?"}]', )!; expect(toolInvokeAgentSpan).toBeDefined(); expect(toolInvokeAgentSpan.name).toBe('invoke_agent'); expect(toolInvokeAgentSpan.status).toBe('ok'); - expect(toolInvokeAgentSpan.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value).toBe( + expect(toolInvokeAgentSpan.attributes[GEN_AI_INPUT_MESSAGES]?.value).toBe( '[{"role":"user","content":"What is the weather in San Francisco?"}]', ); const toolGenerateContentSpan = container.items.find( span => span.name === 'generate_content mock-model-id' && - span.attributes[GEN_AI_REQUEST_AVAILABLE_TOOLS_ATTRIBUTE] !== undefined, + span.attributes[GEN_AI_REQUEST_AVAILABLE_TOOLS] !== undefined, )!; expect(toolGenerateContentSpan).toBeDefined(); expect(toolGenerateContentSpan.name).toBe('generate_content mock-model-id'); expect(toolGenerateContentSpan.status).toBe('ok'); expect(toolGenerateContentSpan.attributes['sentry.op']?.value).toBe('gen_ai.generate_content'); - expect(toolGenerateContentSpan.attributes[GEN_AI_REQUEST_AVAILABLE_TOOLS_ATTRIBUTE]).toBeDefined(); - expect(toolGenerateContentSpan.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]?.value).toBe(15); + expect(toolGenerateContentSpan.attributes[GEN_AI_REQUEST_AVAILABLE_TOOLS]).toBeDefined(); + expect(toolGenerateContentSpan.attributes[GEN_AI_USAGE_INPUT_TOKENS]?.value).toBe(15); const toolExecutionSpan = container.items.find(span => span.name === 'execute_tool getWeather')!; expect(toolExecutionSpan).toBeDefined(); expect(toolExecutionSpan.name).toBe('execute_tool getWeather'); expect(toolExecutionSpan.status).toBe('ok'); expect(toolExecutionSpan.attributes['sentry.op']?.value).toBe('gen_ai.execute_tool'); - expect(toolExecutionSpan.attributes[GEN_AI_TOOL_NAME_ATTRIBUTE]?.value).toBe('getWeather'); - expect(toolExecutionSpan.attributes[GEN_AI_TOOL_DESCRIPTION_ATTRIBUTE]?.value).toBe( + expect(toolExecutionSpan.attributes[GEN_AI_TOOL_NAME]?.value).toBe('getWeather'); + expect(toolExecutionSpan.attributes[GEN_AI_TOOL_DESCRIPTION]?.value).toBe( 'Get the current weather for a location', ); - expect(toolExecutionSpan.attributes[GEN_AI_TOOL_INPUT_ATTRIBUTE]).toBeDefined(); - expect(toolExecutionSpan.attributes[GEN_AI_TOOL_OUTPUT_ATTRIBUTE]).toBeDefined(); + expect(toolExecutionSpan.attributes[GEN_AI_TOOL_INPUT]).toBeDefined(); + expect(toolExecutionSpan.attributes[GEN_AI_TOOL_OUTPUT]).toBeDefined(); }, }) .start() @@ -190,27 +184,27 @@ describe.each(matrix)('Vercel AI integration (version %s)', (version, vercelAiVe span => span.name === 'invoke_agent' && span.attributes['vercel.ai.operationId']?.value === 'ai.generateText' && - span.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE] === undefined && - span.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]?.value === 10, + span.attributes[GEN_AI_INPUT_MESSAGES] === undefined && + span.attributes[GEN_AI_USAGE_INPUT_TOKENS]?.value === 10, )!; expect(firstInvokeAgentSpan).toBeDefined(); expect(firstInvokeAgentSpan.name).toBe('invoke_agent'); expect(firstInvokeAgentSpan.status).toBe('ok'); expect(firstInvokeAgentSpan.attributes['sentry.op']?.value).toBe('gen_ai.invoke_agent'); expect(firstInvokeAgentSpan.attributes['vercel.ai.operationId']?.value).toBe('ai.generateText'); - expect(firstInvokeAgentSpan.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE]?.value).toBe('mock-model-id'); - expect(firstInvokeAgentSpan.attributes[GEN_AI_RESPONSE_MODEL_ATTRIBUTE]?.value).toBe('mock-model-id'); - expect(firstInvokeAgentSpan.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]?.value).toBe(10); - expect(firstInvokeAgentSpan.attributes[GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE]?.value).toBe(20); - expect(firstInvokeAgentSpan.attributes[GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE]?.value).toBe(30); - expect(firstInvokeAgentSpan.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]).toBeUndefined(); + expect(firstInvokeAgentSpan.attributes[GEN_AI_REQUEST_MODEL]?.value).toBe('mock-model-id'); + expect(firstInvokeAgentSpan.attributes[GEN_AI_RESPONSE_MODEL]?.value).toBe('mock-model-id'); + expect(firstInvokeAgentSpan.attributes[GEN_AI_USAGE_INPUT_TOKENS]?.value).toBe(10); + expect(firstInvokeAgentSpan.attributes[GEN_AI_USAGE_OUTPUT_TOKENS]?.value).toBe(20); + expect(firstInvokeAgentSpan.attributes[GEN_AI_USAGE_TOTAL_TOKENS]?.value).toBe(30); + expect(firstInvokeAgentSpan.attributes[GEN_AI_INPUT_MESSAGES]).toBeUndefined(); const firstGenerateContentSpan = container.items.find( span => span.name === 'generate_content mock-model-id' && span.attributes['vercel.ai.operationId']?.value === 'ai.generateText.doGenerate' && - span.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE] === undefined && - span.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]?.value === 10, + span.attributes[GEN_AI_INPUT_MESSAGES] === undefined && + span.attributes[GEN_AI_USAGE_INPUT_TOKENS]?.value === 10, )!; expect(firstGenerateContentSpan).toBeDefined(); expect(firstGenerateContentSpan.name).toBe('generate_content mock-model-id'); @@ -219,25 +213,25 @@ describe.each(matrix)('Vercel AI integration (version %s)', (version, vercelAiVe expect(firstGenerateContentSpan.attributes['vercel.ai.operationId']?.value).toBe( 'ai.generateText.doGenerate', ); - expect(firstGenerateContentSpan.attributes[GEN_AI_SYSTEM_ATTRIBUTE]?.value).toBe('mock-provider'); - expect(firstGenerateContentSpan.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]?.value).toBe(10); - expect(firstGenerateContentSpan.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]).toBeUndefined(); + expect(firstGenerateContentSpan.attributes[GEN_AI_SYSTEM]?.value).toBe('mock-provider'); + expect(firstGenerateContentSpan.attributes[GEN_AI_USAGE_INPUT_TOKENS]?.value).toBe(10); + expect(firstGenerateContentSpan.attributes[GEN_AI_INPUT_MESSAGES]).toBeUndefined(); const secondInvokeAgentSpan = container.items.find( span => span.name === 'invoke_agent' && span.attributes['vercel.ai.operationId']?.value === 'ai.generateText' && - span.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]?.value === 91, + span.attributes[GEN_AI_USAGE_INPUT_TOKENS]?.value === 91, )!; expect(secondInvokeAgentSpan).toBeDefined(); expect(secondInvokeAgentSpan.name).toBe('invoke_agent'); expect(secondInvokeAgentSpan.status).toBe('ok'); expect(secondInvokeAgentSpan.attributes['sentry.op']?.value).toBe('gen_ai.invoke_agent'); - expect(secondInvokeAgentSpan.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value).toBe( + expect(secondInvokeAgentSpan.attributes[GEN_AI_INPUT_MESSAGES]?.value).toBe( '[{"role":"user","content":"Where is the second span?"}]', ); - expect(secondInvokeAgentSpan.attributes[GEN_AI_OUTPUT_MESSAGES_ATTRIBUTE]?.value).toBe( + expect(secondInvokeAgentSpan.attributes[GEN_AI_OUTPUT_MESSAGES]?.value).toBe( '[{"role":"assistant","parts":[{"type":"text","content":"Second span here!"}],"finish_reason":"stop"}]', ); @@ -245,23 +239,22 @@ describe.each(matrix)('Vercel AI integration (version %s)', (version, vercelAiVe span => span.name === 'generate_content mock-model-id' && span.attributes['vercel.ai.operationId']?.value === 'ai.generateText.doGenerate' && - span.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]?.value === 91, + span.attributes[GEN_AI_USAGE_INPUT_TOKENS]?.value === 91, )!; expect(secondGenerateContentSpan).toBeDefined(); expect(secondGenerateContentSpan.name).toBe('generate_content mock-model-id'); expect(secondGenerateContentSpan.status).toBe('ok'); expect(secondGenerateContentSpan.attributes['sentry.op']?.value).toBe('gen_ai.generate_content'); - expect(secondGenerateContentSpan.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]).toBeDefined(); - expect(secondGenerateContentSpan.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value as string).toContain( + expect(secondGenerateContentSpan.attributes[GEN_AI_INPUT_MESSAGES]).toBeDefined(); + expect(secondGenerateContentSpan.attributes[GEN_AI_INPUT_MESSAGES]?.value as string).toContain( 'Where is the second span?', ); - expect(secondGenerateContentSpan.attributes[GEN_AI_OUTPUT_MESSAGES_ATTRIBUTE]?.value as string).toContain( + expect(secondGenerateContentSpan.attributes[GEN_AI_OUTPUT_MESSAGES]?.value as string).toContain( 'Second span here!', ); const toolInvokeAgentSpan = container.items.find( - span => - span.name === 'invoke_agent' && span.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]?.value === 15, + span => span.name === 'invoke_agent' && span.attributes[GEN_AI_USAGE_INPUT_TOKENS]?.value === 15, )!; expect(toolInvokeAgentSpan).toBeDefined(); expect(toolInvokeAgentSpan.name).toBe('invoke_agent'); @@ -270,7 +263,7 @@ describe.each(matrix)('Vercel AI integration (version %s)', (version, vercelAiVe const toolGenerateContentSpan = container.items.find( span => span.name === 'generate_content mock-model-id' && - span.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]?.value === 15, + span.attributes[GEN_AI_USAGE_INPUT_TOKENS]?.value === 15, )!; expect(toolGenerateContentSpan).toBeDefined(); expect(toolGenerateContentSpan.name).toBe('generate_content mock-model-id'); @@ -281,9 +274,9 @@ describe.each(matrix)('Vercel AI integration (version %s)', (version, vercelAiVe expect(toolExecutionSpan.name).toBe('execute_tool getWeather'); expect(toolExecutionSpan.status).toBe('ok'); expect(toolExecutionSpan.attributes['sentry.op']?.value).toBe('gen_ai.execute_tool'); - expect(toolExecutionSpan.attributes[GEN_AI_TOOL_NAME_ATTRIBUTE]?.value).toBe('getWeather'); + expect(toolExecutionSpan.attributes[GEN_AI_TOOL_NAME]?.value).toBe('getWeather'); expect(toolExecutionSpan.attributes[GEN_AI_TOOL_CALL_ID_ATTRIBUTE]?.value).toBe('call-1'); - expect(toolExecutionSpan.attributes[GEN_AI_TOOL_TYPE_ATTRIBUTE]?.value).toBe('function'); + expect(toolExecutionSpan.attributes[GEN_AI_TOOL_TYPE]?.value).toBe('function'); }, }) .start() @@ -335,7 +328,7 @@ describe.each(matrix)('Vercel AI integration (version %s)', (version, vercelAiVe expect(toolSpan.name).toBe('execute_tool getWeather'); expect(toolSpan.status).toBe('error'); expect(toolSpan.attributes['sentry.op']?.value).toBe('gen_ai.execute_tool'); - expect(toolSpan.attributes[GEN_AI_TOOL_NAME_ATTRIBUTE]?.value).toBe('getWeather'); + expect(toolSpan.attributes[GEN_AI_TOOL_NAME]?.value).toBe('getWeather'); }, }) .expect({ @@ -431,36 +424,36 @@ describe.each(matrix)('Vercel AI integration (version %s)', (version, vercelAiVe expect(invokeAgentSpan.name).toBe('invoke_agent weather_agent'); expect(invokeAgentSpan.status).toBe('ok'); expect(invokeAgentSpan.attributes['sentry.op']?.value).toBe('gen_ai.invoke_agent'); - expect(invokeAgentSpan.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE]?.value).toBe('mock-model-id'); + expect(invokeAgentSpan.attributes[GEN_AI_REQUEST_MODEL]?.value).toBe('mock-model-id'); const toolCallsGenerateContentSpan = container.items.find( - span => span.attributes[GEN_AI_RESPONSE_FINISH_REASONS_ATTRIBUTE]?.value === '["tool-calls"]', + span => span.attributes[GEN_AI_RESPONSE_FINISH_REASONS]?.value === '["tool-calls"]', )!; expect(toolCallsGenerateContentSpan).toBeDefined(); expect(toolCallsGenerateContentSpan.name).toBe('generate_content mock-model-id'); expect(toolCallsGenerateContentSpan.status).toBe('ok'); expect(toolCallsGenerateContentSpan.attributes['sentry.op']?.value).toBe('gen_ai.generate_content'); - expect(toolCallsGenerateContentSpan.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]?.value).toBe(10); - expect(toolCallsGenerateContentSpan.attributes[GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE]?.value).toBe(20); + expect(toolCallsGenerateContentSpan.attributes[GEN_AI_USAGE_INPUT_TOKENS]?.value).toBe(10); + expect(toolCallsGenerateContentSpan.attributes[GEN_AI_USAGE_OUTPUT_TOKENS]?.value).toBe(20); const toolSpan = container.items.find(span => span.name === 'execute_tool getWeather')!; expect(toolSpan).toBeDefined(); expect(toolSpan.name).toBe('execute_tool getWeather'); expect(toolSpan.status).toBe('ok'); expect(toolSpan.attributes['sentry.op']?.value).toBe('gen_ai.execute_tool'); - expect(toolSpan.attributes[GEN_AI_TOOL_NAME_ATTRIBUTE]?.value).toBe('getWeather'); + expect(toolSpan.attributes[GEN_AI_TOOL_NAME]?.value).toBe('getWeather'); expect(toolSpan.attributes[GEN_AI_TOOL_CALL_ID_ATTRIBUTE]?.value).toBe('call-1'); - expect(toolSpan.attributes[GEN_AI_TOOL_TYPE_ATTRIBUTE]?.value).toBe('function'); + expect(toolSpan.attributes[GEN_AI_TOOL_TYPE]?.value).toBe('function'); const finalGenerateContentSpan = container.items.find( - span => span.attributes[GEN_AI_RESPONSE_FINISH_REASONS_ATTRIBUTE]?.value === '["stop"]', + span => span.attributes[GEN_AI_RESPONSE_FINISH_REASONS]?.value === '["stop"]', )!; expect(finalGenerateContentSpan).toBeDefined(); expect(finalGenerateContentSpan.name).toBe('generate_content mock-model-id'); expect(finalGenerateContentSpan.status).toBe('ok'); expect(finalGenerateContentSpan.attributes['sentry.op']?.value).toBe('gen_ai.generate_content'); - expect(finalGenerateContentSpan.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]?.value).toBe(15); - expect(finalGenerateContentSpan.attributes[GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE]?.value).toBe(25); + expect(finalGenerateContentSpan.attributes[GEN_AI_USAGE_INPUT_TOKENS]?.value).toBe(15); + expect(finalGenerateContentSpan.attributes[GEN_AI_USAGE_OUTPUT_TOKENS]?.value).toBe(25); }, }) .start() @@ -599,20 +592,20 @@ describe.each(matrix)('Vercel AI integration (version %s)', (version, vercelAiVe // The stream's final usage/finish/output arrive only as the stream drains, after the // channel already resolved the model call. Tapping the stream recovers them onto the // model-call span on every path (v7 channel, v6 OTel, v6 orchestrion). - expect(generateContent.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]?.value).toBe(10); - expect(generateContent.attributes[GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE]?.value).toBe(20); - expect(generateContent.attributes[GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE]?.value).toBe(30); - expect(generateContent.attributes[GEN_AI_RESPONSE_FINISH_REASONS_ATTRIBUTE]?.value).toBe('["stop"]'); - expect(generateContent.attributes[GEN_AI_OUTPUT_MESSAGES_ATTRIBUTE]?.value).toBe( + expect(generateContent.attributes[GEN_AI_USAGE_INPUT_TOKENS]?.value).toBe(10); + expect(generateContent.attributes[GEN_AI_USAGE_OUTPUT_TOKENS]?.value).toBe(20); + expect(generateContent.attributes[GEN_AI_USAGE_TOTAL_TOKENS]?.value).toBe(30); + expect(generateContent.attributes[GEN_AI_RESPONSE_FINISH_REASONS]?.value).toBe('["stop"]'); + expect(generateContent.attributes[GEN_AI_OUTPUT_MESSAGES]?.value).toBe( '[{"role":"assistant","parts":[{"type":"text","content":"Stream response!"}],"finish_reason":"stop"}]', ); // The summed usage and output also land on the parent invoke_agent span, whose own // channel result is otherwise undefined for a stream. - expect(invokeAgent.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]?.value).toBe(10); - expect(invokeAgent.attributes[GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE]?.value).toBe(20); - expect(invokeAgent.attributes[GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE]?.value).toBe(30); - expect(invokeAgent.attributes[GEN_AI_OUTPUT_MESSAGES_ATTRIBUTE]?.value).toBe( + expect(invokeAgent.attributes[GEN_AI_USAGE_INPUT_TOKENS]?.value).toBe(10); + expect(invokeAgent.attributes[GEN_AI_USAGE_OUTPUT_TOKENS]?.value).toBe(20); + expect(invokeAgent.attributes[GEN_AI_USAGE_TOTAL_TOKENS]?.value).toBe(30); + expect(invokeAgent.attributes[GEN_AI_OUTPUT_MESSAGES]?.value).toBe( '[{"role":"assistant","parts":[{"type":"text","content":"Stream response!"}],"finish_reason":"stop"}]', ); }, @@ -645,9 +638,9 @@ describe.each(matrix)('Vercel AI integration (version %s)', (version, vercelAiVe expect(invokeAgent.status).toBe('ok'); expect(invokeAgent.attributes['vercel.ai.operationId']?.value).toBe('ai.streamText'); // Usage is summed across the two streamed model calls (10+15, 20+25, 30+40). - expect(invokeAgent.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]?.value).toBe(25); - expect(invokeAgent.attributes[GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE]?.value).toBe(45); - expect(invokeAgent.attributes[GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE]?.value).toBe(70); + expect(invokeAgent.attributes[GEN_AI_USAGE_INPUT_TOKENS]?.value).toBe(25); + expect(invokeAgent.attributes[GEN_AI_USAGE_OUTPUT_TOKENS]?.value).toBe(45); + expect(invokeAgent.attributes[GEN_AI_USAGE_TOTAL_TOKENS]?.value).toBe(70); const generateContents = container.items.filter( span => span.attributes['sentry.op']?.value === 'gen_ai.generate_content', @@ -657,22 +650,22 @@ describe.each(matrix)('Vercel AI integration (version %s)', (version, vercelAiVe // The step that streamed a tool call: tool-call output part + tool-calls finish reason. const toolStep = generateContents.find( - span => span.attributes[GEN_AI_RESPONSE_FINISH_REASONS_ATTRIBUTE]?.value === '["tool-calls"]', + span => span.attributes[GEN_AI_RESPONSE_FINISH_REASONS]?.value === '["tool-calls"]', )!; expect(toolStep).toBeDefined(); - expect(toolStep.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]?.value).toBe(10); - expect(toolStep.attributes[GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE]?.value).toBe(20); - const toolStepOutput = toolStep.attributes[GEN_AI_OUTPUT_MESSAGES_ATTRIBUTE]?.value as string; + expect(toolStep.attributes[GEN_AI_USAGE_INPUT_TOKENS]?.value).toBe(10); + expect(toolStep.attributes[GEN_AI_USAGE_OUTPUT_TOKENS]?.value).toBe(20); + const toolStepOutput = toolStep.attributes[GEN_AI_OUTPUT_MESSAGES]?.value as string; expect(toolStepOutput).toContain('"type":"tool_call"'); expect(toolStepOutput).toContain('getWeather'); // The step that streamed the final answer text. const textStep = generateContents.find( - span => span.attributes[GEN_AI_RESPONSE_FINISH_REASONS_ATTRIBUTE]?.value === '["stop"]', + span => span.attributes[GEN_AI_RESPONSE_FINISH_REASONS]?.value === '["stop"]', )!; expect(textStep).toBeDefined(); - expect(textStep.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]?.value).toBe(15); - expect(textStep.attributes[GEN_AI_OUTPUT_MESSAGES_ATTRIBUTE]?.value).toContain('Sunny, 72°F.'); + expect(textStep.attributes[GEN_AI_USAGE_INPUT_TOKENS]?.value).toBe(15); + expect(textStep.attributes[GEN_AI_OUTPUT_MESSAGES]?.value).toContain('Sunny, 72°F.'); // A tool span is emitted for the streamed tool call. Its parent and recorded input/output // vary by path during stream consumption (tool i/o is covered by the non-stream scenario @@ -680,7 +673,7 @@ describe.each(matrix)('Vercel AI integration (version %s)', (version, vercelAiVe const executeTool = container.items.find(span => span.name === 'execute_tool getWeather')!; expect(executeTool).toBeDefined(); expect(executeTool.status).toBe('ok'); - expect(executeTool.attributes[GEN_AI_TOOL_NAME_ATTRIBUTE]?.value).toBe('getWeather'); + expect(executeTool.attributes[GEN_AI_TOOL_NAME]?.value).toBe('getWeather'); }, }) .start() @@ -710,19 +703,19 @@ describe.each(matrix)('Vercel AI integration (version %s)', (version, vercelAiVe expect(invokeAgent).toBeDefined(); expect(invokeAgent.status).toBe('ok'); expect(invokeAgent.attributes['vercel.ai.operationId']?.value).toBe('ai.streamText'); - expect(invokeAgent.attributes[GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE]?.value).toBe(30); + expect(invokeAgent.attributes[GEN_AI_USAGE_TOTAL_TOKENS]?.value).toBe(30); const generateContent = container.items.find( span => span.attributes['sentry.op']?.value === 'gen_ai.generate_content', )!; expect(generateContent).toBeDefined(); expect(generateContent.parent_span_id).toBe(invokeAgent.span_id); - expect(generateContent.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]?.value).toBe(12); - expect(generateContent.attributes[GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE]?.value).toBe(18); - expect(generateContent.attributes[GEN_AI_RESPONSE_FINISH_REASONS_ATTRIBUTE]?.value).toBe('["stop"]'); + expect(generateContent.attributes[GEN_AI_USAGE_INPUT_TOKENS]?.value).toBe(12); + expect(generateContent.attributes[GEN_AI_USAGE_OUTPUT_TOKENS]?.value).toBe(18); + expect(generateContent.attributes[GEN_AI_RESPONSE_FINISH_REASONS]?.value).toBe('["stop"]'); // The streamed JSON object is accumulated from the text deltas and captured as the // model's output text (embedded as an escaped JSON string in the output message). - const output = generateContent.attributes[GEN_AI_OUTPUT_MESSAGES_ATTRIBUTE]?.value as string; + const output = generateContent.attributes[GEN_AI_OUTPUT_MESSAGES]?.value as string; expect(output).toContain('San Francisco'); expect(output).toContain('sunny'); }, @@ -800,9 +793,9 @@ describe.each(matrix)('Vercel AI integration (version %s)', (version, vercelAiVe // Cache/reasoning token breakdown and conversation id are derived from the model's // `providerMetadata` — by the OTel processor on v6 and by the channel subscriber on v7, // both via the shared `getProviderMetadataAttributes` helper, so the shape is identical. - expect(generateContent.attributes[GEN_AI_USAGE_INPUT_TOKENS_CACHED_ATTRIBUTE]?.value).toBe(5); + expect(generateContent.attributes[GEN_AI_USAGE_INPUT_TOKENS_CACHED]?.value).toBe(5); expect(generateContent.attributes['gen_ai.usage.output_tokens.reasoning']?.value).toBe(7); - expect(generateContent.attributes[GEN_AI_CONVERSATION_ID_ATTRIBUTE]?.value).toBe('resp_abc123'); + expect(generateContent.attributes[GEN_AI_CONVERSATION_ID]?.value).toBe('resp_abc123'); const invokeAgent = container.items.find( span => span.attributes['sentry.op']?.value === 'gen_ai.invoke_agent', @@ -813,8 +806,8 @@ describe.each(matrix)('Vercel AI integration (version %s)', (version, vercelAiVe // instrumentation surfaces it (as `gen_ai.system_instructions`); v6 has no such option. if (version === '7') { const expected = '[{"type":"text","content":"You are a helpful assistant."}]'; - expect(invokeAgent.attributes[GEN_AI_SYSTEM_INSTRUCTIONS_ATTRIBUTE]?.value).toBe(expected); - expect(generateContent.attributes[GEN_AI_SYSTEM_INSTRUCTIONS_ATTRIBUTE]?.value).toBe(expected); + expect(invokeAgent.attributes[GEN_AI_SYSTEM_INSTRUCTIONS]?.value).toBe(expected); + expect(generateContent.attributes[GEN_AI_SYSTEM_INSTRUCTIONS]?.value).toBe(expected); } }, }) @@ -851,24 +844,24 @@ describe.each(matrix)('Vercel AI integration (version %s)', (version, vercelAiVe .forEach(s => expect(s.attributes['sentry.origin']?.value).toBe(expectedOrigin)); const embedSpan = container.items.find( - span => span.attributes[GEN_AI_EMBEDDINGS_INPUT_ATTRIBUTE]?.value === 'Embedding test!', + span => span.attributes[GEN_AI_EMBEDDINGS_INPUT]?.value === 'Embedding test!', )!; expect(embedSpan).toBeDefined(); expect(embedSpan.name).toBe('embeddings mock-model-id'); expect(embedSpan.status).toBe('ok'); expect(embedSpan.attributes['sentry.op']?.value).toBe('gen_ai.embeddings'); - expect(embedSpan.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE]?.value).toBe('mock-model-id'); - expect(embedSpan.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]?.value).toBe(10); + expect(embedSpan.attributes[GEN_AI_REQUEST_MODEL]?.value).toBe('mock-model-id'); + expect(embedSpan.attributes[GEN_AI_USAGE_INPUT_TOKENS]?.value).toBe(10); const embedManySpan = container.items.find( - span => span.attributes[GEN_AI_EMBEDDINGS_INPUT_ATTRIBUTE]?.value === '["First input","Second input"]', + span => span.attributes[GEN_AI_EMBEDDINGS_INPUT]?.value === '["First input","Second input"]', ); if (embedManyInstrumented) { expect(embedManySpan).toBeDefined(); expect(embedManySpan!.name).toBe('embeddings mock-model-id'); expect(embedManySpan!.status).toBe('ok'); expect(embedManySpan!.attributes['sentry.op']?.value).toBe('gen_ai.embeddings'); - expect(embedManySpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]?.value).toBe(20); + expect(embedManySpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS]?.value).toBe(20); } else { expect(embedManySpan).toBeUndefined(); } @@ -909,10 +902,10 @@ describe.each(matrix)('Vercel AI integration (version %s)', (version, vercelAiVe expect(invokeAgentSpan!.status).toBe('ok'); expect(invokeAgentSpan!.attributes['sentry.op']?.value).toBe('gen_ai.invoke_agent'); expect(invokeAgentSpan!.attributes['vercel.ai.operationId']?.value).toBe('ai.generateObject'); - expect(invokeAgentSpan!.attributes[GEN_AI_RESPONSE_MODEL_ATTRIBUTE]?.value).toBe('mock-model-id'); - expect(invokeAgentSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]?.value).toBe(15); - expect(invokeAgentSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE]?.value).toBe(25); - expect(invokeAgentSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE]?.value).toBe(40); + expect(invokeAgentSpan!.attributes[GEN_AI_RESPONSE_MODEL]?.value).toBe('mock-model-id'); + expect(invokeAgentSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS]?.value).toBe(15); + expect(invokeAgentSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS]?.value).toBe(25); + expect(invokeAgentSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS]?.value).toBe(40); const generateContentSpan = container.items.find(span => span.name === 'generate_content mock-model-id'); expect(generateContentSpan).toBeDefined(); @@ -921,8 +914,8 @@ describe.each(matrix)('Vercel AI integration (version %s)', (version, vercelAiVe expect(generateContentSpan!.attributes['vercel.ai.operationId']?.value).toBe( 'ai.generateObject.doGenerate', ); - expect(generateContentSpan!.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE]?.value).toBe('mock-model-id'); - expect(generateContentSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]?.value).toBe(15); + expect(generateContentSpan!.attributes[GEN_AI_REQUEST_MODEL]?.value).toBe('mock-model-id'); + expect(generateContentSpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS]?.value).toBe(15); }, }) .start() diff --git a/packages/core/src/shared-exports.ts b/packages/core/src/shared-exports.ts index 983367c882f9..8419fa456359 100644 --- a/packages/core/src/shared-exports.ts +++ b/packages/core/src/shared-exports.ts @@ -180,11 +180,7 @@ export type { MetricOptions } from './metrics/public-api'; export { createConsolaReporter } from './integrations/consola'; export { addVercelAiProcessors, getProviderMetadataAttributes } from './tracing/vercel-ai'; export { getTruncatedJsonString, shouldEnableTruncation, resolveAIRecordingOptions } from './tracing/ai/utils'; -export { - GEN_AI_INPUT_MESSAGES_ORIGINAL_LENGTH_ATTRIBUTE, - GEN_AI_REQUEST_MODEL_ATTRIBUTE, - GEN_AI_SYSTEM_INSTRUCTIONS_ATTRIBUTE, -} from './tracing/ai/gen-ai-attributes'; +export { GEN_AI_INPUT_MESSAGES_ORIGINAL_LENGTH_ATTRIBUTE } from './tracing/ai/gen-ai-attributes'; export { _INTERNAL_getSpanContextForToolCallId, _INTERNAL_cleanupToolCallSpanContext } from './tracing/vercel-ai/utils'; export { toolCallSpanContextMap as _INTERNAL_toolCallSpanContextMap } from './tracing/vercel-ai/constants'; export { diff --git a/packages/core/src/tracing/ai/gen-ai-attributes.ts b/packages/core/src/tracing/ai/gen-ai-attributes.ts index 16cdfa17b4b3..35050f19a8f4 100644 --- a/packages/core/src/tracing/ai/gen-ai-attributes.ts +++ b/packages/core/src/tracing/ai/gen-ai-attributes.ts @@ -1,70 +1,20 @@ /** - * OpenAI Integration Telemetry Attributes + * Gen-AI telemetry attributes that are not (yet) covered by `@sentry/conventions`. + * + * Attributes with an equivalent in `@sentry/conventions/attributes` are imported from there directly + * at their call sites. The constants below either have no conventions equivalent, are Sentry-internal + * meta attributes, are span-operation values (not attribute keys), or intentionally emit a different + * key than the current conventions attribute. + * * Based on OpenTelemetry Semantic Conventions for Generative AI * @see https://opentelemetry.io/docs/specs/semconv/gen-ai/ */ -// ============================================================================= -// OPENTELEMETRY SEMANTIC CONVENTIONS FOR GENAI -// ============================================================================= - -/** - * The input messages sent to the model - */ -export const GEN_AI_PROMPT_ATTRIBUTE = 'gen_ai.prompt'; - -/** - * The Generative AI system being used - * For OpenAI, this should always be "openai" - */ -export const GEN_AI_SYSTEM_ATTRIBUTE = 'gen_ai.system'; - -/** - * The name of the model as requested - * Examples: "gpt-4", "gpt-3.5-turbo" - */ -export const GEN_AI_REQUEST_MODEL_ATTRIBUTE = 'gen_ai.request.model'; - /** * Whether streaming was enabled for the request */ export const GEN_AI_REQUEST_STREAM_ATTRIBUTE = 'gen_ai.request.stream'; -/** - * The temperature setting for the model request - */ -export const GEN_AI_REQUEST_TEMPERATURE_ATTRIBUTE = 'gen_ai.request.temperature'; - -/** - * The maximum number of tokens requested - */ -export const GEN_AI_REQUEST_MAX_TOKENS_ATTRIBUTE = 'gen_ai.request.max_tokens'; - -/** - * The frequency penalty setting for the model request - */ -export const GEN_AI_REQUEST_FREQUENCY_PENALTY_ATTRIBUTE = 'gen_ai.request.frequency_penalty'; - -/** - * The presence penalty setting for the model request - */ -export const GEN_AI_REQUEST_PRESENCE_PENALTY_ATTRIBUTE = 'gen_ai.request.presence_penalty'; - -/** - * The top_p (nucleus sampling) setting for the model request - */ -export const GEN_AI_REQUEST_TOP_P_ATTRIBUTE = 'gen_ai.request.top_p'; - -/** - * The top_k setting for the model request - */ -export const GEN_AI_REQUEST_TOP_K_ATTRIBUTE = 'gen_ai.request.top_k'; - -/** - * Stop sequences for the model request - */ -export const GEN_AI_REQUEST_STOP_SEQUENCES_ATTRIBUTE = 'gen_ai.request.stop_sequences'; - /** * The encoding format for the model request */ @@ -75,132 +25,32 @@ export const GEN_AI_REQUEST_ENCODING_FORMAT_ATTRIBUTE = 'gen_ai.request.encoding */ export const GEN_AI_REQUEST_DIMENSIONS_ATTRIBUTE = 'gen_ai.request.dimensions'; -/** - * Array of reasons why the model stopped generating tokens - */ -export const GEN_AI_RESPONSE_FINISH_REASONS_ATTRIBUTE = 'gen_ai.response.finish_reasons'; - -/** - * The name of the model that generated the response - */ -export const GEN_AI_RESPONSE_MODEL_ATTRIBUTE = 'gen_ai.response.model'; - -/** - * The unique identifier for the response - */ -export const GEN_AI_RESPONSE_ID_ATTRIBUTE = 'gen_ai.response.id'; - /** * The reason why the model stopped generating tokens */ export const GEN_AI_RESPONSE_STOP_REASON_ATTRIBUTE = 'gen_ai.response.stop_reason'; -/** - * The number of tokens used in the prompt - */ -export const GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE = 'gen_ai.usage.input_tokens'; - -/** - * The number of tokens used in the response - */ -export const GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE = 'gen_ai.usage.output_tokens'; - -/** - * The total number of tokens used (input + output) - */ -export const GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE = 'gen_ai.usage.total_tokens'; - -/** - * The operation name - */ -export const GEN_AI_OPERATION_NAME_ATTRIBUTE = 'gen_ai.operation.name'; - /** * Original length of messages array, used to indicate truncations had occured */ export const GEN_AI_INPUT_MESSAGES_ORIGINAL_LENGTH_ATTRIBUTE = 'sentry.sdk_meta.gen_ai.input.messages.original_length'; -/** - * The prompt messages - * Only recorded when recordInputs is enabled - */ -export const GEN_AI_INPUT_MESSAGES_ATTRIBUTE = 'gen_ai.input.messages'; - -/** - * The model's response messages including text and tool calls - * Only recorded when recordOutputs is enabled - * Format: stringified array of message objects with role, parts, and finish_reason - * @see https://opentelemetry.io/docs/specs/semconv/registry/attributes/gen-ai/#gen-ai-output-messages - */ -export const GEN_AI_OUTPUT_MESSAGES_ATTRIBUTE = 'gen_ai.output.messages'; - -/** - * The system instructions extracted from system messages - * Only recorded when recordInputs is enabled - * According to OpenTelemetry spec: https://opentelemetry.io/docs/specs/semconv/registry/attributes/gen-ai/#gen-ai-system-instructions - */ -export const GEN_AI_SYSTEM_INSTRUCTIONS_ATTRIBUTE = 'gen_ai.system_instructions'; - -/** - * The response text - * Only recorded when recordOutputs is enabled - */ -export const GEN_AI_RESPONSE_TEXT_ATTRIBUTE = 'gen_ai.response.text'; - -/** - * The available tools from incoming request - * Only recorded when recordInputs is enabled - */ -export const GEN_AI_REQUEST_AVAILABLE_TOOLS_ATTRIBUTE = 'gen_ai.request.available_tools'; - -/** - * Whether the response is a streaming response - */ -export const GEN_AI_RESPONSE_STREAMING_ATTRIBUTE = 'gen_ai.response.streaming'; - -/** - * The tool calls from the response - * Only recorded when recordOutputs is enabled - */ -export const GEN_AI_RESPONSE_TOOL_CALLS_ATTRIBUTE = 'gen_ai.response.tool_calls'; - -/** - * The agent name - */ -export const GEN_AI_AGENT_NAME_ATTRIBUTE = 'gen_ai.agent.name'; - -/** - * The pipeline name - */ -export const GEN_AI_PIPELINE_NAME_ATTRIBUTE = 'gen_ai.pipeline.name'; - -/** - * The conversation ID for linking messages across API calls - * For OpenAI Assistants API: thread_id - * For LangGraph: configurable.thread_id - */ -export const GEN_AI_CONVERSATION_ID_ATTRIBUTE = 'gen_ai.conversation.id'; - /** * The number of cache creation input tokens used + * + * Kept local: `@sentry/conventions` emits `gen_ai.usage.cache_creation.input_tokens` (dotted), which + * differs from the key we emit here. */ export const GEN_AI_USAGE_CACHE_CREATION_INPUT_TOKENS_ATTRIBUTE = 'gen_ai.usage.cache_creation_input_tokens'; /** * The number of cache read input tokens used + * + * Kept local: `@sentry/conventions` emits `gen_ai.usage.cache_read.input_tokens` (dotted), which + * differs from the key we emit here. */ export const GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS_ATTRIBUTE = 'gen_ai.usage.cache_read_input_tokens'; -/** - * The number of cache write input tokens used - */ -export const GEN_AI_USAGE_INPUT_TOKENS_CACHE_WRITE_ATTRIBUTE = 'gen_ai.usage.input_tokens.cache_write'; - -/** - * The number of cached input tokens that were used - */ -export const GEN_AI_USAGE_INPUT_TOKENS_CACHED_ATTRIBUTE = 'gen_ai.usage.input_tokens.cached'; - /** * The span operation name for invoking an agent */ @@ -211,12 +61,6 @@ export const GEN_AI_INVOKE_AGENT_OPERATION_ATTRIBUTE = 'gen_ai.invoke_agent'; */ export const GEN_AI_GENERATE_CONTENT_OPERATION_ATTRIBUTE = 'gen_ai.generate_content'; -/** - * The embeddings input - * Only recorded when recordInputs is enabled - */ -export const GEN_AI_EMBEDDINGS_INPUT_ATTRIBUTE = 'gen_ai.embeddings.input'; - /** * The span operation for embeddings */ @@ -232,33 +76,7 @@ export const GEN_AI_RERANK_DO_RERANK_OPERATION_ATTRIBUTE = 'gen_ai.rerank'; */ export const GEN_AI_EXECUTE_TOOL_OPERATION_ATTRIBUTE = 'gen_ai.execute_tool'; -/** - * The tool name for tool call spans - */ -export const GEN_AI_TOOL_NAME_ATTRIBUTE = 'gen_ai.tool.name'; - /** * The tool call ID */ export const GEN_AI_TOOL_CALL_ID_ATTRIBUTE = 'gen_ai.tool.call.id'; - -/** - * The tool type (e.g., 'function') - */ -export const GEN_AI_TOOL_TYPE_ATTRIBUTE = 'gen_ai.tool.type'; - -/** - * The tool input/arguments - */ -export const GEN_AI_TOOL_INPUT_ATTRIBUTE = 'gen_ai.tool.input'; - -/** - * The tool output/result - */ -export const GEN_AI_TOOL_OUTPUT_ATTRIBUTE = 'gen_ai.tool.output'; - -/** - * The description of the tool being used - * @see https://opentelemetry.io/docs/specs/semconv/registry/attributes/gen-ai/#gen-ai-tool-description - */ -export const GEN_AI_TOOL_DESCRIPTION_ATTRIBUTE = 'gen_ai.tool.description'; diff --git a/packages/core/src/tracing/ai/utils.ts b/packages/core/src/tracing/ai/utils.ts index fd762e61fbe3..a244f13a4481 100644 --- a/packages/core/src/tracing/ai/utils.ts +++ b/packages/core/src/tracing/ai/utils.ts @@ -1,3 +1,8 @@ +// `@sentry/conventions` marks several gen_ai attributes (e.g. `GEN_AI_SYSTEM`, `GEN_AI_PROMPT`, +// `GEN_AI_REQUEST_AVAILABLE_TOOLS`, `GEN_AI_TOOL_*`) as deprecated in favour of newer semconv names. We +// intentionally keep emitting the current names so these spans match what the Sentry product consumes +// today; migrating to the new names is a separate, coordinated change. +/* eslint-disable typescript-eslint/no-deprecated */ /** * Shared utils for AI integrations (OpenAI, Anthropic, Verce.AI, etc.) */ @@ -7,16 +12,16 @@ import { hasSpanStreamingEnabled } from '../spans/hasSpanStreamingEnabled'; import type { Span } from '../../types/span'; import { isThenable } from '../../utils/is'; import { - GEN_AI_RESPONSE_FINISH_REASONS_ATTRIBUTE, - GEN_AI_RESPONSE_ID_ATTRIBUTE, - GEN_AI_RESPONSE_MODEL_ATTRIBUTE, - GEN_AI_RESPONSE_STREAMING_ATTRIBUTE, - GEN_AI_RESPONSE_TEXT_ATTRIBUTE, - GEN_AI_RESPONSE_TOOL_CALLS_ATTRIBUTE, - GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE, - GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE, - GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE, -} from './gen-ai-attributes'; + GEN_AI_RESPONSE_FINISH_REASONS, + GEN_AI_RESPONSE_ID, + GEN_AI_RESPONSE_MODEL, + GEN_AI_RESPONSE_STREAMING, + GEN_AI_RESPONSE_TEXT, + GEN_AI_RESPONSE_TOOL_CALLS, + GEN_AI_USAGE_INPUT_TOKENS, + GEN_AI_USAGE_OUTPUT_TOKENS, + GEN_AI_USAGE_TOTAL_TOKENS, +} from '@sentry/conventions/attributes'; import { truncateGenAiMessages, truncateGenAiStringInput } from './messageTruncation'; export interface AIRecordingOptions { @@ -102,12 +107,12 @@ export function setTokenUsageAttributes( ): void { if (promptTokens !== undefined) { span.setAttributes({ - [GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]: promptTokens, + [GEN_AI_USAGE_INPUT_TOKENS]: promptTokens, }); } if (completionTokens !== undefined) { span.setAttributes({ - [GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE]: completionTokens, + [GEN_AI_USAGE_OUTPUT_TOKENS]: completionTokens, }); } if ( @@ -124,7 +129,7 @@ export function setTokenUsageAttributes( (promptTokens ?? 0) + (completionTokens ?? 0) + (cachedInputTokens ?? 0) + (cachedOutputTokens ?? 0); span.setAttributes({ - [GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE]: totalTokens, + [GEN_AI_USAGE_TOTAL_TOKENS]: totalTokens, }); } } @@ -152,25 +157,25 @@ export function endStreamSpan(span: Span, state: StreamResponseState, recordOutp } const attrs: Record = { - [GEN_AI_RESPONSE_STREAMING_ATTRIBUTE]: true, + [GEN_AI_RESPONSE_STREAMING]: true, }; - if (state.responseId) attrs[GEN_AI_RESPONSE_ID_ATTRIBUTE] = state.responseId; - if (state.responseModel) attrs[GEN_AI_RESPONSE_MODEL_ATTRIBUTE] = state.responseModel; + if (state.responseId) attrs[GEN_AI_RESPONSE_ID] = state.responseId; + if (state.responseModel) attrs[GEN_AI_RESPONSE_MODEL] = state.responseModel; - if (state.promptTokens !== undefined) attrs[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE] = state.promptTokens; - if (state.completionTokens !== undefined) attrs[GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE] = state.completionTokens; + if (state.promptTokens !== undefined) attrs[GEN_AI_USAGE_INPUT_TOKENS] = state.promptTokens; + if (state.completionTokens !== undefined) attrs[GEN_AI_USAGE_OUTPUT_TOKENS] = state.completionTokens; // Use explicit total if provided (OpenAI, Google), otherwise compute from cache tokens (Anthropic) if (state.totalTokens !== undefined) { - attrs[GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE] = state.totalTokens; + attrs[GEN_AI_USAGE_TOTAL_TOKENS] = state.totalTokens; } else if ( state.promptTokens !== undefined || state.completionTokens !== undefined || state.cacheCreationInputTokens !== undefined || state.cacheReadInputTokens !== undefined ) { - attrs[GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE] = + attrs[GEN_AI_USAGE_TOTAL_TOKENS] = (state.promptTokens ?? 0) + (state.completionTokens ?? 0) + (state.cacheCreationInputTokens ?? 0) + @@ -178,13 +183,13 @@ export function endStreamSpan(span: Span, state: StreamResponseState, recordOutp } if (state.finishReasons.length) { - attrs[GEN_AI_RESPONSE_FINISH_REASONS_ATTRIBUTE] = JSON.stringify(state.finishReasons); + attrs[GEN_AI_RESPONSE_FINISH_REASONS] = JSON.stringify(state.finishReasons); } if (recordOutputs && state.responseTexts.length) { - attrs[GEN_AI_RESPONSE_TEXT_ATTRIBUTE] = state.responseTexts.join(''); + attrs[GEN_AI_RESPONSE_TEXT] = state.responseTexts.join(''); } if (recordOutputs && state.toolCalls.length) { - attrs[GEN_AI_RESPONSE_TOOL_CALLS_ATTRIBUTE] = JSON.stringify(state.toolCalls); + attrs[GEN_AI_RESPONSE_TOOL_CALLS] = JSON.stringify(state.toolCalls); } span.setAttributes(attrs); diff --git a/packages/core/src/tracing/anthropic-ai/index.ts b/packages/core/src/tracing/anthropic-ai/index.ts index 64cd105905bc..0958e4561591 100644 --- a/packages/core/src/tracing/anthropic-ai/index.ts +++ b/packages/core/src/tracing/anthropic-ai/index.ts @@ -1,25 +1,30 @@ +// `@sentry/conventions` marks several gen_ai attributes (e.g. `GEN_AI_SYSTEM`, `GEN_AI_PROMPT`, +// `GEN_AI_REQUEST_AVAILABLE_TOOLS`, `GEN_AI_TOOL_*`) as deprecated in favour of newer semconv names. We +// intentionally keep emitting the current names so these spans match what the Sentry product consumes +// today; migrating to the new names is a separate, coordinated change. +/* eslint-disable typescript-eslint/no-deprecated */ import { captureException } from '../../exports'; import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '../../semanticAttributes'; import { SPAN_STATUS_ERROR } from '../../tracing'; import { startSpan, startSpanManual } from '../../tracing/trace'; import type { Span, SpanAttributeValue } from '../../types/span'; import { - GEN_AI_OPERATION_NAME_ATTRIBUTE, - GEN_AI_PROMPT_ATTRIBUTE, - GEN_AI_REQUEST_AVAILABLE_TOOLS_ATTRIBUTE, - GEN_AI_REQUEST_FREQUENCY_PENALTY_ATTRIBUTE, - GEN_AI_REQUEST_MAX_TOKENS_ATTRIBUTE, - GEN_AI_REQUEST_MODEL_ATTRIBUTE, - GEN_AI_REQUEST_STREAM_ATTRIBUTE, - GEN_AI_REQUEST_TEMPERATURE_ATTRIBUTE, - GEN_AI_REQUEST_TOP_K_ATTRIBUTE, - GEN_AI_REQUEST_TOP_P_ATTRIBUTE, - GEN_AI_RESPONSE_ID_ATTRIBUTE, - GEN_AI_RESPONSE_MODEL_ATTRIBUTE, - GEN_AI_RESPONSE_TEXT_ATTRIBUTE, - GEN_AI_RESPONSE_TOOL_CALLS_ATTRIBUTE, - GEN_AI_SYSTEM_ATTRIBUTE, -} from '../ai/gen-ai-attributes'; + GEN_AI_OPERATION_NAME, + GEN_AI_PROMPT, + GEN_AI_REQUEST_AVAILABLE_TOOLS, + GEN_AI_REQUEST_FREQUENCY_PENALTY, + GEN_AI_REQUEST_MAX_TOKENS, + GEN_AI_REQUEST_MODEL, + GEN_AI_REQUEST_TEMPERATURE, + GEN_AI_REQUEST_TOP_K, + GEN_AI_REQUEST_TOP_P, + GEN_AI_RESPONSE_ID, + GEN_AI_RESPONSE_MODEL, + GEN_AI_RESPONSE_TEXT, + GEN_AI_RESPONSE_TOOL_CALLS, + GEN_AI_SYSTEM, +} from '@sentry/conventions/attributes'; +import { GEN_AI_REQUEST_STREAM_ATTRIBUTE } from '../ai/gen-ai-attributes'; import type { InstrumentedMethodEntry } from '../ai/utils'; import { resolveAIRecordingOptions, @@ -50,31 +55,30 @@ export function extractRequestAttributes( operationName: string, ): Record { const attributes: Record = { - [GEN_AI_SYSTEM_ATTRIBUTE]: 'anthropic', - [GEN_AI_OPERATION_NAME_ATTRIBUTE]: operationName, + [GEN_AI_SYSTEM]: 'anthropic', + [GEN_AI_OPERATION_NAME]: operationName, [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ai.anthropic', }; if (args.length > 0 && typeof args[0] === 'object' && args[0] !== null) { const params = args[0] as Record; if (params.tools && Array.isArray(params.tools)) { - attributes[GEN_AI_REQUEST_AVAILABLE_TOOLS_ATTRIBUTE] = JSON.stringify(params.tools); + attributes[GEN_AI_REQUEST_AVAILABLE_TOOLS] = JSON.stringify(params.tools); } - attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE] = params.model ?? 'unknown'; - if ('temperature' in params) attributes[GEN_AI_REQUEST_TEMPERATURE_ATTRIBUTE] = params.temperature; - if ('top_p' in params) attributes[GEN_AI_REQUEST_TOP_P_ATTRIBUTE] = params.top_p; + attributes[GEN_AI_REQUEST_MODEL] = params.model ?? 'unknown'; + if ('temperature' in params) attributes[GEN_AI_REQUEST_TEMPERATURE] = params.temperature; + if ('top_p' in params) attributes[GEN_AI_REQUEST_TOP_P] = params.top_p; if ('stream' in params) attributes[GEN_AI_REQUEST_STREAM_ATTRIBUTE] = params.stream; - if ('top_k' in params) attributes[GEN_AI_REQUEST_TOP_K_ATTRIBUTE] = params.top_k; - if ('frequency_penalty' in params) - attributes[GEN_AI_REQUEST_FREQUENCY_PENALTY_ATTRIBUTE] = params.frequency_penalty; - if ('max_tokens' in params) attributes[GEN_AI_REQUEST_MAX_TOKENS_ATTRIBUTE] = params.max_tokens; + if ('top_k' in params) attributes[GEN_AI_REQUEST_TOP_K] = params.top_k; + if ('frequency_penalty' in params) attributes[GEN_AI_REQUEST_FREQUENCY_PENALTY] = params.frequency_penalty; + if ('max_tokens' in params) attributes[GEN_AI_REQUEST_MAX_TOKENS] = params.max_tokens; } else { if (methodPath === 'models.retrieve' || methodPath === 'models.get') { // models.retrieve(model-id) and models.get(model-id) - attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE] = args[0]; + attributes[GEN_AI_REQUEST_MODEL] = args[0]; } else { - attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE] = 'unknown'; + attributes[GEN_AI_REQUEST_MODEL] = 'unknown'; } } @@ -94,7 +98,7 @@ export function addPrivateRequestAttributes( setMessagesAttribute(span, messages, enableTruncation); if ('prompt' in params) { - span.setAttributes({ [GEN_AI_PROMPT_ATTRIBUTE]: JSON.stringify(params.prompt) }); + span.setAttributes({ [GEN_AI_PROMPT]: JSON.stringify(params.prompt) }); } } @@ -106,7 +110,7 @@ function addContentAttributes(span: Span, response: AnthropicAiResponse): void { if ('content' in response) { if (Array.isArray(response.content)) { span.setAttributes({ - [GEN_AI_RESPONSE_TEXT_ATTRIBUTE]: response.content + [GEN_AI_RESPONSE_TEXT]: response.content .map((item: ContentBlock) => item.text) .filter(text => !!text) .join(''), @@ -120,17 +124,17 @@ function addContentAttributes(span: Span, response: AnthropicAiResponse): void { } } if (toolCalls.length > 0) { - span.setAttributes({ [GEN_AI_RESPONSE_TOOL_CALLS_ATTRIBUTE]: JSON.stringify(toolCalls) }); + span.setAttributes({ [GEN_AI_RESPONSE_TOOL_CALLS]: JSON.stringify(toolCalls) }); } } } // Completions.create if ('completion' in response) { - span.setAttributes({ [GEN_AI_RESPONSE_TEXT_ATTRIBUTE]: response.completion }); + span.setAttributes({ [GEN_AI_RESPONSE_TEXT]: response.completion }); } // Models.countTokens if ('input_tokens' in response) { - span.setAttributes({ [GEN_AI_RESPONSE_TEXT_ATTRIBUTE]: JSON.stringify(response.input_tokens) }); + span.setAttributes({ [GEN_AI_RESPONSE_TEXT]: JSON.stringify(response.input_tokens) }); } } @@ -140,8 +144,8 @@ function addContentAttributes(span: Span, response: AnthropicAiResponse): void { function addMetadataAttributes(span: Span, response: AnthropicAiResponse): void { if ('id' in response && 'model' in response) { span.setAttributes({ - [GEN_AI_RESPONSE_ID_ATTRIBUTE]: response.id, - [GEN_AI_RESPONSE_MODEL_ATTRIBUTE]: response.model, + [GEN_AI_RESPONSE_ID]: response.id, + [GEN_AI_RESPONSE_MODEL]: response.model, }); if ('usage' in response && response.usage) { @@ -207,7 +211,7 @@ function handleStreamingRequest( isStreamRequested: boolean, isStreamingMethod: boolean, ): R | Promise { - const model = requestAttributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE] ?? 'unknown'; + const model = requestAttributes[GEN_AI_REQUEST_MODEL] ?? 'unknown'; const spanConfig = { name: `${operationName} ${model}`, op: `gen_ai.${operationName}`, @@ -291,7 +295,7 @@ function instrumentMethod( const operationName = instrumentedMethod.operation || 'unknown'; const requestAttributes = extractRequestAttributes(args, methodPath, operationName); - const model = requestAttributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE] ?? 'unknown'; + const model = requestAttributes[GEN_AI_REQUEST_MODEL] ?? 'unknown'; const params = typeof args[0] === 'object' ? (args[0] as Record) : undefined; const isStreamRequested = Boolean(params?.stream); diff --git a/packages/core/src/tracing/anthropic-ai/utils.ts b/packages/core/src/tracing/anthropic-ai/utils.ts index 5fbde951343f..c41656c8cf76 100644 --- a/packages/core/src/tracing/anthropic-ai/utils.ts +++ b/packages/core/src/tracing/anthropic-ai/utils.ts @@ -2,11 +2,8 @@ import { captureException } from '../../exports'; import { SPAN_STATUS_ERROR } from '../../tracing'; import type { Span } from '../../types/span'; import type { SpanStatusType } from '../../types/spanStatus'; -import { - GEN_AI_INPUT_MESSAGES_ATTRIBUTE, - GEN_AI_INPUT_MESSAGES_ORIGINAL_LENGTH_ATTRIBUTE, - GEN_AI_SYSTEM_INSTRUCTIONS_ATTRIBUTE, -} from '../ai/gen-ai-attributes'; +import { GEN_AI_INPUT_MESSAGES, GEN_AI_SYSTEM_INSTRUCTIONS } from '@sentry/conventions/attributes'; +import { GEN_AI_INPUT_MESSAGES_ORIGINAL_LENGTH_ATTRIBUTE } from '../ai/gen-ai-attributes'; import { extractSystemInstructions, getTruncatedJsonString } from '../ai/utils'; import { stringify } from '../../utils/string'; import type { AnthropicAiResponse } from './types'; @@ -24,15 +21,13 @@ export function setMessagesAttribute(span: Span, messages: unknown, enableTrunca if (systemInstructions) { span.setAttributes({ - [GEN_AI_SYSTEM_INSTRUCTIONS_ATTRIBUTE]: systemInstructions, + [GEN_AI_SYSTEM_INSTRUCTIONS]: systemInstructions, }); } const filteredLength = Array.isArray(filteredMessages) ? filteredMessages.length : 1; span.setAttributes({ - [GEN_AI_INPUT_MESSAGES_ATTRIBUTE]: enableTruncation - ? getTruncatedJsonString(filteredMessages) - : stringify(filteredMessages), + [GEN_AI_INPUT_MESSAGES]: enableTruncation ? getTruncatedJsonString(filteredMessages) : stringify(filteredMessages), [GEN_AI_INPUT_MESSAGES_ORIGINAL_LENGTH_ATTRIBUTE]: filteredLength, }); } diff --git a/packages/core/src/tracing/google-genai/index.ts b/packages/core/src/tracing/google-genai/index.ts index 68e4d414586a..681fd58800cb 100644 --- a/packages/core/src/tracing/google-genai/index.ts +++ b/packages/core/src/tracing/google-genai/index.ts @@ -1,3 +1,8 @@ +// `@sentry/conventions` marks several gen_ai attributes (e.g. `GEN_AI_SYSTEM`, `GEN_AI_PROMPT`, +// `GEN_AI_REQUEST_AVAILABLE_TOOLS`, `GEN_AI_TOOL_*`) as deprecated in favour of newer semconv names. We +// intentionally keep emitting the current names so these spans match what the Sentry product consumes +// today; migrating to the new names is a separate, coordinated change. +/* eslint-disable typescript-eslint/no-deprecated */ /* eslint-disable max-lines */ import { captureException } from '../../exports'; import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '../../semanticAttributes'; @@ -6,27 +11,27 @@ import { startSpan, startSpanManual } from '../../tracing/trace'; import type { Span, SpanAttributeValue } from '../../types/span'; import { handleCallbackErrors } from '../../utils/handleCallbackErrors'; import { - GEN_AI_EMBEDDINGS_INPUT_ATTRIBUTE, - GEN_AI_INPUT_MESSAGES_ATTRIBUTE, - GEN_AI_INPUT_MESSAGES_ORIGINAL_LENGTH_ATTRIBUTE, - GEN_AI_OPERATION_NAME_ATTRIBUTE, - GEN_AI_REQUEST_AVAILABLE_TOOLS_ATTRIBUTE, - GEN_AI_REQUEST_FREQUENCY_PENALTY_ATTRIBUTE, - GEN_AI_REQUEST_MAX_TOKENS_ATTRIBUTE, - GEN_AI_REQUEST_MODEL_ATTRIBUTE, - GEN_AI_REQUEST_PRESENCE_PENALTY_ATTRIBUTE, - GEN_AI_REQUEST_TEMPERATURE_ATTRIBUTE, - GEN_AI_REQUEST_TOP_K_ATTRIBUTE, - GEN_AI_REQUEST_TOP_P_ATTRIBUTE, - GEN_AI_RESPONSE_MODEL_ATTRIBUTE, - GEN_AI_RESPONSE_TEXT_ATTRIBUTE, - GEN_AI_RESPONSE_TOOL_CALLS_ATTRIBUTE, - GEN_AI_SYSTEM_ATTRIBUTE, - GEN_AI_SYSTEM_INSTRUCTIONS_ATTRIBUTE, - GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE, - GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE, - GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE, -} from '../ai/gen-ai-attributes'; + GEN_AI_EMBEDDINGS_INPUT, + GEN_AI_INPUT_MESSAGES, + GEN_AI_OPERATION_NAME, + GEN_AI_REQUEST_AVAILABLE_TOOLS, + GEN_AI_REQUEST_FREQUENCY_PENALTY, + GEN_AI_REQUEST_MAX_TOKENS, + GEN_AI_REQUEST_MODEL, + GEN_AI_REQUEST_PRESENCE_PENALTY, + GEN_AI_REQUEST_TEMPERATURE, + GEN_AI_REQUEST_TOP_K, + GEN_AI_REQUEST_TOP_P, + GEN_AI_RESPONSE_MODEL, + GEN_AI_RESPONSE_TEXT, + GEN_AI_RESPONSE_TOOL_CALLS, + GEN_AI_SYSTEM, + GEN_AI_SYSTEM_INSTRUCTIONS, + GEN_AI_USAGE_INPUT_TOKENS, + GEN_AI_USAGE_OUTPUT_TOKENS, + GEN_AI_USAGE_TOTAL_TOKENS, +} from '@sentry/conventions/attributes'; +import { GEN_AI_INPUT_MESSAGES_ORIGINAL_LENGTH_ATTRIBUTE } from '../ai/gen-ai-attributes'; import type { InstrumentedMethodEntry } from '../ai/utils'; import { stringify } from '../../utils/string'; import { @@ -76,22 +81,22 @@ function extractConfigAttributes(config: Record): Record = {}; if ('temperature' in config && typeof config.temperature === 'number') { - attributes[GEN_AI_REQUEST_TEMPERATURE_ATTRIBUTE] = config.temperature; + attributes[GEN_AI_REQUEST_TEMPERATURE] = config.temperature; } if ('topP' in config && typeof config.topP === 'number') { - attributes[GEN_AI_REQUEST_TOP_P_ATTRIBUTE] = config.topP; + attributes[GEN_AI_REQUEST_TOP_P] = config.topP; } if ('topK' in config && typeof config.topK === 'number') { - attributes[GEN_AI_REQUEST_TOP_K_ATTRIBUTE] = config.topK; + attributes[GEN_AI_REQUEST_TOP_K] = config.topK; } if ('maxOutputTokens' in config && typeof config.maxOutputTokens === 'number') { - attributes[GEN_AI_REQUEST_MAX_TOKENS_ATTRIBUTE] = config.maxOutputTokens; + attributes[GEN_AI_REQUEST_MAX_TOKENS] = config.maxOutputTokens; } if ('frequencyPenalty' in config && typeof config.frequencyPenalty === 'number') { - attributes[GEN_AI_REQUEST_FREQUENCY_PENALTY_ATTRIBUTE] = config.frequencyPenalty; + attributes[GEN_AI_REQUEST_FREQUENCY_PENALTY] = config.frequencyPenalty; } if ('presencePenalty' in config && typeof config.presencePenalty === 'number') { - attributes[GEN_AI_REQUEST_PRESENCE_PENALTY_ATTRIBUTE] = config.presencePenalty; + attributes[GEN_AI_REQUEST_PRESENCE_PENALTY] = config.presencePenalty; } return attributes; @@ -107,13 +112,13 @@ export function extractRequestAttributes( context?: unknown, ): Record { const attributes: Record = { - [GEN_AI_SYSTEM_ATTRIBUTE]: GOOGLE_GENAI_SYSTEM_NAME, - [GEN_AI_OPERATION_NAME_ATTRIBUTE]: operationName, + [GEN_AI_SYSTEM]: GOOGLE_GENAI_SYSTEM_NAME, + [GEN_AI_OPERATION_NAME]: operationName, [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ai.google_genai', }; if (params) { - attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE] = extractModel(params, context); + attributes[GEN_AI_REQUEST_MODEL] = extractModel(params, context); // Extract generation config parameters if ('config' in params && typeof params.config === 'object' && params.config) { @@ -125,11 +130,11 @@ export function extractRequestAttributes( const functionDeclarations = config.tools.flatMap( (tool: { functionDeclarations: unknown[] }) => tool.functionDeclarations, ); - attributes[GEN_AI_REQUEST_AVAILABLE_TOOLS_ATTRIBUTE] = JSON.stringify(functionDeclarations); + attributes[GEN_AI_REQUEST_AVAILABLE_TOOLS] = JSON.stringify(functionDeclarations); } } } else { - attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE] = extractModel({}, context); + attributes[GEN_AI_REQUEST_MODEL] = extractModel({}, context); } return attributes; @@ -149,10 +154,7 @@ export function addPrivateRequestAttributes( if (operationName === 'embeddings') { const contents = params.contents; if (contents != null) { - span.setAttribute( - GEN_AI_EMBEDDINGS_INPUT_ATTRIBUTE, - typeof contents === 'string' ? contents : JSON.stringify(contents), - ); + span.setAttribute(GEN_AI_EMBEDDINGS_INPUT, typeof contents === 'string' ? contents : JSON.stringify(contents)); } return; } @@ -189,13 +191,13 @@ export function addPrivateRequestAttributes( const { systemInstructions, filteredMessages } = extractSystemInstructions(messages); if (systemInstructions) { - span.setAttribute(GEN_AI_SYSTEM_INSTRUCTIONS_ATTRIBUTE, systemInstructions); + span.setAttribute(GEN_AI_SYSTEM_INSTRUCTIONS, systemInstructions); } const filteredLength = Array.isArray(filteredMessages) ? filteredMessages.length : 0; span.setAttributes({ [GEN_AI_INPUT_MESSAGES_ORIGINAL_LENGTH_ATTRIBUTE]: filteredLength, - [GEN_AI_INPUT_MESSAGES_ATTRIBUTE]: enableTruncation + [GEN_AI_INPUT_MESSAGES]: enableTruncation ? getTruncatedJsonString(filteredMessages) : stringify(filteredMessages), }); @@ -210,7 +212,7 @@ export function addResponseAttributes(span: Span, response: GoogleGenAIResponse, if (!response || typeof response !== 'object') return; if (response.modelVersion) { - span.setAttribute(GEN_AI_RESPONSE_MODEL_ATTRIBUTE, response.modelVersion); + span.setAttribute(GEN_AI_RESPONSE_MODEL, response.modelVersion); } // Add usage metadata if present @@ -218,17 +220,17 @@ export function addResponseAttributes(span: Span, response: GoogleGenAIResponse, const usage = response.usageMetadata; if (typeof usage.promptTokenCount === 'number') { span.setAttributes({ - [GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]: usage.promptTokenCount, + [GEN_AI_USAGE_INPUT_TOKENS]: usage.promptTokenCount, }); } if (typeof usage.candidatesTokenCount === 'number') { span.setAttributes({ - [GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE]: usage.candidatesTokenCount, + [GEN_AI_USAGE_OUTPUT_TOKENS]: usage.candidatesTokenCount, }); } if (typeof usage.totalTokenCount === 'number') { span.setAttributes({ - [GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE]: usage.totalTokenCount, + [GEN_AI_USAGE_TOTAL_TOKENS]: usage.totalTokenCount, }); } } @@ -249,7 +251,7 @@ export function addResponseAttributes(span: Span, response: GoogleGenAIResponse, if (responseTexts.length > 0) { span.setAttributes({ - [GEN_AI_RESPONSE_TEXT_ATTRIBUTE]: responseTexts.join(''), + [GEN_AI_RESPONSE_TEXT]: responseTexts.join(''), }); } } @@ -259,7 +261,7 @@ export function addResponseAttributes(span: Span, response: GoogleGenAIResponse, const functionCalls = response.functionCalls; if (Array.isArray(functionCalls) && functionCalls.length > 0) { span.setAttributes({ - [GEN_AI_RESPONSE_TOOL_CALLS_ATTRIBUTE]: JSON.stringify(functionCalls), + [GEN_AI_RESPONSE_TOOL_CALLS]: JSON.stringify(functionCalls), }); } } @@ -284,7 +286,7 @@ function instrumentMethod( const operationName = instrumentedMethod.operation || 'unknown'; const params = args[0] as Record | undefined; const requestAttributes = extractRequestAttributes(operationName, params, context); - const model = requestAttributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE] ?? 'unknown'; + const model = requestAttributes[GEN_AI_REQUEST_MODEL] ?? 'unknown'; // Check if this is a streaming method if (instrumentedMethod.streaming) { diff --git a/packages/core/src/tracing/langchain/embeddings.ts b/packages/core/src/tracing/langchain/embeddings.ts index f6f70280e2ac..2ff61b969c97 100644 --- a/packages/core/src/tracing/langchain/embeddings.ts +++ b/packages/core/src/tracing/langchain/embeddings.ts @@ -1,15 +1,22 @@ +// `@sentry/conventions` marks several gen_ai attributes (e.g. `GEN_AI_SYSTEM`, `GEN_AI_PROMPT`, +// `GEN_AI_REQUEST_AVAILABLE_TOOLS`, `GEN_AI_TOOL_*`) as deprecated in favour of newer semconv names. We +// intentionally keep emitting the current names so these spans match what the Sentry product consumes +// today; migrating to the new names is a separate, coordinated change. +/* eslint-disable typescript-eslint/no-deprecated */ import { captureException } from '../../exports'; import { SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '../../semanticAttributes'; import { startSpan } from '../../tracing/trace'; import type { SpanAttributeValue } from '../../types/span'; +import { + GEN_AI_EMBEDDINGS_INPUT, + GEN_AI_OPERATION_NAME, + GEN_AI_REQUEST_MODEL, + GEN_AI_SYSTEM, +} from '@sentry/conventions/attributes'; import { GEN_AI_EMBEDDINGS_OPERATION_ATTRIBUTE, - GEN_AI_EMBEDDINGS_INPUT_ATTRIBUTE, - GEN_AI_OPERATION_NAME_ATTRIBUTE, GEN_AI_REQUEST_DIMENSIONS_ATTRIBUTE, GEN_AI_REQUEST_ENCODING_FORMAT_ATTRIBUTE, - GEN_AI_REQUEST_MODEL_ATTRIBUTE, - GEN_AI_SYSTEM_ATTRIBUTE, } from '../ai/gen-ai-attributes'; import { resolveAIRecordingOptions } from '../ai/utils'; import { LANGCHAIN_ORIGIN } from './constants'; @@ -40,11 +47,11 @@ function extractEmbeddingAttributes(instance: unknown): Record const attributes: Record = { [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: LANGCHAIN_ORIGIN, [SEMANTIC_ATTRIBUTE_SENTRY_OP]: GEN_AI_EMBEDDINGS_OPERATION_ATTRIBUTE, - [GEN_AI_OPERATION_NAME_ATTRIBUTE]: 'embeddings', - [GEN_AI_REQUEST_MODEL_ATTRIBUTE]: embeddingsInstance.model ?? 'unknown', + [GEN_AI_OPERATION_NAME]: 'embeddings', + [GEN_AI_REQUEST_MODEL]: embeddingsInstance.model ?? 'unknown', }; - attributes[GEN_AI_SYSTEM_ATTRIBUTE] = inferSystemFromInstance(embeddingsInstance); + attributes[GEN_AI_SYSTEM] = inferSystemFromInstance(embeddingsInstance); if ('dimensions' in embeddingsInstance) { attributes[GEN_AI_REQUEST_DIMENSIONS_ATTRIBUTE] = embeddingsInstance.dimensions; } @@ -68,10 +75,10 @@ export function _INTERNAL_getLangChainEmbeddingsSpanOptions( ): { name: string; op: string; attributes: Record } { const { recordInputs } = resolveAIRecordingOptions(options); const attributes = extractEmbeddingAttributes(instance); - const modelName = attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE] || 'unknown'; + const modelName = attributes[GEN_AI_REQUEST_MODEL] || 'unknown'; if (recordInputs && input != null) { - attributes[GEN_AI_EMBEDDINGS_INPUT_ATTRIBUTE] = typeof input === 'string' ? input : JSON.stringify(input); + attributes[GEN_AI_EMBEDDINGS_INPUT] = typeof input === 'string' ? input : JSON.stringify(input); } return { diff --git a/packages/core/src/tracing/langchain/index.ts b/packages/core/src/tracing/langchain/index.ts index 621ae76acdd5..e9c4554c6796 100644 --- a/packages/core/src/tracing/langchain/index.ts +++ b/packages/core/src/tracing/langchain/index.ts @@ -1,3 +1,8 @@ +// `@sentry/conventions` marks several gen_ai attributes (e.g. `GEN_AI_SYSTEM`, `GEN_AI_PROMPT`, +// `GEN_AI_REQUEST_AVAILABLE_TOOLS`, `GEN_AI_TOOL_*`) as deprecated in favour of newer semconv names. We +// intentionally keep emitting the current names so these spans match what the Sentry product consumes +// today; migrating to the new names is a separate, coordinated change. +/* eslint-disable typescript-eslint/no-deprecated */ /* eslint-disable max-lines */ import { captureException } from '../../exports'; import { SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '../../semanticAttributes'; @@ -5,13 +10,13 @@ import { SPAN_STATUS_ERROR } from '../../tracing'; import { startSpanManual } from '../../tracing/trace'; import type { Span, SpanAttributeValue } from '../../types/span'; import { - GEN_AI_OPERATION_NAME_ATTRIBUTE, - GEN_AI_REQUEST_AVAILABLE_TOOLS_ATTRIBUTE, - GEN_AI_REQUEST_MODEL_ATTRIBUTE, - GEN_AI_TOOL_INPUT_ATTRIBUTE, - GEN_AI_TOOL_NAME_ATTRIBUTE, - GEN_AI_TOOL_OUTPUT_ATTRIBUTE, -} from '../ai/gen-ai-attributes'; + GEN_AI_OPERATION_NAME, + GEN_AI_REQUEST_AVAILABLE_TOOLS, + GEN_AI_REQUEST_MODEL, + GEN_AI_TOOL_INPUT, + GEN_AI_TOOL_NAME, + GEN_AI_TOOL_OUTPUT, +} from '@sentry/conventions/attributes'; import { resolveAIRecordingOptions, shouldEnableTruncation } from '../ai/utils'; import { LANGCHAIN_ORIGIN } from './constants'; import type { @@ -98,8 +103,8 @@ export function createLangChainCallbackHandler(options: LangChainOptions = {}): invocationParams, metadata, ); - const modelName = attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE]; - const operationName = attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE]; + const modelName = attributes[GEN_AI_REQUEST_MODEL]; + const operationName = attributes[GEN_AI_OPERATION_NAME]; startSpanManual( { @@ -141,11 +146,11 @@ export function createLangChainCallbackHandler(options: LangChainOptions = {}): const toolDefsJson = extractToolDefinitions(extraParams); if (toolDefsJson) { - attributes[GEN_AI_REQUEST_AVAILABLE_TOOLS_ATTRIBUTE] = toolDefsJson; + attributes[GEN_AI_REQUEST_AVAILABLE_TOOLS] = toolDefsJson; } - const modelName = attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE]; - const operationName = attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE]; + const modelName = attributes[GEN_AI_REQUEST_MODEL]; + const operationName = attributes[GEN_AI_OPERATION_NAME]; startSpanManual( { @@ -293,12 +298,12 @@ export function createLangChainCallbackHandler(options: LangChainOptions = {}): const attributes: Record = { ...getAgentNameFromMetadata(metadata), [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: LANGCHAIN_ORIGIN, - [GEN_AI_OPERATION_NAME_ATTRIBUTE]: 'execute_tool', - [GEN_AI_TOOL_NAME_ATTRIBUTE]: toolName, + [GEN_AI_OPERATION_NAME]: 'execute_tool', + [GEN_AI_TOOL_NAME]: toolName, }; if (recordInputs) { - attributes[GEN_AI_TOOL_INPUT_ATTRIBUTE] = input; + attributes[GEN_AI_TOOL_INPUT] = input; } startSpanManual( @@ -327,7 +332,7 @@ export function createLangChainCallbackHandler(options: LangChainOptions = {}): const content = outputObj && typeof outputObj === 'object' && 'content' in outputObj ? outputObj.content : output; span.setAttributes({ - [GEN_AI_TOOL_OUTPUT_ATTRIBUTE]: typeof content === 'string' ? content : JSON.stringify(content), + [GEN_AI_TOOL_OUTPUT]: typeof content === 'string' ? content : JSON.stringify(content), }); } exitSpan(runId); diff --git a/packages/core/src/tracing/langchain/utils.ts b/packages/core/src/tracing/langchain/utils.ts index 0f95464788da..ff152760b288 100644 --- a/packages/core/src/tracing/langchain/utils.ts +++ b/packages/core/src/tracing/langchain/utils.ts @@ -1,31 +1,38 @@ +// `@sentry/conventions` marks several gen_ai attributes (e.g. `GEN_AI_SYSTEM`, `GEN_AI_PROMPT`, +// `GEN_AI_REQUEST_AVAILABLE_TOOLS`, `GEN_AI_TOOL_*`) as deprecated in favour of newer semconv names. We +// intentionally keep emitting the current names so these spans match what the Sentry product consumes +// today; migrating to the new names is a separate, coordinated change. +/* eslint-disable typescript-eslint/no-deprecated */ import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '../../semanticAttributes'; import type { SpanAttributeValue } from '../../types/span'; import { stringify } from '../../utils/string'; import { - GEN_AI_AGENT_NAME_ATTRIBUTE, - GEN_AI_INPUT_MESSAGES_ATTRIBUTE, + GEN_AI_AGENT_NAME, + GEN_AI_INPUT_MESSAGES, + GEN_AI_OPERATION_NAME, + GEN_AI_REQUEST_FREQUENCY_PENALTY, + GEN_AI_REQUEST_MAX_TOKENS, + GEN_AI_REQUEST_MODEL, + GEN_AI_REQUEST_PRESENCE_PENALTY, + GEN_AI_REQUEST_TEMPERATURE, + GEN_AI_REQUEST_TOP_P, + GEN_AI_RESPONSE_FINISH_REASONS, + GEN_AI_RESPONSE_ID, + GEN_AI_RESPONSE_MODEL, + GEN_AI_RESPONSE_TEXT, + GEN_AI_RESPONSE_TOOL_CALLS, + GEN_AI_SYSTEM, + GEN_AI_SYSTEM_INSTRUCTIONS, + GEN_AI_USAGE_INPUT_TOKENS, + GEN_AI_USAGE_OUTPUT_TOKENS, + GEN_AI_USAGE_TOTAL_TOKENS, +} from '@sentry/conventions/attributes'; +import { GEN_AI_INPUT_MESSAGES_ORIGINAL_LENGTH_ATTRIBUTE, - GEN_AI_OPERATION_NAME_ATTRIBUTE, - GEN_AI_REQUEST_FREQUENCY_PENALTY_ATTRIBUTE, - GEN_AI_REQUEST_MAX_TOKENS_ATTRIBUTE, - GEN_AI_REQUEST_MODEL_ATTRIBUTE, - GEN_AI_REQUEST_PRESENCE_PENALTY_ATTRIBUTE, GEN_AI_REQUEST_STREAM_ATTRIBUTE, - GEN_AI_REQUEST_TEMPERATURE_ATTRIBUTE, - GEN_AI_REQUEST_TOP_P_ATTRIBUTE, - GEN_AI_RESPONSE_FINISH_REASONS_ATTRIBUTE, - GEN_AI_RESPONSE_ID_ATTRIBUTE, - GEN_AI_RESPONSE_MODEL_ATTRIBUTE, GEN_AI_RESPONSE_STOP_REASON_ATTRIBUTE, - GEN_AI_RESPONSE_TEXT_ATTRIBUTE, - GEN_AI_RESPONSE_TOOL_CALLS_ATTRIBUTE, - GEN_AI_SYSTEM_ATTRIBUTE, - GEN_AI_SYSTEM_INSTRUCTIONS_ATTRIBUTE, GEN_AI_USAGE_CACHE_CREATION_INPUT_TOKENS_ATTRIBUTE, GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS_ATTRIBUTE, - GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE, - GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE, - GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE, } from '../ai/gen-ai-attributes'; import { isContentMedia, stripInlineMediaFromSingleMessage } from '../ai/mediaStripping'; import { extractSystemInstructions, getTruncatedJsonString } from '../ai/utils'; @@ -223,19 +230,19 @@ function extractCommonRequestAttributes( const kwargs = 'kwargs' in serialized ? serialized.kwargs : undefined; const temperature = invocationParams?.temperature ?? langSmithMetadata?.ls_temperature ?? kwargs?.temperature; - setNumberIfDefined(attrs, GEN_AI_REQUEST_TEMPERATURE_ATTRIBUTE, temperature); + setNumberIfDefined(attrs, GEN_AI_REQUEST_TEMPERATURE, temperature); const maxTokens = invocationParams?.max_tokens ?? langSmithMetadata?.ls_max_tokens ?? kwargs?.max_tokens; - setNumberIfDefined(attrs, GEN_AI_REQUEST_MAX_TOKENS_ATTRIBUTE, maxTokens); + setNumberIfDefined(attrs, GEN_AI_REQUEST_MAX_TOKENS, maxTokens); const topP = invocationParams?.top_p ?? kwargs?.top_p; - setNumberIfDefined(attrs, GEN_AI_REQUEST_TOP_P_ATTRIBUTE, topP); + setNumberIfDefined(attrs, GEN_AI_REQUEST_TOP_P, topP); const frequencyPenalty = invocationParams?.frequency_penalty; - setNumberIfDefined(attrs, GEN_AI_REQUEST_FREQUENCY_PENALTY_ATTRIBUTE, frequencyPenalty); + setNumberIfDefined(attrs, GEN_AI_REQUEST_FREQUENCY_PENALTY, frequencyPenalty); const presencePenalty = invocationParams?.presence_penalty; - setNumberIfDefined(attrs, GEN_AI_REQUEST_PRESENCE_PENALTY_ATTRIBUTE, presencePenalty); + setNumberIfDefined(attrs, GEN_AI_REQUEST_PRESENCE_PENALTY, presencePenalty); // LangChain uses `stream`. We only set the attribute if the key actually exists // (some callbacks report `false` even on streamed requests, this stems from LangChain's callback handler). @@ -258,9 +265,9 @@ function baseRequestAttributes( langSmithMetadata?: Record, ): Record { return { - [GEN_AI_SYSTEM_ATTRIBUTE]: stringify(system ?? 'langchain', String), - [GEN_AI_OPERATION_NAME_ATTRIBUTE]: 'chat', - [GEN_AI_REQUEST_MODEL_ATTRIBUTE]: stringify(modelName, String), + [GEN_AI_SYSTEM]: stringify(system ?? 'langchain', String), + [GEN_AI_OPERATION_NAME]: 'chat', + [GEN_AI_REQUEST_MODEL]: stringify(modelName, String), [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: LANGCHAIN_ORIGIN, ...extractCommonRequestAttributes(serialized, invocationParams, langSmithMetadata), }; @@ -292,7 +299,7 @@ export function extractLLMRequestAttributes( const messages = prompts.map(p => ({ role: 'user', content: p })); setIfDefined( attrs, - GEN_AI_INPUT_MESSAGES_ATTRIBUTE, + GEN_AI_INPUT_MESSAGES, enableTruncation ? getTruncatedJsonString(messages) : stringify(messages), ); } @@ -328,7 +335,7 @@ export function extractChatModelRequestAttributes( const { systemInstructions, filteredMessages } = extractSystemInstructions(normalized); if (systemInstructions) { - setIfDefined(attrs, GEN_AI_SYSTEM_INSTRUCTIONS_ATTRIBUTE, systemInstructions); + setIfDefined(attrs, GEN_AI_SYSTEM_INSTRUCTIONS, systemInstructions); } const filteredLength = Array.isArray(filteredMessages) ? filteredMessages.length : 0; @@ -336,7 +343,7 @@ export function extractChatModelRequestAttributes( setIfDefined( attrs, - GEN_AI_INPUT_MESSAGES_ATTRIBUTE, + GEN_AI_INPUT_MESSAGES, enableTruncation ? getTruncatedJsonString(filteredMessages) : stringify(filteredMessages), ); } @@ -372,7 +379,7 @@ function addToolCallsAttributes(generations: LangChainMessage[][], attrs: Record } if (toolCalls.length > 0) { - setIfDefined(attrs, GEN_AI_RESPONSE_TOOL_CALLS_ATTRIBUTE, stringify(toolCalls, String)); + setIfDefined(attrs, GEN_AI_RESPONSE_TOOL_CALLS, stringify(toolCalls, String)); } } @@ -401,18 +408,18 @@ function addTokenUsageAttributes( | undefined; if (tokenUsage) { - setNumberIfDefined(attrs, GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE, tokenUsage.promptTokens); - setNumberIfDefined(attrs, GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE, tokenUsage.completionTokens); - setNumberIfDefined(attrs, GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE, tokenUsage.totalTokens); + setNumberIfDefined(attrs, GEN_AI_USAGE_INPUT_TOKENS, tokenUsage.promptTokens); + setNumberIfDefined(attrs, GEN_AI_USAGE_OUTPUT_TOKENS, tokenUsage.completionTokens); + setNumberIfDefined(attrs, GEN_AI_USAGE_TOTAL_TOKENS, tokenUsage.totalTokens); } else if (anthropicUsage) { - setNumberIfDefined(attrs, GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE, anthropicUsage.input_tokens); - setNumberIfDefined(attrs, GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE, anthropicUsage.output_tokens); + setNumberIfDefined(attrs, GEN_AI_USAGE_INPUT_TOKENS, anthropicUsage.input_tokens); + setNumberIfDefined(attrs, GEN_AI_USAGE_OUTPUT_TOKENS, anthropicUsage.output_tokens); // Compute total when not provided by the provider. const input = Number(anthropicUsage.input_tokens); const output = Number(anthropicUsage.output_tokens); const total = (Number.isNaN(input) ? 0 : input) + (Number.isNaN(output) ? 0 : output); - if (total > 0) setNumberIfDefined(attrs, GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE, total); + if (total > 0) setNumberIfDefined(attrs, GEN_AI_USAGE_TOTAL_TOKENS, total); // Extra Anthropic cache metrics (present only when caching is enabled) if (anthropicUsage.cache_creation_input_tokens !== undefined) @@ -460,7 +467,7 @@ export function extractLlmResponseAttributes( .filter((r): r is string => typeof r === 'string'); if (finishReasons.length > 0) { - setIfDefined(attrs, GEN_AI_RESPONSE_FINISH_REASONS_ATTRIBUTE, stringify(finishReasons, String)); + setIfDefined(attrs, GEN_AI_RESPONSE_FINISH_REASONS, stringify(finishReasons, String)); } // Tool calls metadata (names, IDs) are not PII, so capture them regardless of recordOutputs @@ -473,7 +480,7 @@ export function extractLlmResponseAttributes( .filter(t => typeof t === 'string'); if (texts.length > 0) { - setIfDefined(attrs, GEN_AI_RESPONSE_TEXT_ATTRIBUTE, stringify(texts, String)); + setIfDefined(attrs, GEN_AI_RESPONSE_TEXT, stringify(texts, String)); } } } @@ -489,12 +496,12 @@ export function extractLlmResponseAttributes( // Provider model identifier: `model_name` (OpenAI-style) or `model` (others) // v1 stores this in message.response_metadata.model_name const modelName = llmOutput?.model_name ?? llmOutput?.model ?? v1Message?.response_metadata?.model_name; - if (modelName) setIfDefined(attrs, GEN_AI_RESPONSE_MODEL_ATTRIBUTE, modelName); + if (modelName) setIfDefined(attrs, GEN_AI_RESPONSE_MODEL, modelName); // Response ID: v1 stores this in message.id const responseId = llmOutput?.id ?? v1Message?.id; if (responseId) { - setIfDefined(attrs, GEN_AI_RESPONSE_ID_ATTRIBUTE, responseId); + setIfDefined(attrs, GEN_AI_RESPONSE_ID, responseId); } // Stop reason: v1 stores this in message.response_metadata.finish_reason @@ -511,7 +518,7 @@ export function getAgentNameFromMetadata(metadata?: Record): Re // lc_agent_name is injected by instrumentCompiledGraphInvoke (langgraph integration) const agentName = metadata?.lc_agent_name; if (typeof agentName === 'string') { - attrs[GEN_AI_AGENT_NAME_ATTRIBUTE] = agentName; + attrs[GEN_AI_AGENT_NAME] = agentName; } return attrs; } diff --git a/packages/core/src/tracing/langgraph/index.ts b/packages/core/src/tracing/langgraph/index.ts index daf2f55552ea..8e5680f7ac80 100644 --- a/packages/core/src/tracing/langgraph/index.ts +++ b/packages/core/src/tracing/langgraph/index.ts @@ -1,17 +1,24 @@ +// `@sentry/conventions` marks several gen_ai attributes (e.g. `GEN_AI_SYSTEM`, `GEN_AI_PROMPT`, +// `GEN_AI_REQUEST_AVAILABLE_TOOLS`, `GEN_AI_TOOL_*`) as deprecated in favour of newer semconv names. We +// intentionally keep emitting the current names so these spans match what the Sentry product consumes +// today; migrating to the new names is a separate, coordinated change. +/* eslint-disable typescript-eslint/no-deprecated */ import { captureException } from '../../exports'; import { SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '../../semanticAttributes'; import { SPAN_STATUS_ERROR } from '../../tracing'; import { - GEN_AI_AGENT_NAME_ATTRIBUTE, - GEN_AI_CONVERSATION_ID_ATTRIBUTE, - GEN_AI_INPUT_MESSAGES_ATTRIBUTE, + GEN_AI_AGENT_NAME, + GEN_AI_CONVERSATION_ID, + GEN_AI_INPUT_MESSAGES, + GEN_AI_OPERATION_NAME, + GEN_AI_PIPELINE_NAME, + GEN_AI_REQUEST_AVAILABLE_TOOLS, + GEN_AI_REQUEST_MODEL, + GEN_AI_SYSTEM_INSTRUCTIONS, +} from '@sentry/conventions/attributes'; +import { GEN_AI_INPUT_MESSAGES_ORIGINAL_LENGTH_ATTRIBUTE, GEN_AI_INVOKE_AGENT_OPERATION_ATTRIBUTE, - GEN_AI_OPERATION_NAME_ATTRIBUTE, - GEN_AI_PIPELINE_NAME_ATTRIBUTE, - GEN_AI_REQUEST_AVAILABLE_TOOLS_ATTRIBUTE, - GEN_AI_REQUEST_MODEL_ATTRIBUTE, - GEN_AI_SYSTEM_INSTRUCTIONS_ATTRIBUTE, } from '../ai/gen-ai-attributes'; import { extractSystemInstructions, @@ -54,11 +61,11 @@ export function _INTERNAL_getLangGraphCreateAgentSpanOptions(agentName?: string) const attributes: Record = { [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: LANGGRAPH_ORIGIN, [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'gen_ai.create_agent', - [GEN_AI_OPERATION_NAME_ATTRIBUTE]: 'create_agent', + [GEN_AI_OPERATION_NAME]: 'create_agent', }; if (agentName) { - attributes[GEN_AI_AGENT_NAME_ATTRIBUTE] = agentName; + attributes[GEN_AI_AGENT_NAME] = agentName; } return { @@ -100,7 +107,7 @@ export function instrumentStateGraphCompile( // Extract graph name if (compileOptions?.name && typeof compileOptions.name === 'string') { - span.setAttribute(GEN_AI_AGENT_NAME_ATTRIBUTE, compileOptions.name); + span.setAttribute(GEN_AI_AGENT_NAME, compileOptions.name); span.updateName(`create_agent ${compileOptions.name}`); } @@ -159,7 +166,7 @@ export function instrumentCompiledGraphInvoke( attributes: { [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: LANGGRAPH_ORIGIN, [SEMANTIC_ATTRIBUTE_SENTRY_OP]: GEN_AI_INVOKE_AGENT_OPERATION_ATTRIBUTE, - [GEN_AI_OPERATION_NAME_ATTRIBUTE]: 'invoke_agent', + [GEN_AI_OPERATION_NAME]: 'invoke_agent', }, }, async span => { @@ -167,13 +174,13 @@ export function instrumentCompiledGraphInvoke( const graphName = compileOptions?.name; if (graphName && typeof graphName === 'string') { - span.setAttribute(GEN_AI_PIPELINE_NAME_ATTRIBUTE, graphName); - span.setAttribute(GEN_AI_AGENT_NAME_ATTRIBUTE, graphName); + span.setAttribute(GEN_AI_PIPELINE_NAME, graphName); + span.setAttribute(GEN_AI_AGENT_NAME, graphName); span.updateName(`invoke_agent ${graphName}`); } if (modelName) { - span.setAttribute(GEN_AI_REQUEST_MODEL_ATTRIBUTE, modelName); + span.setAttribute(GEN_AI_REQUEST_MODEL, modelName); } // Extract thread_id from the config (second argument) @@ -182,7 +189,7 @@ export function instrumentCompiledGraphInvoke( const configurable = config?.configurable as Record | undefined; const threadId = configurable?.thread_id; if (threadId && typeof threadId === 'string') { - span.setAttribute(GEN_AI_CONVERSATION_ID_ATTRIBUTE, threadId); + span.setAttribute(GEN_AI_CONVERSATION_ID, threadId); } // Inject callback handler and agent name into invoke config @@ -206,7 +213,7 @@ export function instrumentCompiledGraphInvoke( // Extract available tools from the graph instance const tools = extractToolsFromCompiledGraph(graphInstance); if (tools) { - span.setAttribute(GEN_AI_REQUEST_AVAILABLE_TOOLS_ATTRIBUTE, JSON.stringify(tools)); + span.setAttribute(GEN_AI_REQUEST_AVAILABLE_TOOLS, JSON.stringify(tools)); } // Parse input messages @@ -220,13 +227,13 @@ export function instrumentCompiledGraphInvoke( const { systemInstructions, filteredMessages } = extractSystemInstructions(normalizedMessages); if (systemInstructions) { - span.setAttribute(GEN_AI_SYSTEM_INSTRUCTIONS_ATTRIBUTE, systemInstructions); + span.setAttribute(GEN_AI_SYSTEM_INSTRUCTIONS, systemInstructions); } const enableTruncation = shouldEnableTruncation(options.enableTruncation); const filteredLength = Array.isArray(filteredMessages) ? filteredMessages.length : 0; span.setAttributes({ - [GEN_AI_INPUT_MESSAGES_ATTRIBUTE]: enableTruncation + [GEN_AI_INPUT_MESSAGES]: enableTruncation ? getTruncatedJsonString(filteredMessages) : stringify(filteredMessages), [GEN_AI_INPUT_MESSAGES_ORIGINAL_LENGTH_ATTRIBUTE]: filteredLength, diff --git a/packages/core/src/tracing/langgraph/utils.ts b/packages/core/src/tracing/langgraph/utils.ts index cf37ce18056e..da641ea538d6 100644 --- a/packages/core/src/tracing/langgraph/utils.ts +++ b/packages/core/src/tracing/langgraph/utils.ts @@ -1,25 +1,29 @@ +// `@sentry/conventions` marks several gen_ai attributes (e.g. `GEN_AI_SYSTEM`, `GEN_AI_PROMPT`, +// `GEN_AI_REQUEST_AVAILABLE_TOOLS`, `GEN_AI_TOOL_*`) as deprecated in favour of newer semconv names. We +// intentionally keep emitting the current names so these spans match what the Sentry product consumes +// today; migrating to the new names is a separate, coordinated change. +/* eslint-disable typescript-eslint/no-deprecated */ import { captureException } from '../../exports'; import { SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '../../semanticAttributes'; import { SPAN_STATUS_ERROR } from '../../tracing'; import type { Span, SpanAttributes } from '../../types/span'; import { - GEN_AI_AGENT_NAME_ATTRIBUTE, - GEN_AI_EXECUTE_TOOL_OPERATION_ATTRIBUTE, - GEN_AI_OPERATION_NAME_ATTRIBUTE, - GEN_AI_RESPONSE_FINISH_REASONS_ATTRIBUTE, - GEN_AI_RESPONSE_MODEL_ATTRIBUTE, - GEN_AI_RESPONSE_TEXT_ATTRIBUTE, - GEN_AI_RESPONSE_TOOL_CALLS_ATTRIBUTE, - GEN_AI_TOOL_CALL_ID_ATTRIBUTE, - GEN_AI_TOOL_INPUT_ATTRIBUTE, - GEN_AI_TOOL_OUTPUT_ATTRIBUTE, - GEN_AI_TOOL_DESCRIPTION_ATTRIBUTE, - GEN_AI_TOOL_NAME_ATTRIBUTE, - GEN_AI_TOOL_TYPE_ATTRIBUTE, - GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE, - GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE, - GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE, -} from '../ai/gen-ai-attributes'; + GEN_AI_AGENT_NAME, + GEN_AI_OPERATION_NAME, + GEN_AI_RESPONSE_FINISH_REASONS, + GEN_AI_RESPONSE_MODEL, + GEN_AI_RESPONSE_TEXT, + GEN_AI_RESPONSE_TOOL_CALLS, + GEN_AI_TOOL_DESCRIPTION, + GEN_AI_TOOL_INPUT, + GEN_AI_TOOL_NAME, + GEN_AI_TOOL_OUTPUT, + GEN_AI_TOOL_TYPE, + GEN_AI_USAGE_INPUT_TOKENS, + GEN_AI_USAGE_OUTPUT_TOKENS, + GEN_AI_USAGE_TOTAL_TOKENS, +} from '@sentry/conventions/attributes'; +import { GEN_AI_EXECUTE_TOOL_OPERATION_ATTRIBUTE, GEN_AI_TOOL_CALL_ID_ATTRIBUTE } from '../ai/gen-ai-attributes'; import type { BaseChatModel, LangChainMessage } from '../langchain/types'; import { normalizeLangChainMessages } from '../langchain/utils'; import { startSpan } from '../trace'; @@ -79,9 +83,9 @@ export function wrapToolsWithSpans(tools: unknown[], options: LangGraphOptions, const spanAttributes: SpanAttributes = { [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: LANGGRAPH_ORIGIN, [SEMANTIC_ATTRIBUTE_SENTRY_OP]: GEN_AI_EXECUTE_TOOL_OPERATION_ATTRIBUTE, - [GEN_AI_OPERATION_NAME_ATTRIBUTE]: 'execute_tool', - [GEN_AI_TOOL_NAME_ATTRIBUTE]: toolName, - [GEN_AI_TOOL_TYPE_ATTRIBUTE]: 'function', + [GEN_AI_OPERATION_NAME]: 'execute_tool', + [GEN_AI_TOOL_NAME]: toolName, + [GEN_AI_TOOL_TYPE]: 'function', }; // Read agent name from LangChain's propagated config metadata at call time, @@ -89,11 +93,11 @@ export function wrapToolsWithSpans(tools: unknown[], options: LangGraphOptions, const callConfig = args[1] as Record | undefined; const callAgentName = (callConfig?.metadata as Record)?.lc_agent_name ?? agentName; if (typeof callAgentName === 'string') { - spanAttributes[GEN_AI_AGENT_NAME_ATTRIBUTE] = callAgentName; + spanAttributes[GEN_AI_AGENT_NAME] = callAgentName; } if (toolDescription) { - spanAttributes[GEN_AI_TOOL_DESCRIPTION_ATTRIBUTE] = toolDescription; + spanAttributes[GEN_AI_TOOL_DESCRIPTION] = toolDescription; } // LangGraph ToolNode passes { name, args, id, type: "tool_call" } @@ -106,7 +110,7 @@ export function wrapToolsWithSpans(tools: unknown[], options: LangGraphOptions, if (options.recordInputs) { const toolArgs = 'args' in input && typeof input.args === 'object' ? input.args : input; try { - spanAttributes[GEN_AI_TOOL_INPUT_ATTRIBUTE] = JSON.stringify(toolArgs); + spanAttributes[GEN_AI_TOOL_INPUT] = JSON.stringify(toolArgs); } catch { // skip if not serializable } @@ -130,7 +134,7 @@ export function wrapToolsWithSpans(tools: unknown[], options: LangGraphOptions, const content = resultObj && typeof resultObj === 'object' && 'content' in resultObj ? resultObj.content : result; span.setAttribute( - GEN_AI_TOOL_OUTPUT_ATTRIBUTE, + GEN_AI_TOOL_OUTPUT, typeof content === 'string' ? content : JSON.stringify(content), ); } catch { @@ -242,11 +246,11 @@ export function extractModelMetadata(span: Span, message: LangChainMessage): voi const metadata = msg.response_metadata as Record; if (metadata.model_name && typeof metadata.model_name === 'string') { - span.setAttribute(GEN_AI_RESPONSE_MODEL_ATTRIBUTE, metadata.model_name); + span.setAttribute(GEN_AI_RESPONSE_MODEL, metadata.model_name); } if (metadata.finish_reason && typeof metadata.finish_reason === 'string') { - span.setAttribute(GEN_AI_RESPONSE_FINISH_REASONS_ATTRIBUTE, [metadata.finish_reason]); + span.setAttribute(GEN_AI_RESPONSE_FINISH_REASONS, [metadata.finish_reason]); } } } @@ -299,12 +303,12 @@ export function setResponseAttributes(span: Span, inputMessages: LangChainMessag // (normalization strips tool_calls, so we need to extract them first) const toolCalls = extractToolCalls(newMessages as Array>); if (toolCalls) { - span.setAttribute(GEN_AI_RESPONSE_TOOL_CALLS_ATTRIBUTE, JSON.stringify(toolCalls)); + span.setAttribute(GEN_AI_RESPONSE_TOOL_CALLS, JSON.stringify(toolCalls)); } // Normalize the new messages const normalizedNewMessages = normalizeLangChainMessages(newMessages); - span.setAttribute(GEN_AI_RESPONSE_TEXT_ATTRIBUTE, JSON.stringify(normalizedNewMessages)); + span.setAttribute(GEN_AI_RESPONSE_TEXT, JSON.stringify(normalizedNewMessages)); // Accumulate token usage across all messages let totalInputTokens = 0; @@ -325,12 +329,12 @@ export function setResponseAttributes(span: Span, inputMessages: LangChainMessag // Set accumulated token usage on span if (totalInputTokens > 0) { - span.setAttribute(GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE, totalInputTokens); + span.setAttribute(GEN_AI_USAGE_INPUT_TOKENS, totalInputTokens); } if (totalOutputTokens > 0) { - span.setAttribute(GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE, totalOutputTokens); + span.setAttribute(GEN_AI_USAGE_OUTPUT_TOKENS, totalOutputTokens); } if (totalTokens > 0) { - span.setAttribute(GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE, totalTokens); + span.setAttribute(GEN_AI_USAGE_TOTAL_TOKENS, totalTokens); } } diff --git a/packages/core/src/tracing/openai/index.ts b/packages/core/src/tracing/openai/index.ts index 821e9c68e0ff..578d89517996 100644 --- a/packages/core/src/tracing/openai/index.ts +++ b/packages/core/src/tracing/openai/index.ts @@ -1,3 +1,8 @@ +// `@sentry/conventions` marks several gen_ai attributes (e.g. `GEN_AI_SYSTEM`, `GEN_AI_PROMPT`, +// `GEN_AI_REQUEST_AVAILABLE_TOOLS`, `GEN_AI_TOOL_*`) as deprecated in favour of newer semconv names. We +// intentionally keep emitting the current names so these spans match what the Sentry product consumes +// today; migrating to the new names is a separate, coordinated change. +/* eslint-disable typescript-eslint/no-deprecated */ import { DEBUG_BUILD } from '../../debug-build'; import { captureException } from '../../exports'; import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '../../semanticAttributes'; @@ -6,15 +11,15 @@ import { startSpan, startSpanManual } from '../../tracing/trace'; import type { Span, SpanAttributeValue } from '../../types/span'; import { debug } from '../../utils/debug-logger'; import { - GEN_AI_EMBEDDINGS_INPUT_ATTRIBUTE, - GEN_AI_INPUT_MESSAGES_ATTRIBUTE, - GEN_AI_INPUT_MESSAGES_ORIGINAL_LENGTH_ATTRIBUTE, - GEN_AI_OPERATION_NAME_ATTRIBUTE, - GEN_AI_REQUEST_AVAILABLE_TOOLS_ATTRIBUTE, - GEN_AI_REQUEST_MODEL_ATTRIBUTE, - GEN_AI_SYSTEM_ATTRIBUTE, - GEN_AI_SYSTEM_INSTRUCTIONS_ATTRIBUTE, -} from '../ai/gen-ai-attributes'; + GEN_AI_EMBEDDINGS_INPUT, + GEN_AI_INPUT_MESSAGES, + GEN_AI_OPERATION_NAME, + GEN_AI_REQUEST_AVAILABLE_TOOLS, + GEN_AI_REQUEST_MODEL, + GEN_AI_SYSTEM, + GEN_AI_SYSTEM_INSTRUCTIONS, +} from '@sentry/conventions/attributes'; +import { GEN_AI_INPUT_MESSAGES_ORIGINAL_LENGTH_ATTRIBUTE } from '../ai/gen-ai-attributes'; import type { InstrumentedMethodEntry } from '../ai/utils'; import { stringify } from '../../utils/string'; import { @@ -58,8 +63,8 @@ function extractAvailableTools(params: Record): string | undefi */ export function extractRequestAttributes(args: unknown[], operationName: string): Record { const attributes: Record = { - [GEN_AI_SYSTEM_ATTRIBUTE]: 'openai', - [GEN_AI_OPERATION_NAME_ATTRIBUTE]: operationName, + [GEN_AI_SYSTEM]: 'openai', + [GEN_AI_OPERATION_NAME]: operationName, [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ai.openai', }; @@ -68,12 +73,12 @@ export function extractRequestAttributes(args: unknown[], operationName: string) const availableTools = extractAvailableTools(params); if (availableTools) { - attributes[GEN_AI_REQUEST_AVAILABLE_TOOLS_ATTRIBUTE] = availableTools; + attributes[GEN_AI_REQUEST_AVAILABLE_TOOLS] = availableTools; } Object.assign(attributes, extractRequestParameters(params)); } else { - attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE] = 'unknown'; + attributes[GEN_AI_REQUEST_MODEL] = 'unknown'; } return attributes; @@ -106,7 +111,7 @@ export function addRequestAttributes( } // Store strings as-is, arrays/objects as JSON - span.setAttribute(GEN_AI_EMBEDDINGS_INPUT_ATTRIBUTE, typeof input === 'string' ? input : JSON.stringify(input)); + span.setAttribute(GEN_AI_EMBEDDINGS_INPUT, typeof input === 'string' ? input : JSON.stringify(input)); return; } @@ -123,11 +128,11 @@ export function addRequestAttributes( const { systemInstructions, filteredMessages } = extractSystemInstructions(src); if (systemInstructions) { - span.setAttribute(GEN_AI_SYSTEM_INSTRUCTIONS_ATTRIBUTE, systemInstructions); + span.setAttribute(GEN_AI_SYSTEM_INSTRUCTIONS, systemInstructions); } span.setAttribute( - GEN_AI_INPUT_MESSAGES_ATTRIBUTE, + GEN_AI_INPUT_MESSAGES, enableTruncation ? getTruncatedJsonString(filteredMessages) : stringify(filteredMessages), ); @@ -153,7 +158,7 @@ function instrumentMethod( return function instrumentedCall(...args: T): Promise { const operationName = instrumentedMethod.operation || 'unknown'; const requestAttributes = extractRequestAttributes(args, operationName); - const model = (requestAttributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE] as string) || 'unknown'; + const model = (requestAttributes[GEN_AI_REQUEST_MODEL] as string) || 'unknown'; const params = args[0] as Record | undefined; const isStreamRequested = params && typeof params === 'object' && params.stream === true; diff --git a/packages/core/src/tracing/openai/utils.ts b/packages/core/src/tracing/openai/utils.ts index d83593ecb645..ff20cde5a8d0 100644 --- a/packages/core/src/tracing/openai/utils.ts +++ b/packages/core/src/tracing/openai/utils.ts @@ -1,23 +1,30 @@ +// `@sentry/conventions` marks several gen_ai attributes (e.g. `GEN_AI_SYSTEM`, `GEN_AI_PROMPT`, +// `GEN_AI_REQUEST_AVAILABLE_TOOLS`, `GEN_AI_TOOL_*`) as deprecated in favour of newer semconv names. We +// intentionally keep emitting the current names so these spans match what the Sentry product consumes +// today; migrating to the new names is a separate, coordinated change. +/* eslint-disable typescript-eslint/no-deprecated */ import type { Span } from '../../types/span'; import type { SpanAttributeValue } from '../../types/span'; import { - GEN_AI_CONVERSATION_ID_ATTRIBUTE, + GEN_AI_CONVERSATION_ID, + GEN_AI_REQUEST_FREQUENCY_PENALTY, + GEN_AI_REQUEST_MODEL, + GEN_AI_REQUEST_PRESENCE_PENALTY, + GEN_AI_REQUEST_TEMPERATURE, + GEN_AI_REQUEST_TOP_P, + GEN_AI_RESPONSE_FINISH_REASONS, + GEN_AI_RESPONSE_ID, + GEN_AI_RESPONSE_MODEL, + GEN_AI_RESPONSE_TEXT, + GEN_AI_RESPONSE_TOOL_CALLS, + GEN_AI_USAGE_INPUT_TOKENS, + GEN_AI_USAGE_OUTPUT_TOKENS, + GEN_AI_USAGE_TOTAL_TOKENS, +} from '@sentry/conventions/attributes'; +import { GEN_AI_REQUEST_DIMENSIONS_ATTRIBUTE, GEN_AI_REQUEST_ENCODING_FORMAT_ATTRIBUTE, - GEN_AI_REQUEST_FREQUENCY_PENALTY_ATTRIBUTE, - GEN_AI_REQUEST_MODEL_ATTRIBUTE, - GEN_AI_REQUEST_PRESENCE_PENALTY_ATTRIBUTE, GEN_AI_REQUEST_STREAM_ATTRIBUTE, - GEN_AI_REQUEST_TEMPERATURE_ATTRIBUTE, - GEN_AI_REQUEST_TOP_P_ATTRIBUTE, - GEN_AI_RESPONSE_FINISH_REASONS_ATTRIBUTE, - GEN_AI_RESPONSE_ID_ATTRIBUTE, - GEN_AI_RESPONSE_MODEL_ATTRIBUTE, - GEN_AI_RESPONSE_TEXT_ATTRIBUTE, - GEN_AI_RESPONSE_TOOL_CALLS_ATTRIBUTE, - GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE, - GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE, - GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE, } from '../ai/gen-ai-attributes'; import type { ChatCompletionChunk, ResponseStreamingEvent } from './types'; @@ -58,17 +65,17 @@ export function addResponseAttributes(span: Span, result: unknown, recordOutputs // Response ID if (typeof response.id === 'string') { - attrs[GEN_AI_RESPONSE_ID_ATTRIBUTE] = response.id; + attrs[GEN_AI_RESPONSE_ID] = response.id; } // Response model if (typeof response.model === 'string') { - attrs[GEN_AI_RESPONSE_MODEL_ATTRIBUTE] = response.model; + attrs[GEN_AI_RESPONSE_MODEL] = response.model; } // Conversation ID (conversation objects use id as conversation link) if (response.object === 'conversation' && typeof response.id === 'string') { - attrs[GEN_AI_CONVERSATION_ID_ATTRIBUTE] = response.id; + attrs[GEN_AI_CONVERSATION_ID] = response.id; } // Token usage — supports both naming conventions (chat: prompt_tokens/completion_tokens, responses: input_tokens/output_tokens) @@ -77,16 +84,16 @@ export function addResponseAttributes(span: Span, result: unknown, recordOutputs const inputTokens = usage.prompt_tokens ?? usage.input_tokens; if (typeof inputTokens === 'number') { - attrs[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE] = inputTokens; + attrs[GEN_AI_USAGE_INPUT_TOKENS] = inputTokens; } const outputTokens = usage.completion_tokens ?? usage.output_tokens; if (typeof outputTokens === 'number') { - attrs[GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE] = outputTokens; + attrs[GEN_AI_USAGE_OUTPUT_TOKENS] = outputTokens; } if (typeof usage.total_tokens === 'number') { - attrs[GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE] = usage.total_tokens; + attrs[GEN_AI_USAGE_TOTAL_TOKENS] = usage.total_tokens; } } @@ -97,7 +104,7 @@ export function addResponseAttributes(span: Span, result: unknown, recordOutputs .map(choice => choice.finish_reason) .filter((reason): reason is string => typeof reason === 'string'); if (finishReasons.length > 0) { - attrs[GEN_AI_RESPONSE_FINISH_REASONS_ATTRIBUTE] = JSON.stringify(finishReasons); + attrs[GEN_AI_RESPONSE_FINISH_REASONS] = JSON.stringify(finishReasons); } if (recordOutputs) { @@ -106,7 +113,7 @@ export function addResponseAttributes(span: Span, result: unknown, recordOutputs const message = choice.message as Record | undefined; return (message?.content as string) || ''; }); - attrs[GEN_AI_RESPONSE_TEXT_ATTRIBUTE] = JSON.stringify(responseTexts); + attrs[GEN_AI_RESPONSE_TEXT] = JSON.stringify(responseTexts); // Tool calls from choices const toolCalls = choices @@ -118,7 +125,7 @@ export function addResponseAttributes(span: Span, result: unknown, recordOutputs .flat(); if (toolCalls.length > 0) { - attrs[GEN_AI_RESPONSE_TOOL_CALLS_ATTRIBUTE] = JSON.stringify(toolCalls); + attrs[GEN_AI_RESPONSE_TOOL_CALLS] = JSON.stringify(toolCalls); } } } @@ -126,24 +133,24 @@ export function addResponseAttributes(span: Span, result: unknown, recordOutputs // Finish reason from status (responses API) if (typeof response.status === 'string') { // Only set if not already set from choices - if (!attrs[GEN_AI_RESPONSE_FINISH_REASONS_ATTRIBUTE]) { - attrs[GEN_AI_RESPONSE_FINISH_REASONS_ATTRIBUTE] = JSON.stringify([response.status]); + if (!attrs[GEN_AI_RESPONSE_FINISH_REASONS]) { + attrs[GEN_AI_RESPONSE_FINISH_REASONS] = JSON.stringify([response.status]); } } if (recordOutputs) { // Response text from output_text (responses API) - if (typeof response.output_text === 'string' && !attrs[GEN_AI_RESPONSE_TEXT_ATTRIBUTE]) { - attrs[GEN_AI_RESPONSE_TEXT_ATTRIBUTE] = response.output_text; + if (typeof response.output_text === 'string' && !attrs[GEN_AI_RESPONSE_TEXT]) { + attrs[GEN_AI_RESPONSE_TEXT] = response.output_text; } // Tool calls from output array (responses API) - if (Array.isArray(response.output) && response.output.length > 0 && !attrs[GEN_AI_RESPONSE_TOOL_CALLS_ATTRIBUTE]) { + if (Array.isArray(response.output) && response.output.length > 0 && !attrs[GEN_AI_RESPONSE_TOOL_CALLS]) { const functionCalls = (response.output as Array>).filter( item => item?.type === 'function_call', ); if (functionCalls.length > 0) { - attrs[GEN_AI_RESPONSE_TOOL_CALLS_ATTRIBUTE] = JSON.stringify(functionCalls); + attrs[GEN_AI_RESPONSE_TOOL_CALLS] = JSON.stringify(functionCalls); } } } @@ -173,13 +180,13 @@ function extractConversationId(params: Record): string | undefi */ export function extractRequestParameters(params: Record): Record { const attributes: Record = { - [GEN_AI_REQUEST_MODEL_ATTRIBUTE]: params.model ?? 'unknown', + [GEN_AI_REQUEST_MODEL]: params.model ?? 'unknown', }; - if ('temperature' in params) attributes[GEN_AI_REQUEST_TEMPERATURE_ATTRIBUTE] = params.temperature; - if ('top_p' in params) attributes[GEN_AI_REQUEST_TOP_P_ATTRIBUTE] = params.top_p; - if ('frequency_penalty' in params) attributes[GEN_AI_REQUEST_FREQUENCY_PENALTY_ATTRIBUTE] = params.frequency_penalty; - if ('presence_penalty' in params) attributes[GEN_AI_REQUEST_PRESENCE_PENALTY_ATTRIBUTE] = params.presence_penalty; + if ('temperature' in params) attributes[GEN_AI_REQUEST_TEMPERATURE] = params.temperature; + if ('top_p' in params) attributes[GEN_AI_REQUEST_TOP_P] = params.top_p; + if ('frequency_penalty' in params) attributes[GEN_AI_REQUEST_FREQUENCY_PENALTY] = params.frequency_penalty; + if ('presence_penalty' in params) attributes[GEN_AI_REQUEST_PRESENCE_PENALTY] = params.presence_penalty; if ('stream' in params) attributes[GEN_AI_REQUEST_STREAM_ATTRIBUTE] = params.stream; if ('encoding_format' in params) attributes[GEN_AI_REQUEST_ENCODING_FORMAT_ATTRIBUTE] = params.encoding_format; if ('dimensions' in params) attributes[GEN_AI_REQUEST_DIMENSIONS_ATTRIBUTE] = params.dimensions; @@ -187,7 +194,7 @@ export function extractRequestParameters(params: Record): Recor // Capture conversation ID for linking messages across API calls const conversationId = extractConversationId(params); if (conversationId) { - attributes[GEN_AI_CONVERSATION_ID_ATTRIBUTE] = conversationId; + attributes[GEN_AI_CONVERSATION_ID] = conversationId; } return attributes; diff --git a/packages/core/src/tracing/vercel-ai/index.ts b/packages/core/src/tracing/vercel-ai/index.ts index c905ac980614..a6b9ef45412d 100644 --- a/packages/core/src/tracing/vercel-ai/index.ts +++ b/packages/core/src/tracing/vercel-ai/index.ts @@ -1,3 +1,8 @@ +// `@sentry/conventions` marks several gen_ai attributes (e.g. `GEN_AI_SYSTEM`, `GEN_AI_PROMPT`, +// `GEN_AI_REQUEST_AVAILABLE_TOOLS`, `GEN_AI_TOOL_*`) as deprecated in favour of newer semconv names. We +// intentionally keep emitting the current names so these spans match what the Sentry product consumes +// today; migrating to the new names is a separate, coordinated change. +/* eslint-disable typescript-eslint/no-deprecated */ /* eslint-disable max-lines */ import type { Client } from '../../client'; import { getClient } from '../../currentScopes'; @@ -7,25 +12,25 @@ import type { Event } from '../../types/event'; import type { Span, SpanAttributes, SpanAttributeValue, SpanJSON, StreamedSpanJSON } from '../../types/span'; import { spanToJSON } from '../../utils/spanUtils'; import { - GEN_AI_CONVERSATION_ID_ATTRIBUTE, - GEN_AI_EMBEDDINGS_INPUT_ATTRIBUTE, - GEN_AI_INPUT_MESSAGES_ATTRIBUTE, - GEN_AI_OPERATION_NAME_ATTRIBUTE, - GEN_AI_OUTPUT_MESSAGES_ATTRIBUTE, - GEN_AI_REQUEST_MODEL_ATTRIBUTE, - GEN_AI_RESPONSE_MODEL_ATTRIBUTE, - GEN_AI_TOOL_CALL_ID_ATTRIBUTE, - GEN_AI_TOOL_DESCRIPTION_ATTRIBUTE, - GEN_AI_TOOL_INPUT_ATTRIBUTE, - GEN_AI_TOOL_NAME_ATTRIBUTE, - GEN_AI_TOOL_OUTPUT_ATTRIBUTE, - GEN_AI_TOOL_TYPE_ATTRIBUTE, - GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE, - GEN_AI_USAGE_INPUT_TOKENS_CACHE_WRITE_ATTRIBUTE, - GEN_AI_USAGE_INPUT_TOKENS_CACHED_ATTRIBUTE, - GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE, - GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE, -} from '../ai/gen-ai-attributes'; + GEN_AI_CONVERSATION_ID, + GEN_AI_EMBEDDINGS_INPUT, + GEN_AI_INPUT_MESSAGES, + GEN_AI_OPERATION_NAME, + GEN_AI_OUTPUT_MESSAGES, + GEN_AI_REQUEST_MODEL, + GEN_AI_RESPONSE_MODEL, + GEN_AI_TOOL_DESCRIPTION, + GEN_AI_TOOL_INPUT, + GEN_AI_TOOL_NAME, + GEN_AI_TOOL_OUTPUT, + GEN_AI_TOOL_TYPE, + GEN_AI_USAGE_INPUT_TOKENS, + GEN_AI_USAGE_INPUT_TOKENS_CACHED, + GEN_AI_USAGE_INPUT_TOKENS_CACHE_WRITE, + GEN_AI_USAGE_OUTPUT_TOKENS, + GEN_AI_USAGE_TOTAL_TOKENS, +} from '@sentry/conventions/attributes'; +import { GEN_AI_TOOL_CALL_ID_ATTRIBUTE } from '../ai/gen-ai-attributes'; import { SPAN_TO_OPERATION_NAME, toolCallSpanContextMap, toolDescriptionMap } from './constants'; import type { TokenSummary } from './types'; import { hasSpanStreamingEnabled } from '../spans/hasSpanStreamingEnabled'; @@ -225,7 +230,7 @@ function buildOutputMessages(attributes: Record): void { finish_reason: normalizeFinishReason(finishReason), }; - attributes[GEN_AI_OUTPUT_MESSAGES_ATTRIBUTE] = JSON.stringify([outputMessage]); + attributes[GEN_AI_OUTPUT_MESSAGES] = JSON.stringify([outputMessage]); // Remove the text attribute since it's now captured in gen_ai.output.messages // Note: tool calls attribute is deleted above only if successfully parsed @@ -243,16 +248,16 @@ function buildOutputMessages(attributes: Record): void { * path (SpanJSON) and the streamed span path (StreamedSpanJSON). */ export function processVercelAiSpanAttributes(attributes: Record): void { - renameAttributeKey(attributes, AI_USAGE_COMPLETION_TOKENS_ATTRIBUTE, GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE); - renameAttributeKey(attributes, AI_USAGE_PROMPT_TOKENS_ATTRIBUTE, GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE); - renameAttributeKey(attributes, AI_USAGE_CACHED_INPUT_TOKENS_ATTRIBUTE, GEN_AI_USAGE_INPUT_TOKENS_CACHED_ATTRIBUTE); + renameAttributeKey(attributes, AI_USAGE_COMPLETION_TOKENS_ATTRIBUTE, GEN_AI_USAGE_OUTPUT_TOKENS); + renameAttributeKey(attributes, AI_USAGE_PROMPT_TOKENS_ATTRIBUTE, GEN_AI_USAGE_INPUT_TOKENS); + renameAttributeKey(attributes, AI_USAGE_CACHED_INPUT_TOKENS_ATTRIBUTE, GEN_AI_USAGE_INPUT_TOKENS_CACHED); // Parent spans (ai.streamText, ai.streamObject, etc.) use inputTokens/outputTokens instead of promptTokens/completionTokens - renameAttributeKey(attributes, 'ai.usage.inputTokens', GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE); - renameAttributeKey(attributes, 'ai.usage.outputTokens', GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE); + renameAttributeKey(attributes, 'ai.usage.inputTokens', GEN_AI_USAGE_INPUT_TOKENS); + renameAttributeKey(attributes, 'ai.usage.outputTokens', GEN_AI_USAGE_OUTPUT_TOKENS); // Embedding spans use ai.usage.tokens instead of promptTokens/completionTokens - renameAttributeKey(attributes, AI_USAGE_TOKENS_ATTRIBUTE, GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE); + renameAttributeKey(attributes, AI_USAGE_TOKENS_ATTRIBUTE, GEN_AI_USAGE_INPUT_TOKENS); // AI SDK uses avgOutputTokensPerSecond, map to our expected attribute name renameAttributeKey(attributes, 'ai.response.avgOutputTokensPerSecond', 'ai.response.avgCompletionTokensPerSecond'); @@ -264,20 +269,18 @@ export function processVercelAiSpanAttributes(attributes: Record, oldKey: string, function processToolCallSpan(span: Span, attributes: SpanAttributes): void { span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, 'auto.vercelai.otel'); span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_OP, 'gen_ai.execute_tool'); - span.setAttribute(GEN_AI_OPERATION_NAME_ATTRIBUTE, 'execute_tool'); - renameAttributeKey(attributes, AI_TOOL_CALL_NAME_ATTRIBUTE, GEN_AI_TOOL_NAME_ATTRIBUTE); + span.setAttribute(GEN_AI_OPERATION_NAME, 'execute_tool'); + renameAttributeKey(attributes, AI_TOOL_CALL_NAME_ATTRIBUTE, GEN_AI_TOOL_NAME); renameAttributeKey(attributes, AI_TOOL_CALL_ID_ATTRIBUTE, GEN_AI_TOOL_CALL_ID_ATTRIBUTE); // Store the span context in our global map using the tool call ID. @@ -417,10 +420,10 @@ function processToolCallSpan(span: Span, attributes: SpanAttributes): void { } // https://opentelemetry.io/docs/specs/semconv/registry/attributes/gen-ai/#gen-ai-tool-type - if (!attributes[GEN_AI_TOOL_TYPE_ATTRIBUTE]) { - span.setAttribute(GEN_AI_TOOL_TYPE_ATTRIBUTE, 'function'); + if (!attributes[GEN_AI_TOOL_TYPE]) { + span.setAttribute(GEN_AI_TOOL_TYPE, 'function'); } - const toolName = attributes[GEN_AI_TOOL_NAME_ATTRIBUTE]; + const toolName = attributes[GEN_AI_TOOL_NAME]; if (toolName) { span.updateName(`execute_tool ${toolName}`); } @@ -440,8 +443,8 @@ function processGenerateSpan(span: Span, name: string, attributes: SpanAttribute requestMessagesFromPrompt(span, attributes, enableTruncation); - if (attributes[AI_MODEL_ID_ATTRIBUTE] && !attributes[GEN_AI_RESPONSE_MODEL_ATTRIBUTE]) { - span.setAttribute(GEN_AI_RESPONSE_MODEL_ATTRIBUTE, attributes[AI_MODEL_ID_ATTRIBUTE]); + if (attributes[AI_MODEL_ID_ATTRIBUTE] && !attributes[GEN_AI_RESPONSE_MODEL]) { + span.setAttribute(GEN_AI_RESPONSE_MODEL, attributes[AI_MODEL_ID_ATTRIBUTE]); } span.setAttribute('ai.streaming', name.includes('stream')); @@ -536,7 +539,7 @@ export function getProviderMetadataAttributes(providerMetadata: unknown): Record // OpenAI (v5 uses 'openai', v6 Azure Responses API uses 'azure') const openaiMetadata: OpenAiProviderMetadata | undefined = metadata.openai ?? metadata.azure; if (openaiMetadata) { - setAttributeIfDefined(attributes, GEN_AI_USAGE_INPUT_TOKENS_CACHED_ATTRIBUTE, openaiMetadata.cachedPromptTokens); + setAttributeIfDefined(attributes, GEN_AI_USAGE_INPUT_TOKENS_CACHED, openaiMetadata.cachedPromptTokens); setAttributeIfDefined(attributes, 'gen_ai.usage.output_tokens.reasoning', openaiMetadata.reasoningTokens); setAttributeIfDefined( attributes, @@ -548,38 +551,30 @@ export function getProviderMetadataAttributes(providerMetadata: unknown): Record 'gen_ai.usage.output_tokens.prediction_rejected', openaiMetadata.rejectedPredictionTokens, ); - setAttributeIfDefined(attributes, GEN_AI_CONVERSATION_ID_ATTRIBUTE, openaiMetadata.responseId); + setAttributeIfDefined(attributes, GEN_AI_CONVERSATION_ID, openaiMetadata.responseId); } if (metadata.anthropic) { const cachedInputTokens = metadata.anthropic.usage?.cache_read_input_tokens ?? metadata.anthropic.cacheReadInputTokens; - setAttributeIfDefined(attributes, GEN_AI_USAGE_INPUT_TOKENS_CACHED_ATTRIBUTE, cachedInputTokens); + setAttributeIfDefined(attributes, GEN_AI_USAGE_INPUT_TOKENS_CACHED, cachedInputTokens); const cacheWriteInputTokens = metadata.anthropic.usage?.cache_creation_input_tokens ?? metadata.anthropic.cacheCreationInputTokens; - setAttributeIfDefined(attributes, GEN_AI_USAGE_INPUT_TOKENS_CACHE_WRITE_ATTRIBUTE, cacheWriteInputTokens); + setAttributeIfDefined(attributes, GEN_AI_USAGE_INPUT_TOKENS_CACHE_WRITE, cacheWriteInputTokens); } if (metadata.bedrock?.usage) { + setAttributeIfDefined(attributes, GEN_AI_USAGE_INPUT_TOKENS_CACHED, metadata.bedrock.usage.cacheReadInputTokens); setAttributeIfDefined( attributes, - GEN_AI_USAGE_INPUT_TOKENS_CACHED_ATTRIBUTE, - metadata.bedrock.usage.cacheReadInputTokens, - ); - setAttributeIfDefined( - attributes, - GEN_AI_USAGE_INPUT_TOKENS_CACHE_WRITE_ATTRIBUTE, + GEN_AI_USAGE_INPUT_TOKENS_CACHE_WRITE, metadata.bedrock.usage.cacheWriteInputTokens, ); } if (metadata.deepseek) { - setAttributeIfDefined( - attributes, - GEN_AI_USAGE_INPUT_TOKENS_CACHED_ATTRIBUTE, - metadata.deepseek.promptCacheHitTokens, - ); + setAttributeIfDefined(attributes, GEN_AI_USAGE_INPUT_TOKENS_CACHED, metadata.deepseek.promptCacheHitTokens); setAttributeIfDefined(attributes, 'gen_ai.usage.input_tokens.cache_miss', metadata.deepseek.promptCacheMissTokens); } @@ -595,7 +590,7 @@ function addProviderMetadataToAttributes(attributes: Record): v const derived = getProviderMetadataAttributes(JSON.parse(providerMetadata) as ProviderMetadata); for (const [key, value] of Object.entries(derived)) { // Preserve the original behaviour of not overwriting an already-set conversation id. - if (key === GEN_AI_CONVERSATION_ID_ATTRIBUTE && attributes[key]) { + if (key === GEN_AI_CONVERSATION_ID && attributes[key]) { continue; } attributes[key] = value; diff --git a/packages/core/src/tracing/vercel-ai/utils.ts b/packages/core/src/tracing/vercel-ai/utils.ts index b57a43666202..fab036268351 100644 --- a/packages/core/src/tracing/vercel-ai/utils.ts +++ b/packages/core/src/tracing/vercel-ai/utils.ts @@ -1,15 +1,20 @@ +// `@sentry/conventions` marks several gen_ai attributes (e.g. `GEN_AI_SYSTEM`, `GEN_AI_PROMPT`, +// `GEN_AI_REQUEST_AVAILABLE_TOOLS`, `GEN_AI_TOOL_*`) as deprecated in favour of newer semconv names. We +// intentionally keep emitting the current names so these spans match what the Sentry product consumes +// today; migrating to the new names is a separate, coordinated change. +/* eslint-disable typescript-eslint/no-deprecated */ import type { TraceContext } from '../../types/context'; import type { Span, SpanAttributes, SpanJSON } from '../../types/span'; import { - GEN_AI_INPUT_MESSAGES_ATTRIBUTE, - GEN_AI_INPUT_MESSAGES_ORIGINAL_LENGTH_ATTRIBUTE, - GEN_AI_REQUEST_AVAILABLE_TOOLS_ATTRIBUTE, - GEN_AI_SYSTEM_INSTRUCTIONS_ATTRIBUTE, - GEN_AI_TOOL_DESCRIPTION_ATTRIBUTE, - GEN_AI_TOOL_NAME_ATTRIBUTE, - GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE, - GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE, -} from '../ai/gen-ai-attributes'; + GEN_AI_INPUT_MESSAGES, + GEN_AI_REQUEST_AVAILABLE_TOOLS, + GEN_AI_SYSTEM_INSTRUCTIONS, + GEN_AI_TOOL_DESCRIPTION, + GEN_AI_TOOL_NAME, + GEN_AI_USAGE_INPUT_TOKENS, + GEN_AI_USAGE_OUTPUT_TOKENS, +} from '@sentry/conventions/attributes'; +import { GEN_AI_INPUT_MESSAGES_ORIGINAL_LENGTH_ATTRIBUTE } from '../ai/gen-ai-attributes'; import { extractSystemInstructions, getTruncatedJsonString } from '../ai/utils'; import { stringify } from '../../utils/string'; import { toolCallSpanContextMap } from './constants'; @@ -27,8 +32,8 @@ export function accumulateTokensForParent(span: SpanJSON, tokenAccumulator: Map< return; } - const inputTokens = span.data[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]; - const outputTokens = span.data[GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE]; + const inputTokens = span.data[GEN_AI_USAGE_INPUT_TOKENS]; + const outputTokens = span.data[GEN_AI_USAGE_OUTPUT_TOKENS]; if (typeof inputTokens === 'number' || typeof outputTokens === 'number') { const existing = tokenAccumulator.get(parentSpanId) || { inputTokens: 0, outputTokens: 0 }; @@ -59,10 +64,10 @@ export function applyAccumulatedTokens( } if (accumulated.inputTokens > 0) { - spanOrTrace.data[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE] = accumulated.inputTokens; + spanOrTrace.data[GEN_AI_USAGE_INPUT_TOKENS] = accumulated.inputTokens; } if (accumulated.outputTokens > 0) { - spanOrTrace.data[GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE] = accumulated.outputTokens; + spanOrTrace.data[GEN_AI_USAGE_OUTPUT_TOKENS] = accumulated.outputTokens; } if (accumulated.inputTokens > 0 || accumulated.outputTokens > 0) { spanOrTrace.data['gen_ai.usage.total_tokens'] = accumulated.inputTokens + accumulated.outputTokens; @@ -77,7 +82,7 @@ function buildToolDescriptionMap(spans: SpanJSON[]): Map { const toolDescriptions = new Map(); for (const span of spans) { - const availableTools = span.data[GEN_AI_REQUEST_AVAILABLE_TOOLS_ATTRIBUTE]; + const availableTools = span.data[GEN_AI_REQUEST_AVAILABLE_TOOLS]; if (typeof availableTools !== 'string') { continue; } @@ -109,11 +114,11 @@ export function applyToolDescriptionsAndTokens(spans: SpanJSON[], tokenAccumulat for (const span of spans) { if (span.op === 'gen_ai.execute_tool') { - const toolName = span.data[GEN_AI_TOOL_NAME_ATTRIBUTE]; + const toolName = span.data[GEN_AI_TOOL_NAME]; if (typeof toolName === 'string') { const description = toolDescriptions.get(toolName); if (description) { - span.data[GEN_AI_TOOL_DESCRIPTION_ATTRIBUTE] = description; + span.data[GEN_AI_TOOL_DESCRIPTION] = description; } } } @@ -225,7 +230,7 @@ export function convertUserInputToMessagesFormat(userInput: string): { role: str export function requestMessagesFromPrompt(span: Span, attributes: SpanAttributes, enableTruncation: boolean): void { if ( typeof attributes[AI_PROMPT_ATTRIBUTE] === 'string' && - !attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE] && + !attributes[GEN_AI_INPUT_MESSAGES] && !attributes[AI_PROMPT_MESSAGES_ATTRIBUTE] ) { // No messages array is present, so we need to convert the prompt to the proper messages format @@ -238,7 +243,7 @@ export function requestMessagesFromPrompt(span: Span, attributes: SpanAttributes const { systemInstructions, filteredMessages } = extractSystemInstructions(messages); if (systemInstructions) { - span.setAttribute(GEN_AI_SYSTEM_INSTRUCTIONS_ATTRIBUTE, systemInstructions); + span.setAttribute(GEN_AI_SYSTEM_INSTRUCTIONS, systemInstructions); } const filteredLength = Array.isArray(filteredMessages) ? filteredMessages.length : 0; @@ -246,7 +251,7 @@ export function requestMessagesFromPrompt(span: Span, attributes: SpanAttributes span.setAttributes({ [AI_PROMPT_ATTRIBUTE]: messagesJson, - [GEN_AI_INPUT_MESSAGES_ATTRIBUTE]: messagesJson, + [GEN_AI_INPUT_MESSAGES]: messagesJson, [GEN_AI_INPUT_MESSAGES_ORIGINAL_LENGTH_ATTRIBUTE]: filteredLength, }); } @@ -260,7 +265,7 @@ export function requestMessagesFromPrompt(span: Span, attributes: SpanAttributes const { systemInstructions, filteredMessages } = extractSystemInstructions(messages); if (systemInstructions) { - span.setAttribute(GEN_AI_SYSTEM_INSTRUCTIONS_ATTRIBUTE, systemInstructions); + span.setAttribute(GEN_AI_SYSTEM_INSTRUCTIONS, systemInstructions); } const filteredLength = Array.isArray(filteredMessages) ? filteredMessages.length : 0; @@ -278,7 +283,7 @@ export function requestMessagesFromPrompt(span: Span, attributes: SpanAttributes span.setAttributes({ [AI_PROMPT_MESSAGES_ATTRIBUTE]: messagesJson, - [GEN_AI_INPUT_MESSAGES_ATTRIBUTE]: messagesJson, + [GEN_AI_INPUT_MESSAGES]: messagesJson, [GEN_AI_INPUT_MESSAGES_ORIGINAL_LENGTH_ATTRIBUTE]: filteredLength, }); } diff --git a/packages/core/src/tracing/workers-ai/utils.ts b/packages/core/src/tracing/workers-ai/utils.ts index 23eac0eaa6fc..16eb0525ebe0 100644 --- a/packages/core/src/tracing/workers-ai/utils.ts +++ b/packages/core/src/tracing/workers-ai/utils.ts @@ -1,7 +1,14 @@ +// `@sentry/conventions` marks several gen_ai attributes (e.g. `GEN_AI_SYSTEM`, `GEN_AI_PROMPT`, +// `GEN_AI_REQUEST_AVAILABLE_TOOLS`, `GEN_AI_TOOL_*`) as deprecated in favour of newer semconv names. We +// intentionally keep emitting the current names so these spans match what the Sentry product consumes +// today; migrating to the new names is a separate, coordinated change. +/* eslint-disable typescript-eslint/no-deprecated */ import { GEN_AI_EMBEDDINGS_INPUT, GEN_AI_INPUT_MESSAGES, GEN_AI_OPERATION_NAME, + GEN_AI_OUTPUT_MESSAGES, + GEN_AI_PROVIDER_NAME, GEN_AI_REQUEST_FREQUENCY_PENALTY, GEN_AI_REQUEST_MAX_TOKENS, GEN_AI_REQUEST_MODEL, @@ -9,17 +16,15 @@ import { GEN_AI_REQUEST_TEMPERATURE, GEN_AI_REQUEST_TOP_K, GEN_AI_REQUEST_TOP_P, - GEN_AI_PROVIDER_NAME, + GEN_AI_RESPONSE_TEXT, + GEN_AI_RESPONSE_TOOL_CALLS, GEN_AI_SYSTEM_INSTRUCTIONS, } from '@sentry/conventions/attributes'; import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '../../semanticAttributes'; import type { Span, SpanAttributeValue } from '../../types/span'; import { GEN_AI_INPUT_MESSAGES_ORIGINAL_LENGTH_ATTRIBUTE, - GEN_AI_OUTPUT_MESSAGES_ATTRIBUTE, GEN_AI_REQUEST_STREAM_ATTRIBUTE, - GEN_AI_RESPONSE_TEXT_ATTRIBUTE, - GEN_AI_RESPONSE_TOOL_CALLS_ATTRIBUTE, } from '../ai/gen-ai-attributes'; import { extractSystemInstructions, getTruncatedJsonString, setTokenUsageAttributes } from '../ai/utils'; import { stringify } from '../../utils/string'; @@ -181,7 +186,7 @@ export function setOutputMessagesAttribute( } if (parts.length > 0) { - span.setAttribute(GEN_AI_OUTPUT_MESSAGES_ATTRIBUTE, JSON.stringify([{ role: 'assistant', parts }])); + span.setAttribute(GEN_AI_OUTPUT_MESSAGES, JSON.stringify([{ role: 'assistant', parts }])); } } @@ -208,16 +213,16 @@ export function addResponseAttributes(span: Span, result: unknown, recordOutputs let responseText: string | undefined; if (typeof response.response === 'string') { responseText = response.response; - span.setAttribute(GEN_AI_RESPONSE_TEXT_ATTRIBUTE, response.response); + span.setAttribute(GEN_AI_RESPONSE_TEXT, response.response); } else if (response.response != null) { responseText = JSON.stringify(response.response); - span.setAttribute(GEN_AI_RESPONSE_TEXT_ATTRIBUTE, responseText); + span.setAttribute(GEN_AI_RESPONSE_TEXT, responseText); } const toolCalls = Array.isArray(response.tool_calls) && response.tool_calls.length > 0 ? response.tool_calls : undefined; if (toolCalls) { - span.setAttribute(GEN_AI_RESPONSE_TOOL_CALLS_ATTRIBUTE, JSON.stringify(toolCalls)); + span.setAttribute(GEN_AI_RESPONSE_TOOL_CALLS, JSON.stringify(toolCalls)); } setOutputMessagesAttribute(span, { responseText, toolCalls }); diff --git a/packages/core/test/lib/tracing/langchain-embeddings.test.ts b/packages/core/test/lib/tracing/langchain-embeddings.test.ts index f1bed062b4b2..0c9651ea6ad1 100644 --- a/packages/core/test/lib/tracing/langchain-embeddings.test.ts +++ b/packages/core/test/lib/tracing/langchain-embeddings.test.ts @@ -1,12 +1,14 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; import { - GEN_AI_EMBEDDINGS_INPUT_ATTRIBUTE, + GEN_AI_EMBEDDINGS_INPUT, + GEN_AI_OPERATION_NAME, + GEN_AI_REQUEST_MODEL, + GEN_AI_SYSTEM, +} from '@sentry/conventions/attributes'; +import { GEN_AI_EMBEDDINGS_OPERATION_ATTRIBUTE, - GEN_AI_OPERATION_NAME_ATTRIBUTE, GEN_AI_REQUEST_DIMENSIONS_ATTRIBUTE, GEN_AI_REQUEST_ENCODING_FORMAT_ATTRIBUTE, - GEN_AI_REQUEST_MODEL_ATTRIBUTE, - GEN_AI_SYSTEM_ATTRIBUTE, } from '../../../src/tracing/ai/gen-ai-attributes'; import { instrumentEmbeddingMethod, instrumentLangChainEmbeddings } from '../../../src/tracing/langchain/embeddings'; @@ -55,9 +57,9 @@ describe('instrumentEmbeddingMethod', () => { expect(capturedSpanConfig).toBeDefined(); expect(capturedSpanConfig!.name).toBe('embeddings text-embedding-3-small'); expect(capturedSpanConfig!.op).toBe(GEN_AI_EMBEDDINGS_OPERATION_ATTRIBUTE); - expect(capturedSpanConfig!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE]).toBe('embeddings'); - expect(capturedSpanConfig!.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE]).toBe('text-embedding-3-small'); - expect(capturedSpanConfig!.attributes[GEN_AI_SYSTEM_ATTRIBUTE]).toBe('openai'); + expect(capturedSpanConfig!.attributes[GEN_AI_OPERATION_NAME]).toBe('embeddings'); + expect(capturedSpanConfig!.attributes[GEN_AI_REQUEST_MODEL]).toBe('text-embedding-3-small'); + expect(capturedSpanConfig!.attributes[GEN_AI_SYSTEM]).toBe('openai'); expect(capturedSpanConfig!.attributes[GEN_AI_REQUEST_DIMENSIONS_ATTRIBUTE]).toBe(1536); expect(capturedSpanConfig!.attributes[GEN_AI_REQUEST_ENCODING_FORMAT_ATTRIBUTE]).toBe('float'); expect(original).toHaveBeenCalledWith('Hello world'); @@ -69,10 +71,10 @@ describe('instrumentEmbeddingMethod', () => { const wrapped = instrumentEmbeddingMethod(original, { recordInputs: true }); await wrapped.call(instance, 'Hello world'); - expect(capturedSpanConfig!.attributes[GEN_AI_EMBEDDINGS_INPUT_ATTRIBUTE]).toBe('Hello world'); + expect(capturedSpanConfig!.attributes[GEN_AI_EMBEDDINGS_INPUT]).toBe('Hello world'); await wrapped.call(instance, ['doc1', 'doc2']); - expect(capturedSpanConfig!.attributes[GEN_AI_EMBEDDINGS_INPUT_ATTRIBUTE]).toBe('["doc1","doc2"]'); + expect(capturedSpanConfig!.attributes[GEN_AI_EMBEDDINGS_INPUT]).toBe('["doc1","doc2"]'); }); it('captures exception on failure', async () => { @@ -93,7 +95,7 @@ describe('instrumentEmbeddingMethod', () => { const wrapped = instrumentEmbeddingMethod(original); await wrapped.call({ constructor: { name: 'GoogleGenerativeAIEmbeddings' }, model: 'test' }, 'test'); - expect(capturedSpanConfig!.attributes[GEN_AI_SYSTEM_ATTRIBUTE]).toBe('google_genai'); + expect(capturedSpanConfig!.attributes[GEN_AI_SYSTEM]).toBe('google_genai'); }); it('handles missing instance properties gracefully', async () => { @@ -103,8 +105,8 @@ describe('instrumentEmbeddingMethod', () => { await wrapped.call({}, 'test'); expect(capturedSpanConfig!.name).toBe('embeddings unknown'); - expect(capturedSpanConfig!.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE]).toBe('unknown'); - expect(capturedSpanConfig!.attributes[GEN_AI_SYSTEM_ATTRIBUTE]).toBe('langchain'); + expect(capturedSpanConfig!.attributes[GEN_AI_REQUEST_MODEL]).toBe('unknown'); + expect(capturedSpanConfig!.attributes[GEN_AI_SYSTEM]).toBe('langchain'); expect(capturedSpanConfig!.attributes[GEN_AI_REQUEST_DIMENSIONS_ATTRIBUTE]).toBeUndefined(); }); }); @@ -126,9 +128,9 @@ describe('instrumentLangChainEmbeddings', () => { expect(wrapped).toBe(instance); await wrapped.embedQuery('test'); - expect(capturedSpanConfig!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE]).toBe('embeddings'); + expect(capturedSpanConfig!.attributes[GEN_AI_OPERATION_NAME]).toBe('embeddings'); await wrapped.embedDocuments(['doc1']); - expect(capturedSpanConfig!.attributes[GEN_AI_OPERATION_NAME_ATTRIBUTE]).toBe('embeddings'); + expect(capturedSpanConfig!.attributes[GEN_AI_OPERATION_NAME]).toBe('embeddings'); }); }); diff --git a/packages/core/test/lib/tracing/langchain-utils.test.ts b/packages/core/test/lib/tracing/langchain-utils.test.ts index f39e01b5c625..cbfc384685cf 100644 --- a/packages/core/test/lib/tracing/langchain-utils.test.ts +++ b/packages/core/test/lib/tracing/langchain-utils.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it, vi } from 'vitest'; -import { GEN_AI_INPUT_MESSAGES_ATTRIBUTE } from '../../../src/tracing/ai/gen-ai-attributes'; +import { GEN_AI_INPUT_MESSAGES } from '@sentry/conventions/attributes'; import type { LangChainMessage } from '../../../src/tracing/langchain/types'; import { _INTERNAL_mergeLangChainCallbackHandler, @@ -242,7 +242,7 @@ describe('extractChatModelRequestAttributes with multimodal content', () => { ]; const attrs = extractChatModelRequestAttributes(serialized, messages, true, true); - const inputMessages = attrs[GEN_AI_INPUT_MESSAGES_ATTRIBUTE] as string | undefined; + const inputMessages = attrs[GEN_AI_INPUT_MESSAGES] as string | undefined; expect(inputMessages).toBeDefined(); expect(inputMessages).not.toContain(b64Data); diff --git a/packages/core/test/lib/tracing/vercel-ai-request-messages.test.ts b/packages/core/test/lib/tracing/vercel-ai-request-messages.test.ts index b2230c917344..a0af09a7d0f9 100644 --- a/packages/core/test/lib/tracing/vercel-ai-request-messages.test.ts +++ b/packages/core/test/lib/tracing/vercel-ai-request-messages.test.ts @@ -1,11 +1,8 @@ import { describe, expect, it } from 'vitest'; import { getTruncatedJsonString } from '../../../src/tracing/ai/utils'; import { stringify } from '../../../src/utils/string'; -import { - GEN_AI_INPUT_MESSAGES_ATTRIBUTE, - GEN_AI_INPUT_MESSAGES_ORIGINAL_LENGTH_ATTRIBUTE, - GEN_AI_SYSTEM_INSTRUCTIONS_ATTRIBUTE, -} from '../../../src/tracing/ai/gen-ai-attributes'; +import { GEN_AI_INPUT_MESSAGES, GEN_AI_SYSTEM_INSTRUCTIONS } from '@sentry/conventions/attributes'; +import { GEN_AI_INPUT_MESSAGES_ORIGINAL_LENGTH_ATTRIBUTE } from '../../../src/tracing/ai/gen-ai-attributes'; import { requestMessagesFromPrompt } from '../../../src/tracing/vercel-ai/utils'; import { AI_PROMPT_MESSAGES_ATTRIBUTE } from '../../../src/tracing/vercel-ai/vercel-ai-attributes'; import type { Span, SpanAttributes } from '../../../src/types/span'; @@ -39,9 +36,9 @@ describe('requestMessagesFromPrompt (ai.prompt.messages string branch)', () => { requestMessagesFromPrompt(span, attributes, /* enableTruncation */ false); expect(recorded[AI_PROMPT_MESSAGES_ATTRIBUTE]).toBe(original); - expect(recorded[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]).toBe(original); + expect(recorded[GEN_AI_INPUT_MESSAGES]).toBe(original); expect(recorded[GEN_AI_INPUT_MESSAGES_ORIGINAL_LENGTH_ATTRIBUTE]).toBe(1); - expect(recorded[GEN_AI_SYSTEM_INSTRUCTIONS_ATTRIBUTE]).toBeUndefined(); + expect(recorded[GEN_AI_SYSTEM_INSTRUCTIONS]).toBeUndefined(); }); it('extracts the system message and re-serializes the remainder when truncation is off', () => { @@ -55,7 +52,7 @@ describe('requestMessagesFromPrompt (ai.prompt.messages string branch)', () => { requestMessagesFromPrompt(span, attributes, false); - expect(recorded[GEN_AI_SYSTEM_INSTRUCTIONS_ATTRIBUTE]).toBe(JSON.stringify([{ type: 'text', content: 'be nice' }])); + expect(recorded[GEN_AI_SYSTEM_INSTRUCTIONS]).toBe(JSON.stringify([{ type: 'text', content: 'be nice' }])); // System message removed; output is the SDK's own serialization of just the remainder. expect(recorded[AI_PROMPT_MESSAGES_ATTRIBUTE]).toBe(stringify([{ role: 'user', content: 'hello' }])); expect(recorded[AI_PROMPT_MESSAGES_ATTRIBUTE]).not.toBe(original); diff --git a/packages/core/test/lib/tracing/workers-ai-streaming.test.ts b/packages/core/test/lib/tracing/workers-ai-streaming.test.ts index 8de48172d6d8..2b46f77eedc9 100644 --- a/packages/core/test/lib/tracing/workers-ai-streaming.test.ts +++ b/packages/core/test/lib/tracing/workers-ai-streaming.test.ts @@ -1,14 +1,14 @@ import { describe, expect, it } from 'vitest'; import type { Span } from '../../../src'; import { - GEN_AI_OUTPUT_MESSAGES_ATTRIBUTE, - GEN_AI_RESPONSE_STREAMING_ATTRIBUTE, - GEN_AI_RESPONSE_TEXT_ATTRIBUTE, - GEN_AI_RESPONSE_TOOL_CALLS_ATTRIBUTE, - GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE, - GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE, - GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE, -} from '../../../src/tracing/ai/gen-ai-attributes'; + GEN_AI_OUTPUT_MESSAGES, + GEN_AI_RESPONSE_STREAMING, + GEN_AI_RESPONSE_TEXT, + GEN_AI_RESPONSE_TOOL_CALLS, + GEN_AI_USAGE_INPUT_TOKENS, + GEN_AI_USAGE_OUTPUT_TOKENS, + GEN_AI_USAGE_TOTAL_TOKENS, +} from '@sentry/conventions/attributes'; import { instrumentWorkersAiStream } from '../../../src/tracing/workers-ai/streaming'; function createMockSpan(): { span: Span; attributes: Record; ended: () => boolean } { @@ -68,11 +68,11 @@ describe('instrumentWorkersAiStream', () => { const instrumented = instrumentWorkersAiStream(streamFromChunks(chunks), span, true); await new Response(instrumented).text(); - expect(attributes[GEN_AI_RESPONSE_STREAMING_ATTRIBUTE]).toBe(true); - expect(attributes[GEN_AI_RESPONSE_TEXT_ATTRIBUTE]).toBe('The capital of France is Paris.'); - expect(attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]).toBe(12); - expect(attributes[GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE]).toBe(7); - expect(attributes[GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE]).toBe(19); + expect(attributes[GEN_AI_RESPONSE_STREAMING]).toBe(true); + expect(attributes[GEN_AI_RESPONSE_TEXT]).toBe('The capital of France is Paris.'); + expect(attributes[GEN_AI_USAGE_INPUT_TOKENS]).toBe(12); + expect(attributes[GEN_AI_USAGE_OUTPUT_TOKENS]).toBe(7); + expect(attributes[GEN_AI_USAGE_TOTAL_TOKENS]).toBe(19); expect(ended()).toBe(true); }); @@ -86,8 +86,8 @@ describe('instrumentWorkersAiStream', () => { const instrumented = instrumentWorkersAiStream(streamFromChunks(chunks), span, false); await new Response(instrumented).text(); - expect(attributes[GEN_AI_RESPONSE_TEXT_ATTRIBUTE]).toBeUndefined(); - expect(attributes[GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE]).toBe(1); + expect(attributes[GEN_AI_RESPONSE_TEXT]).toBeUndefined(); + expect(attributes[GEN_AI_USAGE_OUTPUT_TOKENS]).toBe(1); }); it('ignores malformed SSE payloads without throwing', async () => { @@ -97,7 +97,7 @@ describe('instrumentWorkersAiStream', () => { const instrumented = instrumentWorkersAiStream(streamFromChunks(chunks), span, true); await new Response(instrumented).text(); - expect(attributes[GEN_AI_RESPONSE_TEXT_ATTRIBUTE]).toBe('ok'); + expect(attributes[GEN_AI_RESPONSE_TEXT]).toBe('ok'); expect(ended()).toBe(true); }); @@ -117,11 +117,11 @@ describe('instrumentWorkersAiStream', () => { const instrumented = instrumentWorkersAiStream(streamFromChunks(chunks), span, true); await new Response(instrumented).text(); - expect(attributes[GEN_AI_RESPONSE_STREAMING_ATTRIBUTE]).toBe(true); - expect(attributes[GEN_AI_RESPONSE_TEXT_ATTRIBUTE]).toBe('The capital of France is Paris.'); - expect(attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]).toBe(12); - expect(attributes[GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE]).toBe(7); - expect(attributes[GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE]).toBe(19); + expect(attributes[GEN_AI_RESPONSE_STREAMING]).toBe(true); + expect(attributes[GEN_AI_RESPONSE_TEXT]).toBe('The capital of France is Paris.'); + expect(attributes[GEN_AI_USAGE_INPUT_TOKENS]).toBe(12); + expect(attributes[GEN_AI_USAGE_OUTPUT_TOKENS]).toBe(7); + expect(attributes[GEN_AI_USAGE_TOTAL_TOKENS]).toBe(19); expect(ended()).toBe(true); }); @@ -136,7 +136,7 @@ describe('instrumentWorkersAiStream', () => { const instrumented = instrumentWorkersAiStream(streamFromChunks(chunks), span, true); await new Response(instrumented).text(); - const toolCalls = JSON.parse(attributes[GEN_AI_RESPONSE_TOOL_CALLS_ATTRIBUTE] as string); + const toolCalls = JSON.parse(attributes[GEN_AI_RESPONSE_TOOL_CALLS] as string); expect(toolCalls).toEqual([ { index: 0, @@ -147,7 +147,7 @@ describe('instrumentWorkersAiStream', () => { ]); // The product reads model output from `gen_ai.output.messages`; tool calls must appear there too. - expect(JSON.parse(attributes[GEN_AI_OUTPUT_MESSAGES_ATTRIBUTE] as string)).toEqual([ + expect(JSON.parse(attributes[GEN_AI_OUTPUT_MESSAGES] as string)).toEqual([ { role: 'assistant', parts: [ @@ -175,7 +175,7 @@ describe('instrumentWorkersAiStream', () => { const instrumented = instrumentWorkersAiStream(streamFromChunks(chunks), span, true); await new Response(instrumented).text(); - const toolCalls = JSON.parse(attributes[GEN_AI_RESPONSE_TOOL_CALLS_ATTRIBUTE] as string); + const toolCalls = JSON.parse(attributes[GEN_AI_RESPONSE_TOOL_CALLS] as string); expect(toolCalls).toEqual([ { index: 0, @@ -185,7 +185,7 @@ describe('instrumentWorkersAiStream', () => { }, ]); - expect(JSON.parse(attributes[GEN_AI_OUTPUT_MESSAGES_ATTRIBUTE] as string)).toEqual([ + expect(JSON.parse(attributes[GEN_AI_OUTPUT_MESSAGES] as string)).toEqual([ { role: 'assistant', parts: [{ type: 'tool_call', id: 'call_1', name: 'getRepoInfo', arguments: '{"owner":"cloudflare"}' }], @@ -203,10 +203,10 @@ describe('instrumentWorkersAiStream', () => { const instrumented = instrumentWorkersAiStream(streamFromChunks(chunks), span, false); await new Response(instrumented).text(); - expect(attributes[GEN_AI_RESPONSE_TEXT_ATTRIBUTE]).toBeUndefined(); - expect(attributes[GEN_AI_RESPONSE_TOOL_CALLS_ATTRIBUTE]).toBeUndefined(); - expect(attributes[GEN_AI_OUTPUT_MESSAGES_ATTRIBUTE]).toBeUndefined(); - expect(attributes[GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE]).toBe(1); + expect(attributes[GEN_AI_RESPONSE_TEXT]).toBeUndefined(); + expect(attributes[GEN_AI_RESPONSE_TOOL_CALLS]).toBeUndefined(); + expect(attributes[GEN_AI_OUTPUT_MESSAGES]).toBeUndefined(); + expect(attributes[GEN_AI_USAGE_OUTPUT_TOKENS]).toBe(1); }); it('ends the span when the consumer cancels the stream', async () => { @@ -219,6 +219,6 @@ describe('instrumentWorkersAiStream', () => { await reader.cancel('no longer needed'); expect(ended()).toBe(true); - expect(attributes[GEN_AI_RESPONSE_STREAMING_ATTRIBUTE]).toBe(true); + expect(attributes[GEN_AI_RESPONSE_STREAMING]).toBe(true); }); }); diff --git a/packages/core/test/lib/utils/workers-ai-utils.test.ts b/packages/core/test/lib/utils/workers-ai-utils.test.ts index 3eefe0bf79bc..24304c3f23dc 100644 --- a/packages/core/test/lib/utils/workers-ai-utils.test.ts +++ b/packages/core/test/lib/utils/workers-ai-utils.test.ts @@ -13,17 +13,15 @@ import { GEN_AI_REQUEST_TEMPERATURE, GEN_AI_REQUEST_TOP_K, GEN_AI_REQUEST_TOP_P, + GEN_AI_OUTPUT_MESSAGES, + GEN_AI_RESPONSE_TEXT, + GEN_AI_RESPONSE_TOOL_CALLS, GEN_AI_SYSTEM_INSTRUCTIONS, GEN_AI_USAGE_INPUT_TOKENS, GEN_AI_USAGE_OUTPUT_TOKENS, GEN_AI_USAGE_TOTAL_TOKENS, } from '@sentry/conventions/attributes'; -import { - GEN_AI_OUTPUT_MESSAGES_ATTRIBUTE, - GEN_AI_REQUEST_STREAM_ATTRIBUTE, - GEN_AI_RESPONSE_TEXT_ATTRIBUTE, - GEN_AI_RESPONSE_TOOL_CALLS_ATTRIBUTE, -} from '../../../src/tracing/ai/gen-ai-attributes'; +import { GEN_AI_REQUEST_STREAM_ATTRIBUTE } from '../../../src/tracing/ai/gen-ai-attributes'; import { WORKERS_AI_ORIGIN, WORKERS_AI_PROVIDER_NAME } from '../../../src/tracing/workers-ai/constants'; import { addRequestAttributes, @@ -205,7 +203,7 @@ describe('workers-ai utils', () => { addResponseAttributes(span, { response: 'Paris' }, false); - expect(attributes[GEN_AI_RESPONSE_TEXT_ATTRIBUTE]).toBeUndefined(); + expect(attributes[GEN_AI_RESPONSE_TEXT]).toBeUndefined(); }); it('records response text when recordOutputs is true', () => { @@ -213,8 +211,8 @@ describe('workers-ai utils', () => { addResponseAttributes(span, { response: 'Paris' }, true); - expect(attributes[GEN_AI_RESPONSE_TEXT_ATTRIBUTE]).toBe('Paris'); - expect(attributes[GEN_AI_OUTPUT_MESSAGES_ATTRIBUTE]).toBe( + expect(attributes[GEN_AI_RESPONSE_TEXT]).toBe('Paris'); + expect(attributes[GEN_AI_OUTPUT_MESSAGES]).toBe( JSON.stringify([{ role: 'assistant', parts: [{ type: 'text', content: 'Paris' }] }]), ); }); @@ -225,9 +223,9 @@ describe('workers-ai utils', () => { addResponseAttributes(span, { tool_calls: toolCalls }, true); - expect(attributes[GEN_AI_RESPONSE_TOOL_CALLS_ATTRIBUTE]).toBe(JSON.stringify(toolCalls)); + expect(attributes[GEN_AI_RESPONSE_TOOL_CALLS]).toBe(JSON.stringify(toolCalls)); // The product reads model output from `gen_ai.output.messages`; tool calls must appear there too. - expect(attributes[GEN_AI_OUTPUT_MESSAGES_ATTRIBUTE]).toBe( + expect(attributes[GEN_AI_OUTPUT_MESSAGES]).toBe( JSON.stringify([ { role: 'assistant', @@ -245,7 +243,7 @@ describe('workers-ai utils', () => { addResponseAttributes(span, { tool_calls: toolCalls }, true); - expect(attributes[GEN_AI_OUTPUT_MESSAGES_ATTRIBUTE]).toBe( + expect(attributes[GEN_AI_OUTPUT_MESSAGES]).toBe( JSON.stringify([ { role: 'assistant', @@ -261,9 +259,9 @@ describe('workers-ai utils', () => { addResponseAttributes(span, { response: 'Looking that up', tool_calls: toolCalls }, true); - expect(attributes[GEN_AI_RESPONSE_TEXT_ATTRIBUTE]).toBe('Looking that up'); - expect(attributes[GEN_AI_RESPONSE_TOOL_CALLS_ATTRIBUTE]).toBe(JSON.stringify(toolCalls)); - expect(attributes[GEN_AI_OUTPUT_MESSAGES_ATTRIBUTE]).toBe( + expect(attributes[GEN_AI_RESPONSE_TEXT]).toBe('Looking that up'); + expect(attributes[GEN_AI_RESPONSE_TOOL_CALLS]).toBe(JSON.stringify(toolCalls)); + expect(attributes[GEN_AI_OUTPUT_MESSAGES]).toBe( JSON.stringify([ { role: 'assistant', @@ -281,7 +279,7 @@ describe('workers-ai utils', () => { addResponseAttributes(span, { response: 'Paris', tool_calls: [{ name: 'lookup' }] }, false); - expect(attributes[GEN_AI_OUTPUT_MESSAGES_ATTRIBUTE]).toBeUndefined(); + expect(attributes[GEN_AI_OUTPUT_MESSAGES]).toBeUndefined(); }); it('serializes non-string response payloads as JSON', () => { @@ -289,7 +287,7 @@ describe('workers-ai utils', () => { addResponseAttributes(span, { response: { translated_text: 'Bonjour' } }, true); - expect(attributes[GEN_AI_RESPONSE_TEXT_ATTRIBUTE]).toBe(JSON.stringify({ translated_text: 'Bonjour' })); + expect(attributes[GEN_AI_RESPONSE_TEXT]).toBe(JSON.stringify({ translated_text: 'Bonjour' })); }); it('ignores raw Response objects', () => { diff --git a/packages/server-utils/src/integrations/tracing-channel/anthropic.ts b/packages/server-utils/src/integrations/tracing-channel/anthropic.ts index a6425437c7f7..56d534db3f8f 100644 --- a/packages/server-utils/src/integrations/tracing-channel/anthropic.ts +++ b/packages/server-utils/src/integrations/tracing-channel/anthropic.ts @@ -1,3 +1,4 @@ +import { GEN_AI_REQUEST_MODEL } from '@sentry/conventions/attributes'; import * as diagnosticsChannel from 'node:diagnostics_channel'; import type { AnthropicAiOptions, AnthropicAiResponse, IntegrationFn, Span, SpanAttributeValue } from '@sentry/core'; import { @@ -7,7 +8,6 @@ import { debug, defineIntegration, extractAnthropicRequestAttributes, - GEN_AI_REQUEST_MODEL_ATTRIBUTE, instrumentAsyncIterableStream, instrumentMessageStream, resolveAIRecordingOptions, @@ -116,7 +116,7 @@ function createGenAiSpan( const enableTruncation = shouldEnableTruncation(options.enableTruncation); const attributes = extractAnthropicRequestAttributes(args, methodPath, operation); - const model = (attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE] as string) || 'unknown'; + const model = (attributes[GEN_AI_REQUEST_MODEL] as string) || 'unknown'; attributes[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN] = ORIGIN; const span = startInactiveSpan({ diff --git a/packages/server-utils/src/integrations/tracing-channel/google-genai.ts b/packages/server-utils/src/integrations/tracing-channel/google-genai.ts index 312e4716cf61..345b402d347d 100644 --- a/packages/server-utils/src/integrations/tracing-channel/google-genai.ts +++ b/packages/server-utils/src/integrations/tracing-channel/google-genai.ts @@ -1,3 +1,4 @@ +import { GEN_AI_REQUEST_MODEL } from '@sentry/conventions/attributes'; import * as diagnosticsChannel from 'node:diagnostics_channel'; import type { GoogleGenAIOptions, GoogleGenAIResponse, IntegrationFn, Span } from '@sentry/core'; import { @@ -7,7 +8,6 @@ import { debug, defineIntegration, extractGoogleGenAIRequestAttributes, - GEN_AI_REQUEST_MODEL_ATTRIBUTE, getActiveSpan, instrumentGoogleGenAIStream, resolveAIRecordingOptions, @@ -119,7 +119,7 @@ function createGenAiSpan( const enableTruncation = shouldEnableTruncation(options.enableTruncation); const attributes = extractGoogleGenAIRequestAttributes(operation, params, data.self); - const model = (attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE] as string) || 'unknown'; + const model = (attributes[GEN_AI_REQUEST_MODEL] as string) || 'unknown'; attributes[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN] = ORIGIN; const span = startInactiveSpan({ diff --git a/packages/server-utils/src/vercel-ai/vercel-ai-dc-subscriber.ts b/packages/server-utils/src/vercel-ai/vercel-ai-dc-subscriber.ts index 0b9e7f39e101..c79540eda569 100644 --- a/packages/server-utils/src/vercel-ai/vercel-ai-dc-subscriber.ts +++ b/packages/server-utils/src/vercel-ai/vercel-ai-dc-subscriber.ts @@ -17,6 +17,7 @@ import { GEN_AI_RESPONSE_MODEL, GEN_AI_RESPONSE_STREAMING, GEN_AI_SYSTEM, + GEN_AI_SYSTEM_INSTRUCTIONS, GEN_AI_TOOL_INPUT, GEN_AI_TOOL_NAME, GEN_AI_TOOL_OUTPUT, @@ -31,7 +32,6 @@ import { captureException, GEN_AI_CONVERSATION_ID_ATTRIBUTE, GEN_AI_INPUT_MESSAGES_ORIGINAL_LENGTH_ATTRIBUTE, - GEN_AI_SYSTEM_INSTRUCTIONS_ATTRIBUTE, getClient, getProviderMetadataAttributes, getTruncatedJsonString, @@ -732,7 +732,7 @@ function buildInputMessageAttributes( // `gen_ai.system_instructions` as `[{ type: 'text', content }]`; mirror that shape here. const instructions = asString(event.instructions); if (instructions) { - attributes[GEN_AI_SYSTEM_INSTRUCTIONS_ATTRIBUTE] = stringify([{ type: 'text', content: instructions }]); + attributes[GEN_AI_SYSTEM_INSTRUCTIONS] = stringify([{ type: 'text', content: instructions }]); } // The AI SDK start events extend `StandardizedPrompt`; messages live on `messages`, otherwise the From 7c6ee15328cabd46ae17346e85fed91a7eae5944 Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Thu, 23 Jul 2026 14:41:46 +0200 Subject: [PATCH 2/2] ref(core): Drop explanatory comment above no-deprecated disables Co-Authored-By: Claude Opus 4.8 (1M context) --- packages/core/src/tracing/ai/utils.ts | 4 ---- packages/core/src/tracing/anthropic-ai/index.ts | 4 ---- packages/core/src/tracing/google-genai/index.ts | 4 ---- packages/core/src/tracing/langchain/embeddings.ts | 4 ---- packages/core/src/tracing/langchain/index.ts | 4 ---- packages/core/src/tracing/langchain/utils.ts | 4 ---- packages/core/src/tracing/langgraph/index.ts | 4 ---- packages/core/src/tracing/langgraph/utils.ts | 4 ---- packages/core/src/tracing/openai/index.ts | 4 ---- packages/core/src/tracing/openai/utils.ts | 4 ---- packages/core/src/tracing/vercel-ai/index.ts | 4 ---- packages/core/src/tracing/vercel-ai/utils.ts | 4 ---- packages/core/src/tracing/workers-ai/utils.ts | 4 ---- 13 files changed, 52 deletions(-) diff --git a/packages/core/src/tracing/ai/utils.ts b/packages/core/src/tracing/ai/utils.ts index a244f13a4481..9a7dabced2d6 100644 --- a/packages/core/src/tracing/ai/utils.ts +++ b/packages/core/src/tracing/ai/utils.ts @@ -1,7 +1,3 @@ -// `@sentry/conventions` marks several gen_ai attributes (e.g. `GEN_AI_SYSTEM`, `GEN_AI_PROMPT`, -// `GEN_AI_REQUEST_AVAILABLE_TOOLS`, `GEN_AI_TOOL_*`) as deprecated in favour of newer semconv names. We -// intentionally keep emitting the current names so these spans match what the Sentry product consumes -// today; migrating to the new names is a separate, coordinated change. /* eslint-disable typescript-eslint/no-deprecated */ /** * Shared utils for AI integrations (OpenAI, Anthropic, Verce.AI, etc.) diff --git a/packages/core/src/tracing/anthropic-ai/index.ts b/packages/core/src/tracing/anthropic-ai/index.ts index 0958e4561591..38b3313fa0d6 100644 --- a/packages/core/src/tracing/anthropic-ai/index.ts +++ b/packages/core/src/tracing/anthropic-ai/index.ts @@ -1,7 +1,3 @@ -// `@sentry/conventions` marks several gen_ai attributes (e.g. `GEN_AI_SYSTEM`, `GEN_AI_PROMPT`, -// `GEN_AI_REQUEST_AVAILABLE_TOOLS`, `GEN_AI_TOOL_*`) as deprecated in favour of newer semconv names. We -// intentionally keep emitting the current names so these spans match what the Sentry product consumes -// today; migrating to the new names is a separate, coordinated change. /* eslint-disable typescript-eslint/no-deprecated */ import { captureException } from '../../exports'; import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '../../semanticAttributes'; diff --git a/packages/core/src/tracing/google-genai/index.ts b/packages/core/src/tracing/google-genai/index.ts index 681fd58800cb..1049db1980ec 100644 --- a/packages/core/src/tracing/google-genai/index.ts +++ b/packages/core/src/tracing/google-genai/index.ts @@ -1,7 +1,3 @@ -// `@sentry/conventions` marks several gen_ai attributes (e.g. `GEN_AI_SYSTEM`, `GEN_AI_PROMPT`, -// `GEN_AI_REQUEST_AVAILABLE_TOOLS`, `GEN_AI_TOOL_*`) as deprecated in favour of newer semconv names. We -// intentionally keep emitting the current names so these spans match what the Sentry product consumes -// today; migrating to the new names is a separate, coordinated change. /* eslint-disable typescript-eslint/no-deprecated */ /* eslint-disable max-lines */ import { captureException } from '../../exports'; diff --git a/packages/core/src/tracing/langchain/embeddings.ts b/packages/core/src/tracing/langchain/embeddings.ts index 2ff61b969c97..c5bbc6dd5f0a 100644 --- a/packages/core/src/tracing/langchain/embeddings.ts +++ b/packages/core/src/tracing/langchain/embeddings.ts @@ -1,7 +1,3 @@ -// `@sentry/conventions` marks several gen_ai attributes (e.g. `GEN_AI_SYSTEM`, `GEN_AI_PROMPT`, -// `GEN_AI_REQUEST_AVAILABLE_TOOLS`, `GEN_AI_TOOL_*`) as deprecated in favour of newer semconv names. We -// intentionally keep emitting the current names so these spans match what the Sentry product consumes -// today; migrating to the new names is a separate, coordinated change. /* eslint-disable typescript-eslint/no-deprecated */ import { captureException } from '../../exports'; import { SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '../../semanticAttributes'; diff --git a/packages/core/src/tracing/langchain/index.ts b/packages/core/src/tracing/langchain/index.ts index e9c4554c6796..b6407692e49a 100644 --- a/packages/core/src/tracing/langchain/index.ts +++ b/packages/core/src/tracing/langchain/index.ts @@ -1,7 +1,3 @@ -// `@sentry/conventions` marks several gen_ai attributes (e.g. `GEN_AI_SYSTEM`, `GEN_AI_PROMPT`, -// `GEN_AI_REQUEST_AVAILABLE_TOOLS`, `GEN_AI_TOOL_*`) as deprecated in favour of newer semconv names. We -// intentionally keep emitting the current names so these spans match what the Sentry product consumes -// today; migrating to the new names is a separate, coordinated change. /* eslint-disable typescript-eslint/no-deprecated */ /* eslint-disable max-lines */ import { captureException } from '../../exports'; diff --git a/packages/core/src/tracing/langchain/utils.ts b/packages/core/src/tracing/langchain/utils.ts index ff152760b288..c99d5c5ddcbd 100644 --- a/packages/core/src/tracing/langchain/utils.ts +++ b/packages/core/src/tracing/langchain/utils.ts @@ -1,7 +1,3 @@ -// `@sentry/conventions` marks several gen_ai attributes (e.g. `GEN_AI_SYSTEM`, `GEN_AI_PROMPT`, -// `GEN_AI_REQUEST_AVAILABLE_TOOLS`, `GEN_AI_TOOL_*`) as deprecated in favour of newer semconv names. We -// intentionally keep emitting the current names so these spans match what the Sentry product consumes -// today; migrating to the new names is a separate, coordinated change. /* eslint-disable typescript-eslint/no-deprecated */ import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '../../semanticAttributes'; import type { SpanAttributeValue } from '../../types/span'; diff --git a/packages/core/src/tracing/langgraph/index.ts b/packages/core/src/tracing/langgraph/index.ts index 8e5680f7ac80..2b6eac1dc182 100644 --- a/packages/core/src/tracing/langgraph/index.ts +++ b/packages/core/src/tracing/langgraph/index.ts @@ -1,7 +1,3 @@ -// `@sentry/conventions` marks several gen_ai attributes (e.g. `GEN_AI_SYSTEM`, `GEN_AI_PROMPT`, -// `GEN_AI_REQUEST_AVAILABLE_TOOLS`, `GEN_AI_TOOL_*`) as deprecated in favour of newer semconv names. We -// intentionally keep emitting the current names so these spans match what the Sentry product consumes -// today; migrating to the new names is a separate, coordinated change. /* eslint-disable typescript-eslint/no-deprecated */ import { captureException } from '../../exports'; import { SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '../../semanticAttributes'; diff --git a/packages/core/src/tracing/langgraph/utils.ts b/packages/core/src/tracing/langgraph/utils.ts index da641ea538d6..5d755f2802c5 100644 --- a/packages/core/src/tracing/langgraph/utils.ts +++ b/packages/core/src/tracing/langgraph/utils.ts @@ -1,7 +1,3 @@ -// `@sentry/conventions` marks several gen_ai attributes (e.g. `GEN_AI_SYSTEM`, `GEN_AI_PROMPT`, -// `GEN_AI_REQUEST_AVAILABLE_TOOLS`, `GEN_AI_TOOL_*`) as deprecated in favour of newer semconv names. We -// intentionally keep emitting the current names so these spans match what the Sentry product consumes -// today; migrating to the new names is a separate, coordinated change. /* eslint-disable typescript-eslint/no-deprecated */ import { captureException } from '../../exports'; import { SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '../../semanticAttributes'; diff --git a/packages/core/src/tracing/openai/index.ts b/packages/core/src/tracing/openai/index.ts index 578d89517996..52d2df8eeafd 100644 --- a/packages/core/src/tracing/openai/index.ts +++ b/packages/core/src/tracing/openai/index.ts @@ -1,7 +1,3 @@ -// `@sentry/conventions` marks several gen_ai attributes (e.g. `GEN_AI_SYSTEM`, `GEN_AI_PROMPT`, -// `GEN_AI_REQUEST_AVAILABLE_TOOLS`, `GEN_AI_TOOL_*`) as deprecated in favour of newer semconv names. We -// intentionally keep emitting the current names so these spans match what the Sentry product consumes -// today; migrating to the new names is a separate, coordinated change. /* eslint-disable typescript-eslint/no-deprecated */ import { DEBUG_BUILD } from '../../debug-build'; import { captureException } from '../../exports'; diff --git a/packages/core/src/tracing/openai/utils.ts b/packages/core/src/tracing/openai/utils.ts index ff20cde5a8d0..0e7520015042 100644 --- a/packages/core/src/tracing/openai/utils.ts +++ b/packages/core/src/tracing/openai/utils.ts @@ -1,7 +1,3 @@ -// `@sentry/conventions` marks several gen_ai attributes (e.g. `GEN_AI_SYSTEM`, `GEN_AI_PROMPT`, -// `GEN_AI_REQUEST_AVAILABLE_TOOLS`, `GEN_AI_TOOL_*`) as deprecated in favour of newer semconv names. We -// intentionally keep emitting the current names so these spans match what the Sentry product consumes -// today; migrating to the new names is a separate, coordinated change. /* eslint-disable typescript-eslint/no-deprecated */ import type { Span } from '../../types/span'; import type { SpanAttributeValue } from '../../types/span'; diff --git a/packages/core/src/tracing/vercel-ai/index.ts b/packages/core/src/tracing/vercel-ai/index.ts index a6b9ef45412d..e8c1e9c9b785 100644 --- a/packages/core/src/tracing/vercel-ai/index.ts +++ b/packages/core/src/tracing/vercel-ai/index.ts @@ -1,7 +1,3 @@ -// `@sentry/conventions` marks several gen_ai attributes (e.g. `GEN_AI_SYSTEM`, `GEN_AI_PROMPT`, -// `GEN_AI_REQUEST_AVAILABLE_TOOLS`, `GEN_AI_TOOL_*`) as deprecated in favour of newer semconv names. We -// intentionally keep emitting the current names so these spans match what the Sentry product consumes -// today; migrating to the new names is a separate, coordinated change. /* eslint-disable typescript-eslint/no-deprecated */ /* eslint-disable max-lines */ import type { Client } from '../../client'; diff --git a/packages/core/src/tracing/vercel-ai/utils.ts b/packages/core/src/tracing/vercel-ai/utils.ts index fab036268351..d5b59808c722 100644 --- a/packages/core/src/tracing/vercel-ai/utils.ts +++ b/packages/core/src/tracing/vercel-ai/utils.ts @@ -1,7 +1,3 @@ -// `@sentry/conventions` marks several gen_ai attributes (e.g. `GEN_AI_SYSTEM`, `GEN_AI_PROMPT`, -// `GEN_AI_REQUEST_AVAILABLE_TOOLS`, `GEN_AI_TOOL_*`) as deprecated in favour of newer semconv names. We -// intentionally keep emitting the current names so these spans match what the Sentry product consumes -// today; migrating to the new names is a separate, coordinated change. /* eslint-disable typescript-eslint/no-deprecated */ import type { TraceContext } from '../../types/context'; import type { Span, SpanAttributes, SpanJSON } from '../../types/span'; diff --git a/packages/core/src/tracing/workers-ai/utils.ts b/packages/core/src/tracing/workers-ai/utils.ts index 16eb0525ebe0..2e258b9c122b 100644 --- a/packages/core/src/tracing/workers-ai/utils.ts +++ b/packages/core/src/tracing/workers-ai/utils.ts @@ -1,7 +1,3 @@ -// `@sentry/conventions` marks several gen_ai attributes (e.g. `GEN_AI_SYSTEM`, `GEN_AI_PROMPT`, -// `GEN_AI_REQUEST_AVAILABLE_TOOLS`, `GEN_AI_TOOL_*`) as deprecated in favour of newer semconv names. We -// intentionally keep emitting the current names so these spans match what the Sentry product consumes -// today; migrating to the new names is a separate, coordinated change. /* eslint-disable typescript-eslint/no-deprecated */ import { GEN_AI_EMBEDDINGS_INPUT,