Skip to content

botocore: add genai metrics to bedrock extension #3326

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

Merged
merged 17 commits into from
Mar 5, 2025
Merged
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
([#3275](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3275))
- `opentelemetry-instrumentation-botocore` Add support for GenAI tool events
([#3302](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3302))
- `opentelemetry-instrumentation-botocore` Add support for GenAI metrics
([#3326](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3326))
- `opentelemetry-instrumentation` make it simpler to initialize auto-instrumentation programmatically
([#3273](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3273))
- Add `opentelemetry-instrumentation-vertexai>=2.0b0` to `opentelemetry-bootstrap`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ classifiers = [
"Programming Language :: Python :: 3.13",
]
dependencies = [
"opentelemetry-api ~= 1.12",
"opentelemetry-api ~= 1.30",
"opentelemetry-instrumentation == 0.52b0.dev",
"opentelemetry-semantic-conventions == 0.52b0.dev",
"opentelemetry-propagator-aws-xray ~= 1.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,38 @@ def response_hook(span, service_name, operation_name, result):
)
ec2 = self.session.create_client("ec2", region_name="us-west-2")
ec2.describe_instances()

Extensions
----------

The instrumentation supports creating extensions for AWS services for enriching what is collected. We have extensions
for the following AWS services:

- Bedrock Runtime
- DynamoDB
- Lambda
- SNS
- SQS

Bedrock Runtime
***************

This extension implements the GenAI semantic conventions for the following API calls:

- Converse
- ConverseStream
- InvokeModel
- InvokeModelWithResponseStream

For the Converse and ConverseStream APIs tracing, events and metrics are implemented.

For the InvokeModel and InvokeModelWithResponseStream APIs tracing, events and metrics implemented only for a subset of
the available models, namely:
- Amazon Titan models
- Amazon Nova models
- Anthropic Claude

There is no support for tool calls with Amazon Models for the InvokeModel and InvokeModelWithResponseStream APIs.
"""

import logging
Expand Down Expand Up @@ -104,6 +136,7 @@ def response_hook(span, service_name, operation_name, result):
suppress_http_instrumentation,
unwrap,
)
from opentelemetry.metrics import Instrument, Meter, get_meter
from opentelemetry.propagators.aws.aws_xray_propagator import AwsXRayPropagator
from opentelemetry.semconv.trace import SpanAttributes
from opentelemetry.trace import get_tracer
Expand Down Expand Up @@ -134,6 +167,10 @@ def _instrument(self, **kwargs):
self._tracers = {}
# event_loggers are lazy initialized per-extension in _get_event_logger
self._event_loggers = {}
# meters are lazy initialized per-extension in _get_meter
self._meters = {}
# metrics are lazy initial per-extension in _get_metrics
self._metrics: Dict[str, Dict[str, Instrument]] = {}

self.request_hook = kwargs.get("request_hook")
self.response_hook = kwargs.get("response_hook")
Expand All @@ -144,6 +181,7 @@ def _instrument(self, **kwargs):

self.tracer_provider = kwargs.get("tracer_provider")
self.event_logger_provider = kwargs.get("event_logger_provider")
self.meter_provider = kwargs.get("meter_provider")

wrap_function_wrapper(
"botocore.client",
Expand Down Expand Up @@ -201,6 +239,38 @@ def _get_event_logger(self, extension: _AwsSdkExtension):

return self._event_loggers[instrumentation_name]

def _get_meter(self, extension: _AwsSdkExtension):
"""This is a multiplexer in order to have an event logger per extension"""

instrumentation_name = self._get_instrumentation_name(extension)
meter = self._meters.get(instrumentation_name)
if meter:
return meter

schema_version = extension.meter_schema_version()
self._meters[instrumentation_name] = get_meter(
instrumentation_name,
"",
schema_url=f"https://opentelemetry.io/schemas/{schema_version}",
meter_provider=self.meter_provider,
)

return self._meters[instrumentation_name]

def _get_metrics(
self, extension: _AwsSdkExtension, meter: Meter
) -> Dict[str, Instrument]:
"""This is a multiplexer for lazy initial metrics required by extensions"""
instrumentation_name = self._get_instrumentation_name(extension)
metrics = self._metrics.get(instrumentation_name)
if metrics is not None:
return metrics

self._metrics.setdefault(instrumentation_name, {})
metrics = self._metrics[instrumentation_name]
_safe_invoke(extension.setup_metrics, meter, metrics)
return metrics

def _uninstrument(self, **kwargs):
unwrap(BaseClient, "_make_api_call")
unwrap(Endpoint, "prepare_request")
Expand Down Expand Up @@ -244,8 +314,11 @@ def _patched_api_call(self, original_func, instance, args, kwargs):

tracer = self._get_tracer(extension)
event_logger = self._get_event_logger(extension)
meter = self._get_meter(extension)
metrics = self._get_metrics(extension, meter)
instrumentor_ctx = _BotocoreInstrumentorContext(
event_logger=event_logger
event_logger=event_logger,
metrics=metrics,
)
with tracer.start_as_current_span(
call_context.span_name,
Expand Down
Loading