AI EngineeringZero to ProductionHome·About·What’s new·Contact
AWS AI Automation · Chapter W11

Vision & speech

Rekognition, Transcribe, Translate, and Polly are narrow, cheap media APIs. Compose them with Claude for reasoning and you have a full vision/speech automation.

⏱️ ~1.5 hours🧪 3 labs🎯 Intermediate
⚙️ To run this for realThe code here is complete and correct as written. To actually execute it you'll need:
  • AWS credentials (aws configure) + pip install boto3
Reading and learning works without any of this — run when you're ready. IDs/ARNs in examples are placeholders; swap in your own.

Learning objectives

  • Detect labels, text, and moderation flags in images with Rekognition.
  • Transcribe audio to text and translate it across languages.
  • Synthesize speech with Polly.
  • Compose these into a media-processing automation.
▶ Runnable companionEvery code block in this lesson is also a standalone file under code/aws11-media-vision-speech/ in the course, with a README on how to run it. Read here, run there — no need to copy-paste.

The media AI services intermediate

Here's the trap almost every team falls into first. A product manager says "we need the app to understand photos and voice notes," and an engineer immediately starts scoping a computer-vision model to train, a speech dataset to label, GPUs to rent. Three months and a five-figure bill later they have something that recognises cats 80% of the time. Meanwhile the competitor shipped the same feature in an afternoon by calling four AWS APIs that already do all of that — trained on far more data than any single team could gather. The lesson of this chapter: for common vision and speech tasks, you almost never build the model. You rent one, by the request.

Think of these four services as specialist contractors you can hire by the minute. You don't teach the electrician how electricity works; you hand them the job and they hand you back the result. Rekognition looks at a picture and tells you what's in it. Transcribe listens to audio and writes down the words. Translate takes text in one language and rewrites it in another. Polly takes text and speaks it aloud. Each does exactly one narrow thing, extremely well, for a fraction of a cent per call — because AWS amortises the training cost across millions of customers.

So why is this chapter in an AI engineering course if you're not building the models? Because the interesting work has moved up a level. These services are perception — they turn the messy analog world (pixels, sound waves) into clean text and structured data. But they don't reason. Rekognition can tell you a photo contains a "Truck" and a "Person"; it can't tell you "this looks like an insurance claim for a rear-end collision." That reasoning step is where Claude comes in. The pattern you'll build over and over: a narrow perception API converts the world into text, then an LLM reasons over that text, then maybe another narrow API turns the answer back into speech or another language. The engineering skill isn't training models — it's composing them.

The common mistake here is reaching for the LLM too early. Claude can read an image directly (you saw that in the Files/vision chapters), so why bother with Rekognition? Because for high-volume, well-defined tasks — moderating ten thousand uploads a day, transcribing every support call — a purpose-built service is cheaper, faster, and returns structured fields (confidence scores, timestamps, bounding boxes) that an LLM's free-text answer won't. Use the cheap narrow service for the bulk perception work; save the expensive general reasoner for the judgement calls. Knowing which tool to point at which part of the problem is the whole game.

Four pay-per-use services cover vision and speech: Rekognition (images/video), Transcribe (speech→text), Translate (text→text across languages), and Polly (text→speech). Like Textract, they are narrow and cheap — and pair well with Claude for the reasoning step.

the pattern the whole chapter circles: perception → reasoning → back to the world PERCEPTION world → text Rekognition · image Transcribe · speech Claude reasons over text OUTPUT text → world Translate · language Polly · speech the automation IS the composition You don't build the models — you compose them. Narrow perception APIs turn pixels and sound into text, Claude does the reasoning that the narrow services can't, and output APIs turn the answer back into speech or another language.
🗺️ How to read this diagram

Read left to right: the world enters on the left as pixels and sound, flows through Claude in the middle, and leaves on the right transformed back into something a human hears or reads.

  • The blue "perception" boxes on the left are the narrow services that convert the messy analog world into clean text — Rekognition for images, Transcribe for speech. They see, but they don't reason.
  • The purple box in the middle is Claude, doing the one thing the narrow services can't: reasoning over the text they produced ("this looks like a rear-end collision claim").
  • The green "output" boxes on the right turn Claude's answer back into the human's world — Translate into another language, Polly into spoken audio. The amber bar underneath is the payoff: no single model does all this, the automation is how you wire the narrow pieces together.

