> ## Documentation Index
> Fetch the complete documentation index at: https://docs.bronto.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Amazon Bedrock LLM observability with OpenTelemetry

> Capture token usage, model metadata, and prompt/response content from Amazon Bedrock calls using OpenTelemetry GenAI auto-instrumentation, and analyze them in Bronto.

This page covers instrumenting an application that calls the Amazon Bedrock `Converse` / `InvokeModel` APIs so that GenAI telemetry — token usage, model, latency, finish reason, and prompt/response content — flows to Bronto following the [OpenTelemetry GenAI semantic conventions](https://github.com/open-telemetry/semantic-conventions-genai/blob/main/docs/gen-ai/aws-bedrock.md).

<Note>
  The conventions and instrumentation are in active development. This page was verified on **July 17, 2026**.
</Note>

For the broader ingestion picture (the Collector and where Bedrock fits among AWS services), see [Amazon Bedrock Logs, Metrics and Traces](/integrations/aws-bedrock). For the attributes Bronto surfaces and how to search them, see [LLM Observability](/ai-features/llm-observability).

## Prerequisites

* Python 3.9 or later, and `pip`
* An application that calls Amazon Bedrock via `boto3` (the `bedrock-runtime` client)
* A running OTel Collector configured to forward to Bronto — see [Connect OpenTelemetry to Bronto](/agent-setup/open-telemetry) or, on AWS, [ADOT](/integrations/aws-adot)

## Install dependencies

```bash theme={"dark"}
pip install \
  opentelemetry-sdk \
  opentelemetry-exporter-otlp-proto-http \
  opentelemetry-instrumentation-botocore \
  boto3
```

<Warning>
  **Version matters for Bedrock.** The Bedrock GenAI extension lives in `opentelemetry-instrumentation-botocore`, and it was added relatively recently. Older releases (for example `0.50b0`) ship **no Bedrock extension at all** — Bedrock calls are then traced only as generic AWS API spans (`rpc.system`, `rpc.method=Converse`, `aws.request_id`) with **no token usage, model, or prompt/response**. If your traces are missing GenAI data, this is almost always the cause.

  Use `opentelemetry-instrumentation-botocore>=0.55b0`; this guide was validated with `0.64b0` and `opentelemetry-sdk==1.43.0`.
</Warning>

## Enable Bedrock GenAI instrumentation

The botocore instrumentation patches the `bedrock-runtime` client automatically. To include message content in its trace-correlated log events, set this before instrumentation starts:

```bash theme={"dark"}
# Capture prompt and response content (off by default)
export OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT=true
```

Instrument either with the zero-code wrapper:

```bash theme={"dark"}
opentelemetry-instrument python app.py
```

…or programmatically at startup (set the environment variables **before** this runs):

```python instrument.py theme={"dark"}
from opentelemetry.instrumentation.botocore import BotocoreInstrumentor

BotocoreInstrumentor().instrument()
```

With this in place, recognized Bedrock models produce a client span named like `chat <model-id>` carrying GenAI attributes. Unrecognized or newly introduced inference-profile IDs can retain the generic `Bedrock Runtime.Converse` name; inspect the attributes rather than relying only on the span name.

## What you get on the span

Botocore `0.64b0` emits the following span attributes when the corresponding value is available:

| Attribute                        | Example                                   | Notes                                                                                        |
| -------------------------------- | ----------------------------------------- | -------------------------------------------------------------------------------------------- |
| `gen_ai.system`                  | `aws.bedrock`                             | Provider identifier emitted by this botocore release                                         |
| `gen_ai.operation.name`          | `chat`                                    | `chat` for most calls; `text_completion` for Amazon Titan text completion via `InvokeModel`  |
| `gen_ai.request.model`           | `eu.amazon.nova-micro-v1:0`               | Requested model or inference-profile ID                                                      |
| `gen_ai.request.max_tokens`      | `512`                                     | Maximum output tokens requested                                                              |
| `gen_ai.request.temperature`     | `0.7`                                     | Sampling temperature                                                                         |
| `gen_ai.request.top_p`           | `0.9`                                     | Top-p sampling value                                                                         |
| `gen_ai.request.stop_sequences`  | `["END"]`                                 | Array of requested stop sequences                                                            |
| `gen_ai.usage.input_tokens`      | `9`                                       | Input tokens reported by Bedrock; estimated for some model-specific `InvokeModel` responses  |
| `gen_ai.usage.output_tokens`     | `36`                                      | Output tokens reported by Bedrock; estimated for some model-specific `InvokeModel` responses |
| `gen_ai.response.finish_reasons` | `["end_turn"]`                            | Array containing the response stop reason                                                    |
| `server.address`                 | `bedrock-runtime.eu-west-1.amazonaws.com` | Bedrock Runtime endpoint                                                                     |
| `server.port`                    | `443`                                     | Endpoint port                                                                                |
| `error.type`                     | `ValidationException`                     | Present when the operation ends with an error                                                |

The span is a `CLIENT` span and is normally named `chat <model-id>` or `text_completion <model-id>`. Request parameters, usage, and finish reason are conditional: botocore omits a field when the request or selected model does not expose it.

<Note>
  The general-purpose `BotocoreInstrumentor` also adds AWS SDK attributes such as `rpc.system = aws-api`, `rpc.method`, `rpc.service`, and `aws.request_id`. The request ID is useful for correlating the span with CloudTrail and other AWS-side logs. Inspect a real span with a Collector `debug` exporter or a local `ConsoleSpanExporter` before relying on version-sensitive AWS attributes.
</Note>

## Metrics emitted by botocore

When an OpenTelemetry meter provider is configured, the same instrumentation emits:

| Metric                             | Unit    | Dimensions                                                                                                                      |
| ---------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `gen_ai.client.operation.duration` | Seconds | `gen_ai.system`, `gen_ai.operation.name`, `gen_ai.request.model`, `server.address`, `server.port`, and `error.type` on failures |
| `gen_ai.client.token.usage`        | Tokens  | The same dimensions plus `gen_ai.token.type` to distinguish input and completion tokens                                         |

## Capture input and output content

### Automatic content events

Botocore `0.64b0` records messages as trace-correlated OTel log events rather than `gen_ai.input.messages` / `gen_ai.output.messages` span attributes. Depending on the request, it emits `gen_ai.system.message`, `gen_ai.user.message`, `gen_ai.assistant.message`, `gen_ai.tool.message`, and `gen_ai.choice`. With content capture disabled, these events can still be emitted but their message bodies are empty.

<Warning>
  Prompts, responses, system instructions, and tool data can contain sensitive information. Enable content capture only after applying appropriate access controls, redaction, and retention policies.
</Warning>

### Put input messages on the Bedrock span

If you need the current `gen_ai.input.messages` field directly on the botocore-created Bedrock client span, add a `request_hook`. The hook runs while that span is active. Use this setup instead of the basic `BotocoreInstrumentor().instrument()` call above. This example normalizes text messages sent through `Converse` or `ConverseStream` and stores the structured value as JSON:

```python theme={"dark"}
import json

from opentelemetry.instrumentation.botocore import BotocoreInstrumentor


def bedrock_request_hook(span, service_name, operation_name, api_params):
    if service_name != "bedrock-runtime" or operation_name not in {
        "Converse",
        "ConverseStream",
    }:
        return

    messages = []
    for message in api_params.get("messages", []):
        parts = [
            {"type": "text", "content": block["text"]}
            for block in message.get("content", [])
            if "text" in block
        ]
        messages.append({"role": message["role"], "parts": parts})

    if messages:
        span.set_attribute("gen_ai.input.messages", json.dumps(messages))


BotocoreInstrumentor().instrument(request_hook=bedrock_request_hook)
```

Extend the normalizer if your requests include images, documents, tool calls, or tool results. `InvokeModel` request bodies are provider-specific and require model-specific parsing.

Botocore already records generated output in the `gen_ai.choice` content event. An application can additionally add `gen_ai.output.messages` to an enclosing application span or a structured log after the response has been consumed. This is often simpler for streaming responses, because their final output is not available when the initial botocore response hook runs.

## Verify in Bronto

After running your application, open [Log Search](https://app.bronto.io/search) and filter by the `service.name` you set on your resource. Find all Bedrock spans emitted by botocore:

```sql theme={"dark"}
"$gen_ai.system" = 'aws.bedrock'
```

Find calls to a particular model or inference profile:

```sql theme={"dark"}
"$gen_ai.system" = 'aws.bedrock'
AND "$gen_ai.request.model" = 'eu.amazon.nova-micro-v1:0'
```

Find calls that stopped because the model completed its turn. Botocore emits an array on the span; Bronto indexes its first element as `.0`:

```sql theme={"dark"}
"$gen_ai.response.finish_reasons.0" = 'end_turn'
```

Find unusually large responses:

```sql theme={"dark"}
"$gen_ai.usage.output_tokens" > 1000
```

For captured request content, search the structured body of the emitted user-message event:

```sql theme={"dark"}
"$event.name" = 'gen_ai.user.message'
AND "$content.0.text" ILIKE '%refund%'
```

For captured response content, search the choice event:

```sql theme={"dark"}
"$event.name" = 'gen_ai.choice'
AND "$message.content.0.text" ILIKE '%refund approved%'
```

If you installed the request hook above, search the input attribute on the Bedrock span directly:

```sql theme={"dark"}
"$gen_ai.input.messages" ILIKE '%refund%'
```

In aggregate views, select **Sum**, **Average**, **Median**, or a percentile for `$gen_ai.usage.input_tokens` or `$gen_ai.usage.output_tokens`, then group by `$gen_ai.request.model`. This compares token usage across Bedrock models using fields botocore emits today. See [Visualizations](/Search-and-Visualize/Log-Visualization) for dashboard options.

## Direct export to Bronto

If you are not running a Collector (for local development or short-lived jobs), export OTLP straight to Bronto by adding your API key. See the [direct export section](/opentelemetry/python#direct-export-to-bronto) on the Python page; point the exporters at the Bronto ingestion endpoints:

| Region | Traces endpoint                            | Logs endpoint                            | Metrics endpoint                            |
| ------ | ------------------------------------------ | ---------------------------------------- | ------------------------------------------- |
| EU     | `https://ingestion.eu.bronto.io/v1/traces` | `https://ingestion.eu.bronto.io/v1/logs` | `https://ingestion.eu.bronto.io/v1/metrics` |
| US     | `https://ingestion.us.bronto.io/v1/traces` | `https://ingestion.us.bronto.io/v1/logs` | `https://ingestion.us.bronto.io/v1/metrics` |

<Note>
  OTLP metrics ingestion (`/v1/metrics`) is currently in beta. Logs and traces are generally available. See [API Keys](/Account-Management/API-Keys) to create a key with ingestion permissions.
</Note>

## References

* [Amazon Bedrock Logs, Metrics and Traces](/integrations/aws-bedrock)
* [LLM Observability](/ai-features/llm-observability)
* [Semantic conventions for AWS Bedrock](https://github.com/open-telemetry/semantic-conventions-genai/blob/main/docs/gen-ai/aws-bedrock.md)
* [opentelemetry-instrumentation-botocore](https://pypi.org/project/opentelemetry-instrumentation-botocore/)
