Aira
Integrations

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

gated-bedrock-agent.py
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

gated-bedrock-agent-runtime.py
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

  1. Your code calls bedrock.invoke_model(modelId=..., body=...).
  2. The wrapper calls aira.authorize(action_type="model_invoked", details="Bedrock invoke_model: anthropic...").
  3. Policy engine runs. If denied, the wrapper raises AiraInvocationDenied.
  4. Bedrock is never called. No tokens billed, no response generated.
  5. The denied PolicyEvaluation row 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:

Methodaction_typeDetails string
invoke_modelmodel_invokedBedrock invoke_model: <modelId>
invoke_agentagent_invokedBedrock 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 changesYou want to include request body content in the details string
You want blanket gating on every Bedrock callYou need different policies per call branch
You want the simplest possible audit trailYou'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 calls bedrock.invoke_model will 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's details column. If you want to gate on prompt content, pass a details builder 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 INTEGRATIONS registry (aira/extras/__init__.py) as kind="gate", pre_execution_gate=True.

On this page