---
title: Amazon Bedrock
description: Auto-instrument the Bedrock Runtime client so Converse and InvokeModel calls land in AgentPing with tokens, cost, and latency across every model family.
section: providers
order: 6
---

# Amazon Bedrock

Auto-instrument the Bedrock Runtime client (`boto3` on PyPI, `@aws-sdk/client-bedrock-runtime` on npm). `Converse`, `ConverseStream`, `InvokeModel` and `InvokeModelWithResponseStream` calls inside an active run emit an `llm_call` event with model id, token usage, latency, and, for Converse, prompt-cache counts, stop reason and tool use. Cost is computed server-side from the [rate card](/docs/spend), keyed on the Bedrock model id.

## Install / enable

Python:

```bash
pip install "agentping-io[bedrock]"
```

```python
import agentping
import boto3

agentping.init()
agentping.instrument_bedrock()

client = boto3.client("bedrock-runtime", region_name="eu-west-2")

with agentping.run("support-triage", customer_id="acme-corp"):
    reply = client.converse(
        modelId="anthropic.claude-sonnet-4-5",
        messages=[{"role": "user", "content": [{"text": "classify this ticket"}]}],
    )
```

boto3 builds its clients dynamically, so `instrument_bedrock()` patches `botocore.client.BaseClient._make_api_call` and acts only on the `bedrock-runtime` service; every other AWS call passes straight through. Idempotent and global; call it once at module load. Supported `botocore` versions: 1.34 up to, but not including, 3.0. Async clients (`aioboto3`, `aiobotocore`) are not covered.

TypeScript:

```bash
npm install @agentping/sdk @aws-sdk/client-bedrock-runtime
```

```typescript
import { BedrockRuntimeClient, ConverseCommand } from "@aws-sdk/client-bedrock-runtime";
import * as agentping from "@agentping/sdk";

agentping.init({ apiKey: process.env.AGENTPING_API_KEY });

const run = agentping.run("answer-bot");
const bedrock = agentping.instrumentBedrock(
  new BedrockRuntimeClient({ region: "eu-west-2" }),
  { run },
);

const reply = await bedrock.send(
  new ConverseCommand({
    modelId: "anthropic.claude-sonnet-4-5",
    messages: [{ role: "user", content: [{ text: "hi" }] }],
  }),
);

await run.finish({ status: "success" });
```

`instrumentBedrock` wraps `send` and inspects the command class, so `ConverseCommand`, `ConverseStreamCommand`, `InvokeModelCommand` and `InvokeModelWithResponseStreamCommand` are recognised and everything else passes through.

## What's captured

| Field | Converse / ConverseStream | InvokeModel / InvokeModelWithResponseStream |
|---|---|---|
| `provider` | `bedrock` | `bedrock` |
| `model` | `modelId` from the request | `modelId` from the request |
| `input_tokens` | `usage.inputTokens` plus `cacheReadInputTokens` plus `cacheWriteInputTokens` (Converse reports net of cache; the SDK reports gross) | `x-amzn-bedrock-input-token-count` response header |
| `output_tokens` | `usage.outputTokens` | `x-amzn-bedrock-output-token-count` response header |
| `cached_input_tokens` | `usage.cacheReadInputTokens` | not available |
| `cache_creation_input_tokens` | `usage.cacheWriteInputTokens` | not available |
| `finish_reason` | `stopReason` | not available |
| `tool_calls` | Number of `toolUse` blocks in the output message | not available |
| `latency_ms` | Wall-clock time of the call | Wall-clock time of the call |
| `stream` | `true` on ConverseStream | `true` on InvokeModelWithResponseStream |

Converse is the richer path: it normalises usage across every model family, so prefer it where the model supports it. `InvokeModel` bodies are model-specific and the wrapper does not parse them; token counts come from the response headers only. Bedrock reports no reasoning-token count on either path. Zero-valued fields are omitted.

A call that raises is recorded as an `llm_call` with `status: "error"`, `error` and `exception`, then re-raised.

## Streaming

`ConverseStream` is captured in full. The wrapper reads the terminal `metadata` event for usage, `messageStop` for the stop reason and counts `contentBlockStart` events carrying `toolUse`; the `llm_call` fires once the event stream is fully consumed or closed.

```python
with agentping.run("answer-bot"):
    response = client.converse_stream(
        modelId="anthropic.claude-sonnet-4-5",
        messages=[{"role": "user", "content": [{"text": "explain briefly"}]}],
    )
    for event in response["stream"]:
        if "contentBlockDelta" in event:
            print(event["contentBlockDelta"]["delta"].get("text", ""), end="", flush=True)
    # llm_call is emitted here
```

`InvokeModelWithResponseStream` is recorded when the call returns, from the token-count headers, and the body stream is not observed. Models that only report usage inside the streamed body land with latency but without token counts; use Converse for those.

## Source / notes

- Python: `agentping.instrument_bedrock()` in [agent-ping-python](https://github.com/agent-ping/agent-ping-python)
- TypeScript: `instrumentBedrock` in [agent-ping-typescript](https://github.com/agent-ping/agent-ping-typescript)

Rates are keyed on `provider` plus `model`, and Bedrock calls carry `provider: bedrock` with the Bedrock model id (`anthropic.claude-sonnet-4-5`, `eu.anthropic.claude-sonnet-4-5`, `amazon.nova-pro-v1:0`), so the default Anthropic rows do not apply to them. Bedrock calls land with their token counts and appear in the unpriced-models list until you add the model id's per-million rates under Spend, Rate cards; add each inference-profile id you use. See [Spend](/docs/spend).
