Skip to content

Latest commit

 

History

History

python-bedrock

README.md

telemetry-dev-bedrock

AWS Bedrock instrumentation for the telemetry.dev Python SDK.

Docs: https://telemetry.dev/docs/integrations/bedrock

This package instruments boto3/botocore clients for:

  • bedrock-runtime
    • Converse
    • ConverseStream
    • InvokeModel
    • InvokeModelWithResponseStream
    • ApplyGuardrail
  • bedrock-agent-runtime
    • InvokeAgent
    • InvokeInlineAgent
    • Rerank
    • Retrieve
    • RetrieveAndGenerate
    • RetrieveAndGenerateStream
    • InvokeFlow

Telemetry is emitted through telemetry_dev.start_span. No AWS request parameters are mutated.

Install

uv add telemetry-dev telemetry-dev-bedrock boto3

or with pip:

pip install telemetry-dev telemetry-dev-bedrock boto3

Requires Python 3.10 or later and boto3 1.36 or later.

Quickstart

init() reads your project API key from the TELEMETRY_DEV_API_KEY environment variable, so set it before you start the app.

import boto3
import telemetry_dev
from telemetry_dev_bedrock import wrap_bedrock

telemetry_dev.init(
    service_name="bedrock-app",
    environment="production",
)

bedrock = wrap_bedrock(boto3.client("bedrock-runtime", region_name="us-east-1"))

bedrock.converse(
    modelId="global.anthropic.claude-haiku-5-5",
    messages=[{"role": "user", "content": [{"text": "Hello"}]}],
)

telemetry_dev.shutdown()

Versions of telemetry-dev before 0.3.0 sent data to https://ingest.telemetry.dev by default. That hostname reaches the same service and stays supported: if a firewall allows only it, set TELEMETRY_DEV_BASE_URL to it.

wrap_bedrock accepts either a bedrock-runtime or bedrock-agent-runtime boto3 client. It patches the operation methods on that instance and is idempotent.

Global instrumentation

from telemetry_dev_bedrock import instrument_bedrock, uninstrument_bedrock

instrument_bedrock()
# bedrock-runtime and bedrock-agent-runtime clients created before or after this point are covered.
uninstrument_bedrock()

Global instrumentation wraps botocore.client.BaseClient._make_api_call and filters by service name, so unrelated boto3 clients pass through untouched. Per-client wrap_bedrock() clients are skipped by the global wrapper to avoid double spans.

Options

Option Default Applies to Notes
capture_agent_trace False Agent Runtime streams Aggregates trace usage and counts by default. When enabled, also attaches trace events as td.metadata.agent_trace, filtered as described below.

td.metadata.agent_trace is metadata, which is cut at max_attribute_length when you set one (at 65,536 characters by default with telemetry-dev 0.3.2 or earlier) but never passed to your mask, so each trace event is rebuilt from an explicit allowlist instead:

  • Envelope fields: agentId, agentAliasId, agentVersion, sessionId, collaboratorName, eventTime, and the alias ARNs in callerChain.
  • Content the agent sends to a model, tool, or guardrail (modelInvocationInput, invocationInput, inputAssessments) is kept as received when capture_input is on.
  • Content that comes back (modelInvocationOutput, rationale, observation, outputAssessments, custom orchestration event) is kept as received when capture_output is on.
  • A failure reason (failureTrace.failureReason, and td.metadata.failure_reason) is free-form text, so it is kept only when both are on. td.metadata.failure always records that the invocation failed, with td.metadata.failure_code when AWS sends one.
  • When a mask is set, all of this content is withheld.
  • Withheld content and the trace structure around it keep only trace ids, step types, the foundation model, guardrail action, failure code, and the usage, timings, and clientRequestId from AWS's trace metadata.

Any other field, including unknown trace types and the AWS SDK's $unknown union member, is omitted even when both capture options are on; a content part that is kept is exported whole, including any fields inside it that are not listed here. Errors are not covered by this filtering: an AWS exception, including one raised inside the stream, records the provider's error message on the span when both capture_input and capture_output are on, and the message passes through mask.

agent = wrap_bedrock(
    boto3.client("bedrock-agent-runtime"),
    capture_agent_trace=True,
)

Signal coverage

All instrumented AWS responses/errors include gen_ai.response.id from the AWS request id, plus aws.http.status_code, aws.request.attempts when retries occurred, and aws.request.total_retry_delay_ms when the SDK exposes them.

Operation Span type Span name Captured fields
Converse generation chat {modelId} normalized input/output messages, system instructions, sampling params, usage/cache usage, finish reason, request id, retry attempts, prompt-router response model, server latency, guardrail metadata
ConverseStream generation chat {modelId} pull-through stream output, time-to-first-chunk, usage/latency from metadata, partial output on early break/error
InvokeModel generation or embedding chat {modelId} / embeddings {modelId} native JSON request/response bodies, provider-native sampling, provider-native usage, embedding output type, HTTP-header token fallback
InvokeModelWithResponseStream generation or embedding chat {modelId} / embeddings {modelId} provider-native chunk text where known, optional final amazon-bedrock-invocationMetrics usage
ApplyGuardrail span apply_guardrail {guardrailIdentifier} guardrail input/output, action, action reason, request id
InvokeAgent / InvokeInlineAgent agent invoke_agent {agentId} / invoke_agent {agentName} user input, streamed answer, session/memory ids, alias id, trace usage aggregation, trace event count, return-control output
Rerank span (rerank operation) rerank {modelArn basename} queries, inline sources, model request fields, ranked results and scores, result counts, pagination metadata, request id
Retrieve span retrieve {knowledgeBaseId} query, retrieval results, guardrail action, result count metadata
RetrieveAndGenerate / stream generation retrieve_and_generate {modelArn basename} input text, generated text, citations count, session id, guardrail action
InvokeFlow agent invoke_flow {flowIdentifier} inputs, flow output events, completion reason

