Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
be08bab
add new bedrock agentcore patching and attributes
Oct 10, 2025
98d567a
add changelog
Oct 10, 2025
52744ef
Merge branch 'main' into bedrock-agentcore-resource-support
liustve Oct 10, 2025
17716ac
lint fix
Oct 10, 2025
25f91ac
fix arn names
Oct 10, 2025
9e39da9
fix arn namse in test
Oct 13, 2025
28c1fae
add app signals attribute translation
Oct 16, 2025
dc491e9
Merge branch 'main' into bedrock-agentcore-support-v2
liustve Oct 16, 2025
82bd892
move gen ai constants to avoid botocore dependency
Oct 16, 2025
7dbabb5
lint fix
Oct 16, 2025
e0b5fd6
linting fix
Oct 16, 2025
32f27b2
add contract tests
Oct 16, 2025
2fb7895
lint fix
Oct 16, 2025
7968b5b
add Changelog.md
Oct 16, 2025
77031e6
lint fix
Oct 16, 2025
f51f997
lint fix
Oct 16, 2025
6a54c95
rename function
Oct 16, 2025
37c398e
fix cfn primary identifier parsing
Oct 17, 2025
d29964e
lint fix
Oct 17, 2025
2741494
change changelog.md
Oct 17, 2025
2d7f5ca
Merge branch 'main' into bedrock-agentcore-support-v2
liustve Oct 17, 2025
03d6654
use elifs and change var namings
Oct 17, 2025
f2ac7f7
Merge branch 'bedrock-agentcore-support-v2' of https://github.com/aws…
Oct 17, 2025
b6df9ef
Merge branch 'main' into bedrock-agentcore-support-v2
liustve Oct 21, 2025
a812b8f
add comments for agentcore handlers and fix tests
Oct 21, 2025
b8e3f89
Merge branch 'bedrock-agentcore-support-v2' of https://github.com/aws…
Oct 21, 2025
39b641d
add more test coverage
Oct 21, 2025
16d49ea
Merge branch 'main' into bedrock-agentcore-support-v2
liustve Oct 21, 2025
ea5954a
Merge branch 'main' into bedrock-agentcore-support-v2
liustve Oct 21, 2025
f69a1f6
Merge branch 'main' into bedrock-agentcore-support-v2
liustve Oct 22, 2025
e1784b6
Merge remote-tracking branch 'origin/main' into bedrock-agentcore-sup…
Oct 24, 2025
165ac99
lint fix
Oct 24, 2025
38401a4
Merge branch 'main' into bedrock-agentcore-support-v2
liustve Oct 25, 2025
6a7e849
Empty commit to test author fix
liustve Oct 27, 2025
f6be3dc
Merge branch 'bedrock-agentcore-support-v2' of https://github.com/aws…
liustve Oct 27, 2025
6b2ba93
Merge branch 'main' into bedrock-agentcore-support-v2
liustve Oct 29, 2025
ab555a4
Merge branch 'main' into bedrock-agentcore-support-v2
liustve Oct 29, 2025
6877d39
Merge branch 'main' into bedrock-agentcore-support-v2
liustve Oct 29, 2025
a8b4e28
Merge branch 'main' into bedrock-agentcore-support-v2
liustve Oct 31, 2025
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ For any change that affects end users of this package, please add an entry under
If your change does not need a CHANGELOG entry, add the "skip changelog" label to your PR.

