From 9e12117cee2032ff5fe8de27bb3dda424ddbc2f2 Mon Sep 17 00:00:00 2001 From: Andrew Kent Date: Mon, 10 Nov 2025 20:49:27 -0700 Subject: [PATCH] update anthropic span attributes to match other SDKs --- .../otel/AnthropicTelemetryBuilder.java | 6 ++-- .../BraintrustAnthropicSpanAttributes.java | 3 +- .../otel/InstrumentedMessageService.java | 19 ++++++++----- .../otel/MessageAttributesGetter.java | 2 +- .../anthropic/BraintrustAnthropicTest.java | 28 +++++++++---------- 5 files changed, 32 insertions(+), 26 deletions(-) diff --git a/src/main/java/dev/braintrust/instrumentation/anthropic/otel/AnthropicTelemetryBuilder.java b/src/main/java/dev/braintrust/instrumentation/anthropic/otel/AnthropicTelemetryBuilder.java index e4e7205e..dc2198c2 100644 --- a/src/main/java/dev/braintrust/instrumentation/anthropic/otel/AnthropicTelemetryBuilder.java +++ b/src/main/java/dev/braintrust/instrumentation/anthropic/otel/AnthropicTelemetryBuilder.java @@ -6,7 +6,6 @@ import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.instrumentation.api.incubator.semconv.genai.GenAiAttributesExtractor; import io.opentelemetry.instrumentation.api.incubator.semconv.genai.GenAiClientMetrics; -import io.opentelemetry.instrumentation.api.incubator.semconv.genai.GenAiSpanNameExtractor; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; import io.opentelemetry.instrumentation.api.instrumenter.SpanKindExtractor; @@ -39,11 +38,14 @@ public AnthropicTelemetryBuilder setCaptureMessageContent(boolean captureMessage * AnthropicTelemetryBuilder}. */ public AnthropicTelemetry build() { + // Use a custom span name extractor that returns just the operation name + // without appending the model name (unlike the default GenAiSpanNameExtractor) Instrumenter messageInstrumenter = Instrumenter.builder( openTelemetry, INSTRUMENTATION_NAME, - GenAiSpanNameExtractor.create(MessageAttributesGetter.INSTANCE)) + request -> + MessageAttributesGetter.INSTANCE.getOperationName(request)) .addAttributesExtractor( GenAiAttributesExtractor.create(MessageAttributesGetter.INSTANCE)) .addOperationMetrics(GenAiClientMetrics.get()) diff --git a/src/main/java/dev/braintrust/instrumentation/anthropic/otel/BraintrustAnthropicSpanAttributes.java b/src/main/java/dev/braintrust/instrumentation/anthropic/otel/BraintrustAnthropicSpanAttributes.java index 4d01dbe3..8bad4482 100644 --- a/src/main/java/dev/braintrust/instrumentation/anthropic/otel/BraintrustAnthropicSpanAttributes.java +++ b/src/main/java/dev/braintrust/instrumentation/anthropic/otel/BraintrustAnthropicSpanAttributes.java @@ -32,8 +32,7 @@ public static void setInputMessages(Span span, List messages) { */ @SneakyThrows public static void setOutputMessage(Span span, Message message) { - span.setAttribute( - "braintrust.output_json", JSON_MAPPER.writeValueAsString(new Message[] {message})); + span.setAttribute("braintrust.output_json", JSON_MAPPER.writeValueAsString(message)); } /** diff --git a/src/main/java/dev/braintrust/instrumentation/anthropic/otel/InstrumentedMessageService.java b/src/main/java/dev/braintrust/instrumentation/anthropic/otel/InstrumentedMessageService.java index 4e33900d..bf0e70e1 100644 --- a/src/main/java/dev/braintrust/instrumentation/anthropic/otel/InstrumentedMessageService.java +++ b/src/main/java/dev/braintrust/instrumentation/anthropic/otel/InstrumentedMessageService.java @@ -81,17 +81,20 @@ private Message create(MessageCreateParams inputMessage, RequestOptions requestO Context context = instrumenter.start(parentContext, inputMessage); Message outputMessage; try (Scope ignored = context.makeCurrent()) { + Span currentSpan = Span.current(); + // Set provider metadata + currentSpan.setAttribute("provider", "anthropic"); + List inputMessages = new ArrayList<>(inputMessage.messages()); - // Put system in the input message so the backend will pick it up in the LLM display + // Append system message to the end so the backend will pick it up in the LLM display if (inputMessage.system().isPresent()) { inputMessages.add( - 0, MessageParam.builder() .role(MessageParam.Role.of("system")) .content(inputMessage.system().get().asString()) .build()); } - BraintrustAnthropicSpanAttributes.setInputMessages(Span.current(), inputMessages); + BraintrustAnthropicSpanAttributes.setInputMessages(currentSpan, inputMessages); outputMessage = delegate.create(inputMessage, requestOptions); BraintrustAnthropicSpanAttributes.setOutputMessage(Span.current(), outputMessage); } catch (Throwable t) { @@ -125,18 +128,20 @@ private StreamResponse createStreamingWithAttributes( MessageCreateParams inputMessage, RequestOptions requestOptions, boolean newSpan) { + Span span = Span.fromContext(context); + // Set provider metadata + span.setAttribute("provider", "anthropic"); + List inputMessages = new ArrayList<>(inputMessage.messages()); - // Put system in the input message so the backend will pick it up in the LLM display + // Append system message to the end so the backend will pick it up in the LLM display if (inputMessage.system().isPresent()) { inputMessages.add( - 0, MessageParam.builder() .role(MessageParam.Role.of("system")) .content(inputMessage.system().get().asString()) .build()); } - BraintrustAnthropicSpanAttributes.setInputMessages( - Span.fromContext(context), inputMessages); + BraintrustAnthropicSpanAttributes.setInputMessages(span, inputMessages); StreamResponse result = delegate.createStreaming(inputMessage, requestOptions); diff --git a/src/main/java/dev/braintrust/instrumentation/anthropic/otel/MessageAttributesGetter.java b/src/main/java/dev/braintrust/instrumentation/anthropic/otel/MessageAttributesGetter.java index a531d4c8..ca54787b 100644 --- a/src/main/java/dev/braintrust/instrumentation/anthropic/otel/MessageAttributesGetter.java +++ b/src/main/java/dev/braintrust/instrumentation/anthropic/otel/MessageAttributesGetter.java @@ -13,7 +13,7 @@ enum MessageAttributesGetter implements GenAiAttributesGetter