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 cbb90ce72bca..6ba0dcc08cb3 100644 --- a/dev-packages/node-integration-tests/suites/tracing/anthropic/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/anthropic/test.ts @@ -8,6 +8,7 @@ import { 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, @@ -301,7 +302,9 @@ describe('Anthropic integration', () => { expect(span.attributes[GEN_AI_RESPONSE_ID_ATTRIBUTE].value).toBe('msg_stream_1'); } - const detailedStreamSpan = requestStreamSpans[0]; + const detailedStreamSpan = requestStreamSpans.find( + span => span.attributes[GEN_AI_RESPONSE_FINISH_REASONS_ATTRIBUTE]?.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'); @@ -426,6 +429,7 @@ describe('Anthropic integration', () => { 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); } diff --git a/packages/core/src/tracing/anthropic-ai/streaming.ts b/packages/core/src/tracing/anthropic-ai/streaming.ts index d8bc8d3fad2e..68bc9a281001 100644 --- a/packages/core/src/tracing/anthropic-ai/streaming.ts +++ b/packages/core/src/tracing/anthropic-ai/streaming.ts @@ -72,12 +72,15 @@ function isErrorEvent(event: AnthropicAiStreamingEvent, span: Span): boolean { */ function handleMessageMetadata(event: AnthropicAiStreamingEvent, state: StreamingState): void { - // The token counts shown in the usage field of the message_delta event are cumulative. + // Cumulative token counts and the final stop reason both arrive on the message_delta event. // @see https://docs.anthropic.com/en/docs/build-with-claude/streaming#event-types - if (event.type === 'message_delta' && event.usage) { - if ('output_tokens' in event.usage && typeof event.usage.output_tokens === 'number') { + if (event.type === 'message_delta') { + if (event.usage && typeof event.usage.output_tokens === 'number') { state.completionTokens = event.usage.output_tokens; } + if (event.delta?.stop_reason) { + state.finishReasons.push(event.delta.stop_reason); + } } if (event.message) { @@ -85,7 +88,6 @@ function handleMessageMetadata(event: AnthropicAiStreamingEvent, state: Streamin if (message.id) state.responseId = message.id; if (message.model) state.responseModel = message.model; - if (message.stop_reason) state.finishReasons.push(message.stop_reason); if (message.usage) { if (typeof message.usage.input_tokens === 'number') state.promptTokens = message.usage.input_tokens;