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
- Python:
agentping.instrument_bedrock()in agent-ping-python - TypeScript:
instrumentBedrockin 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.