Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ Unless noted otherwise, every example below uses `braintrust.auto_instrument()`.
| `agno/` | Agno agents and teams, sync + async, streaming + non-streaming |
| `anthropic/` | Sync and async Anthropic clients |
| `autogen/` | AutoGen `AssistantAgent` backed by `OpenAIChatCompletionClient` |
| `bedrock_runtime/` | boto3 Bedrock Runtime `converse()` call against Amazon Nova Lite |
| `claude_agent_sdk/` | Claude Agent SDK subprocess query |
| `cohere/` | Cohere `ClientV2` chat call |
| `crewai/` | CrewAI `Agent` + `Task` + `Crew` end-to-end |
Expand Down
25 changes: 25 additions & 0 deletions examples/bedrock_runtime/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# AWS Bedrock + Braintrust

Calls `braintrust.auto_instrument()` to patch boto3's Bedrock Runtime client factory, then runs a single `client.converse(...)` call against Amazon Nova Lite. The trace captures a parent `answer_question` span plus a child `bedrock.converse` LLM span with input, output, metadata, and token metrics.

## Run

```bash
export BRAINTRUST_API_KEY=...
export AWS_BEARER_TOKEN_BEDROCK=...
export AWS_REGION=us-east-1
export AWS_DEFAULT_REGION=us-east-1

uv sync
uv run python example.py
```

If you use IAM credentials instead of a Bedrock API key, set the usual AWS variables instead:

```bash
export AWS_ACCESS_KEY_ID=...
export AWS_SECRET_ACCESS_KEY=...
export AWS_SESSION_TOKEN=... # if using temporary credentials
```

The default model is `us.amazon.nova-lite-v1:0`. Override it with `BRAINTRUST_BEDROCK_MODEL` if needed.
38 changes: 38 additions & 0 deletions examples/bedrock_runtime/example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#!/usr/bin/env python
"""boto3 Bedrock Runtime client traced via braintrust.auto_instrument()."""

import os

import braintrust


braintrust.auto_instrument()
braintrust.init_logger(project="example-bedrock")

import boto3 # noqa: E402


MODEL = os.getenv("BRAINTRUST_BEDROCK_MODEL", "us.amazon.nova-lite-v1:0")
REGION = os.getenv("AWS_REGION") or os.getenv("AWS_DEFAULT_REGION") or "us-east-1"


@braintrust.traced
def answer_question(question: str) -> str:
client = boto3.client("bedrock-runtime", region_name=REGION)
response = client.converse(
modelId=MODEL,
messages=[{"role": "user", "content": [{"text": question}]}],
inferenceConfig={"maxTokens": 64, "temperature": 0},
)
message = response["output"]["message"]
return "".join(block.get("text", "") for block in message.get("content", []))


def main() -> None:
answer = answer_question("What is the capital of Australia? Reply in one sentence.")
print(answer)
braintrust.flush()


if __name__ == "__main__":
main()
12 changes: 12 additions & 0 deletions examples/bedrock_runtime/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[project]
name = "braintrust-bedrock-example"
version = "0.1.0"
description = "boto3 Bedrock Runtime client traced with Braintrust"
requires-python = ">=3.10"
dependencies = [
"braintrust",
"boto3",
]

[tool.uv.sources]
braintrust = { path = "../../py", editable = true }
13 changes: 13 additions & 0 deletions py/noxfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,19 @@ def test_cohere(session, version):
_run_tests(session, f"{INTEGRATION_DIR}/cohere/test_cohere.py", version=version)


BOTO3_VERSIONS = _get_matrix_versions("boto3")


@nox.session()
@nox.parametrize("version", BOTO3_VERSIONS, ids=BOTO3_VERSIONS)
def test_bedrock_runtime(session, version):
"""Test the boto3 Bedrock Runtime integration."""
_install_test_deps(session)
_install_matrix_dep(session, "boto3", version)
_install_matrix_dep(session, "botocore", version)
_run_tests(session, f"{INTEGRATION_DIR}/bedrock_runtime/test_bedrock_runtime.py", version=version)


