AWS Bedrock
Trace AWS Bedrock (Converse + InvokeModel) in Python and TypeScript.
Wrap a Bedrock runtime client. Both the Converse API and the legacy InvokeModel API are traced. provider is always bedrock; system is the underlying model vendor (anthropic, amazon, meta, …), inferred from the model id.
Prerequisites
pip install -U "neatlogs[bedrock]" boto3Usage
import os
import neatlogs
neatlogs.init(api_key=os.environ["NEATLOGS_API_KEY"], workflow_name="bedrock-demo")
import boto3
client = neatlogs.wrap(boto3.client("bedrock-runtime", region_name="us-east-1"))
resp = client.converse(
modelId="us.anthropic.claude-haiku-4-5-20251001-v1:0",
messages=[{"role": "user", "content": [{"text": "What is AWS Bedrock?"}]}],
inferenceConfig={"temperature": 0.2, "maxTokens": 512},
)
print(resp["output"]["message"]["content"][0]["text"])
neatlogs.flush()
neatlogs.shutdown()wrap() opens a WORKFLOW root for you, so this renders with no extra wrapper.
Making several calls in one run? Group them into one trace with a
WORKFLOWroot.
For cross-region models use the inference-profile id (e.g. us.anthropic.claude-...) rather than the bare model id.
Sessions & end-user
Say you're running a chat assistant on Bedrock's converse() and you'd like every back-and-forth turn to land under one session, tied to the person you're talking to — that's a wrap with identify(). Since neatlogs.wrap() already opens the trace root, dropping your converse() call inside an identify() block is enough for it to pick up the session and end-user:
# Same session_id every turn → one session; end_user_id attributes it to your user.
with neatlogs.identify(session_id=f"conv_{conversation_id}", end_user_id=user_id):
resp = client.converse(
modelId="us.anthropic.claude-haiku-4-5-20251001-v1:0",
messages=[{"role": "user", "content": [{"text": message}]}],
)The trick is to feed the same session_id back on each turn so they stitch together into one conversation.
Not every run is a conversation. A standalone workflow — a one-off job, not a back-and-forth — is a single trace with no turns to group. You still attribute it to the customer it ran for, sourcing the id and any metadata from your own user or request object:
A standalone workflow must not carry a session_id. Omit it entirely — Neatlogs sets session_id = trace_id, so the run is its own single-turn session, fully attributed to the end-user. Reusing one session_id across unrelated runs wrongly folds them into a single multi-turn conversation.
def summarize(document, user):
# Standalone workflow — attribute to the end-user; do NOT pass session_id.
with neatlogs.identify(
end_user_id=str(user.id),
end_user_metadata={"plan": user.plan},
):
resp = client.converse(
modelId="us.anthropic.claude-haiku-4-5-20251001-v1:0",
messages=[{"role": "user", "content": [{"text": f"Summarize:\n{document}"}]}],
)
return resp["output"]["message"]["content"][0]["text"]Sessions and End-User Identity walk through the whole picture.