StreamingBody behavior

InvokeModel returns a botocore StreamingBody. The integration reads it once to capture output and token usage, then replaces it with a new StreamingBody over the same bytes. Caller code can still call response["body"].read() normally.

Event streams are wrapped lazily. The wrapper does not pre-read events, preserves backpressure, forwards unknown attributes to the inner stream, and calls close() on the inner stream when closed.

Message normalization

Converse messages follow the OpenTelemetry GenAI non-normative LLM-call examples:

  • text blocks become { "type": "text", "content": ... }
  • toolUse becomes { "type": "tool_call", "id", "name", "arguments" }
  • toolResult becomes { "type": "tool_call_response", "id", "response" }
  • reasoning text becomes { "type": "reasoning", "content" }
  • image/document/video/audio bytes become blob parts without byte content
  • S3/URI sources become { "type": "uri", "uri", "modality" }

InvokeModel captures provider-native JSON bodies verbatim. A body whose content type is not JSON is not captured, and that is not a failure. A JSON-typed or untyped body that is not UTF-8 or not valid JSON records no output, sets telemetry.dev.capture.truncated, and is reported to on_error (see below); a blank body records no output and is not a failure.

Semantics and guarantees

  • Provider is emitted as amazon-bedrock. This intentionally differs from the OpenTelemetry registry value aws.bedrock so telemetry.dev pricing keys match Bedrock model IDs exactly. The raw caller modelId, modelArn, or foundationModel is emitted as the model.
  • Instrumentation is fail-open. Normalization, span updates, and span endings are guarded so telemetry failures do not break caller code. When the response of a successful non-streamed call (such as Converse, ApplyGuardrail, Rerank, Retrieve, or RetrieveAndGenerate) fails to map, the failure is reported to on_error and the span ends with the AWS response metadata, without output, and with telemetry.dev.capture.truncated; it is not marked failed. When a stream response's own fields fail to map, the failure is reported and the stream still records its output from its events.
  • Requests are never modified.
  • Streams end spans exactly once. Exhaustion records full output and finish reason; early break, close(), GeneratorExit, or stream errors record partial output and set telemetry.dev.capture.truncated.
  • Event-stream reconstruction and streamed InvokeModel bodies are kept up to 16 MiB, and all streams in flight share the max_stream_bytes bound of the core (64 MiB by default). These bounds need a telemetry-dev core with max_stream_bytes; with an older core, each stream keeps up to 64 KiB and 1,024 items. InvokeModel bodies and Rerank inputs and results are retained up to 16 MiB each; a body over that bound records no output and sets telemetry.dev.capture.truncated. The configured max_attribute_length does not shrink these retention bounds; the core applies it to the exported attribute after the mask runs.
  • A stream cut at its retention bound, closed early, or failed sets telemetry.dev.capture.truncated. An event that fails to map, or a failure while the time to first chunk or an output-chunk timestamp is recorded, is reported to on_error; only the first such failure of a stream is reported. A failure before the event's output is kept also sets the attribute, and no later text or content is kept; so does an InvokeModel stream chunk that is not UTF-8 or not JSON (a blank chunk holds no output and is skipped). Agent text is decoded across chunks, so a character split between two chunks is kept whole; Agent text that is not UTF-8, or a stream that ends inside a character, loses that text like a failed event and is reported; an Agent returnControl that arrives later still replaces the text output whole, also with a mask configured. A failure in the event's usage, metrics, or Agent trace summaries loses only that metadata and does not set the attribute, and so does a timing failure. A failure while Agent trace content is captured (capture_agent_trace) loses that trace and sets the attribute, but later text is still kept, also with a mask configured. A stream whose output fails to map when it ends records no output and sets the attribute. An InvokeModel body that is closed before it is fully read, whose read fails, or that fails to map (for example, a body that is not UTF-8 or not JSON) records no output and sets the attribute too; a mapping failure is reported to on_error. With a mask configured, incomplete reconstructed output is omitted because the mask cannot inspect the complete value. Agent trace events share the bound, but dropping them only sets the attribute; Agent text is omitted only when a text chunk was dropped or failed to map, or the stream was closed early or failed. A normally exhausted Agent stream that returned control keeps its complete returnControl output even if earlier text or trace metadata passed the bound; an early close or failure still omits the output when a mask is configured.
  • boto3 has no async client. aiobotocore is a separate package and is out of scope.

Coverage gaps

The following calls pass through unless a supported operation above is used:

  • CountTokens
  • InvokeGuardrailChecks
  • InvokeModelWithBidirectionalStream
  • StartAsyncInvoke, GetAsyncInvoke, ListAsyncInvokes
  • Agent Runtime session CRUD
  • GenerateQuery
  • OptimizePrompt
  • AgenticRetrieveStream

Provider-native InvokeModel usage is best-effort. Anthropic Claude, Amazon Titan/Nova, Meta Llama, and Titan embeddings expose known token fields. Cohere and Mistral text models do not consistently include usage in the JSON body; spans still capture request/response bodies and finish reasons where present.

License

MIT. See LICENSE.