Skip to content

Commit d45ebfa

Browse files
authored
Implement genai events for bedrock (non-streaming) (#13473)
1 parent 87088a1 commit d45ebfa

File tree

16 files changed

+1389
-47
lines changed

16 files changed

+1389
-47
lines changed

instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/genai/GenAiAttributesGetter.java

-1
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@ public interface GenAiAttributesGetter<REQUEST, RESPONSE> {
5050
@Nullable
5151
Double getRequestTopP(REQUEST request);
5252

53-
@Nullable
5453
List<String> getResponseFinishReasons(REQUEST request, RESPONSE response);
5554

5655
@Nullable

instrumentation/aws-sdk/README.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ For more information, see the respective public setters in the `AwsSdkTelemetryB
66
- [SDK v2](./aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/AwsSdkTelemetryBuilder.java)
77

88
| System property | Type | Default | Description |
9-
|--------------------------------------------------------------------------| ------- | ------- |---------------------------------------------------------------------------------------------------------------------------------------|
9+
|--------------------------------------------------------------------------|---------|---------|---------------------------------------------------------------------------------------------------------------------------------------|
1010
| `otel.instrumentation.aws-sdk.experimental-span-attributes` | Boolean | `false` | Enable the capture of experimental span attributes. |
1111
| `otel.instrumentation.aws-sdk.experimental-use-propagator-for-messaging` | Boolean | `false` | v2 only, inject into SNS/SQS attributes with configured propagator: See [v2 README](aws-sdk-2.2/library/README.md#trace-propagation). |
1212
| `otel.instrumentation.aws-sdk.experimental-record-individual-http-error` | Boolean | `false` | v2 only, record errors returned by each individual HTTP request as events for the SDK span. |
13+
| `otel.instrumentation.genai.capture-message-content` | Boolean | `false` | v2 only, record content of user and LLM messages when using Bedrock. |

instrumentation/aws-sdk/aws-sdk-2.2/javaagent/build.gradle.kts

+9
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,15 @@ testing {
147147
implementation("software.amazon.awssdk:bedrockruntime:2.25.63")
148148
}
149149
}
150+
151+
targets {
152+
all {
153+
testTask.configure {
154+
// TODO run tests both with and without genai message capture
155+
systemProperty("otel.instrumentation.genai.capture-message-content", "true")
156+
}
157+
}
158+
}
150159
}
151160
}
152161
}

instrumentation/aws-sdk/aws-sdk-2.2/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/autoconfigure/AwsSdkSingletons.java

+5
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ public final class AwsSdkSingletons {
2424
.setMessagingReceiveInstrumentationEnabled(messagingReceiveInstrumentationEnabled())
2525
.setUseConfiguredPropagatorForMessaging(useMessagingPropagator())
2626
.setRecordIndividualHttpError(recordIndividualHttpError())
27+
.setGenaiCaptureMessageContent(genaiCaptureMessageContent())
2728
.build();
2829

2930
private static boolean hasAgentConfiguration() {
@@ -67,6 +68,10 @@ private static boolean recordIndividualHttpError() {
6768
"otel.instrumentation.aws-sdk.experimental-record-individual-http-error", false);
6869
}
6970

71+
private static boolean genaiCaptureMessageContent() {
72+
return getBoolean("otel.instrumentation.genai.capture-message-content", false);
73+
}
74+
7075
private static boolean getBoolean(String name, boolean defaultValue) {
7176
if (HAS_INSTRUMENTATION_CONFIG) {
7277
return AgentInstrumentationConfig.get().getBoolean(name, defaultValue);

instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/AwsSdkTelemetry.java

+10-2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
package io.opentelemetry.instrumentation.awssdk.v2_2;
77

88
import io.opentelemetry.api.OpenTelemetry;
9+
import io.opentelemetry.api.logs.Logger;
910
import io.opentelemetry.context.propagation.TextMapPropagator;
1011
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
1112
import io.opentelemetry.instrumentation.awssdk.v2_2.internal.AwsSdkInstrumenterFactory;
@@ -56,10 +57,12 @@ public static AwsSdkTelemetryBuilder builder(OpenTelemetry openTelemetry) {
5657
private final Instrumenter<ExecutionAttributes, Response> producerInstrumenter;
5758
private final Instrumenter<ExecutionAttributes, Response> dynamoDbInstrumenter;
5859
private final Instrumenter<ExecutionAttributes, Response> bedrockRuntimeInstrumenter;
60+
private final Logger eventLogger;
5961
private final boolean captureExperimentalSpanAttributes;
6062
@Nullable private final TextMapPropagator messagingPropagator;
6163
private final boolean useXrayPropagator;
6264
private final boolean recordIndividualHttpError;
65+
private final boolean genAiCaptureMessageContent;
6366

6467
AwsSdkTelemetry(
6568
OpenTelemetry openTelemetry,
@@ -68,7 +71,8 @@ public static AwsSdkTelemetryBuilder builder(OpenTelemetry openTelemetry) {
6871
boolean useMessagingPropagator,
6972
boolean useXrayPropagator,
7073
boolean recordIndividualHttpError,
71-
boolean messagingReceiveInstrumentationEnabled) {
74+
boolean messagingReceiveInstrumentationEnabled,
75+
boolean genAiCaptureMessageContent) {
7276
this.useXrayPropagator = useXrayPropagator;
7377
this.messagingPropagator =
7478
useMessagingPropagator ? openTelemetry.getPropagators().getTextMapPropagator() : null;
@@ -88,8 +92,10 @@ public static AwsSdkTelemetryBuilder builder(OpenTelemetry openTelemetry) {
8892
this.producerInstrumenter = instrumenterFactory.producerInstrumenter();
8993
this.dynamoDbInstrumenter = instrumenterFactory.dynamoDbInstrumenter();
9094
this.bedrockRuntimeInstrumenter = instrumenterFactory.bedrockRuntimeInstrumenter();
95+
this.eventLogger = instrumenterFactory.eventLogger();
9196
this.captureExperimentalSpanAttributes = captureExperimentalSpanAttributes;
9297
this.recordIndividualHttpError = recordIndividualHttpError;
98+
this.genAiCaptureMessageContent = genAiCaptureMessageContent;
9399
}
94100

95101
/**
@@ -104,10 +110,12 @@ public ExecutionInterceptor newExecutionInterceptor() {
104110
producerInstrumenter,
105111
dynamoDbInstrumenter,
106112
bedrockRuntimeInstrumenter,
113+
eventLogger,
107114
captureExperimentalSpanAttributes,
108115
messagingPropagator,
109116
useXrayPropagator,
110-
recordIndividualHttpError);
117+
recordIndividualHttpError,
118+
genAiCaptureMessageContent);
111119
}
112120

113121
/**

instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/AwsSdkTelemetryBuilder.java

+15-1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ public final class AwsSdkTelemetryBuilder {
2424
private boolean recordIndividualHttpError;
2525
private boolean useXrayPropagator = true;
2626
private boolean messagingReceiveInstrumentationEnabled;
27+
private boolean genaiCaptureMessageContent;
2728

2829
AwsSdkTelemetryBuilder(OpenTelemetry openTelemetry) {
2930
this.openTelemetry = openTelemetry;
@@ -115,6 +116,18 @@ public AwsSdkTelemetryBuilder setMessagingReceiveInstrumentationEnabled(
115116
return this;
116117
}
117118

119+
/**
120+
* Set whether Generative AI events include full content of user and assistant messages.
121+
*
122+
* <p>Note that full content can have data privacy and size concerns and care should be taken when
123+
* enabling this.
124+
*/
125+
@CanIgnoreReturnValue
126+
public AwsSdkTelemetryBuilder setGenaiCaptureMessageContent(boolean genaiCaptureMessageContent) {
127+
this.genaiCaptureMessageContent = genaiCaptureMessageContent;
128+
return this;
129+
}
130+
118131
/**
119132
* Returns a new {@link AwsSdkTelemetry} with the settings of this {@link AwsSdkTelemetryBuilder}.
120133
*/
@@ -126,6 +139,7 @@ public AwsSdkTelemetry build() {
126139
useMessagingPropagator,
127140
useXrayPropagator,
128141
recordIndividualHttpError,
129-
messagingReceiveInstrumentationEnabled);
142+
messagingReceiveInstrumentationEnabled,
143+
genaiCaptureMessageContent);
130144
}
131145
}

instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/AwsSdkInstrumenterFactory.java

+5
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
import io.opentelemetry.api.OpenTelemetry;
1212
import io.opentelemetry.api.common.AttributesBuilder;
13+
import io.opentelemetry.api.logs.Logger;
1314
import io.opentelemetry.api.trace.Span;
1415
import io.opentelemetry.context.Context;
1516
import io.opentelemetry.context.propagation.TextMapPropagator;
@@ -236,6 +237,10 @@ public Instrumenter<ExecutionAttributes, Response> bedrockRuntimeInstrumenter()
236237
true);
237238
}
238239

240+
public Logger eventLogger() {
241+
return openTelemetry.getLogsBridge().get(INSTRUMENTATION_NAME);
242+
}
243+
239244
private static <REQUEST, RESPONSE> Instrumenter<REQUEST, RESPONSE> createInstrumenter(
240245
OpenTelemetry openTelemetry,
241246
SpanNameExtractor<REQUEST> spanNameExtractor,

instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/BedrockRuntimeAccess.java

+28
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55

66
package io.opentelemetry.instrumentation.awssdk.v2_2.internal;
77

8+
import io.opentelemetry.api.logs.Logger;
9+
import io.opentelemetry.context.Context;
810
import io.opentelemetry.javaagent.tooling.muzzle.NoMuzzle;
911
import java.util.List;
1012
import javax.annotation.Nullable;
@@ -37,6 +39,11 @@ static boolean isBedrockRuntimeRequest(SdkRequest request) {
3739
return enabled && BedrockRuntimeImpl.isBedrockRuntimeRequest(request);
3840
}
3941

42+
@NoMuzzle
43+
static boolean isBedrockRuntimeResponse(SdkResponse response) {
44+
return enabled && BedrockRuntimeImpl.isBedrockRuntimeResponse(response);
45+
}
46+
4047
@Nullable
4148
@NoMuzzle
4249
static String getModelId(SdkRequest request) {
@@ -84,4 +91,25 @@ static Long getUsageInputTokens(SdkResponse response) {
8491
static Long getUsageOutputTokens(SdkResponse response) {
8592
return enabled ? BedrockRuntimeImpl.getUsageOutputTokens(response) : null;
8693
}
94+
95+
@NoMuzzle
96+
static void recordRequestEvents(
97+
Context otelContext, Logger eventLogger, SdkRequest request, boolean captureMessageContent) {
98+
if (enabled) {
99+
BedrockRuntimeImpl.recordRequestEvents(
100+
otelContext, eventLogger, request, captureMessageContent);
101+
}
102+
}
103+
104+
@NoMuzzle
105+
static void recordResponseEvents(
106+
Context otelContext,
107+
Logger eventLogger,
108+
SdkResponse response,
109+
boolean captureMessageContent) {
110+
if (enabled) {
111+
BedrockRuntimeImpl.recordResponseEvents(
112+
otelContext, eventLogger, response, captureMessageContent);
113+
}
114+
}
87115
}

instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/internal/BedrockRuntimeAttributesGetter.java

+17-6
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
package io.opentelemetry.instrumentation.awssdk.v2_2.internal;
77

88
import static io.opentelemetry.instrumentation.awssdk.v2_2.internal.TracingExecutionInterceptor.SDK_REQUEST_ATTRIBUTE;
9+
import static java.util.Collections.emptyList;
910

1011
import io.opentelemetry.instrumentation.api.incubator.semconv.genai.GenAiAttributesGetter;
1112
import java.util.Arrays;
@@ -25,7 +26,7 @@ private static final class GenAiOperationNameIncubatingValues {
2526
private GenAiOperationNameIncubatingValues() {}
2627
}
2728

28-
private static final class GenAiSystemIncubatingValues {
29+
static final class GenAiSystemIncubatingValues {
2930
static final String AWS_BEDROCK = "aws.bedrock";
3031

3132
private GenAiSystemIncubatingValues() {}
@@ -113,13 +114,15 @@ public Double getRequestTopP(ExecutionAttributes executionAttributes) {
113114
return BedrockRuntimeAccess.getTopP(executionAttributes.getAttribute(SDK_REQUEST_ATTRIBUTE));
114115
}
115116

116-
@Nullable
117117
@Override
118118
public List<String> getResponseFinishReasons(
119-
ExecutionAttributes executionAttributes, Response response) {
119+
ExecutionAttributes executionAttributes, @Nullable Response response) {
120+
if (response == null) {
121+
return emptyList();
122+
}
120123
String stopReason = BedrockRuntimeAccess.getStopReason(response.getSdkResponse());
121124
if (stopReason == null) {
122-
return null;
125+
return emptyList();
123126
}
124127
return Arrays.asList(stopReason);
125128
}
@@ -138,13 +141,21 @@ public String getResponseModel(ExecutionAttributes executionAttributes, Response
138141

139142
@Nullable
140143
@Override
141-
public Long getUsageInputTokens(ExecutionAttributes executionAttributes, Response response) {
144+
public Long getUsageInputTokens(
145+
ExecutionAttributes executionAttributes, @Nullable Response response) {
146+
if (response == null) {
147+
return null;
148+
}
142149
return BedrockRuntimeAccess.getUsageInputTokens(response.getSdkResponse());
143150
}
144151

145152
@Nullable
146153
@Override
147-
public Long getUsageOutputTokens(ExecutionAttributes executionAttributes, Response response) {
154+
public Long getUsageOutputTokens(
155+
ExecutionAttributes executionAttributes, @Nullable Response response) {
156+
if (response == null) {
157+
return null;
158+
}
148159
return BedrockRuntimeAccess.getUsageOutputTokens(response.getSdkResponse());
149160
}
150161
}

0 commit comments

Comments
 (0)