AWS AI
The equivalent build on AWS: Bedrock, SageMaker, Textract, Transcribe and Kendra.
AWS splits the same ground differently. Bedrock is the managed foundation-model service, SageMaker is where you train and host your own, and a set of task services covers speech, vision, documents and search. The design process from Designing AI solutions is unchanged. Only the service names move.
The service map
| Need | AWS service | Azure equivalent |
|---|---|---|
| Text generation and reasoning | Amazon Bedrock | AI Foundry model catalogue |
| Train and host your own model | Amazon SageMaker AI | Azure Machine Learning |
| Speech to text | Amazon Transcribe | AI Speech |
| Text to speech | Amazon Polly | AI Speech |
| Image analysis and moderation | Amazon Rekognition | AI Vision |
| Structured extraction from forms | Amazon Textract | Document Intelligence |
| Entities, sentiment, PII in text | Amazon Comprehend | AI Language |
| Managed enterprise search | Amazon Kendra | AI Search |
| Vector store for RAG | OpenSearch Serverless, Aurora pgvector | AI Search vector index |
| Managed RAG pipeline | Bedrock Knowledge Bases | AI Search + Foundry |
| Output filtering and policy | Bedrock Guardrails | Content Safety |
Calling Bedrock
The converse API normalises across model families, so moving between Claude, Llama, Mistral and Nova is a model-id change rather than a rewrite. Prefer it to the older invoke API.
import boto3
bedrock = boto3.client("bedrock-runtime", region_name="eu-central-1")
response = bedrock.converse(
modelId="anthropic.claude-sonnet-4-20250514-v1:0",
system=[{"text": "You are an AI assistant that helps solve problems."}],
messages=[
{"role": "user", "content": [{"text": "Summarise this clinic note."}]}
],
inferenceConfig={"maxTokens": 800, "temperature": 0},
)
print(response["output"]["message"]["content"][0]["text"])
print(response["usage"])Request model access first
AccessDeniedException that is a permissions checkbox, not a bug.Streaming uses a separate call and yields chunks as they are produced:
stream = bedrock.converse_stream(
modelId=MODEL_ID,
messages=[{"role": "user", "content": [{"text": prompt}]}],
)
for event in stream["stream"]:
if "contentBlockDelta" in event:
print(event["contentBlockDelta"]["delta"]["text"], end="", flush=True)Retrieval on AWS
There are two routes, and the choice is mostly about how much control you want over chunking.
Bedrock Knowledge Bases (managed)
Point it at an S3 prefix, choose an embedding model and a vector store, and it handles chunking, embedding and sync. Then retrieve_and_generate does retrieval and generation in one call, returning citations. Fastest path to a working RAG system.
agent = boto3.client("bedrock-agent-runtime", region_name="eu-central-1")
response = agent.retrieve_and_generate(
input={"text": "What is the screening interval for retinopathy?"},
retrieveAndGenerateConfiguration={
"type": "KNOWLEDGE_BASE",
"knowledgeBaseConfiguration": {
"knowledgeBaseId": KB_ID,
"modelArn": MODEL_ARN,
},
},
)
print(response["output"]["text"])
for citation in response["citations"]:
for ref in citation["retrievedReferences"]:
print(ref["location"]) # always show where it came fromYour own index (full control)
Embed with Bedrock, store in OpenSearch Serverless or Postgres with pgvector, and write the retrieval yourself. Choose this when your chunking is domain-specific (legal clauses, Hadith isnad chains, clinical sections) and a generic splitter would cut across meaning.
Speech
transcribe = boto3.client("transcribe")
transcribe.start_transcription_job(
TranscriptionJobName=job_name,
Media={"MediaFileUri": "s3://clinic-audio/consultation.m4a"},
MediaFormat="m4a",
IdentifyLanguage=True, # or LanguageCode="en-NG"
Settings={
"ShowSpeakerLabels": True,
"MaxSpeakerLabels": 2,
"VocabularyName": "clinical-terms", # domain terms, biggest accuracy win
},
)Transcribe is asynchronous for files: you start a job and poll or take an EventBridge event. Use the streaming API for live audio. Polly handles synthesis and accepts the same SSML you would write for Azure.
Custom vocabulary is the highest-leverage setting
Documents and vision
Textract is the Document Intelligence equivalent. Analysis with the QUERIES feature is usually what you want: you ask for the fields you need in plain language rather than mapping a whole form.
textract = boto3.client("textract")
response = textract.analyze_document(
Document={"S3Object": {"Bucket": "invoices", "Name": "invoice-1234.pdf"}},
FeatureTypes=["QUERIES", "FORMS", "TABLES"],
QueriesConfig={
"Queries": [
{"Text": "What is the invoice total?", "Alias": "total"},
{"Text": "Who is the vendor?", "Alias": "vendor"},
{"Text": "What is the due date?", "Alias": "due_date"},
]
},
)
for block in response["Blocks"]:
if block["BlockType"] == "QUERY_RESULT":
# Confidence is per answer; route the weak ones to a human.
print(block["Text"], round(block["Confidence"], 1))Rekognition covers labels, moderation, text in images and faces. As on Azure, the face capabilities carry the heaviest compliance burden, and several jurisdictions regulate them specifically.
Guardrails
Bedrock Guardrails apply policy independently of the model: denied topics, word filters, PII redaction and contextual grounding checks. The grounding check is the useful one for RAG: it scores whether the answer is actually supported by the passages you supplied.
response = bedrock.converse(
modelId=MODEL_ID,
messages=messages,
guardrailConfig={
"guardrailIdentifier": GUARDRAIL_ID,
"guardrailVersion": "DRAFT",
"trace": "enabled", # log why something was blocked
},
)Start in report mode
Cost control
- On-demand to start. Provisioned throughput only makes sense at sustained, predictable volume, because it bills whether you use it or not.
- Cap
maxTokenson every call. The single most effective control. - Route by difficulty. Classification on a small model, escalating to a larger one only when needed.
- Set a Budgets alert at a fraction of expected monthly spend, going to a human who will act.
- Watch idle infrastructure. SageMaker endpoints, OpenSearch collections and Kendra indexes bill continuously.
Region matters twice
Deciding between the two: Choosing a platform.
