Skip to content

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

NeedAWS serviceAzure equivalent
Text generation and reasoningAmazon BedrockAI Foundry model catalogue
Train and host your own modelAmazon SageMaker AIAzure Machine Learning
Speech to textAmazon TranscribeAI Speech
Text to speechAmazon PollyAI Speech
Image analysis and moderationAmazon RekognitionAI Vision
Structured extraction from formsAmazon TextractDocument Intelligence
Entities, sentiment, PII in textAmazon ComprehendAI Language
Managed enterprise searchAmazon KendraAI Search
Vector store for RAGOpenSearch Serverless, Aurora pgvectorAI Search vector index
Managed RAG pipelineBedrock Knowledge BasesAI Search + Foundry
Output filtering and policyBedrock GuardrailsContent 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.

py
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

Bedrock models are not enabled by default. Each family has to be requested per account and per region in the console before any call succeeds, and some require accepting supplier terms. Do this on day one. Teams routinely lose an afternoon to an AccessDeniedException that is a permissions checkbox, not a bug.

Streaming uses a separate call and yields chunks as they are produced:

py
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.

py
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 from

Your 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

py
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

Proper nouns, drug names and place names are where transcription accuracy collapses, and they are usually the words carrying the meaning. Twenty relevant terms in a custom vocabulary beats any amount of post-processing.

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.

py
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.

py
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

A guardrail set to block on day one will refuse things you did not intend, and you will hear about it from users rather than from your dashboard. Run it in report mode for a week and read what it catches first.

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 maxTokens on 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

Model availability varies by region, and so does data residency. Confirm the model you chose is offered in the region your data is allowed to live in, before you build around it.

Deciding between the two: Choosing a platform.