In short: rent narrow services for perception and output, spend the expensive general reasoner only on the judgement in the middle.

Rekognition: see an image intermediate

Picture the feature nobody wants to own. Your app lets users upload profile photos, and one morning a moderator's queue has ten thousand new images in it. A human can eyeball maybe a few hundred an hour before their judgement goes numb — and the ones you actually need to catch (violence, nudity, hate symbols) are buried in a sea of selfies and sunsets. You cannot hire your way out of this, and you cannot ignore it, because the one bad image you miss is the one that ends up on the news. This is the problem Rekognition was built for: machine vision that never gets tired.

The mental model is simple. Rekognition is a pre-trained pair of eyes attached to an API. You show it an image (by pointing at a file already sitting in S3, or by sending the raw bytes), and it hands back a list of labels — the things it recognises — each with a confidence score from 0 to 100. "Truck, 99.4" means it is 99.4% sure the picture contains a truck. You didn't train it; AWS did, on a corpus larger than any one company could assemble. That's the entire value proposition: object and scene recognition you can add in an afternoon, priced per image.

Notice the two different questions the lab asks the same image, because they map to two different jobs. detect_labels answers "what is in this picture?" — useful for auto-tagging a photo library, routing an image to the right team, or making a catalogue searchable. detect_moderation_labels is a separate call that answers "is this picture safe?" — it looks specifically for categories like violence, nudity, and drugs. Keeping these as distinct calls is deliberate: tagging and safety are different concerns with different cost and latency budgets, and you often want the moderation gate to run on every upload while richer labelling runs only when needed.

The confidence score is where beginners go wrong, so anchor on it now. A label is not a yes/no fact; it is a probability. If you treat "Weapon, 55.0" the same as "Weapon, 99.0" you will either block innocent photos or wave through dangerous ones. The correct pattern — which the practice ladder makes you build — is to pick a threshold (say, 80.0) and turn the score into a decision: flag anything at or above the line, let the rest through. Choosing that threshold is a product judgement, not an AWS setting: a children's app sets it low and paranoid; a photo-editing tool sets it high and permissive. Rekognition gives you the number; you own where the line sits.

Rekognition returns probabilities, not verdicts — YOU pick the line that becomes a decision Weapon · 92.5 a per-label score Tobacco · 40.0 a per-label score threshold ≥ 80.0 ? BLOCK · flagged PUBLISH · passes at/above below move the line → change the app's tolerance The threshold, not the score, is the decision. Rekognition hands back a probability per label; a line you choose (here 80.0) is what turns "Weapon · 92.5" into a hard block and lets "Tobacco · 40.0" through.
🗺️ How to read this diagram

Follow one label left to right: it starts as a raw score, meets the threshold in the middle, and ends as a block-or-publish verdict on the right.

  • The two white boxes on the left are labels Rekognition returned, each carrying a confidence from 0–100 — a probability, not a yes/no fact.
  • The amber box is the single comparison you own: is the score at or above the line (80.0)? Everything hinges on this one number.
  • The solid arrow to the red "BLOCK" box is a score that met the line; the dashed arrow to the green "PUBLISH" box is one that fell below. The blue bar underneath is the point: moving that line up or down is how a paranoid children's app and a permissive photo tool use the same API differently.

In short: the API gives you a number; the threshold you pick is what makes it a decision.

Example code for learning — review, test, and adapt it before running against real or production systems. Commands can create, change, or delete resources. See the Terms & Disclaimer.
Lab W11.1
rekognition.pyimport boto3
rek = boto3.client("rekognition", region_name="us-east-1")
img = {"S3Object": {"Bucket": "my-media", "Name": "photo.jpg"}}

labels = rek.detect_labels(Image=img, MaxLabels=5)
print([(l["Name"], round(l["Confidence"], 1)) for l in labels["Labels"]])

# content moderation — useful as an upload guardrail
mod = rek.detect_moderation_labels(Image=img)
print("flagged:", [m["Name"] for m in mod["ModerationLabels"]])
[('Truck', 99.4), ('Vehicle', 99.4), ('Wheel', 92.1), ('Machine', 92.1), ('Person', 88.0)]
flagged: []
▶ How this works