INSTRUCTOR_VERSIONS = _get_matrix_versions("instructor")


Expand Down
11 changes: 11 additions & 0 deletions py/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,14 @@ latest = "pytest==9.0.3"
[tool.braintrust.matrix.braintrust-core]
latest = "braintrust-core==0.0.59"

[tool.braintrust.matrix.boto3]
latest = "boto3==1.43.29"
"1.34.116" = "boto3==1.34.116"

[tool.braintrust.matrix.botocore]
latest = "botocore==1.43.29"
"1.34.116" = "botocore==1.34.116"

# ---------------------------------------------------------------------------
# Vendor packages — optional third-party packages the SDK can work without.
# Keys are matrix keys; values are Python import names. The noxfile uses this
Expand All @@ -461,6 +469,7 @@ agentscope = ["agentscope"]
agno = ["agno"]
autogen = ["autogen-agentchat"]
anthropic = ["anthropic"]
bedrock_runtime = ["boto3", "botocore"]
cohere = ["cohere"]
claude_agent_sdk = ["claude-agent-sdk"]
crewai = ["crewai"]
Expand Down Expand Up @@ -488,6 +497,8 @@ anthropic = "anthropic"
cohere = "cohere"
autoevals = "autoevals"
braintrust-core = "braintrust_core"
boto3 = "boto3"
botocore = "botocore"
crewai = "crewai"
dspy = "dspy"
google-adk = "google.adk"
Expand Down
5 changes: 5 additions & 0 deletions py/src/braintrust/auto.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
AgnoIntegration,
AnthropicIntegration,
AutoGenIntegration,
BedrockRuntimeIntegration,
ClaudeAgentSDKIntegration,
CohereIntegration,
CrewAIIntegration,
Expand Down Expand Up @@ -72,6 +73,7 @@ def auto_instrument(
openai_agents: bool = True,
cohere: bool = True,
autogen: bool = True,
bedrock: bool = True,
crewai: bool = True,
strands: bool = True,
temporal: bool = True,
Expand Down Expand Up @@ -106,6 +108,7 @@ def auto_instrument(
openai_agents: Enable OpenAI Agents SDK instrumentation (default: True)
cohere: Enable Cohere instrumentation (default: True)
autogen: Enable AutoGen instrumentation (default: True)
bedrock: Enable boto3 Bedrock Runtime instrumentation (default: True)
crewai: Enable CrewAI instrumentation (default: True)
strands: Enable Strands Agents instrumentation (default: True)
temporal: Enable Temporal instrumentation (default: True)
Expand Down Expand Up @@ -195,6 +198,8 @@ def auto_instrument(
results["cohere"] = _instrument_integration(CohereIntegration)
if autogen:
results["autogen"] = _instrument_integration(AutoGenIntegration)
if bedrock:
results["bedrock_runtime"] = _instrument_integration(BedrockRuntimeIntegration)
if crewai:
results["crewai"] = _instrument_integration(CrewAIIntegration)
if strands:
Expand Down
6 changes: 6 additions & 0 deletions py/src/braintrust/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,12 @@ def get_vcr_config():
"openai-api-key",
"x-goog-api-key",
"x-bt-auth-token",
"x-amz-security-token",
"x-amz-date",
"x-amz-content-sha256",
"amz-sdk-invocation-id",
"amz-sdk-request",
"x-amzn-bedrock-api-key",
],
}

Expand Down
2 changes: 2 additions & 0 deletions py/src/braintrust/integrations/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from .agno import AgnoIntegration
from .anthropic import AnthropicIntegration
from .autogen import AutoGenIntegration
from .bedrock_runtime import BedrockRuntimeIntegration
from .claude_agent_sdk import ClaudeAgentSDKIntegration
from .cohere import CohereIntegration
from .crewai import CrewAIIntegration
Expand All @@ -29,6 +30,7 @@
"AgnoIntegration",
"AnthropicIntegration",
"AutoGenIntegration",
"BedrockRuntimeIntegration",
"ClaudeAgentSDKIntegration",
"CohereIntegration",
"CrewAIIntegration",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
"""Test auto_instrument for boto3 Bedrock Runtime."""

import os


os.environ.setdefault("AWS_EC2_METADATA_DISABLED", "true")
if not os.environ.get("AWS_PROFILE") and not os.environ.get("AWS_BEARER_TOKEN_BEDROCK"):
os.environ.setdefault("AWS_ACCESS_KEY_ID", "testing")
os.environ.setdefault("AWS_SECRET_ACCESS_KEY", "testing")
os.environ.setdefault("AWS_SESSION_TOKEN", "testing")

import boto3
from braintrust.auto import auto_instrument
from braintrust.integrations.test_utils import autoinstrument_test_context


MODEL = os.getenv("BRAINTRUST_BEDROCK_CONVERSE_MODEL", "us.amazon.nova-lite-v1:0")
REGION = os.getenv("AWS_REGION") or os.getenv("AWS_DEFAULT_REGION") or "us-east-1"

results = auto_instrument()
assert results.get("bedrock_runtime") is True

results2 = auto_instrument()
assert results2.get("bedrock_runtime") is True

with autoinstrument_test_context("test_auto_bedrock_runtime", integration="bedrock_runtime") as memory_logger:
client = boto3.client("bedrock-runtime", region_name=REGION)
response = client.converse(
modelId=MODEL,
messages=[{"role": "user", "content": [{"text": "Say hello in one word."}]}],
inferenceConfig={"maxTokens": 20, "temperature": 0},
)
assert response["output"]["message"]["role"] == "assistant"

spans = memory_logger.pop()
assert len(spans) == 1, f"Expected 1 span, got {len(spans)}"
span = spans[0]
assert span["metadata"]["provider"] == "bedrock"
assert span["metadata"]["model"] == MODEL
assert span["metadata"]["endpoint"] == "converse"
assert span["span_attributes"]["name"] == "bedrock.converse"

print("SUCCESS")
17 changes: 17 additions & 0 deletions py/src/braintrust/integrations/bedrock_runtime/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
"""Public entry points for the boto3 Bedrock Runtime integration."""

from .integration import BedrockRuntimeIntegration
from .patchers import wrap_bedrock_client


__all__ = ["BedrockRuntimeIntegration", "setup_bedrock", "wrap_bedrock"]


def setup_bedrock() -> bool:
"""Patch botocore client creation to auto-wrap Bedrock Runtime clients."""
return BedrockRuntimeIntegration.setup()


def wrap_bedrock(client):
"""Instrument a boto3 Bedrock Runtime client instance in place."""
return wrap_bedrock_client(client)
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
interactions:
- request:
body: '{"messages": [{"role": "user", "content": [{"text": "Say hello in one word."}]}],
"inferenceConfig": {"maxTokens": 20, "temperature": 0}}'
headers:
Content-Length:
- '137'
Content-Type:
- !!binary |
YXBwbGljYXRpb24vanNvbg==
User-Agent:
- !!binary |
Qm90bzMvMS40My4yOSBtZC9Cb3RvY29yZSMxLjQzLjI5IHVhLzIuMSBvcy9tYWNvcyMyNS41LjAg
bWQvYXJjaCNhcm02NCBsYW5nL3B5dGhvbiMzLjE0LjMgbWQvcHlpbXBsI0NQeXRob24gbS9aLGIs
RCwzIGNmZy9yZXRyeS1tb2RlI2xlZ2FjeSBCb3RvY29yZS8xLjQzLjI5
method: POST
uri: https://bedrock-runtime.us-east-1.amazonaws.com/model/us.amazon.nova-lite-v1%3A0/converse
response:
body:
string: '{"metrics":{"latencyMs":511},"output":{"message":{"content":[{"text":"Hi."}],"role":"assistant"}},"stopReason":"end_turn","usage":{"inputTokens":6,"outputTokens":3,"serverToolUsage":{},"totalTokens":9}}'
headers:
Connection:
- keep-alive
Content-Length:
- '202'
Content-Type:
- application/json
Date:
- Mon, 15 Jun 2026 20:24:03 GMT
x-amzn-RequestId:
- caa9fd4f-69d2-459b-baf5-0ad888a79845
status:
code: 200
message: OK
version: 1
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
interactions:
- request:
body: '{"system": [{"text": "You answer with concise lowercase text."}], "messages":
[{"role": "user", "content": [{"text": "Say hello in one word."}]}], "inferenceConfig":
{"maxTokens": 20, "temperature": 0, "topP": 0.9, "stopSequences": ["STOP"]}}'
headers:
Content-Length:
- '242'
Content-Type:
- !!binary |
YXBwbGljYXRpb24vanNvbg==
User-Agent:
- !!binary |
Qm90bzMvMS40My4yOSBtZC9Cb3RvY29yZSMxLjQzLjI5IHVhLzIuMSBvcy9tYWNvcyMyNS41LjAg
bWQvYXJjaCNhcm02NCBsYW5nL3B5dGhvbiMzLjE0LjMgbWQvcHlpbXBsI0NQeXRob24gbS9iLDMs
WixEIGNmZy9yZXRyeS1tb2RlI2xlZ2FjeSBCb3RvY29yZS8xLjQzLjI5
method: POST
uri: https://bedrock-runtime.us-east-1.amazonaws.com/model/us.amazon.nova-lite-v1%3A0/converse
response:
body:
string: '{"metrics":{"latencyMs":613},"output":{"message":{"content":[{"text":"hi"}],"role":"assistant"}},"stopReason":"end_turn","usage":{"inputTokens":13,"outputTokens":2,"serverToolUsage":{},"totalTokens":15}}'
headers:
Connection:
- keep-alive
Content-Length:
- '203'
Content-Type:
- application/json
Date:
- Mon, 15 Jun 2026 20:24:01 GMT
x-amzn-RequestId:
- 7c3bdcc8-1124-46b5-982e-3ced2d1a5371
status:
code: 200
message: OK
version: 1
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
interactions:
- request:
body: '{"system": [{"text": "You answer with concise lowercase text."}], "messages":
[{"role": "user", "content": [{"text": "Say hello in one word."}]}], "inferenceConfig":
{"maxTokens": 20, "temperature": 0, "topP": 0.9, "stopSequences": ["STOP"]}}'
headers:
Content-Length:
- '242'
Content-Type:
- !!binary |
YXBwbGljYXRpb24vanNvbg==
User-Agent:
- !!binary |
Qm90bzMvMS40My4yOSBtZC9Cb3RvY29yZSMxLjQzLjI5IHVhLzIuMSBvcy9tYWNvcyMyNS41LjAg
bWQvYXJjaCNhcm02NCBsYW5nL3B5dGhvbiMzLjE0LjMgbWQvcHlpbXBsI0NQeXRob24gbS9iLDMs
WixEIGNmZy9yZXRyeS1tb2RlI2xlZ2FjeSBCb3RvY29yZS8xLjQzLjI5
method: POST
uri: https://bedrock-runtime.us-east-1.amazonaws.com/model/us.amazon.nova-lite-v1%3A0/converse
response:
body:
string: '{"metrics":{"latencyMs":486},"output":{"message":{"content":[{"text":"hi"}],"role":"assistant"}},"stopReason":"end_turn","usage":{"inputTokens":13,"outputTokens":2,"serverToolUsage":{},"totalTokens":15}}'
headers:
Connection:
- keep-alive
Content-Length:
- '203'
Content-Type:
- application/json
Date:
- Mon, 15 Jun 2026 20:24:00 GMT
x-amzn-RequestId:
- 1935727d-5309-4a70-8c15-1ce0ffbe877e
status:
code: 200
message: OK
version: 1
Loading
Loading