bedrock-fallback-engine
AWS-native resilience for Amazon Bedrock. Cross-region, cross-model failover with schema validation and per-route CloudWatch telemetry.
Watch us-east-1 fail. Watch the request still succeed. ↓
Same endpoint. Same response. Different region. Every failover is a CloudWatch metric.
Architecture
flowchart LR
A[Request] --> B[Engine.invoke]
B --> C{Route 1<br/>primary}
C -->|fail| D{Route 2<br/>cross-region}
D -->|fail| E{Route 3<br/>cross-region profile}
E -->|fail| F{Route 4<br/>Nova}
F -->|fail| G{Route 5<br/>Titan}
C -->|ok| Z[Result]
D -->|ok| Z
E -->|ok| Z
F -->|ok| Z
G -->|ok| Z
G -->|fail| Y[RouteExhausted]
Z --> M[CloudWatch]
Y --> M
style C fill:#3ad082,color:#000
style D fill:#48a8ec,color:#000
style E fill:#48a8ec,color:#000
style F fill:#48a8ec,color:#000
style G fill:#e89632,color:#000
style Y fill:#e84e4e,color:#000
style M fill:#9457d4,color:#000
The problem
Amazon Bedrock throttles. A region goes down. A model hits quota. When that happens mid-agent-workflow, your entire pipeline dies.
bedrock-fallback-engine wraps the Converse API in a route chain:
- Cross-region failover — us-east-1 throttled? Try us-west-2.
- Cross-model failover — Claude down? Try Nova.
- Cross-provider failover — Bedrock-native models all sick? Try Llama.
- Schema validation — malformed JSON fails over to the next route instead of crashing.
- Token accounting — every invocation reports input/output tokens and which route served it.
Install
pip install bedrock-fallback-engine
Or with Docker for the demo:
docker build -t bedrock-demo -f demo/Dockerfile .
docker run -p 8000:8000 bedrock-demo
Requires AWS credentials configured (aws configure, env vars, or IAM role) for real Bedrock calls. The demo runs without credentials in dry-run mode.
Deploy to AWS Lambda
The library ships a small CLI that builds a Lambda-ready deployment zip:
pip install bedrock-fallback-engine
# Build a minimal zip (assumes Lambda's Python 3.12 runtime provides boto3)
bedrock-fallback-engine package
# → ✅ Wrote dist/lambda_layer.zip
# Size: 1.24 MB
# Deploy it
aws lambda update-function-code \
--function-name my-triage-fn \
--zip-file fileb://dist/lambda_layer.zip
Options:
| Flag | Default | Meaning |
|---|---|---|
--output / -o |
dist/lambda_layer.zip |
Output path |
--include-boto3 |
off | Bundle boto3 (Lambda's runtime already provides it) |
--verbose / -v |
off | Debug-level logging |
If your Lambda uses a custom runtime without boto3, pass --include-boto3 to bundle it.
## Quickstart
```python
from pydantic import BaseModel
from bedrock_fallback_engine import BedrockFallbackEngine
class Triage(BaseModel):
category: str
urgency: str
engine = BedrockFallbackEngine(schema=Triage)
result = engine.invoke([
{"role": "user", "content": [{"text": "Classify: dashboard won't load."}]}
])
print(result.value) # {"category": "bug", "urgency": "high"}
print(result.route.label) # "primary"
print(result.input_tokens) # 42
print(result.output_tokens) # 11
If the primary region or model fails, the next route runs automatically. If every route fails, you get a BedrockRouteExhausted with the full error trail — each entry tagged with the route label that produced it.
Default routes
us-east-1 Claude 3.5 Sonnet → us-west-2 Claude 3.5 Sonnet → us-east-1 Nova Pro → us-west-2 Llama 3 70B
Bring your own:
from bedrock_fallback_engine import BedrockRoute, BedrockFallbackEngine
engine = BedrockFallbackEngine(routes=[
BedrockRoute("eu-west-1", "anthropic.claude-3-5-sonnet-20241022-v2:0"),
BedrockRoute("eu-central-1", "amazon.nova-pro-v1:0"),
])
Live failover demo
The demo/ folder contains a FastAPI app that demonstrates cross-region failover in real time. It runs without AWS credentials — the chaos client returns canned responses so you can try it locally in 30 seconds.
Run it locally
pip install -e .
pip install -r demo/requirements.txt
BEDROCK_DEMO_DRY_RUN=1 uvicorn demo.app:app --reload
Leave that terminal running. Then, in a second terminal, either:
- Interactive playground: open
http://localhost:8000/docsin your browser (FastAPI auto-generates Swagger UI), or - Direct
curlcalls: use the examples below.
Happy path — served by primary
curl -i -X POST http://localhost:8000/triage \
-H 'content-type: application/json' \
-d '{"text": "My dashboard is broken"}' | grep X-Route
X-Route-Served-By: primary
X-Route-Region: us-east-1
Failover path — served by backup
Add ?simulate_throttle=true to force the primary route to fail:
curl -i -X POST 'http://localhost:8000/triage?simulate_throttle=true' \
-H 'content-type: application/json' \
-d '{"text": "My dashboard is broken"}' | grep X-Route
X-Route-Served-By: cross-region
X-Route-Region: us-west-2
Same response body. Same status code. User never knew.
Response headers
Every invocation returns the routing decision as headers — perfect for demos, debugging, and dashboards:
| Header | Meaning |
|---|---|
X-Route-Served-By |
Which route label answered |
X-Route-Region |
AWS region that answered |
X-Route-Model |
Bedrock model that answered |
X-Input-Tokens |
Prompt tokens consumed |
X-Output-Tokens |
Response tokens generated |
X-Latency-Ms |
Route call latency in milliseconds |
X-Attempts |
Total attempts before success |
Endpoints
| Method | Path | Purpose |
|---|---|---|
GET |
/health |
Service health + route list |
POST |
/triage |
Classify a ticket (with automatic failover) |
GET |
/chaos/status |
Which routes are currently forced to fail |
POST |
/chaos/clear |
Reset the chaos set |
Run with real Bedrock
export AWS_ACCESS_KEY_ID=...
export AWS_SECRET_ACCESS_KEY=...
export AWS_DEFAULT_REGION=us-east-1
unset BEDROCK_DEMO_DRY_RUN
uvicorn demo.app:app --reload
Run with Docker
docker build -t bedrock-demo -f demo/Dockerfile .
docker run -p 8000:8000 bedrock-demo
Full demo docs: demo/README.md
## CloudWatch telemetry
Pass a `CloudWatchMetricsEmitter` to get structured metrics on every invocation.
```python
from bedrock_fallback_engine import (
BedrockFallbackEngine,
CloudWatchMetricsEmitter,
)
metrics = CloudWatchMetricsEmitter(namespace="MyApp/Bedrock", flush_size=20)
engine = BedrockFallbackEngine(schema=Triage, metrics=metrics)
engine.invoke([...]) # metrics emitted automatically
Metrics emitted:
| Metric | Unit | Dimensions |
|---|---|---|
InvocationCount |
Count | RouteLabel, Region, ModelId, Status |
InvocationLatency |
Milliseconds | RouteLabel, Region, ModelId |
ThrottleCount |
Count | RouteLabel, Region, ModelId |
TransientErrorCount |
Count | RouteLabel, Region, ModelId |
FallbackTriggered |
Count | FromRoute, ToRoute |
RouteExhausted |
Count | — |
InputTokens |
Count | RouteLabel, Region, ModelId |
OutputTokens |
Count | RouteLabel, Region, ModelId |
Telemetry is best-effort — if CloudWatch is unreachable, the engine keeps working. Buffer auto-flushes at flush_size points. Call metrics.flush() explicitly at shutdown to drain the buffer.
Example CloudWatch Insights query
fields @timestamp, RouteLabel, FromRoute, ToRoute
| filter MetricName = "FallbackTriggered"
| sort @timestamp desc
AWS-native emergency fallback
The default route chain includes two AWS-native resilience tiers:
- Cross-region inference profile —
us.anthropic.claude-3-5-sonnet-20241022-v2:0. Bedrock auto-routes this across any US region with capacity. Platform-level resilience. - Emergency Titan route —
amazon.titan-text-premier-v1:0. Amazon's own model. Rarely throttled, always available.
No local AI. No external service. Pure Bedrock → Bedrock → Bedrock failover.
Design principles
- Zero surprises. Route order is explicit; failover is deterministic.
- Dependency-light. Only
boto3andpydanticat runtime. - Two loops, clearly separated. Retries happen within a route; failover happens between routes.
- Testable. Inject a fake client factory; no AWS calls in the test suite.
- Observable. Every route attempt, retry, failure, and success emits an event you can hook into CloudWatch, Prometheus, or a logger.
How retries vs. failover work
Route 1 (us-east-1, Claude)
├─ attempt 1 fail
├─ attempt 2 fail (retry within route)
└─ attempt 3 fail → give up on route
Route 2 (us-west-2, Claude)
└─ attempt 1 success ← returned
Retries (max_retries) apply inside each route. Failover is between routes. When a route can't be saved, the engine doesn't retry it forever — it moves on.
Roadmap
- Cross-region / cross-model failover
- Schema validation with failover
- Structured error trail
- Token usage per invocation
- CloudWatch metrics emission
- Local emergency fallback (Ollama)
- Lambda deployment packaging
License
MIT — see LICENSE.
Metadata
Release files for bedrock-fallback-engine 0.2.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| bedrock_fallback_engine-0.2.1.tar.gz | 22.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| bedrock_fallback_engine-0.2.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 40.5 kB
Release files / bedrock_fallback_engine-0.2.1.tar.gz
| Download URL | bedrock_fallback_engine-0.2.1.tar.gz |
|---|---|
| Size | 22.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
03f1f09faf8bb30bca2415ee797b7960267697563bbd0e2f4e8db4481f3cab97
|
|
BLAKE2b-256 checksum How to use checksums |
62dfcce141fab9923db8bff98dcaf3b7d4e064ac265f9604b9f2dd1b467d117c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 15, 2026.
Transparency logRelease files / bedrock_fallback_engine-0.2.1-py3-none-any.whl
| Download URL | bedrock_fallback_engine-0.2.1-py3-none-any.whl |
|---|---|
| Size | 17.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
2369189ea66b0a4ee074532aa8d0ca5e58962396733850e4ddf4867b2ed468a2
|
|
BLAKE2b-256 checksum How to use checksums |
36449fac0cb10f1dd584552050ab803725acd1821ce3ce507515fe6a1293b7ad
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 15, 2026.
Transparency log