Rekognition is AWS's vision service: you hand it an image and it tells you what's in the picture. This block points it at a photo sitting in an S3 bucket (AWS's file storage) and asks two questions — "what objects are here?" and "is this image safe?". Nothing is trained by you; the model already knows how to recognise common things.

  1. boto3.client("rekognition", region_name="us-east-1") creates the connection object (AWS calls it a client) that lets Python send commands to Rekognition in the us-east-1 data-centre region. boto3 is the official AWS library for Python.
  2. img = {"S3Object": {"Bucket": "my-media", "Name": "photo.jpg"}} does not upload anything — it just says "the picture is the file photo.jpg inside the bucket named my-media". You point at where the file already lives.
  3. rek.detect_labels(Image=img, MaxLabels=5) asks "what do you see?" and caps the answer at the 5 most confident guesses. Each guess ("label") comes with a confidence score from 0–100.
  4. The print([(l["Name"], round(l["Confidence"], 1)) ...]) line loops over the labels and builds a tidy list of (name, confidence) pairs, rounding the score to one decimal.
  5. rek.detect_moderation_labels(Image=img) is a separate check for unsafe content (violence, nudity, etc.). It returns a list of flags — useful as a guardrail before you let a user's upload through.

What the output means: The first line lists what Rekognition saw, most-confident first — e.g. ('Truck', 99.4) means it is 99.4% sure the photo contains a truck. flagged: [] is an empty list, meaning nothing was flagged as unsafe — the image passed the moderation check.

Try this: Change MaxLabels=5 to MaxLabels=2 and you'll get only the top two guesses. A non-empty flagged list is your cue to reject or hold the upload for review.

Transcribe + Translate advanced

The bug that catches everyone the first time looks like this: you write result = transcribe(audio), print result, and get back… almost nothing. No transcript, no error, just an object saying the job is "IN_PROGRESS." You assume the call failed and start debugging code that was working fine. The real issue is that you expected a synchronous answer from an asynchronous service — and the difference between those two words is the single most important idea in this section.

Here's the analogy that makes it stick. Translate is a vending machine: you put in an English sentence, the Spanish one drops out immediately, in the same call. Transcribe is a laundromat: you drop off your audio, get a ticket (the job name), leave, and come back later to collect the finished transcript. Nothing is wrong when the drop-off returns without your clothes — that's how it's supposed to work. Long audio can take seconds or minutes to process, far longer than a single web request should block, so Transcribe deliberately hands the work off to the background and lets you check on it later.

That "check on it later" is called polling, and it's a pattern you'll use for every long-running AWS job. You call start_transcription_job with a unique name and the S3 location of the audio; it returns right away. Then you call get_transcription_job in a loop, reading its status field, until that status flips to COMPLETED (or FAILED). Only then is the transcript ready — and even then it isn't in the response, it's written to a file in S3 that you fetch separately. Three steps, not one: start, poll, fetch. Beginners write the first step and wonder where their text went.

Why does the design split perception (Transcribe) from language (Translate) into two services at all? Because they're different problems with different shapes. Turning sound waves into words is a heavy, batch-friendly job that benefits from running async on big audio files. Rewriting finished text from one language to another is light and instant. Chaining them — transcribe a call, then translate the transcript — is a taste of the composition pattern that runs through this whole chapter: each service does one narrow thing, and the automation is how you wire them together. The common trap is forgetting the language codes differ between services (Transcribe wants a locale like en-US; Translate wants a bare language like en), a small mismatch that produces confusing errors until you notice it.

Transcribe is asynchronous: start a job over an S3 audio file, poll for completion, read the transcript from S3. Then Translate can localize it.

Translate — synchronous one call, answer in the reply translate_text TranslatedText right away Transcribe — asynchronous start · poll · fetch (3 steps) start_transcription returns instantly get_transcription poll status… read from S3 transcript JSON loop until COMPLETED Vending machine vs. laundromat. Translate hands back the answer in the same call; Transcribe is three steps — start the job, poll get_transcription_job until COMPLETED, then fetch the transcript from S3.
🗺️ How to read this diagram

Compare the two halves side by side: the left is the simple one-call service, the right is the multi-step one — the whole point is how different their shapes are.

  • On the left, one blue box (translate_text) feeds straight into a green box (TranslatedText): the answer arrives in the very same call. That is synchronous — the vending machine.
  • On the right, the flow drops downward through three boxes: start the job (returns instantly, empty-handed), the amber poll box with a dashed loop-back arrow meaning "keep asking until status reads COMPLETED," and only then the green "read from S3" box where the transcript actually lives.
  • The payoff is the shape difference: the transcript is never in the start call's reply — that green box being separate is exactly the bug that catches everyone the first time.

In short: Translate = one call; Transcribe = start, poll, fetch — design for the wait.

Lab W11.2
transcribe.pyimport boto3
ts = boto3.client("transcribe", region_name="us-east-1")

ts.start_transcription_job(
    TranscriptionJobName="call-001",
    Media={"MediaFileUri": "s3://my-media/support-call.mp3"},
    MediaFormat="mp3",
    LanguageCode="en-US",
)
# poll get_transcription_job until COMPLETED, then fetch the transcript JSON from S3

tr = boto3.client("translate", region_name="us-east-1")
out = tr.translate_text(Text="The database is down.",
                        SourceLanguageCode="en", TargetLanguageCode="es")
print(out["TranslatedText"])
La base de datos está caída.
▶ How this works

This block chains two services. Transcribe turns recorded speech into written text (speech-to-text). Translate then rewrites text from one language into another. Transcribe is asynchronous: instead of answering instantly, you start a job, it works in the background, and you check back later — the way you might drop clothes at a laundromat and return when they're done.

  1. ts.start_transcription_job(...) kicks off the job. TranscriptionJobName="call-001" is a label you invent so you can find this job again later. Media={"MediaFileUri": "s3://..."} tells it which audio file (in S3) to transcribe.
  2. MediaFormat="mp3" and LanguageCode="en-US" tell it the file is an MP3 and the speech is US English — hints that make transcription more accurate.
  3. The comment line # poll get_transcription_job until COMPLETED marks the step you'd normally add: polling means repeatedly asking "done yet?" in a loop until the status reads COMPLETED, then reading the finished transcript (returned as a JSON file in S3).
  4. tr = boto3.client("translate", ...) opens a second client, this time for Translate.
  5. tr.translate_text(Text="The database is down.", SourceLanguageCode="en", TargetLanguageCode="es") asks it to turn that English sentence (en) into Spanish (es). The result lives under the "TranslatedText" key of the reply.

What the output means: La base de datos está caída. — the Spanish translation of "The database is down." Only the Translate step prints here; the Transcribe job runs in the background and is fetched separately.

Try this: Change TargetLanguageCode="es" to "fr" (French) or "de" (German) and re-run just the Translate part. The source stays English; only the target language changes.

Polly: speak expert

Consider the accessibility ticket you can't close by hand. Your product needs to speak — status alerts read aloud in a warehouse where nobody can look at a screen, an IVR phone menu, a language-learning app that pronounces words, an audiobook version of every article. The old way was to book a voice actor, record every phrase, and re-record the whole library every time the copy changed. That doesn't scale to text that's generated on the fly, and it certainly doesn't scale to the summaries a Claude-powered agent writes in real time. You need speech you can generate from arbitrary text, on demand. That's Polly.

Polly is the exact mirror image of Transcribe, and holding the two side by side is the fastest way to understand both. Transcribe goes sound → text; Polly goes text → sound. You hand it a string, tell it which voice to use (Polly ships dozens — Joanna and Matthew are US-English, and there are voices across many languages) and what audio format you want, and it synthesises spoken audio. Unlike Transcribe it answers synchronously — the vending-machine model again — so there's no job to poll.

The one detail that trips people up is what comes back: not a file, not a URL, but a stream of raw bytes. Audio isn't text, so you can't just print it or write it in text mode. The idiom is to open a file in binary mode ("wb"), .read() the whole audio stream, and write those bytes to disk as, say, an .mp3. Get the mode wrong — open in text mode — and Python will mangle the bytes and hand you a file that won't play. That "binary, not text" instinct is worth internalising; it applies to every media byte-stream you'll ever save.

Zoom back out and Polly is the closing bracket on the pattern this chapter keeps circling. The perception services (Transcribe, Rekognition) turn the world into text; Claude reasons over the text; Polly (and Translate) turn the answer back into the human's world — spoken, and in their language. That's what the "full loop" callout below is describing: transcribe a call, analyse it, summarise it with Claude, then speak the summary back. Each service is one narrow contractor; the product is the composition. Once you can see that shape, you can assemble a startling amount of "AI" functionality out of a handful of cheap, boring, reliable APIs — which is exactly how real production systems are built.

the full loop — a support call in, a spoken summary out Transcribe call → text Comprehend sentiment + PII Claude summarize Polly text → speech perception reasoning output W13 wires this with Step Functions Four narrow contractors, one pipeline. Transcribe turns the call into text, Comprehend finds sentiment and PII, Claude writes the summary, and Polly speaks it back — each does one thing, and the automation is the chain.
🗺️ How to read this diagram

Read the four boxes left to right — that is the exact order data flows through the full loop, following the blue arrows.

  • The two blue boxes on the left are perception: Transcribe turns the recorded call into text, then Comprehend reads that text for sentiment and PII (the W10 service).
  • The purple box is the reasoning step: Claude writes the one-paragraph summary — the judgement the narrow services can't do.
  • The green box on the right is output: Polly speaks the summary back to a human. The labels underneath (perception → reasoning → output) show it is the same shape from §services, and the amber bar notes W13 stitches these calls together with Step Functions.

In short: each box is one cheap narrow service; the product is the left-to-right composition.

Lab W11.3
polly.pyimport boto3
polly = boto3.client("polly", region_name="us-east-1")

audio = polly.synthesize_speech(
    Text="Deployment complete. All systems healthy.",
    OutputFormat="mp3", VoiceId="Joanna",
)
with open("status.mp3", "wb") as f:
    f.write(audio["AudioStream"].read())
print("wrote status.mp3")
▶ How this works

Polly is the mirror image of Transcribe: it turns written text into spoken audio (text-to-speech). This block hands Polly a sentence and saves the spoken version as an MP3 file you can play.

  1. polly = boto3.client("polly", region_name="us-east-1") opens the connection to the Polly service.
  2. polly.synthesize_speech(Text="Deployment complete...", OutputFormat="mp3", VoiceId="Joanna") is the core call: give it the words, ask for MP3 audio, and pick a voice — Joanna is one of Polly's built-in US-English voices. It returns the audio as a stream of bytes, not a file yet.
  3. with open("status.mp3", "wb") as f: opens a new file for writing in binary mode ("wb") — binary because audio is raw bytes, not text. The with block automatically closes the file when done.
  4. f.write(audio["AudioStream"].read()) reads all the audio bytes Polly sent back and writes them into status.mp3 on your disk.

What the output means: It prints wrote status.mp3 and leaves a real, playable status.mp3 file in your folder that says "Deployment complete. All systems healthy." in Joanna's voice.

Try this: Swap VoiceId="Joanna" for "Matthew" (a male US voice) and re-run — you'll get the same words in a different voice. Change the Text= to hear your own message.

The full loopTranscribe a support call → Comprehend the sentiment (W10) → summarize with Claude (W2) → Polly a spoken summary. Each service does one thing; the automation is the composition. W13 wires it with Step Functions.

Exercise W11.1 — Voice-of-customer pipeline

Context: Support recordings are a goldmine, but raw audio is unsearchable and full of PII. The voice-of-customer pattern chains media services: transcribe the call, analyze and redact it, summarize it, and even speak the summary back.

Your task: Transcribe a recorded call, detect sentiment and PII with Comprehend, redact the PII, and have Claude write a one-paragraph summary — then output both the text and a Polly audio version.

Requirements:

  • Transcribe the call audio to text (async Transcribe job, then read the transcript)
  • Run Comprehend on the transcript for both sentiment and PII entities
  • Redact the detected PII from the text before it goes any further downstream
  • Have Claude on Bedrock write a one-paragraph summary of the redacted transcript
  • Render that summary to speech with Polly and save the audio
  • Produce both artifacts: the summary text and the audio file

💡 Hint: Redact off the PII spans Comprehend returns before you send anything to Claude, so no personal data reaches the summary or the audio.

🪜 Practice ladder beginner → industry

Six graded exercises, easy to real-world. Try each before opening its solution.

Exercise 1 · Detect labels in an imageBeginner

Context: Rekognition is managed computer vision: hand it an image and it returns what it sees, with a confidence for each label. It is the fastest way to add object/scene understanding without training a model.

Your task: Use Rekognition detect_labels on an S3 image and print each detected label alongside its confidence.

Requirements:

  • Call detect_labels on a boto3 rekognition client with Image pointing at an S3Object
  • Cap the results with MaxLabels so you get the top few, not everything
  • Iterate the returned Labels list
  • Print each label's Name and its Confidence (round the confidence for readability)

💡 Hint: The response is a ranked Labels list; each item already carries both Name and Confidence.

Show solution

Rekognition returns Labels with Name and Confidence; MaxLabels caps the count.

import boto3

rek = boto3.client("rekognition", region_name="us-east-1")
resp = rek.detect_labels(
    Image={"S3Object": {"Bucket": "my-media", "Name": "photo.jpg"}},
    MaxLabels=5,
)
for lbl in resp["Labels"]:
    print(lbl["Name"], round(lbl["Confidence"], 1))
Exercise 2 · Translate text synchronouslyIntermediate

Context: Translate is a synchronous, one-call service: unlike Transcribe it returns immediately, which makes it the simplest building block for reaching users in their own language.

Your task: Use Translate translate_text to convert an English sentence to Spanish and print the translation.

Requirements:

  • Call translate_text on a boto3 translate client
  • Pass the source Text plus SourceLanguageCode="en" and TargetLanguageCode="es"
  • Read the result off the TranslatedText field of the single response
  • Understand this call is synchronous — no job to start or poll

💡 Hint: One request, one response — there is no async job here; the answer is on TranslatedText.

Show solution

Translate is synchronous: one call returns TranslatedText immediately.

import boto3

tr = boto3.client("translate", region_name="us-east-1")
resp = tr.translate_text(
    Text="Where is the nearest station?",
    SourceLanguageCode="en", TargetLanguageCode="es",
)
print(resp["TranslatedText"])
Exercise 3 · Text to speech with PollyAdvanced

Context: Polly is the mirror of Transcribe: it turns text into natural speech. It streams back raw audio bytes, so you choose a voice and format and write the stream to a file yourself.

Your task: Use Polly synthesize_speech to render text to an MP3 and save the returned audio stream to a file.

Requirements:

  • Call synthesize_speech on a boto3 polly client with the Text to speak
  • Request OutputFormat="mp3" and pick a VoiceId (e.g. Joanna)
  • The response's AudioStream is a readable byte stream, not a URL
  • Open a file in binary mode and write AudioStream.read() to disk

💡 Hint: The audio comes back as a streaming body; .read() it and write the bytes with "wb".

Show solution

Polly returns an AudioStream of bytes; pick a VoiceId and write the stream out.

import boto3

polly = boto3.client("polly", region_name="us-east-1")
resp = polly.synthesize_speech(
    Text="Your order has shipped.",
    OutputFormat="mp3", VoiceId="Joanna",
)
with open("out.mp3", "wb") as f:
    f.write(resp["AudioStream"].read())
print("wrote out.mp3")
Exercise 4 · Start an async transcription jobExpert

Context: Long audio can't be transcribed in a single blocking call, so Transcribe is asynchronous: you start a job, it works in the background, and the transcript eventually lands in S3. You must design for the wait.

Your task: Use Transcribe start_transcription_job on an S3 audio file, then show how you check progress with get_transcription_job — making the async, poll-for-completion nature explicit.

Requirements:

  • Call start_transcription_job on a boto3 transcribe client with a unique TranscriptionJobName
  • Point Media.MediaFileUri at the S3 audio and set MediaFormat and LanguageCode
  • Recognize the call returns immediately without a transcript
  • Poll get_transcription_job and read TranscriptionJob.TranscriptionJobStatus until it is COMPLETED
  • Note the finished transcript is delivered to S3, not inline in the start call

💡 Hint: Starting the job and getting the transcript are two different calls; treat status as something you poll, not something you receive up front.

Show solution

Transcribe is asynchronous: start the job, then poll for completion; the transcript lands in S3.

import boto3

ts = boto3.client("transcribe", region_name="us-east-1")
ts.start_transcription_job(
    TranscriptionJobName="call-42",
    Media={"MediaFileUri": "s3://my-media/call.mp3"},
    MediaFormat="mp3", LanguageCode="en-US",
)
# poll until COMPLETED:
job = ts.get_transcription_job(TranscriptionJobName="call-42")
print(job["TranscriptionJob"]["TranscriptionJobStatus"])
Exercise 5 · Content-moderation gate before publishingProfessional

Context: User-generated images are a liability the moment you publish them. Rekognition's moderation labels plus a confidence threshold turn raw scores into a hard publish/block decision — a guardrail you own in code.

Your task: Use Rekognition detect_moderation_labels and refuse to publish an image if any moderation label exceeds a confidence threshold. Keep the gate logic runnable offline.

Requirements:

  • The gate takes a list of moderation labels (each with Name and Confidence) and a threshold (e.g. 80.0)
  • Flag every label whose Confidence meets or exceeds the threshold
  • Return a decision such as {"publish": not flagged, "flagged": […]}
  • Publish only when nothing is flagged
  • Prove it offline with a Rekognition-shaped sample response; note the live call is detect_moderation_labels(…)["ModerationLabels"]

💡 Hint: Separate the decision from the API: the threshold comparison is pure and testable offline, and the real call just supplies the list of labels.

Show solution

Moderation acts as a guardrail; a threshold turns confidence scores into a publish/block decision.

def publish_ok(moderation_labels, threshold=80.0):
    flagged = [m["Name"] for m in moderation_labels
               if m["Confidence"] >= threshold]
    return {"publish": not flagged, "flagged": flagged}

# offline with a sample Rekognition-shaped response:
labels = [{"Name": "Violence", "Confidence": 92.5},
          {"Name": "Tobacco",  "Confidence": 40.0}]
print(publish_ok(labels))   # publish False, flagged ['Violence']
# In prod: rek.detect_moderation_labels(Image=...)['ModerationLabels']
Exercise 6 · A multilingual voice-note support pipelineIndustry scenario

Context: Callers leave voice notes in many languages, and a good support experience answers each one in the caller's own language. That means composing several narrow media services around Claude — speech-to-text, translate, reason, text-to-speech.

Your task: Design the multilingual voice-note pipeline: Transcribe (async) → Translate to English → Claude for intent → Polly reply in the caller's language. Model the language routing and the async wait offline.

Requirements:

  • Lay out the ordered steps as data (service call + its key arguments) so the plan is inspectable
  • Transcribe in the caller's language and show the poll-until-COMPLETED wait as an explicit step
  • Translate caller→English before reasoning, then English→caller before replying
  • Route the Polly VoiceId from the caller's language via a lookup, falling back to a default voice
  • Runs offline — it emits the plan, it does not make AWS calls

💡 Hint: Each stage is a distinct AWS service; model the flow as a list of (step, args) tuples and drive the voice choice off a language→voice map.

Show solution

Compose narrow media services around Claude: speech-to-text, translate, reason, text-to-speech. Each step is a distinct AWS service.

VOICE_BY_LANG = {"es": "Lucia", "en": "Joanna", "de": "Vicki"}

def pipeline_plan(caller_lang):
    return [
        ("transcribe.start_transcription_job", {"LanguageCode": caller_lang}),
        ("poll get_transcription_job", "until COMPLETED"),
        ("translate.translate_text", {"from": caller_lang, "to": "en"}),
        ("bedrock.converse", "classify intent"),
        ("translate.translate_text", {"from": "en", "to": caller_lang}),
        ("polly.synthesize_speech", {"VoiceId": VOICE_BY_LANG.get(caller_lang, "Joanna")}),
    ]

for step, arg in pipeline_plan("es"):
    print(step, arg)

✓ Checkpoint — you can move on when you can…

  • Detect labels and moderation flags with Rekognition.
  • Run an async Transcribe job and translate the result.
  • Synthesize speech with Polly.
  • Compose media services + Claude into one pipeline.

Knowledge check check yourself

✓ Knowledge check

Transcribe and Translate are chained in the lab, but they behave differently. Which one is asynchronous and what does that require of your code?

Show answer
Transcribe is asynchronous: you start_transcription_job, poll get_transcription_job until COMPLETED, then read the transcript JSON from S3. Translate answers synchronously in the call, returning TranslatedText immediately.
✓ Knowledge check

The chapter frames these media APIs as narrow services composed with Claude. Describe the 'full loop' example and the principle it illustrates.

Show answer
Transcribe a support call -> detect sentiment/PII with Comprehend -> summarize with Claude -> speak the summary with Polly. Each service does one narrow thing well; the automation is the composition of them (wired with Step Functions in W13).
© 2026 studybydoing.in · AI Engineering: Zero to Production · All rights reserved. · About · Privacy Policy · Terms · Contact
Educational content, provided as-is and without warranty. Code samples are examples — review, test, and adapt them before using in production. See the Terms of Use & Disclaimer. Use at your own risk.
© studybydoing.in