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

Add Bedrock, Bedrock Agent, Bedrock Agent Runtime Support. #209

Merged
merged 31 commits into from
Jul 9, 2024
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
e194302
Initial commit.
zzhlogin May 29, 2024
e5cbaa9
Add comment.
zzhlogin May 29, 2024
9fccf79
More write.
zzhlogin May 31, 2024
702e980
Add log.
zzhlogin May 31, 2024
0c60bd6
Add response support.
zzhlogin Jun 4, 2024
f023749
Add bedrock runtime support.
zzhlogin Jun 11, 2024
e6c49ea
Enrich unit tests.
zzhlogin Jun 11, 2024
7919ab2
Merge branch 'main' into enhance_bedrock_runtime
zzhlogin Jun 12, 2024
26b29e5
Initial commit to add bedrock support.
zzhlogin Jun 12, 2024
7abb862
Code Cleanup.
zzhlogin Jun 12, 2024
bbc9c00
Add more support.
zzhlogin Jun 12, 2024
253eef7
Enrich unit tests.
zzhlogin Jun 12, 2024
d1821d0
Fix unit tests.
zzhlogin Jun 12, 2024
d583b59
Add support for bedrock remaining attributes.
zzhlogin Jun 13, 2024
5a2cda9
Code clean up.
zzhlogin Jun 13, 2024
a0fc9c9
Address lint.
zzhlogin Jun 13, 2024
16881c5
Use AWS::Bedrock.
zzhlogin Jun 26, 2024
653ee38
Use AWS::Bedrock.
zzhlogin Jun 26, 2024
b180ab2
Merge branch 'main' into enhance_bedrock_runtime
zzhlogin Jun 26, 2024
0c59b6e
Use AWS::BedrockRuntime as remote service name.
zzhlogin Jun 28, 2024
09b8a2d
Merge from main.
zzhlogin Jun 28, 2024
d78bda6
merge in bedrockruntime PR, and contract tests.
zzhlogin Jul 1, 2024
4bc536c
Merge branch 'main' into enhance_bedrock
zzhlogin Jul 4, 2024
ff74faa
Remove BedrockRuntime and contract tests.
zzhlogin Jul 4, 2024
7c6625a
Address comments.
zzhlogin Jul 5, 2024
bbcdb2c
Merge branch 'main' into enhance_bedrock
zzhlogin Jul 5, 2024
92de1b0
Add comments.
zzhlogin Jul 8, 2024
be09802
Merge from main.
zzhlogin Jul 8, 2024
1e318bb
Apply lint.
zzhlogin Jul 8, 2024
4df6563
Fix merge from main.
zzhlogin Jul 8, 2024
724c30a
Remove _do_extract_bedrock_runtime_attributes.
zzhlogin Jul 9, 2024
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 @@ -17,3 +17,8 @@
AWS_QUEUE_URL: str = "aws.sqs.queue_url"
AWS_QUEUE_NAME: str = "aws.sqs.queue_name"
AWS_STREAM_NAME: str = "aws.kinesis.stream_name"
AWS_BEDROCK_DATASOURCE_ID: str = "aws.bedrock.data_source.id"
AWS_BEDROCK_KNOWLEDGEBASE_ID: str = "aws.bedrock.knowledge_base.id"
thpierce marked this conversation as resolved.
Show resolved Hide resolved
AWS_BEDROCK_AGENT_ID: str = "aws.bedrock.agent.id"
AWS_BEDROCK_GUARDRAIL_ID: str = "aws.bedrock.guardrail.id"
GEN_AI_REQUEST_MODEL: str = "gen_ai.request.model"
thpierce marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
from urllib.parse import ParseResult, urlparse

