Claude on AWS Bedrock is the LLM integration we default to for production workflow automation. Not because of hype — because it fits the operational constraints that matter: it runs in your AWS account, data doesn't leave your VPC, IAM controls access, and you're not managing API keys in plaintext.
This post covers Claude Bedrock workflow automation from the integration patterns that work in production to the failure modes you need to handle before you ship.
Why Bedrock Over Direct API
For teams already running on AWS, Bedrock has structural advantages over calling Anthropic's API directly:
Data residency: Inference happens in your AWS region. Input data doesn't transit to external API endpoints.
IAM integration: Bedrock access is controlled via IAM policies. No API key management, no rotation overhead, no key leakage risk.
Cost predictability: Bedrock pricing is per-token, same model, billed to your AWS account alongside other infrastructure. One consolidated bill.
VPC compatibility: Bedrock can be accessed from Lambda within a VPC. For regulated environments, this matters.
Audit trail: CloudTrail logs Bedrock API calls. You know who called what model when.
The trade-offs: Bedrock model availability lags Anthropic's direct API by weeks or months. New Claude versions aren't available on day one. For most production use cases, the stability of existing models is a feature not a bug.
Claude Bedrock Workflow Automation: Core Integration
Basic Invocation
import boto3
import json
bedrock = boto3.client("bedrock-runtime", region_name="us-east-1")
def invoke_claude(prompt: str, max_tokens: int = 2048) -> str:
response = bedrock.invoke_model(
modelId="anthropic.claude-3-sonnet-20240229-v1:0",
body=json.dumps({
"anthropic_version": "bedrock-2023-05-31",
"max_tokens": max_tokens,
"messages": [
{"role": "user", "content": prompt}
]
}),
contentType="application/json",
accept="application/json"
)
body = json.loads(response["body"].read())
return body["content"][0]["text"]
For workflow automation, you almost always want structured output. Use JSON schema in your prompt and validate the response:
def extract_structured(document_text: str, schema: dict) -> dict:
prompt = f"""
Extract information from the following document and return valid JSON
matching this schema: {json.dumps(schema)}
If a field cannot be found, use null.
Return JSON only, no explanation.
Document:
{document_text}
"""
raw = invoke_claude(prompt)
# Strip markdown code blocks if present
if raw.startswith(" "): raw = raw.split("```")[1] if raw.startswith("json"): raw = raw[4:]
try:
result = json.loads(raw.strip())
return validate_against_schema(result, schema)
except json.JSONDecodeError as e:
# Log and route to human review
raise ExtractionError(f"Model returned non-JSON: {raw[:200]}")
### System Prompts for Workflow Context
For workflow automation, system prompts define the model's role and constraints:
python def build_extraction_prompt(system_context: str, document: str, fields: list) -> list: return [ { "role": "user", "content": [ { "type": "text", "text": f"System context: {system_context}\n\nDocument:\n{document}\n\nExtract: {fields}" } ] } ]
Keep system prompts specific and constrained. "You are a helpful assistant" produces worse extraction results than "You are extracting invoice fields. Return only the requested JSON. Do not add commentary."
### Streaming for Long Documents
For documents where latency matters and content is long, use the streaming API:
python def invoke_claude_streaming(prompt: str) -> str: response = bedrock.invoke_model_with_response_stream( modelId="anthropic.claude-3-sonnet-20240229-v1:0", body=json.dumps({ "anthropic_version": "bedrock-2023-05-31", "max_tokens": 4096, "messages": [{"role": "user", "content": prompt}] }) )
full_response = ""
for event in response["body"]:
chunk = json.loads(event["chunk"]["bytes"])
if chunk.get("type") == "content_block_delta":
full_response += chunk["delta"].get("text", "")
return full_response
### Bedrock Agents vs. Direct Model Invocation
Bedrock Agents is AWS's managed agent framework. It handles tool use, session memory, and action group execution. For complex multi-step workflows, it reduces boilerplate.
We use Bedrock Agents when:
- The workflow requires the model to decide which tool to call (tool selection)
- Session context needs to persist across invocations
- You want AWS to manage the agent loop
We use direct model invocation when:
- The workflow is deterministic (always call extraction, then validation, then routing)
- We need maximum control over prompt construction
- Latency is critical and agent overhead matters
For most business document processing, direct invocation with deterministic pipeline orchestration (Step Functions) outperforms Bedrock Agents in both latency and debuggability.
## Handling Failures in Production
### Token Limit Errors
Long documents exceed context windows. Handle this proactively:
python def chunk_document(text: str, max_chars: int = 150000) -> list[str]: """Claude 3 Sonnet: ~200K token context. ~4 chars/token rough estimate.""" if len(text) <= max_chars: return [text]
# Split on logical boundaries (paragraphs, pages)
chunks = []
current = ""
for paragraph in text.split("\n\n"):
if len(current) + len(paragraph) > max_chars:
chunks.append(current)
current = paragraph
else:
current += "\n\n" + paragraph
if current:
chunks.append(current)
return chunks
### Throttling and Rate Limits
Bedrock has service quotas per account per region. For high-volume workflows, request limit increases via AWS Support. In the interim, implement exponential backoff:
python import time from botocore.exceptions import ClientError
def invoke_with_retry(prompt: str, max_retries: int = 3) -> str: for attempt in range(max_retries): try: return invoke_claude(prompt) except ClientError as e: if e.response["Error"]["Code"] == "ThrottlingException": wait = (2 ** attempt) + random.uniform(0, 1) time.sleep(wait) else: raise raise MaxRetriesExceeded() ```
Non-JSON Responses
Even with explicit JSON instructions, models occasionally produce non-JSON output (especially on edge-case inputs). Always wrap parsing in try/except and route failures to a DLQ or human review queue — never crash the pipeline.
Cost Management
Bedrock costs are token-based. For high-volume workflows:
- Minimize prompt length: Long prompts with boilerplate cost money at scale. Trim system prompts, use references instead of repeating context.
- Cache repeated prompts: For identical prompts (e.g., same system prompt + same document type), Bedrock prompt caching reduces cost on cached tokens.
- Right-size the model: Claude Haiku is significantly cheaper than Sonnet. For classification tasks, Haiku often performs comparably. Run evals on your specific task before defaulting to Sonnet.
- Monitor token usage: Log input and output token counts per invocation. CloudWatch metrics + cost allocation tags give you per-workflow cost visibility.
Production Deployment Patterns
Lambda + Bedrock: The standard pattern. Lambda handles orchestration, Bedrock handles inference. IAM role grants bedrock:InvokeModel permission. No secrets to manage.
ECS/Fargate + Bedrock: For long-running extraction tasks that exceed Lambda's 15-minute limit. Task role grants Bedrock access.
Batch processing via SQS: SQS queue + Lambda consumer with concurrency limits prevents Bedrock throttling on traffic spikes.
For the full infrastructure patterns, see our AWS Cloud Infrastructure capability and our guide on AI workflow automation for business.
Frequently Asked Questions
Is data sent to Anthropic when using Bedrock?
No. When using Amazon Bedrock, your data is processed within AWS infrastructure and is not used to train foundation models without your consent. AWS maintains the infrastructure; Anthropic provides the model weights. Your inputs don't leave your AWS account during inference.
Which Claude model should we use on Bedrock?
For document extraction: Claude 3 Sonnet balances accuracy and cost well. For simple classification: Claude 3 Haiku is faster and cheaper with comparable accuracy on focused tasks. For complex reasoning tasks: Claude 3 Opus where available. Always benchmark on your actual task and data before committing to a model.
How do we handle PII in documents processed through Bedrock?
PII in documents is a data handling question, not just a Bedrock question. Best practice: encrypt documents at rest in S3, use VPC endpoints for Bedrock API calls, log prompts and responses in CloudWatch Logs with appropriate retention policies, and ensure your Bedrock inference region matches your data residency requirements.
Can Bedrock workflows integrate with on-premise systems?
Yes, via API. Lambda in a VPC with VPN or Direct Connect access to on-premise systems bridges cloud inference to on-prem data stores and ERPs. For read-heavy workflows, consider replicating necessary data to RDS or DynamoDB to reduce on-prem API latency.
What's the latency of Bedrock Claude invocations?
Claude 3 Sonnet on Bedrock typically returns in 2–8 seconds for standard document extraction prompts (varies with input/output length and current load). For latency-sensitive workflows, optimize prompt length, use streaming, and consider Haiku for tasks where accuracy trade-off is acceptable.
Discuss your automation project → rutagon.com/contact | 907-841-8407 | contact@rutagon.com