Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import com.anthropic.models.messages.Message;
import com.anthropic.models.messages.MessageCreateParams;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.logs.Logger;
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;

/** Entrypoint for instrumenting Anthropic clients. */
Expand All @@ -23,24 +22,18 @@ public static AnthropicTelemetryBuilder builder(OpenTelemetry openTelemetry) {
}

private final Instrumenter<MessageCreateParams, Message> messageInstrumenter;

private final Logger eventLogger;

private final boolean captureMessageContent;

AnthropicTelemetry(
Instrumenter<MessageCreateParams, Message> messageInstrumenter,
Logger eventLogger,
boolean captureMessageContent) {
this.messageInstrumenter = messageInstrumenter;
this.eventLogger = eventLogger;
this.captureMessageContent = captureMessageContent;
}

/** Wraps the provided AnthropicClient, enabling telemetry for it. */
public AnthropicClient wrap(AnthropicClient client) {
return new InstrumentedAnthropicClient(
client, messageInstrumenter, eventLogger, captureMessageContent)
return new InstrumentedAnthropicClient(client, messageInstrumenter, captureMessageContent)
.createProxy();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import com.anthropic.models.messages.MessageCreateParams;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.logs.Logger;
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;
Expand Down Expand Up @@ -50,7 +49,6 @@ public AnthropicTelemetry build() {
.addOperationMetrics(GenAiClientMetrics.get())
.buildInstrumenter(SpanKindExtractor.alwaysClient());

Logger eventLogger = openTelemetry.getLogsBridge().get(INSTRUMENTATION_NAME);
return new AnthropicTelemetry(messageInstrumenter, eventLogger, captureMessageContent);
return new AnthropicTelemetry(messageInstrumenter, captureMessageContent);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package dev.braintrust.instrumentation.anthropic.otel;

import com.anthropic.models.messages.Message;
import com.anthropic.models.messages.MessageParam;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.opentelemetry.api.trace.Span;
import java.util.List;
import lombok.SneakyThrows;

/** Centralized class for setting all Anthropic-related span attributes. */
final class BraintrustAnthropicSpanAttributes {
private static final ObjectMapper JSON_MAPPER = new ObjectMapper();

// GenAI semantic convention constants
static final String OPERATION_CHAT = "chat";
static final String SYSTEM_ANTHROPIC = "anthropic";

private BraintrustAnthropicSpanAttributes() {}

/**
* Sets the braintrust.input_json attribute with the input messages. This captures the user's
* prompt and system messages before sending to Anthropic.
*/
@SneakyThrows
public static void setInputMessages(Span span, List<MessageParam> messages) {
span.setAttribute("braintrust.input_json", JSON_MAPPER.writeValueAsString(messages));
}

/**
* Sets the braintrust.output_json attribute with the output message. This captures the
* assistant's response from Anthropic.
*/
@SneakyThrows
public static void setOutputMessage(Span span, Message message) {
span.setAttribute(
"braintrust.output_json", JSON_MAPPER.writeValueAsString(new Message[] {message}));
}

/**
* Sets the braintrust.output_json attribute with a JSON array. This is used for streaming
* responses where the output is built incrementally.
*/
@SneakyThrows
public static void setOutputJson(Span span, String outputJson) {
span.setAttribute("braintrust.output_json", outputJson);
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,21 @@
import com.anthropic.client.AnthropicClient;
import com.anthropic.models.messages.Message;
import com.anthropic.models.messages.MessageCreateParams;
import io.opentelemetry.api.logs.Logger;
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
import java.lang.reflect.Method;

final class InstrumentedAnthropicClient
extends DelegatingInvocationHandler<AnthropicClient, InstrumentedAnthropicClient> {

private final Instrumenter<MessageCreateParams, Message> messageInstrumenter;
private final Logger eventLogger;
private final boolean captureMessageContent;

InstrumentedAnthropicClient(
AnthropicClient delegate,
Instrumenter<MessageCreateParams, Message> messageInstrumenter,
Logger eventLogger,
boolean captureMessageContent) {
super(delegate);
this.messageInstrumenter = messageInstrumenter;
this.eventLogger = eventLogger;
this.captureMessageContent = captureMessageContent;
}

Expand All @@ -36,10 +32,7 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl
Class<?>[] parameterTypes = method.getParameterTypes();
if (methodName.equals("messages") && parameterTypes.length == 0) {
return new InstrumentedMessageService(
delegate.messages(),
messageInstrumenter,
eventLogger,
captureMessageContent)
delegate.messages(), messageInstrumenter, captureMessageContent)
.createProxy();
}
return super.invoke(proxy, method, args);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@
import com.anthropic.models.messages.RawMessageStreamEvent;
import com.anthropic.models.messages.TextBlockParam;
import com.anthropic.services.blocking.MessageService;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.opentelemetry.api.logs.Logger;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.Scope;
Expand All @@ -18,24 +16,19 @@
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import lombok.SneakyThrows;

final class InstrumentedMessageService
extends DelegatingInvocationHandler<MessageService, InstrumentedMessageService> {
private static final ObjectMapper JSON_MAPPER = new ObjectMapper();

private final Instrumenter<MessageCreateParams, Message> instrumenter;
private final Logger eventLogger;
private final boolean captureMessageContent;

InstrumentedMessageService(
MessageService delegate,
Instrumenter<MessageCreateParams, Message> instrumenter,
Logger eventLogger,
boolean captureMessageContent) {
super(delegate);
this.instrumenter = instrumenter;
this.eventLogger = eventLogger;
this.captureMessageContent = captureMessageContent;
}

Expand Down Expand Up @@ -79,7 +72,6 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl
return super.invoke(proxy, method, args);
}

@SneakyThrows
private Message create(MessageCreateParams inputMessage, RequestOptions requestOptions) {
Context parentContext = Context.current();
if (!instrumenter.shouldStart(parentContext, inputMessage)) {
Expand All @@ -99,14 +91,9 @@ private Message create(MessageCreateParams inputMessage, RequestOptions requestO
.content(inputMessage.system().get().asString())
.build());
}
Span.current()
.setAttribute(
"braintrust.input_json", JSON_MAPPER.writeValueAsString(inputMessages));
BraintrustAnthropicSpanAttributes.setInputMessages(Span.current(), inputMessages);
outputMessage = delegate.create(inputMessage, requestOptions);
Span.current()
.setAttribute(
"braintrust.output_json",
JSON_MAPPER.writeValueAsString(new Message[] {outputMessage}));
BraintrustAnthropicSpanAttributes.setOutputMessage(Span.current(), outputMessage);
} catch (Throwable t) {
instrumenter.end(context, inputMessage, null, t);
throw t;
Expand All @@ -120,20 +107,20 @@ private StreamResponse<RawMessageStreamEvent> createStreaming(
MessageCreateParams inputMessage, RequestOptions requestOptions) {
Context parentContext = Context.current();
if (!instrumenter.shouldStart(parentContext, inputMessage)) {
return createStreamingWithLogs(parentContext, inputMessage, requestOptions, false);
return createStreamingWithAttributes(
parentContext, inputMessage, requestOptions, false);
}

Context context = instrumenter.start(parentContext, inputMessage);
try (Scope ignored = context.makeCurrent()) {
return createStreamingWithLogs(context, inputMessage, requestOptions, true);
return createStreamingWithAttributes(context, inputMessage, requestOptions, true);
} catch (Throwable t) {
instrumenter.end(context, inputMessage, null, t);
throw t;
}
}

@SneakyThrows
private StreamResponse<RawMessageStreamEvent> createStreamingWithLogs(
private StreamResponse<RawMessageStreamEvent> createStreamingWithAttributes(
Context context,
MessageCreateParams inputMessage,
RequestOptions requestOptions,
Expand All @@ -148,21 +135,15 @@ private StreamResponse<RawMessageStreamEvent> createStreamingWithLogs(
.content(inputMessage.system().get().asString())
.build());
}
Span.fromContext(context)
.setAttribute(
"braintrust.input_json", JSON_MAPPER.writeValueAsString(inputMessages));
BraintrustAnthropicSpanAttributes.setInputMessages(
Span.fromContext(context), inputMessages);

StreamResponse<RawMessageStreamEvent> result =
delegate.createStreaming(inputMessage, requestOptions);
return new TracingStreamedResponse(
result,
new StreamListener(
context,
inputMessage,
instrumenter,
eventLogger,
captureMessageContent,
newSpan));
context, inputMessage, instrumenter, captureMessageContent, newSpan));
}

private static String contentToString(MessageCreateParams.System content) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ enum MessageAttributesGetter implements GenAiAttributesGetter<MessageCreateParam

@Override
public String getOperationName(MessageCreateParams request) {
return GenAiAttributes.GenAiOperationNameIncubatingValues.CHAT;
return BraintrustAnthropicSpanAttributes.OPERATION_CHAT;
}

@Override
public String getSystem(MessageCreateParams request) {
return GenAiAttributes.GenAiProviderNameIncubatingValues.ANTHROPIC;
return BraintrustAnthropicSpanAttributes.SYSTEM_ANTHROPIC;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.logs.Logger;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.context.Context;
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
Expand All @@ -25,7 +24,6 @@ final class StreamListener {
private final Context context;
private final MessageCreateParams request;
private final Instrumenter<MessageCreateParams, Message> instrumenter;
private final Logger eventLogger;
private final boolean captureMessageContent;
private final boolean newSpan;
private final AtomicBoolean hasEnded;
Expand All @@ -42,13 +40,11 @@ final class StreamListener {
Context context,
MessageCreateParams request,
Instrumenter<MessageCreateParams, Message> instrumenter,
Logger eventLogger,
boolean captureMessageContent,
boolean newSpan) {
this.context = context;
this.request = request;
this.instrumenter = instrumenter;
this.eventLogger = eventLogger;
this.captureMessageContent = captureMessageContent;
this.newSpan = newSpan;
hasEnded = new AtomicBoolean();
Expand Down Expand Up @@ -93,9 +89,8 @@ void onEvent(RawMessageStreamEvent event) {
message.put("content", contentBuilder.toString());
outputArray.add(message);

Span.fromContext(context)
.setAttribute(
"braintrust.output_json", JSON_MAPPER.writeValueAsString(outputArray));
BraintrustAnthropicSpanAttributes.setOutputJson(
Span.fromContext(context), JSON_MAPPER.writeValueAsString(outputArray));
}
}

Expand Down
Loading