from amazon.opentelemetry.distro._aws_attribute_keys import (
AWS_BEDROCK_AGENT_ID,
AWS_BEDROCK_DATASOURCE_ID,
AWS_BEDROCK_GUARDRAIL_ID,
AWS_BEDROCK_KNOWLEDGEBASE_ID,
AWS_LOCAL_OPERATION,
AWS_LOCAL_SERVICE,
AWS_QUEUE_NAME,
Expand All @@ -17,6 +21,7 @@
AWS_REMOTE_SERVICE,
AWS_SPAN_KIND,
AWS_STREAM_NAME,
GEN_AI_REQUEST_MODEL,
)
from amazon.opentelemetry.distro._aws_span_processing_util import (
LOCAL_ROOT,
Expand Down Expand Up @@ -80,6 +85,8 @@
_NORMALIZED_KINESIS_SERVICE_NAME: str = "AWS::Kinesis"
_NORMALIZED_S3_SERVICE_NAME: str = "AWS::S3"
_NORMALIZED_SQS_SERVICE_NAME: str = "AWS::SQS"
_NORMALIZED_BEDROCK_SERVICE_NAME: str = "AWS::Bedrock"
_NORMALIZED_BEDROCK_RUNTIME_SERVICE_NAME: str = "AWS::BedrockRuntime"
_DB_CONNECTION_STRING_TYPE: str = "DB::Connection"

# Special DEPENDENCY attribute value if GRAPHQL_OPERATION_TYPE attribute key is present.
Expand Down Expand Up @@ -291,9 +298,18 @@ def _normalize_remote_service_name(span: ReadableSpan, service_name: str) -> str
If the span is an AWS SDK span, normalize the name to align with <a
href="https://docs.aws.amazon.com/cloudcontrolapi/latest/userguide/supported-resources.html">AWS Cloud Control
resource format</a> as much as possible. Long term, we would like to normalize service name in the upstream.

For Bedrock, Bedrock Agent, Bedrock Agent Runtime, we follow the cloudformation template,
use RemoteService as AWS::Bedrock. For BedrockRuntime, there is no corresponding reference in Cloudformation found,
so we use AWS::BedrockRuntime.
thpierce marked this conversation as resolved.
Show resolved Hide resolved
"""
if is_aws_sdk_span(span):
return "AWS::" + service_name
aws_sdk_service_mapping = {
"Bedrock Agent": _NORMALIZED_BEDROCK_SERVICE_NAME,
"Bedrock Agent Runtime": _NORMALIZED_BEDROCK_SERVICE_NAME,
"Bedrock Runtime": _NORMALIZED_BEDROCK_RUNTIME_SERVICE_NAME,
thpierce marked this conversation as resolved.
Show resolved Hide resolved
}
return aws_sdk_service_mapping.get(service_name, "AWS::" + service_name)
return service_name


Expand Down Expand Up @@ -342,6 +358,7 @@ def _generate_remote_operation(span: ReadableSpan) -> str:
return remote_operation


# pylint: disable=too-many-branches
def _set_remote_type_and_identifier(span: ReadableSpan, attributes: BoundedAttributes) -> None:
"""
Remote resource attributes {@link AwsAttributeKeys#AWS_REMOTE_RESOURCE_TYPE} and {@link
Expand Down Expand Up @@ -375,6 +392,21 @@ def _set_remote_type_and_identifier(span: ReadableSpan, attributes: BoundedAttri
remote_resource_identifier = _escape_delimiters(
SqsUrlParser.get_queue_name(span.attributes.get(AWS_QUEUE_URL))
)
elif is_key_present(span, AWS_BEDROCK_AGENT_ID):
remote_resource_type = _NORMALIZED_BEDROCK_SERVICE_NAME + "::Agent"
remote_resource_identifier = _escape_delimiters(span.attributes.get(AWS_BEDROCK_AGENT_ID))
elif is_key_present(span, AWS_BEDROCK_DATASOURCE_ID):
remote_resource_type = _NORMALIZED_BEDROCK_SERVICE_NAME + "::DataSource"
remote_resource_identifier = _escape_delimiters(span.attributes.get(AWS_BEDROCK_DATASOURCE_ID))
elif is_key_present(span, AWS_BEDROCK_GUARDRAIL_ID):
remote_resource_type = _NORMALIZED_BEDROCK_SERVICE_NAME + "::Guardrail"
remote_resource_identifier = _escape_delimiters(span.attributes.get(AWS_BEDROCK_GUARDRAIL_ID))
elif is_key_present(span, AWS_BEDROCK_KNOWLEDGEBASE_ID):
remote_resource_type = _NORMALIZED_BEDROCK_SERVICE_NAME + "::KnowledgeBase"
remote_resource_identifier = _escape_delimiters(span.attributes.get(AWS_BEDROCK_KNOWLEDGEBASE_ID))
elif is_key_present(span, GEN_AI_REQUEST_MODEL):
remote_resource_type = _NORMALIZED_BEDROCK_SERVICE_NAME + "::Model"
remote_resource_identifier = _escape_delimiters(span.attributes.get(GEN_AI_REQUEST_MODEL))
elif is_db_span(span):
remote_resource_type = _DB_CONNECTION_STRING_TYPE
remote_resource_identifier = _get_db_connection(span)
Expand Down
thpierce marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0
# Modifications Copyright The OpenTelemetry Authors. Licensed under the Apache License 2.0 License.
thpierce marked this conversation as resolved.
Show resolved Hide resolved
import abc
import inspect
from typing import Dict, Optional

from opentelemetry.instrumentation.botocore.extensions.types import (
_AttributeMapT,
_AwsSdkCallContext,
_AwsSdkExtension,
_BotoResultT,
)
from opentelemetry.trace.span import Span


class _BedrockAgentOperation(abc.ABC):
thpierce marked this conversation as resolved.
Show resolved Hide resolved
request_attributes: Optional[Dict[str, str]] = None
response_attributes: Optional[Dict[str, str]] = None

@classmethod
@abc.abstractmethod
def operation_names(cls):
pass


class _AgentOperation(_BedrockAgentOperation):
"""
This class primarily supports BedrockAgent Agent related operations.
thpierce marked this conversation as resolved.
Show resolved Hide resolved
"""

request_attributes = {
"aws.bedrock.agent.id": "agentId",
thpierce marked this conversation as resolved.
Show resolved Hide resolved
}
response_attributes = {
"aws.bedrock.agent.id": "agentId",
}

@classmethod
def operation_names(cls):
return [
"CreateAgentActionGroup",
"CreateAgentAlias",
"DeleteAgentActionGroup",
"DeleteAgentAlias",
"DeleteAgent",
"DeleteAgentVersion",
"GetAgentActionGroup",
"GetAgentAlias",
"GetAgent",
"GetAgentVersion",
"ListAgentActionGroups",
"ListAgentAliases",
"ListAgentKnowledgeBases",
"ListAgentVersions",
"PrepareAgent",
"UpdateAgentActionGroup",
"UpdateAgentAlias",
"UpdateAgent",
]


class _KnowledgeBaseOperation(_BedrockAgentOperation):
"""
This class primarily supports BedrockAgent KnowledgeBase related operations.
thpierce marked this conversation as resolved.
Show resolved Hide resolved

Note: The 'CreateDataSource' operation does not have a 'dataSourceId' in the context,
but it always comes with a 'knowledgeBaseId'. Therefore, we categorize it under 'knowledgeBaseId' operations.
"""

request_attributes = {
"aws.bedrock.knowledge_base.id": "knowledgeBaseId",
}
response_attributes = {
"aws.bedrock.knowledge_base.id": "knowledgeBaseId",
}

@classmethod
def operation_names(cls):
return [
"AssociateAgentKnowledgeBase",
thpierce marked this conversation as resolved.
Show resolved Hide resolved
"CreateDataSource",
"DeleteKnowledgeBase",
"DisassociateAgentKnowledgeBase",
"GetAgentKnowledgeBase",
"GetKnowledgeBase",
"ListDataSources",
"UpdateAgentKnowledgeBase",
]


class _DataSourceOperation(_BedrockAgentOperation):
"""
This class primarily supports BedrockAgent DataSource related operations.
thpierce marked this conversation as resolved.
Show resolved Hide resolved
"""

request_attributes = {
"aws.bedrock.data_source.id": "dataSourceId",
}
response_attributes = {
"aws.bedrock.data_source.id": "dataSourceId",
}

@classmethod
def operation_names(cls):
return ["DeleteDataSource", "GetDataSource", "UpdateDataSource"]


# _OPERATION_NAME_TO_ClASS_MAPPING maps operation names to their corresponding classes
# by iterating over all subclasses of _BedrockAgentOperation and extract operation
# by call operation_names() function.
_OPERATION_NAME_TO_ClASS_MAPPING = {
thpierce marked this conversation as resolved.
Show resolved Hide resolved
op_name: op_class
for op_class in [_KnowledgeBaseOperation, _DataSourceOperation, _AgentOperation]
for op_name in op_class.operation_names()
if inspect.isclass(op_class) and issubclass(op_class, _BedrockAgentOperation) and not inspect.isabstract(op_class)
}


class _BedrockAgentExtension(_AwsSdkExtension):
thpierce marked this conversation as resolved.
Show resolved Hide resolved
"""
This class is an extension for <a
href="https://docs.aws.amazon.com/bedrock/latest/APIReference/API_Operations_Agents_for_Amazon_Bedrock.html">
Agents for Amazon Bedrock</a>.

This class primarily identify three types of resource based operations: _AgentOperation, _KnowledgeBaseOperation,
and _DataSourceOperation. We only support operations that are related to the resource
and where the context contains the resource ID.
"""

def __init__(self, call_context: _AwsSdkCallContext):
super().__init__(call_context)
self._operation_class = _OPERATION_NAME_TO_ClASS_MAPPING.get(call_context.operation)

def extract_attributes(self, attributes: _AttributeMapT):
if self._operation_class is None:
return
for attribute_key, request_param_key in self._operation_class.request_attributes.items():
request_param_value = self._call_context.params.get(request_param_key)
if request_param_value:
attributes[attribute_key] = request_param_value

def on_success(self, span: Span, result: _BotoResultT):
if self._operation_class is None:
return

for attribute_key, response_param_key in self._operation_class.response_attributes.items():
response_param_value = result.get(response_param_key)
if response_param_value:
span.set_attribute(
attribute_key,
response_param_value,
)


class _BedrockAgentRuntimeExtension(_AwsSdkExtension):
thpierce marked this conversation as resolved.
Show resolved Hide resolved
"""
This class is an extension for <a
href="https://docs.aws.amazon.com/bedrock/latest/APIReference/API_Operations_Agents_for_Amazon_Bedrock_Runtime.html">
Agents for Amazon Bedrock Runtime</a>.
"""

def extract_attributes(self, attributes: _AttributeMapT):
agent_id = self._call_context.params.get("agentId")
if agent_id:
attributes["aws.bedrock.agent.id"] = agent_id

knowledge_base_id = self._call_context.params.get("knowledgeBaseId")
if knowledge_base_id:
attributes["aws.bedrock.knowledge_base.id"] = knowledge_base_id


class _BedrockExtension(_AwsSdkExtension):
thpierce marked this conversation as resolved.
Show resolved Hide resolved
"""
This class is an extension for <a
href="https://docs.aws.amazon.com/bedrock/latest/APIReference/API_Operations_Amazon_Bedrock.html">Bedrock</a>.
"""

# pylint: disable=no-self-use
def on_success(self, span: Span, result: _BotoResultT):
thpierce marked this conversation as resolved.
Show resolved Hide resolved
# guardrailId can only be retrieved from the response, not from the request
guardrail_id = result.get("guardrailId")
if guardrail_id:
span.set_attribute(
"aws.bedrock.guardrail.id",
guardrail_id,
)


class _BedrockRuntimeExtension(_AwsSdkExtension):
thpierce marked this conversation as resolved.
Show resolved Hide resolved
"""
This class is an extension for <a
href="https://docs.aws.amazon.com/bedrock/latest/APIReference/API_Operations_Amazon_Bedrock_Runtime.html">
Amazon Bedrock Runtime</a>.
"""

def extract_attributes(self, attributes: _AttributeMapT):
attributes["gen_ai.system"] = "aws_bedrock"

model_id = self._call_context.params.get("modelId")
if model_id:
attributes["gen_ai.request.model"] = model_id
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@
# Modifications Copyright The OpenTelemetry Authors. Licensed under the Apache License 2.0 License.
import importlib

from amazon.opentelemetry.distro.patches._bedrock_patches import ( # noqa # pylint: disable=unused-import
_BedrockAgentExtension,
_BedrockAgentRuntimeExtension,
_BedrockExtension,
_BedrockRuntimeExtension,
)
from opentelemetry.instrumentation.botocore.extensions import _KNOWN_EXTENSIONS
from opentelemetry.instrumentation.botocore.extensions.sqs import _SqsExtension
from opentelemetry.instrumentation.botocore.extensions.types import _AttributeMapT, _AwsSdkExtension
Expand All @@ -17,6 +23,7 @@ def _apply_botocore_instrumentation_patches() -> None:
_apply_botocore_kinesis_patch()
_apply_botocore_s3_patch()
_apply_botocore_sqs_patch()
_apply_botocore_bedrock_patch()


def _apply_botocore_kinesis_patch() -> None:
Expand Down Expand Up @@ -65,6 +72,19 @@ def patch_extract_attributes(self, attributes: _AttributeMapT):
_SqsExtension.extract_attributes = patch_extract_attributes


def _apply_botocore_bedrock_patch() -> None:
"""Botocore instrumentation patch for Bedrock, Bedrock Agent, Bedrock Runtime and Bedrock Agent Runtime

This patch adds an extension to the upstream's list of known extension for Bedrock.
Extensions allow for custom logic for adding service-specific information to spans, such as attributes.
Specifically, we are adding logic to add the AWS_BEDROCK attributes referenced in _aws_attribute_keys.
thpierce marked this conversation as resolved.
Show resolved Hide resolved
"""
_KNOWN_EXTENSIONS["bedrock"] = _lazy_load(".", "_BedrockExtension")
_KNOWN_EXTENSIONS["bedrock-agent"] = _lazy_load(".", "_BedrockAgentExtension")
_KNOWN_EXTENSIONS["bedrock-agent-runtime"] = _lazy_load(".", "_BedrockAgentRuntimeExtension")
_KNOWN_EXTENSIONS["bedrock-runtime"] = _lazy_load(".", "_BedrockRuntimeExtension")


# The OpenTelemetry Authors code
def _lazy_load(module, cls):
"""Clone of upstream opentelemetry.instrumentation.botocore.extensions.lazy_load
Expand Down
Loading
Loading