AWS Bedrock
Gate AWS Bedrock invoke_model and invoke_agent calls through Aira's policy engine. Denied invocations never hit Bedrock.
Kind: gate · Pre-execution gate: yes · Peer dep: boto3
What this integration actually does
AiraBedrockHandler wraps a boto3 Bedrock client so every call to invoke_model and invoke_agent runs through aira.authorize() first. If the policy engine denies, the wrapped method raises AiraInvocationDenied and Bedrock is never called — no tokens billed, no side effects.
Successful calls are notarized with outcome="completed" and failed calls (any exception from the underlying Bedrock call) are notarized with outcome="failed" so the action transitions correctly in Aira.
Install
pip install aira-sdk[bedrock]This pulls in boto3 as a peer dependency. If you already have boto3 installed you're covered.
Full example — gated invoke_model
import boto3
from aira import Aira
from aira.extras.bedrock import AiraBedrockHandler, AiraInvocationDenied
aira = Aira(api_key="aira_live_...")
handler = AiraBedrockHandler(client=aira, agent_id="support-agent")
# Create the raw Bedrock client
bedrock = boto3.client("bedrock-runtime", region_name="eu-west-1")
# Wrap it — this is where authorize() runs
bedrock.invoke_model = handler.wrap_invoke_model(bedrock)
try:
response = bedrock.invoke_model(
modelId="anthropic.claude-3-5-sonnet-20241022-v2:0",
body='{"anthropic_version": "bedrock-2023-05-31", "max_tokens": 1024, "messages": [{"role": "user", "content": "Draft a refund reply"}]}',
)
print(response["body"].read())
except AiraInvocationDenied as e:
print(f"Aira blocked: {e.code} — {e.message}")Full example — gated invoke_agent
import boto3
from aira import Aira
from aira.extras.bedrock import AiraBedrockHandler, AiraInvocationDenied
aira = Aira(api_key="aira_live_...")
handler = AiraBedrockHandler(client=aira, agent_id="support-agent")
bedrock_agent = boto3.client("bedrock-agent-runtime", region_name="eu-west-1")
bedrock_agent.invoke_agent = handler.wrap_invoke_agent(bedrock_agent)
response = bedrock_agent.invoke_agent(
agentId="AGENT123",
agentAliasId="TSTALIASID",
sessionId="sess-001",
inputText="Process refund request RMA-9876",
)What happens when a policy denies
- Your code calls
bedrock.invoke_model(modelId=..., body=...). - The wrapper calls
aira.authorize(action_type="model_invoked", details="Bedrock invoke_model: anthropic..."). - Policy engine runs. If denied, the wrapper raises
AiraInvocationDenied. - Bedrock is never called. No tokens billed, no response generated.
- The denied
PolicyEvaluationrow persists for audit — you can see exactly why this invocation was blocked.
Action types
The wrapper uses different action_type strings so you can write policies targeting one or the other:
| Method | action_type | Details string |
|---|---|---|
invoke_model | model_invoked | Bedrock invoke_model: <modelId> |
invoke_agent | agent_invoked | Bedrock invoke_agent: <agentId> |
Write a policy like:
{
"mode": "rules",
"conditions": [
{"field": "action_type", "op": "eq", "value": "model_invoked"},
{"field": "details", "op": "contains", "value": "anthropic.claude-3-opus"}
],
"decision": "require_approval"
}...to require human approval for any call to a specific model family without affecting other Bedrock calls.
When to use this vs inline aira.authorize()
| Use the wrapper when... | Call authorize() inline when... |
|---|---|
You're already using boto3.client("bedrock-runtime") and want minimal code changes | You want to include request body content in the details string |
| You want blanket gating on every Bedrock call | You need different policies per call branch |
| You want the simplest possible audit trail | You're building a custom Bedrock client wrapper |
Known limits
- The wrapper replaces the method in place. After
bedrock.invoke_model = handler.wrap_invoke_model(bedrock), any subsequent code that callsbedrock.invoke_modelwill go through Aira. If you have existing code that relies on the unwrapped method, use a different boto3 client instance. - Details strings do not include the prompt body. By default we use
f"Bedrock invoke_model: {modelId}"to keep auth latency low and avoid sending the full prompt to Aira'sdetailscolumn. If you want to gate on prompt content, pass adetailsbuilder callback — open an issue if you need this, it's a small addition. - Streaming responses are supported because we only wrap the call site, not the returned stream. You get one authorize + one notarize per stream open.
Proof it works
- Python:
tests/test_extras_bedrock.py(132 lines) - Pinned in the
INTEGRATIONSregistry (aira/extras/__init__.py) askind="gate",pre_execution_gate=True.
Related
- Policies — configuring model-specific policies
- Content scan policies — regex-based scan over action details