## Unreleased
- Add Resource and CFN Attributes for Bedrock AgentCore spans
([#495](https://github.com/aws-observability/aws-otel-python-instrumentation/pull/495))
- Add botocore instrumentation extension for Bedrock AgentCore services with span attributes
([#490](https://github.com/aws-observability/aws-otel-python-instrumentation/pull/490))
- [PATCH] Only decode JSON input buffer in Anthropic Claude streaming
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,22 @@

from amazon.opentelemetry.distro._aws_attribute_keys import (
AWS_AUTH_ACCESS_KEY,
AWS_AUTH_CREDENTIAL_PROVIDER_ARN,
AWS_AUTH_REGION,
AWS_BEDROCK_AGENT_ID,
AWS_BEDROCK_AGENTCORE_BROWSER_ARN,
AWS_BEDROCK_AGENTCORE_CODE_INTERPRETER_ARN,
AWS_BEDROCK_AGENTCORE_GATEWAY_ARN,
AWS_BEDROCK_AGENTCORE_MEMORY_ARN,
AWS_BEDROCK_AGENTCORE_RUNTIME_ARN,
AWS_BEDROCK_AGENTCORE_RUNTIME_ENDPOINT_ARN,
AWS_BEDROCK_DATA_SOURCE_ID,
AWS_BEDROCK_GUARDRAIL_ARN,
AWS_BEDROCK_GUARDRAIL_ID,
AWS_BEDROCK_KNOWLEDGE_BASE_ID,
AWS_CLOUDFORMATION_PRIMARY_IDENTIFIER,
AWS_DYNAMODB_TABLE_ARN,
AWS_GATEWAY_TARGET_ID,
AWS_KINESIS_STREAM_ARN,
AWS_KINESIS_STREAM_NAME,
AWS_LAMBDA_FUNCTION_ARN,
Expand Down Expand Up @@ -63,6 +71,13 @@
SERVICE_METRIC,
MetricAttributeGenerator,
)
from amazon.opentelemetry.distro.patches.semconv._incubating.attributes.gen_ai_attributes import (
GEN_AI_BROWSER_ID,
GEN_AI_CODE_INTERPRETER_ID,
GEN_AI_GATEWAY_ID,
GEN_AI_MEMORY_ID,
GEN_AI_RUNTIME_ID,
)
from amazon.opentelemetry.distro.regional_resource_arn_parser import RegionalResourceArnParser
from amazon.opentelemetry.distro.sqs_url_parser import SqsUrlParser
from opentelemetry.sdk.resources import Resource
Expand Down Expand Up @@ -105,6 +120,7 @@
_NORMALIZED_SQS_SERVICE_NAME: str = "AWS::SQS"
_NORMALIZED_BEDROCK_SERVICE_NAME: str = "AWS::Bedrock"
_NORMALIZED_BEDROCK_RUNTIME_SERVICE_NAME: str = "AWS::BedrockRuntime"
_NORMALIZED_BEDROCK_AGENTCORE_SERVICE_NAME: str = "AWS::BedrockAgentCore"
_NORMALIZED_SECRETSMANAGER_SERVICE_NAME: str = "AWS::SecretsManager"
_NORMALIZED_SNS_SERVICE_NAME: str = "AWS::SNS"
_NORMALIZED_STEPFUNCTIONS_SERVICE_NAME: str = "AWS::StepFunctions"
Expand All @@ -118,6 +134,19 @@
# Special DEPENDENCY attribute value if GRAPHQL_OPERATION_TYPE attribute key is present.
_GRAPHQL: str = "graphql"

# AWS SDK service mapping for normalization
_AWS_SDK_SERVICE_MAPPING = {
"Bedrock Agent": _NORMALIZED_BEDROCK_SERVICE_NAME,
"Bedrock Agent Runtime": _NORMALIZED_BEDROCK_SERVICE_NAME,
"Bedrock Runtime": _NORMALIZED_BEDROCK_RUNTIME_SERVICE_NAME,
"Bedrock AgentCore Control": _NORMALIZED_BEDROCK_AGENTCORE_SERVICE_NAME,
"Bedrock AgentCore": _NORMALIZED_BEDROCK_AGENTCORE_SERVICE_NAME,
"Secrets Manager": _NORMALIZED_SECRETSMANAGER_SERVICE_NAME,
"SNS": _NORMALIZED_SNS_SERVICE_NAME,
"SFN": _NORMALIZED_STEPFUNCTIONS_SERVICE_NAME,
"Lambda": _NORMALIZED_LAMBDA_SERVICE_NAME,
}

_logger: Logger = getLogger(__name__)


Expand Down Expand Up @@ -327,16 +356,6 @@ def _normalize_remote_service_name(span: ReadableSpan, service_name: str) -> str
as the associated remote resource (Model) is not listed in Cloud Control.
"""
if is_aws_sdk_span(span):
aws_sdk_service_mapping = {
"Bedrock Agent": _NORMALIZED_BEDROCK_SERVICE_NAME,
"Bedrock Agent Runtime": _NORMALIZED_BEDROCK_SERVICE_NAME,
"Bedrock Runtime": _NORMALIZED_BEDROCK_RUNTIME_SERVICE_NAME,
"Secrets Manager": _NORMALIZED_SECRETSMANAGER_SERVICE_NAME,
"SNS": _NORMALIZED_SNS_SERVICE_NAME,
"SFN": _NORMALIZED_STEPFUNCTIONS_SERVICE_NAME,
"Lambda": _NORMALIZED_LAMBDA_SERVICE_NAME,
}

# Special handling for Lambda invoke operations
if _is_lambda_invoke_operation(span):
lambda_function_name = span.attributes.get(AWS_LAMBDA_FUNCTION_NAME)
Expand All @@ -345,7 +364,7 @@ def _normalize_remote_service_name(span: ReadableSpan, service_name: str) -> str
# is missing rather than falling back to a generic service name
return lambda_function_name if lambda_function_name else UNKNOWN_REMOTE_SERVICE

return aws_sdk_service_mapping.get(service_name, "AWS::" + service_name)
return _AWS_SDK_SERVICE_MAPPING.get(service_name, "AWS::" + service_name)
return service_name


Expand Down Expand Up @@ -466,6 +485,16 @@ def _set_remote_type_and_identifier(span: ReadableSpan, attributes: BoundedAttri
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 (
_AWS_SDK_SERVICE_MAPPING.get(str(span.attributes.get(_RPC_SERVICE)))
== _NORMALIZED_BEDROCK_AGENTCORE_SERVICE_NAME
):
agentcore_type, agentcore_identifier, agentcore_cfn_id = (
_get_bedrock_agentcore_resource_type_and_identifier(span)
)
remote_resource_type = agentcore_type
remote_resource_identifier = _escape_delimiters(agentcore_identifier) if agentcore_identifier else None
cloudformation_primary_identifier = _escape_delimiters(agentcore_cfn_id) if agentcore_cfn_id else None
elif is_key_present(span, AWS_SECRETSMANAGER_SECRET_ARN):
remote_resource_type = _NORMALIZED_SECRETSMANAGER_SERVICE_NAME + "::Secret"
remote_resource_identifier = _escape_delimiters(
Expand Down Expand Up @@ -674,6 +703,201 @@ def _set_span_kind_for_dependency(span: ReadableSpan, attributes: BoundedAttribu
attributes[AWS_SPAN_KIND] = span_kind


def _get_bedrock_agentcore_resource_type_and_identifier(
span: ReadableSpan,
) -> tuple[Optional[str], Optional[str], Optional[str]]:
"""Get BedrockAgentCore resource type, identifier, and CFN primary identifier based on span attributes."""
attrs = span.attributes
if not attrs:
return None, None, None

def format_resource_type(resource_type: Optional[str]) -> Optional[str]:
return f"{_NORMALIZED_BEDROCK_AGENTCORE_SERVICE_NAME}::{resource_type}" if resource_type else None

for handler in [
_handle_browser_attrs,
_handle_gateway_attrs,
_handle_runtime_attrs,
_handle_code_interpreter_attrs,
_handle_identity_attrs,
_handle_memory_attrs,
]:
resource_type, resource_identifier, cfn_primary_identifier = handler(attrs)
if resource_type:
return format_resource_type(resource_type), resource_identifier, cfn_primary_identifier

return None, None, None


def _handle_browser_attrs(attrs: BoundedAttributes) -> tuple[Optional[str], Optional[str], Optional[str]]:
"""
Handler for BedrockAgentCore Browser resources.
Returns (resource_type, resource_identifier, cfn_primary_identifier).
"""
browser_id = attrs.get(GEN_AI_BROWSER_ID)
browser_arn = attrs.get(AWS_BEDROCK_AGENTCORE_BROWSER_ARN)
if browser_id or browser_arn:
agentcore_cfn_identifier = None
if browser_id:
agentcore_cfn_identifier = str(browser_id)
elif browser_arn:
agentcore_cfn_identifier = extract_bedrock_agentcore_resource_id_from_arn(str(browser_arn))

# Uses browser ID as both resource identifier and CFN primary identifier.
# aws.browser.v1 is a managed AWS resource, custom IDs are user-defined resources.
resource_type = "Browser" if agentcore_cfn_identifier == "aws.browser.v1" else "BrowserCustom"
return resource_type, agentcore_cfn_identifier, agentcore_cfn_identifier
return None, None, None


def _handle_gateway_attrs(attrs: BoundedAttributes) -> tuple[Optional[str], Optional[str], Optional[str]]:
"""
Handler for BedrockAgentCore Gateway resources.
Returns (resource_type, resource_identifier, cfn_primary_identifier).
"""
gateway_id = attrs.get(GEN_AI_GATEWAY_ID)
gateway_arn = attrs.get(AWS_BEDROCK_AGENTCORE_GATEWAY_ARN)
gateway_target_id = attrs.get(AWS_GATEWAY_TARGET_ID)

if gateway_target_id:
if gateway_id:
agentcore_cfn_identifier = str(gateway_id)
elif gateway_arn:
agentcore_cfn_identifier = extract_bedrock_agentcore_resource_id_from_arn(str(gateway_arn))
else:
agentcore_cfn_identifier = str(gateway_target_id)

# GatewayTarget contains two primary identifiers: gateway ID and gateway target ID.
# Uses gateway ID and/or gateway target ID as both resource identifier and CFN primary identifier.
# If gateway ID exists or can be extracted from ARN, use it as CFN primary identifier,
# otherwise use target ID.
return "GatewayTarget", agentcore_cfn_identifier, agentcore_cfn_identifier

if gateway_arn or gateway_id:
agentcore_cfn_identifier = None
if gateway_id:
agentcore_cfn_identifier = str(gateway_id)
elif gateway_arn:
agentcore_cfn_identifier = extract_bedrock_agentcore_resource_id_from_arn(str(gateway_arn))

# Uses gateway ID as both resource identifier and CFN primary identifier.
# If gateway ID is not available, extract it from the gateway ARN.
return "Gateway", agentcore_cfn_identifier, agentcore_cfn_identifier

return None, None, None


def _handle_runtime_attrs(attrs: BoundedAttributes) -> tuple[Optional[str], Optional[str], Optional[str]]:
"""
Handler for BedrockAgentCore Runtime resources.
Returns (resource_type, resource_identifier, cfn_primary_identifier).
"""
runtime_id = attrs.get(GEN_AI_RUNTIME_ID)
runtime_arn = attrs.get(AWS_BEDROCK_AGENTCORE_RUNTIME_ARN)
runtime_endpoint_arn = attrs.get(AWS_BEDROCK_AGENTCORE_RUNTIME_ENDPOINT_ARN)

if runtime_endpoint_arn:
agentcore_cfn_identifier = extract_bedrock_agentcore_resource_id_from_arn(str(runtime_endpoint_arn))

# Uses extracted ID as resource identifier and full ARN as CFN primary identifier.
return "RuntimeEndpoint", agentcore_cfn_identifier, str(runtime_endpoint_arn)

if runtime_arn or runtime_id:
agentcore_cfn_identifier = None
if runtime_id:
agentcore_cfn_identifier = str(runtime_id)
elif runtime_arn:
agentcore_cfn_identifier = extract_bedrock_agentcore_resource_id_from_arn(str(runtime_arn))

# Uses runtime ID as both resource identifier and CFN primary identifier.
# If runtime ID is not available, extract it from the runtime ARN.
return "Runtime", agentcore_cfn_identifier, agentcore_cfn_identifier

return None, None, None


def _handle_code_interpreter_attrs(attrs: BoundedAttributes) -> tuple[Optional[str], Optional[str], Optional[str]]:
"""
Handler for BedrockAgentCore CodeInterpreter resources.
Returns (resource_type, resource_identifier, cfn_primary_identifier).
"""
code_interpreter_id = attrs.get(GEN_AI_CODE_INTERPRETER_ID)
code_interpreter_arn = attrs.get(AWS_BEDROCK_AGENTCORE_CODE_INTERPRETER_ARN)

if code_interpreter_id or code_interpreter_arn:
agentcore_cfn_identifier = None
if code_interpreter_id:
agentcore_cfn_identifier = str(code_interpreter_id)
elif code_interpreter_arn:
agentcore_cfn_identifier = extract_bedrock_agentcore_resource_id_from_arn(str(code_interpreter_arn))

# Uses code interpreter ID as both resource identifier and CFN primary identifier.
# aws.codeinterpreter.v1 is a managed AWS resource, custom IDs are user-defined resources.
resource_type = (
"CodeInterpreter" if agentcore_cfn_identifier == "aws.codeinterpreter.v1" else "CodeInterpreterCustom"
)
return resource_type, agentcore_cfn_identifier, agentcore_cfn_identifier

return None, None, None


def _handle_identity_attrs(attrs: BoundedAttributes) -> tuple[Optional[str], Optional[str], Optional[str]]:
"""
Handler for BedrockAgentCore Identity resources.
Returns (resource_type, resource_identifier, cfn_primary_identifier).
"""
credential_arn = attrs.get(AWS_AUTH_CREDENTIAL_PROVIDER_ARN)

if credential_arn:
credential_arn_str = str(credential_arn)
resource_type = None
if "apikeycredentialprovider" in credential_arn_str:
resource_type = "APIKeyCredentialProvider"
elif "oauth2credentialprovider" in credential_arn_str:
resource_type = "OAuth2CredentialProvider"
agentcore_cfn_identifier = extract_bedrock_agentcore_resource_id_from_arn(credential_arn_str)

# Uses extracted ID from credential ARN as both resource identifier and CFN primary identifier.
# Resource type is determined by credential provider type in the ARN.
return resource_type, agentcore_cfn_identifier, agentcore_cfn_identifier

return None, None, None


def _handle_memory_attrs(attrs: BoundedAttributes) -> tuple[Optional[str], Optional[str], Optional[str]]:
"""
Handler for BedrockAgentCore Memory resources.
Returns (resource_type, resource_identifier, cfn_primary_identifier).
"""
memory_id = attrs.get(GEN_AI_MEMORY_ID)
memory_arn = attrs.get(AWS_BEDROCK_AGENTCORE_MEMORY_ARN)

if memory_id or memory_arn:
agentcore_cfn_identifier = None
agentcore_cfn_primary_identifier = None
if memory_id:
agentcore_cfn_identifier = str(memory_id)
if memory_arn:
agentcore_cfn_identifier = agentcore_cfn_identifier or extract_bedrock_agentcore_resource_id_from_arn(
str(memory_arn)
)
agentcore_cfn_primary_identifier = str(memory_arn)

# Uses memory ID as resource identifier and ARN as CFN primary identifier when available.
# If memory ID is not available, extract it from the memory ARN.
return "Memory", agentcore_cfn_identifier, agentcore_cfn_primary_identifier

return None, None, None


def extract_bedrock_agentcore_resource_id_from_arn(arn: str) -> Optional[str]:
"""Extract resource ID from ARN resource part."""
resource_part = RegionalResourceArnParser.extract_resource_name_from_arn(arn)
if not resource_part:
return None
return resource_part.split("/")[-1] if "/" in resource_part else resource_part


def _log_unknown_attribute(attribute_key: str, span: ReadableSpan) -> None:
message: str = "No valid %s value found for %s span %s"
if _logger.isEnabledFor(DEBUG):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@
AWS_BEDROCK_AGENTCORE_WORKLOAD_IDENTITY_ARN,
AWS_GATEWAY_TARGET_ID,
)
from amazon.opentelemetry.distro.patches.semconv._incubating.attributes.gen_ai_attributes import (
GEN_AI_BROWSER_ID,
GEN_AI_CODE_INTERPRETER_ID,
GEN_AI_GATEWAY_ID,
GEN_AI_MEMORY_ID,
GEN_AI_RUNTIME_ID,
)
from opentelemetry.instrumentation.botocore.extensions.types import (
_AttributeMapT,
_AwsSdkExtension,
Expand All @@ -21,12 +28,6 @@
)
from opentelemetry.trace.span import Span

GEN_AI_RUNTIME_ID = "gen_ai.runtime.id"
GEN_AI_BROWSER_ID = "gen_ai.browser.id"
GEN_AI_CODE_INTERPRETER_ID = "gen_ai.code_interpreter.id"
GEN_AI_MEMORY_ID = "gen_ai.memory.id"
GEN_AI_GATEWAY_ID = "gen_ai.gateway.id"

# Mapping of flattened JSON paths to attribute keys
_ATTRIBUTE_MAPPING = {
"agentRuntimeArn": AWS_BEDROCK_AGENTCORE_RUNTIME_ARN,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0
"""
Utility module holding attribute keys for incubating Gen AI semantic conventions.
Remove this once we've contributed them to upstream.
"""

GEN_AI_RUNTIME_ID = "gen_ai.runtime.id"
GEN_AI_BROWSER_ID = "gen_ai.browser.id"
GEN_AI_CODE_INTERPRETER_ID = "gen_ai.code_interpreter.id"
GEN_AI_MEMORY_ID = "gen_ai.memory.id"
GEN_AI_GATEWAY_ID = "gen_ai.gateway.id"
Loading