Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

support bedrock and bedrock runtime resources #5

Merged
merged 13 commits into from
Jul 9, 2024
5 changes: 5 additions & 0 deletions instrumentation/aws-sdk/aws-sdk-2.2/library/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ plugins {

dependencies {
implementation("io.opentelemetry.contrib:opentelemetry-aws-xray-propagator")
implementation("com.fasterxml.jackson.core:jackson-databind") {
version {
strictly("[2.13.3,)")
zzhlogin marked this conversation as resolved.
Show resolved Hide resolved
}
}

library("software.amazon.awssdk:aws-core:2.2.0")
library("software.amazon.awssdk:sqs:2.2.0")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.instrumentation.awssdk.v2_2;

import static io.opentelemetry.api.common.AttributeKey.stringKey;

import io.opentelemetry.api.common.AttributeKey;

final class AwsExperimentalAttributes {
zzhlogin marked this conversation as resolved.
Show resolved Hide resolved
static final AttributeKey<String> AWS_BUCKET_NAME = stringKey("aws.bucket.name");
static final AttributeKey<String> AWS_QUEUE_URL = stringKey("aws.queue.url");
static final AttributeKey<String> AWS_QUEUE_NAME = stringKey("aws.queue.name");
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_BEDROCK_GUARDRAIL_ID =
zzhlogin marked this conversation as resolved.
Show resolved Hide resolved
stringKey("aws.bedrock.guardrail_id");
static final AttributeKey<String> AWS_BEDROCK_AGENT_ID = stringKey("aws.bedrock.agent_id");
static final AttributeKey<String> AWS_BEDROCK_DATASOURCE_ID =
stringKey("aws.bedrock.datasource_id");
static final AttributeKey<String> AWS_BEDROCK_KNOWLEDGEBASE_ID =
stringKey("aws.bedrock.knowledgebase_id");
static final AttributeKey<String> GEN_AI_FINISH_REASON =
stringKey("gen_ai.response.finish_reason");
static final AttributeKey<String> GEN_AI_PROMPT_TOKENS = stringKey("gen_ai.usage.prompt_tokens");
static final AttributeKey<String> GEN_AI_COMPLETION_TOKENS =
stringKey("gen_ai.usage.completion_tokens");
zzhlogin marked this conversation as resolved.
Show resolved Hide resolved

// OTel GenAI/LLM group has defined gen_ai attributes but not yet add it in
// semantic-conventions-java package
// https://github.com/open-telemetry/semantic-conventions/blob/main/docs/gen-ai/gen-ai-spans.md#genai-attributes
static final AttributeKey<String> GEN_AI_MODEL = stringKey("gen_ai.request.model");
static final AttributeKey<String> GEN_AI_TEMPERATURE = stringKey("gen_ai.request.temperature");
static final AttributeKey<String> GEN_AI_TOP_P = stringKey("gen_ai.request.top_p");
static final AttributeKey<String> GEN_AI_MAX_TOKENS = stringKey("gen_ai.request.max_tokens");
static final AttributeKey<String> GEN_AI_SYSTEM = stringKey("gen_ai.system");

private AwsExperimentalAttributes() {}

static boolean isGenAiAttribute(String attributeKey) {
return attributeKey.equals(GEN_AI_MODEL.getKey())
|| attributeKey.equals(GEN_AI_FINISH_REASON.getKey())
|| attributeKey.equals(GEN_AI_PROMPT_TOKENS.getKey())
|| attributeKey.equals(GEN_AI_COMPLETION_TOKENS.getKey())
|| attributeKey.equals(GEN_AI_TEMPERATURE.getKey())
|| attributeKey.equals(GEN_AI_TOP_P.getKey())
|| attributeKey.equals(GEN_AI_MAX_TOKENS.getKey())
|| attributeKey.equals(GEN_AI_SYSTEM.getKey());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@

package io.opentelemetry.instrumentation.awssdk.v2_2;

import static io.opentelemetry.instrumentation.awssdk.v2_2.AwsSdkRequestType.BEDROCK;
import static io.opentelemetry.instrumentation.awssdk.v2_2.AwsSdkRequestType.BEDROCKAGENTOPERATION;
import static io.opentelemetry.instrumentation.awssdk.v2_2.AwsSdkRequestType.BEDROCKDATASOURCEOPERATION;
import static io.opentelemetry.instrumentation.awssdk.v2_2.AwsSdkRequestType.BEDROCKKNOWLEDGEBASEOPERATION;
import static io.opentelemetry.instrumentation.awssdk.v2_2.AwsSdkRequestType.BEDROCKRUNTIME;
import static io.opentelemetry.instrumentation.awssdk.v2_2.AwsSdkRequestType.DYNAMODB;
import static io.opentelemetry.instrumentation.awssdk.v2_2.AwsSdkRequestType.KINESIS;
import static io.opentelemetry.instrumentation.awssdk.v2_2.AwsSdkRequestType.S3;
Expand Down Expand Up @@ -32,6 +37,43 @@ enum AwsSdkRequest {
S3Request(S3, "S3Request"),
SqsRequest(SQS, "SqsRequest"),
KinesisRequest(KINESIS, "KinesisRequest"),
BedrockRequest(BEDROCK, "BedrockRequest"),
BedrockAgentRuntimeRequest(BEDROCKAGENTOPERATION, "BedrockAgentRuntimeRequest"),
BedrockRuntimeRequest(BEDROCKRUNTIME, "BedrockRuntimeRequest"),
// BedrockAgent API based requests
BedrockCreateAgentActionGroupRequest(BEDROCKAGENTOPERATION, "CreateAgentActionGroupRequest"),
BedrockCreateAgentAliasRequest(BEDROCKAGENTOPERATION, "CreateAgentAliasRequest"),
BedrockDeleteAgentActionGroupRequest(BEDROCKAGENTOPERATION, "DeleteAgentActionGroupRequest"),
BedrockDeleteAgentAliasRequest(BEDROCKAGENTOPERATION, "DeleteAgentAliasRequest"),
BedrockDeleteAgentVersionRequest(BEDROCKAGENTOPERATION, "DeleteAgentVersionRequest"),
BedrockGetAgentActionGroupRequest(BEDROCKAGENTOPERATION, "GetAgentActionGroupRequest"),
BedrockGetAgentAliasRequest(BEDROCKAGENTOPERATION, "GetAgentAliasRequest"),
BedrockGetAgentRequest(BEDROCKAGENTOPERATION, "GetAgentRequest"),
BedrockGetAgentVersionRequest(BEDROCKAGENTOPERATION, "GetAgentVersionRequest"),
BedrockListAgentActionGroupsRequest(BEDROCKAGENTOPERATION, "ListAgentActionGroupsRequest"),
BedrockListAgentAliasesRequest(BEDROCKAGENTOPERATION, "ListAgentAliasesRequest"),
BedrockListAgentKnowledgeBasesRequest(BEDROCKAGENTOPERATION, "ListAgentKnowledgeBasesRequest"),
BedrocListAgentVersionsRequest(BEDROCKAGENTOPERATION, "ListAgentVersionsRequest"),
BedrockPrepareAgentRequest(BEDROCKAGENTOPERATION, "PrepareAgentRequest"),
BedrockUpdateAgentActionGroupRequest(BEDROCKAGENTOPERATION, "UpdateAgentActionGroupRequest"),
BedrockUpdateAgentAliasRequest(BEDROCKAGENTOPERATION, "UpdateAgentAliasRequest"),
BedrockUpdateAgentRequest(BEDROCKAGENTOPERATION, "UpdateAgentRequest"),
BedrockBedrockAgentRequest(BEDROCKAGENTOPERATION, "BedrockAgentRequest"),
BedrockDeleteDataSourceRequest(BEDROCKDATASOURCEOPERATION, "DeleteDataSourceRequest"),
BedrockGetDataSourceRequest(BEDROCKDATASOURCEOPERATION, "GetDataSourceRequest"),
BedrockUpdateDataSourceRequest(BEDROCKDATASOURCEOPERATION, "UpdateDataSourceRequest"),
BedrocAssociateAgentKnowledgeBaseRequest(
BEDROCKKNOWLEDGEBASEOPERATION, "AssociateAgentKnowledgeBaseRequest"),
BedrockCreateDataSourceRequest(BEDROCKKNOWLEDGEBASEOPERATION, "CreateDataSourceRequest"),
BedrockDeleteKnowledgeBaseRequest(BEDROCKKNOWLEDGEBASEOPERATION, "DeleteKnowledgeBaseRequest"),
BedrockDisassociateAgentKnowledgeBaseRequest(
BEDROCKKNOWLEDGEBASEOPERATION, "DisassociateAgentKnowledgeBaseRequest"),
BedrockGetAgentKnowledgeBaseRequest(
BEDROCKKNOWLEDGEBASEOPERATION, "GetAgentKnowledgeBaseRequest"),
BedrockGetKnowledgeBaseRequest(BEDROCKKNOWLEDGEBASEOPERATION, "GetKnowledgeBaseRequest"),
BedrockListDataSourcesRequest(BEDROCKKNOWLEDGEBASEOPERATION, "ListDataSourcesRequest"),
BedrockUpdateAgentKnowledgeBaseRequest(
BEDROCKKNOWLEDGEBASEOPERATION, "UpdateAgentKnowledgeBaseRequest"),
// specific requests
BatchGetItem(
DYNAMODB,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,48 @@

package io.opentelemetry.instrumentation.awssdk.v2_2;

import static io.opentelemetry.instrumentation.awssdk.v2_2.AwsExperimentalAttributes.AWS_BEDROCK_AGENT_ID;
import static io.opentelemetry.instrumentation.awssdk.v2_2.AwsExperimentalAttributes.AWS_BEDROCK_DATASOURCE_ID;
import static io.opentelemetry.instrumentation.awssdk.v2_2.AwsExperimentalAttributes.AWS_BEDROCK_GUARDRAIL_ID;
import static io.opentelemetry.instrumentation.awssdk.v2_2.AwsExperimentalAttributes.AWS_BEDROCK_KNOWLEDGEBASE_ID;
import static io.opentelemetry.instrumentation.awssdk.v2_2.AwsExperimentalAttributes.AWS_BUCKET_NAME;
import static io.opentelemetry.instrumentation.awssdk.v2_2.AwsExperimentalAttributes.AWS_QUEUE_NAME;
import static io.opentelemetry.instrumentation.awssdk.v2_2.AwsExperimentalAttributes.AWS_QUEUE_URL;
import static io.opentelemetry.instrumentation.awssdk.v2_2.AwsExperimentalAttributes.AWS_STREAM_NAME;
import static io.opentelemetry.instrumentation.awssdk.v2_2.AwsExperimentalAttributes.AWS_TABLE_NAME;
import static io.opentelemetry.instrumentation.awssdk.v2_2.AwsExperimentalAttributes.GEN_AI_COMPLETION_TOKENS;
import static io.opentelemetry.instrumentation.awssdk.v2_2.AwsExperimentalAttributes.GEN_AI_FINISH_REASON;
import static io.opentelemetry.instrumentation.awssdk.v2_2.AwsExperimentalAttributes.GEN_AI_MAX_TOKENS;
import static io.opentelemetry.instrumentation.awssdk.v2_2.AwsExperimentalAttributes.GEN_AI_MODEL;
import static io.opentelemetry.instrumentation.awssdk.v2_2.AwsExperimentalAttributes.GEN_AI_PROMPT_TOKENS;
import static io.opentelemetry.instrumentation.awssdk.v2_2.AwsExperimentalAttributes.GEN_AI_TEMPERATURE;
import static io.opentelemetry.instrumentation.awssdk.v2_2.AwsExperimentalAttributes.GEN_AI_TOP_P;
import static io.opentelemetry.instrumentation.awssdk.v2_2.FieldMapping.request;
import static io.opentelemetry.instrumentation.awssdk.v2_2.FieldMapping.response;

import java.util.Collections;
import java.util.List;
import java.util.Map;

enum AwsSdkRequestType {
S3(request("aws.bucket.name", "Bucket")),
SQS(request("aws.queue.url", "QueueUrl"), request("aws.queue.name", "QueueName")),
KINESIS(request("aws.stream.name", "StreamName")),
DYNAMODB(request("aws.table.name", "TableName"));
S3(request(AWS_BUCKET_NAME.getKey(), "Bucket")),
SQS(request(AWS_QUEUE_URL.getKey(), "QueueUrl"), request(AWS_QUEUE_NAME.getKey(), "QueueName")),
KINESIS(request(AWS_STREAM_NAME.getKey(), "StreamName")),
DYNAMODB(request(AWS_TABLE_NAME.getKey(), "TableName")),
BEDROCK(request(AWS_BEDROCK_GUARDRAIL_ID.getKey(), "guardrailIdentifier")),
BEDROCKAGENTOPERATION(
request(AWS_BEDROCK_AGENT_ID.getKey(), "agentId"),
response(AWS_BEDROCK_AGENT_ID.getKey(), "agentId")),
BEDROCKDATASOURCEOPERATION(request(AWS_BEDROCK_DATASOURCE_ID.getKey(), "dataSourceId")),
BEDROCKKNOWLEDGEBASEOPERATION(request(AWS_BEDROCK_KNOWLEDGEBASE_ID.getKey(), "knowledgeBaseId")),
BEDROCKRUNTIME(
request(GEN_AI_MODEL.getKey(), "modelId"),
request(GEN_AI_TEMPERATURE.getKey(), "body"),
request(GEN_AI_TOP_P.getKey(), "body"),
request(GEN_AI_MAX_TOKENS.getKey(), "body"),
response(GEN_AI_FINISH_REASON.getKey(), "body"),
response(GEN_AI_PROMPT_TOKENS.getKey(), "body"),
response(GEN_AI_COMPLETION_TOKENS.getKey(), "body"));

// Wrapping in unmodifiableMap
@SuppressWarnings("ImmutableEnumChecker")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,15 @@ private void mapToAttributes(
for (int i = 1; i < path.size() && target != null; i++) {
target = next(target, path.get(i));
}
String value;
if (target != null) {
String value = serializer.serialize(target);
if (AwsExperimentalAttributes.isGenAiAttribute(fieldMapping.getAttribute())) {
value = serializer.serialize(fieldMapping.getAttribute(), target);
span.setAttribute("gen_ai.system", "AWS Bedrock");
zzhlogin marked this conversation as resolved.
Show resolved Hide resolved
} else {
value = serializer.serialize(target);
}

zzhlogin marked this conversation as resolved.
Show resolved Hide resolved
if (!StringUtils.isEmpty(value)) {
span.setAttribute(fieldMapping.getAttribute(), value);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,17 @@

package io.opentelemetry.instrumentation.awssdk.v2_2;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import software.amazon.awssdk.core.SdkBytes;
import software.amazon.awssdk.core.SdkPojo;
import software.amazon.awssdk.http.ContentStreamProvider;
import software.amazon.awssdk.http.SdkHttpFullRequest;
Expand All @@ -20,6 +24,7 @@
import software.amazon.awssdk.utils.StringUtils;

class Serializer {
private static final ObjectMapper objectMapper = new ObjectMapper();

@Nullable
String serialize(Object target) {
Expand All @@ -37,6 +42,9 @@ String serialize(Object target) {
if (target instanceof Map) {
return serialize(((Map<?, ?>) target).keySet());
}
if (target instanceof SdkBytes) {
return serialize((SdkBytes) target);
zzhlogin marked this conversation as resolved.
Show resolved Hide resolved
}
// simple type
return target.toString();
}
Expand Down Expand Up @@ -65,4 +73,120 @@ private String serialize(Collection<?> collection) {
String serialized = collection.stream().map(this::serialize).collect(Collectors.joining(","));
return (StringUtils.isEmpty(serialized) ? null : "[" + serialized + "]");
}

@Nullable
String serialize(String attributeName, Object target) {
try {
JsonNode jsonBody;
if (target instanceof SdkBytes) {
jsonBody = objectMapper.readTree(((SdkBytes) target).asUtf8String());
} else {
if (target != null) {
return target.toString();
zzhlogin marked this conversation as resolved.
Show resolved Hide resolved
}
return null;
}
switch (attributeName) {
case "gen_ai.response.finish_reason":
return getFinishReason(jsonBody);
zzhlogin marked this conversation as resolved.
Show resolved Hide resolved
case "gen_ai.usage.completion_tokens":
return getOutputTokens(jsonBody);
case "gen_ai.usage.prompt_tokens":
return getInputTokens(jsonBody);
case "gen_ai.request.top_p":
return getTopP(jsonBody);
case "gen_ai.request.temperature":
return getTemperature(jsonBody);
case "gen_ai.request.max_tokens":
return getMaxTokens(jsonBody);
default:
return null;
zzhlogin marked this conversation as resolved.
Show resolved Hide resolved
}
} catch (RuntimeException | JsonProcessingException e) {
throw new IllegalStateException("Failed to instantiate operation class", e);
zzhlogin marked this conversation as resolved.
Show resolved Hide resolved
}
}

private static String getFinishReason(JsonNode body) {
if (body.has("stop_reason")) {
return body.get("stop_reason").asText();
} else if (body.has("results")) {
JsonNode result = body.get("results").get(0);
if (result.has("completionReason")) {
return result.get("completionReason").asText();
}
}
return null;
zzhlogin marked this conversation as resolved.
Show resolved Hide resolved
}

private static String getInputTokens(JsonNode body) {
if (body.has("prompt_token_count")) {
zzhlogin marked this conversation as resolved.
Show resolved Hide resolved
return String.valueOf(body.get("prompt_token_count").asInt());
} else if (body.has("inputTextTokenCount")) {
return String.valueOf(body.get("inputTextTokenCount").asInt());
} else if (body.has("usage")) {
JsonNode usage = body.get("usage");
if (usage.has("input_tokens")) {
return String.valueOf(usage.get("input_tokens").asInt());
}
}
return null;
}

private static String getOutputTokens(JsonNode body) {
if (body.has("generation_token_count")) {
return String.valueOf(body.get("generation_token_count").asInt());
} else if (body.has("results")) {
JsonNode result = body.get("results").get(0);
if (result.has("tokenCount")) {
return String.valueOf(result.get("tokenCount").asInt());
}
} else if (body.has("inputTextTokenCount")) {
return String.valueOf(body.get("inputTextTokenCount").asInt());
zzhlogin marked this conversation as resolved.
Show resolved Hide resolved
} else if (body.has("usage")) {
JsonNode usage = body.get("usage");
if (usage.has("output_tokens")) {
return String.valueOf(usage.get("output_tokens").asInt());
}
}
return null;
}

private static String getTopP(JsonNode body) {
if (body.has("top_p")) {
return String.valueOf(body.get("top_p").asDouble());
} else if (body.has("textGenerationConfig")) {
JsonNode usage = body.get("textGenerationConfig");
if (usage.has("topP")) {
return String.valueOf(usage.get("topP").asInt());
}
}
return null;
}

private static String getTemperature(JsonNode body) {
if (body.has("temperature")) {
return String.valueOf(body.get("temperature").asDouble());
} else if (body.has("textGenerationConfig")) {
JsonNode usage = body.get("textGenerationConfig");
if (usage.has("temperature")) {
return String.valueOf(usage.get("temperature").asDouble());
}
}
return null;
}

private static String getMaxTokens(JsonNode body) {
if (body.has("max_tokens")) {
return String.valueOf(body.get("max_tokens").asInt());
} else if (body.has("max_gen_len")) {
return String.valueOf(body.get("max_gen_len").asInt());
} else if (body.has("textGenerationConfig")) {
JsonNode usage = body.get("textGenerationConfig");
if (usage.has("maxTokenCount")) {
return String.valueOf(usage.get("maxTokenCount").asInt());
}
}
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,25 @@ abstract class AbstractAws2ClientTest extends AbstractAws2ClientCoreTest {
"$SemanticAttributes.MESSAGING_SYSTEM" "AmazonSQS"
} else if (service == "Kinesis") {
"aws.stream.name" "somestream"
} else if (service == "Kinesis") {
"aws.stream.name" "somestream"
} else if (service == "Bedrock" && operation == "GetGuardrail") {
"aws.bedrock.guardrail_id" "guardrailId"
} else if (service == "BedrockAgent" && operation == "GetAgent") {
"aws.bedrock.agent_id" "agentId"
} else if (service == "BedrockAgent" && operation == "GetKnowledgeBase") {
"aws.bedrock.knowledgebase_id" "knowledgeBaseId"
} else if (service == "BedrockAgent" && operation == "GetDataSource") {
"aws.bedrock.datasource_id" "datasourceId"
} else if (service == "BedrockRuntime" && operation == "InvokeModel") {
"gen_ai.request.top_p" "0.9"
"gen_ai.request.temperature" "0.7"
"gen_ai.request.model" "meta.llama2-13b-chat-v1"
"gen_ai.request.max_tokens" "100"
"gen_ai.system" "AWS Bedrock"
"gen_ai.response.finish_reasons" "length"
}

}
}
}
Expand Down
Loading