Skip to content

Commit

Permalink
Add bedrock runtime Java SDK V1
Browse files Browse the repository at this point in the history
This PR add AWS Bedrock and BedrockRuntime support for AWS Java SDK V1 with the following change:

A. **Bedrock**:
Extract `guardrailId` from API response, and add into `"aws.bedrock.guardrail.id"` span attribute.

B. **Bedrock Agent**:
Extract `agentId` from both API request and response, and add into `"aws.bedrock.agent.id"` span attribute.
Extract `knowledgeBaseId` from API request, and add into `"aws.bedrock.knowledgebase.id"` span attribute.
Extract `dataSourceId` from both API request and response,, and add into `"aws.bedrock.datasource.id"` span attribute.

The instrumentation is on API operation level, we make sure only one attribute is extracted per API call, there will be no overlap/conflict  to identify the resource.

C. **Bedrock Agent Runtime**:
Extract `agentId` from API request, and add into `"aws.bedrock.agent.id"` span attribute.
Extract `knowledgeBaseId` from API request, and add into `"aws.bedrock.knowledgebase.id"` span attribute.

D. **Bedrock Runtime**:
Extract the following attributes and add into span according to [ Gen AI semantic-conventions](https://github.com/open-telemetry/semantic-conventions/blob/main/docs/attributes-registry/gen-ai.md):

```
gen_ai.request.model
gen_ai.system
```
  • Loading branch information
zzhlogin authored Jul 9, 2024
1 parent 305f32e commit 359d60f
Show file tree
Hide file tree
Showing 9 changed files with 372 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ dependencies {
testLibrary("com.amazonaws:aws-java-sdk-kinesis:1.11.106")
testLibrary("com.amazonaws:aws-java-sdk-dynamodb:1.11.106")
testLibrary("com.amazonaws:aws-java-sdk-sns:1.11.106")
testLibrary("com.amazonaws:aws-java-sdk-bedrock:1.12.744")
testLibrary("com.amazonaws:aws-java-sdk-bedrockagent:1.12.744")
testLibrary("com.amazonaws:aws-java-sdk-bedrockagentruntime:1.12.744")
testLibrary("com.amazonaws:aws-java-sdk-bedrockruntime:1.12.744")

testImplementation(project(":instrumentation:aws-sdk:aws-sdk-1.11:testing"))

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ dependencies {
testLibrary("com.amazonaws:aws-java-sdk-dynamodb:1.11.106")
testLibrary("com.amazonaws:aws-java-sdk-sns:1.11.106")
testLibrary("com.amazonaws:aws-java-sdk-sqs:1.11.106")
testLibrary("com.amazonaws:aws-java-sdk-bedrock:1.12.744")
testLibrary("com.amazonaws:aws-java-sdk-bedrockagent:1.12.744")
testLibrary("com.amazonaws:aws-java-sdk-bedrockagentruntime:1.12.744")
testLibrary("com.amazonaws:aws-java-sdk-bedrockruntime:1.12.744")

// last version that does not use json protocol
latestDepTestLibrary("com.amazonaws:aws-java-sdk-sqs:1.12.583")
Expand Down
4 changes: 4 additions & 0 deletions instrumentation/aws-sdk/aws-sdk-1.11/library/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ dependencies {
testLibrary("com.amazonaws:aws-java-sdk-kinesis:1.11.106")
testLibrary("com.amazonaws:aws-java-sdk-dynamodb:1.11.106")
testLibrary("com.amazonaws:aws-java-sdk-sns:1.11.106")
testLibrary("com.amazonaws:aws-java-sdk-bedrock:1.12.744")
testLibrary("com.amazonaws:aws-java-sdk-bedrockagent:1.12.744")
testLibrary("com.amazonaws:aws-java-sdk-bedrockagentruntime:1.12.744")
testLibrary("com.amazonaws:aws-java-sdk-bedrockruntime:1.12.744")

// last version that does not use json protocol
latestDepTestLibrary("com.amazonaws:aws-java-sdk-sqs:1.12.583")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.instrumentation.awssdk.v1_11;

import static io.opentelemetry.instrumentation.awssdk.v1_11.AwsExperimentalAttributes.AWS_AGENT_ID;
import static io.opentelemetry.instrumentation.awssdk.v1_11.AwsExperimentalAttributes.AWS_DATASOURCE_ID;
import static io.opentelemetry.instrumentation.awssdk.v1_11.AwsExperimentalAttributes.AWS_KNOWLEDGEBASE_ID;

import io.opentelemetry.api.common.AttributeKey;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;

enum AwsBedrockResourceType {
AGENT_TYPE(AWS_AGENT_ID, RequestAccess::getAgentId),
DATA_SOURCE_TYPE(AWS_DATASOURCE_ID, RequestAccess::getDataSourceId),
KNOWLEDGE_BASE_TYPE(AWS_KNOWLEDGEBASE_ID, RequestAccess::getKnowledgeBaseId);

@SuppressWarnings("ImmutableEnumChecker")
private final AttributeKey<String> keyAttribute;

@SuppressWarnings("ImmutableEnumChecker")
private final Function<Object, String> attributeValueAccessor;

AwsBedrockResourceType(
AttributeKey<String> keyAttribute, Function<Object, String> attributeValueAccessor) {
this.keyAttribute = keyAttribute;
this.attributeValueAccessor = attributeValueAccessor;
}

public AttributeKey<String> getKeyAttribute() {
return keyAttribute;
}

public Function<Object, String> getAttributeValueAccessor() {
return attributeValueAccessor;
}

public static AwsBedrockResourceType getRequestType(String requestClass) {
return AwsBedrockResourceTypeMap.BEDROCK_REQUEST_MAP.get(requestClass);
}

public static AwsBedrockResourceType getResponseType(String responseClass) {
return AwsBedrockResourceTypeMap.BEDROCK_RESPONSE_MAP.get(responseClass);
}

private static class AwsBedrockResourceTypeMap {
private static final Map<String, AwsBedrockResourceType> BEDROCK_REQUEST_MAP = new HashMap<>();
private static final Map<String, AwsBedrockResourceType> BEDROCK_RESPONSE_MAP = new HashMap<>();

// Bedrock request/response mapping
// We only support operations that are related to the resource and where the context contains
// the AgentID/DataSourceID/KnowledgeBaseID.
// AgentID
private static final List<String> agentRequestClasses =
Arrays.asList(
"CreateAgentActionGroupRequest",
"CreateAgentAliasRequest",
"DeleteAgentActionGroupRequest",
"DeleteAgentAliasRequest",
"DeleteAgentRequest",
"DeleteAgentVersionRequest",
"GetAgentActionGroupRequest",
"GetAgentAliasRequest",
"GetAgentRequest",
"GetAgentVersionRequest",
"ListAgentActionGroupsRequest",
"ListAgentAliasesRequest",
"ListAgentKnowledgeBasesRequest",
"ListAgentVersionsRequest",
"PrepareAgentRequest",
"UpdateAgentActionGroupRequest",
"UpdateAgentAliasRequest",
"UpdateAgentRequest");
private static final List<String> agentResponseClasses =
Arrays.asList(
"DeleteAgentAliasResult",
"DeleteAgentResult",
"DeleteAgentVersionResult",
"PrepareAgentResult");
// DataSourceID
private static final List<String> dataSourceRequestClasses =
Arrays.asList("DeleteDataSourceRequest", "GetDataSourceRequest", "UpdateDataSourceRequest");
private static final List<String> dataSourceResponseClasses =
Arrays.asList("DeleteDataSourceResult");
// KnowledgeBaseID
private static final List<String> knowledgeBaseRequestClasses =
Arrays.asList(
"AssociateAgentKnowledgeBaseRequest",
"CreateDataSourceRequest",
"DeleteKnowledgeBaseRequest",
"DisassociateAgentKnowledgeBaseRequest",
"GetAgentKnowledgeBaseRequest",
"GetKnowledgeBaseRequest",
"ListDataSourcesRequest",
"UpdateAgentKnowledgeBaseRequest");
private static final List<String> knowledgeBaseResponseClasses =
Arrays.asList("DeleteKnowledgeBaseResult");

private AwsBedrockResourceTypeMap() {}

static {
// Populate the BEDROCK_REQUEST_MAP
for (String agentRequestClass : agentRequestClasses) {
BEDROCK_REQUEST_MAP.put(agentRequestClass, AwsBedrockResourceType.AGENT_TYPE);
}
for (String dataSourceRequestClass : dataSourceRequestClasses) {
BEDROCK_REQUEST_MAP.put(dataSourceRequestClass, AwsBedrockResourceType.DATA_SOURCE_TYPE);
}
for (String knowledgeBaseRequestClass : knowledgeBaseRequestClasses) {
BEDROCK_REQUEST_MAP.put(
knowledgeBaseRequestClass, AwsBedrockResourceType.KNOWLEDGE_BASE_TYPE);
}

// Populate the BEDROCK_RESPONSE_MAP
for (String agentResponseClass : agentResponseClasses) {
BEDROCK_REQUEST_MAP.put(agentResponseClass, AwsBedrockResourceType.AGENT_TYPE);
}
for (String dataSourceResponseClass : dataSourceResponseClasses) {
BEDROCK_REQUEST_MAP.put(dataSourceResponseClass, AwsBedrockResourceType.DATA_SOURCE_TYPE);
}
for (String knowledgeBaseResponseClass : knowledgeBaseResponseClasses) {
BEDROCK_REQUEST_MAP.put(
knowledgeBaseResponseClass, AwsBedrockResourceType.KNOWLEDGE_BASE_TYPE);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,16 @@ final class AwsExperimentalAttributes {
static final AttributeKey<String> AWS_STREAM_NAME = stringKey("aws.stream.name");
static final AttributeKey<String> AWS_TABLE_NAME = stringKey("aws.table.name");
static final AttributeKey<String> AWS_REQUEST_ID = stringKey("aws.requestId");
static final AttributeKey<String> AWS_AGENT_ID = stringKey("aws.bedrock.agent.id");
static final AttributeKey<String> AWS_KNOWLEDGEBASE_ID =
stringKey("aws.bedrock.knowledgebase.id");
static final AttributeKey<String> AWS_DATASOURCE_ID = stringKey("aws.bedrock.datasource.id");
static final AttributeKey<String> AWS_GUARDRAIL_ID = stringKey("aws.bedrock.guardrail.id");

// TODO: Merge in gen_ai attributes in opentelemetry-semconv-incubating once upgrade to v1.26.0
static final AttributeKey<String> AWS_BEDROCK_RUNTIME_MODEL_ID =
stringKey("gen_ai.request.model");
static final AttributeKey<String> AWS_BEDROCK_SYSTEM = stringKey("gen_ai.system");

private AwsExperimentalAttributes() {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,13 @@
package io.opentelemetry.instrumentation.awssdk.v1_11;

import static io.opentelemetry.instrumentation.awssdk.v1_11.AwsExperimentalAttributes.AWS_AGENT;
import static io.opentelemetry.instrumentation.awssdk.v1_11.AwsExperimentalAttributes.AWS_AGENT_ID;
import static io.opentelemetry.instrumentation.awssdk.v1_11.AwsExperimentalAttributes.AWS_BEDROCK_RUNTIME_MODEL_ID;
import static io.opentelemetry.instrumentation.awssdk.v1_11.AwsExperimentalAttributes.AWS_BEDROCK_SYSTEM;
import static io.opentelemetry.instrumentation.awssdk.v1_11.AwsExperimentalAttributes.AWS_BUCKET_NAME;
import static io.opentelemetry.instrumentation.awssdk.v1_11.AwsExperimentalAttributes.AWS_ENDPOINT;
import static io.opentelemetry.instrumentation.awssdk.v1_11.AwsExperimentalAttributes.AWS_GUARDRAIL_ID;
import static io.opentelemetry.instrumentation.awssdk.v1_11.AwsExperimentalAttributes.AWS_KNOWLEDGEBASE_ID;
import static io.opentelemetry.instrumentation.awssdk.v1_11.AwsExperimentalAttributes.AWS_QUEUE_NAME;
import static io.opentelemetry.instrumentation.awssdk.v1_11.AwsExperimentalAttributes.AWS_QUEUE_URL;
import static io.opentelemetry.instrumentation.awssdk.v1_11.AwsExperimentalAttributes.AWS_REQUEST_ID;
Expand All @@ -21,34 +26,36 @@
import io.opentelemetry.api.common.AttributesBuilder;
import io.opentelemetry.context.Context;
import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor;
import java.util.Objects;
import java.util.function.Function;
import javax.annotation.Nullable;

class AwsSdkExperimentalAttributesExtractor
implements AttributesExtractor<Request<?>, Response<?>> {
private static final String COMPONENT_NAME = "java-aws-sdk";
private static final String BEDROCK_SERVICE = "AmazonBedrock";
private static final String BEDROCK_AGENT_SERVICE = "AWSBedrockAgent";
private static final String BEDROCK_AGENT_RUNTIME_SERVICE = "AWSBedrockAgentRuntime";
private static final String BEDROCK_RUNTIME_SERVICE = "AmazonBedrockRuntime";

@Override
public void onStart(AttributesBuilder attributes, Context parentContext, Request<?> request) {
attributes.put(AWS_AGENT, COMPONENT_NAME);
attributes.put(AWS_ENDPOINT, request.getEndpoint().toString());

Object originalRequest = request.getOriginalRequest();
setRequestAttribute(attributes, AWS_BUCKET_NAME, originalRequest, RequestAccess::getBucketName);
setRequestAttribute(attributes, AWS_QUEUE_URL, originalRequest, RequestAccess::getQueueUrl);
setRequestAttribute(attributes, AWS_QUEUE_NAME, originalRequest, RequestAccess::getQueueName);
setRequestAttribute(attributes, AWS_STREAM_NAME, originalRequest, RequestAccess::getStreamName);
setRequestAttribute(attributes, AWS_TABLE_NAME, originalRequest, RequestAccess::getTableName);
}
String requestClassName = originalRequest.getClass().getSimpleName();
setAttribute(attributes, AWS_BUCKET_NAME, originalRequest, RequestAccess::getBucketName);
setAttribute(attributes, AWS_QUEUE_URL, originalRequest, RequestAccess::getQueueUrl);
setAttribute(attributes, AWS_QUEUE_NAME, originalRequest, RequestAccess::getQueueName);
setAttribute(attributes, AWS_STREAM_NAME, originalRequest, RequestAccess::getStreamName);
setAttribute(attributes, AWS_TABLE_NAME, originalRequest, RequestAccess::getTableName);

private static void setRequestAttribute(
AttributesBuilder attributes,
AttributeKey<String> key,
Object request,
Function<Object, String> getter) {
String value = getter.apply(request);
if (value != null) {
attributes.put(key, value);
// Get serviceName defined in the AWS Java SDK V1 Request class.
String serviceName = request.getServiceName();
// Extract request attributes only for Bedrock services.
if (isBedrockService(serviceName)) {
bedrockOnStart(attributes, originalRequest, requestClassName, serviceName);
}
}

Expand All @@ -59,12 +66,108 @@ public void onEnd(
Request<?> request,
@Nullable Response<?> response,
@Nullable Throwable error) {
if (response != null && response.getAwsResponse() instanceof AmazonWebServiceResponse) {
AmazonWebServiceResponse<?> awsResp = (AmazonWebServiceResponse<?>) response.getAwsResponse();
String requestId = awsResp.getRequestId();
if (requestId != null) {
attributes.put(AWS_REQUEST_ID, requestId);
if (response != null) {
Object awsResp = response.getAwsResponse();
if (awsResp instanceof AmazonWebServiceResponse) {
AmazonWebServiceResponse<?> awsWebServiceResponse = (AmazonWebServiceResponse<?>) awsResp;
String requestId = awsWebServiceResponse.getRequestId();
if (requestId != null) {
attributes.put(AWS_REQUEST_ID, requestId);
}
}
// Get serviceName defined in the AWS Java SDK V1 Request class.
String serviceName = request.getServiceName();
// Extract response attributes for Bedrock services
if (awsResp != null && isBedrockService(serviceName)) {
bedrockOnEnd(attributes, awsResp, serviceName);
}
}
}

private static void bedrockOnStart(
AttributesBuilder attributes,
Object originalRequest,
String requestClassName,
String serviceName) {
switch (serviceName) {
case BEDROCK_SERVICE:
setAttribute(attributes, AWS_GUARDRAIL_ID, originalRequest, RequestAccess::getGuardrailId);
break;
case BEDROCK_AGENT_SERVICE:
AwsBedrockResourceType resourceType =
AwsBedrockResourceType.getRequestType(requestClassName);
if (resourceType != null) {
setAttribute(
attributes,
resourceType.getKeyAttribute(),
originalRequest,
resourceType.getAttributeValueAccessor());
}
break;
case BEDROCK_AGENT_RUNTIME_SERVICE:
setAttribute(attributes, AWS_AGENT_ID, originalRequest, RequestAccess::getAgentId);
setAttribute(
attributes, AWS_KNOWLEDGEBASE_ID, originalRequest, RequestAccess::getKnowledgeBaseId);
break;
case BEDROCK_RUNTIME_SERVICE:
if (!Objects.equals(requestClassName, "InvokeModelRequest")) {
break;
}
attributes.put(AWS_BEDROCK_SYSTEM, "aws_bedrock");
Function<Object, String> getter = RequestAccess::getModelId;
String modelId = getter.apply(originalRequest);
attributes.put(AWS_BEDROCK_RUNTIME_MODEL_ID, modelId);
break;
default:
break;
}
}

private static void bedrockOnEnd(
AttributesBuilder attributes, Object awsResp, String serviceName) {
switch (serviceName) {
case BEDROCK_SERVICE:
setAttribute(attributes, AWS_GUARDRAIL_ID, awsResp, RequestAccess::getGuardrailId);
break;
case BEDROCK_AGENT_SERVICE:
String responseClassName = awsResp.getClass().getSimpleName();
AwsBedrockResourceType resourceType =
AwsBedrockResourceType.getResponseType(responseClassName);
if (resourceType != null) {
setAttribute(
attributes,
resourceType.getKeyAttribute(),
awsResp,
resourceType.getAttributeValueAccessor());
}
break;
case BEDROCK_AGENT_RUNTIME_SERVICE:
setAttribute(attributes, AWS_AGENT_ID, awsResp, RequestAccess::getAgentId);
setAttribute(attributes, AWS_KNOWLEDGEBASE_ID, awsResp, RequestAccess::getKnowledgeBaseId);
break;
default:
break;
}
}

private static boolean isBedrockService(String serviceName) {
// Check if the serviceName belongs to Bedrock Services defined in AWS Java SDK V1.
// For example <a
// href="https://github.com/aws/aws-sdk-java/blob/38031248a696468e19a4670c0c4585637d5e7cc6/aws-java-sdk-bedrock/src/main/java/com/amazonaws/services/bedrock/AmazonBedrock.java#L34">AmazonBedrock</a>
return serviceName.equals(BEDROCK_SERVICE)
|| serviceName.equals(BEDROCK_AGENT_SERVICE)
|| serviceName.equals(BEDROCK_AGENT_RUNTIME_SERVICE)
|| serviceName.equals(BEDROCK_RUNTIME_SERVICE);
}

private static void setAttribute(
AttributesBuilder attributes,
AttributeKey<String> key,
Object request,
Function<Object, String> getter) {
String value = getter.apply(request);
if (value != null) {
attributes.put(key, value);
}
}
}
Loading

0 comments on commit 359d60f

Please sign in to comment.