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, keyed on the Bedrock model id.

Install / enable

Python:

pip install "agentping-io[bedrock]"
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:

npm install @agentping/sdk @aws-sdk/client-bedrock-runtime
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.

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

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.