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

Observability & cost

You can't operate what you can't see. Model-invocation logging, CloudWatch alarms, cost controls, governance, and an incident runbook with a kill switch — O4, in AWS primitives.

⏱️ ~1.5 hours🧪 2 labs🎯 Advanced→Expert
⚙️ To run this for realThe code here is complete and correct as written. To actually execute it you'll need:
  • AWS credentials (aws configure) + Bedrock model access enabled in your region + pip install boto3
  • 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

  • Enable Bedrock model-invocation logging to CloudWatch/S3.
  • Build CloudWatch metrics, alarms, and a cost view for AI usage.
  • Apply governance: least-privilege IAM, data residency, and audit trails.
  • Assemble the AI incident runbook with a kill switch.
▶ Runnable companionEvery code block in this lesson is also a standalone file under code/aws14-observability-cost-governance/ in the course, with a README on how to run it. Read here, run there — no need to copy-paste.

You can't operate what you can't see advanced

Start with the story every team eventually lives. You ship an AI feature on Friday. It works in the demo, so you move on. The following Wednesday, finance forwards you a bill that is 8× what you projected, a customer complains the assistant "went crazy" last Tuesday at 3pm, and your CTO asks a simple question you cannot answer: which caller spent that money, and what exactly did the model say? You have no logs, no alarm history, no cost breakdown. The system was running the whole time — you just had no window into it. That blind week is what this chapter exists to prevent.

The mental model to hold is a car dashboard. An engine can be running perfectly and still be seconds from disaster if the temperature gauge is buried, the fuel light is disconnected, and there's no odometer. Observability is the dashboard for your AI system: gauges you can read at a glance (latency, error rate), a warning light that goes off before you're stranded (alarms, budgets), and a trip log of where you've been (the invocation log). None of it makes the engine faster — it makes the engine operable, which is a different and equally essential property.

The precise idea is this: observability is the ability to answer questions about your system's behaviour from the outputs it emits — without shipping new code to investigate. For a production AI system those outputs cluster into four signals worth memorising, because a healthy dashboard shows all four: latency (are calls fast enough?), errors (are calls failing or being throttled?), cost (are we spending what we expected?), and quality (are the answers still good?). Miss any one and you have a blind spot exactly where an incident will hide — cost runaways sneak past a latency-only dashboard; a model quietly degrading in quality never trips an error alarm.

The common mistake is treating observability as something you "add later," after the feature works. But you can't reconstruct history you never recorded — the invocation log only captures calls made after you enable it, an alarm can only page on a metric it was already watching, and a budget only alerts once you've set the threshold. Everything in this chapter is a decision made before the incident, so that when the incident comes you're reading gauges instead of guessing. The rest of the chapter walks the four signals in AWS primitives: logging (the audit trail), metrics + alarms + budgets (the gauges and warning lights), and governance + a kill switch (who's allowed to drive, and how you stop the car).

This is the AWS-specific version of your O4 chapter. A live AI system needs the four signals — latency, errors, cost, quality — plus an audit trail of every model call. AWS gives you the primitives; you wire them.

a healthy AI dashboard shows all four signals — miss one and an incident hides there latency fast enough? errors failing / throttled? cost spending as planned? quality answers still good? invocation log — the shared audit trail a latency-only dashboard misses a cost runaway; an error alarm never catches quality quietly degrading Four signals, one audit trail. A production AI dashboard must show latency, errors, cost, and quality together — each covers a blind spot the others can't see — all anchored on the invocation log that records what actually happened.
🗺️ How to read this diagram

Read the top row left to right — those are the four signals you watch — then follow the dashed arrows down to the single foundation they all rest on.

  • The four coloured boxes across the top are the signals to memorise: latency, errors, cost, and quality. Each asks one plain question; a healthy dashboard answers all four.
  • The dashed arrows converge downward onto the green box — the invocation log — showing that every signal ultimately traces back to the recorded record of what was asked and answered.
  • The payoff is the caption line at the bottom: watching only one signal leaves a blind spot exactly where the next incident hides — cost slips past a latency dashboard, quality decay never trips an error alarm.

In short: observability means reading all four gauges at once, on top of an audit trail you turned on before you needed it.

Model-invocation logging advanced

Picture the aftermath of that "the assistant went crazy" complaint. A customer swears the bot gave dangerous advice at 3pm Tuesday. You open your application logs and find… that a Bedrock call happened, returned HTTP 200, and took 900ms. That's it. You have the envelope — a call occurred — but not the letter inside it: what prompt went in, what text came back. You cannot reproduce it, can't confirm or deny the complaint, can't tell your compliance team what was actually said. This is the gap model-invocation logging closes.

Think of it like the flight recorder on an aircraft. The plane flies fine without a black box — but the moment something goes wrong, the difference between "we know exactly what happened" and "we're guessing" is whether the recorder was running. Bedrock model-invocation logging is that recorder for your model calls: a single account-and-region setting that tells Bedrock to write down every invocation — the input prompt, the output completion, and the token counts — to CloudWatch Logs, an S3 bucket, or both. Crucially, it's a platform-level switch. You flip it once and every subsequent Bedrock call is captured automatically; you don't wrap each call site in your own logging code, and you can't accidentally forget it on one path.

Two properties are worth burning into memory because they trip people up. First, logging is not retroactive — it only records calls made after you enable it. There is no way to recover the prompt from last Tuesday if the recorder wasn't on, which is exactly why "enable it later" is a trap; the audit trail you'll wish you had is the one you turned on before you needed it. Second, the textDataDeliveryEnabled flag is the difference between recording metadata (a call happened, N tokens) and recording the actual text of prompts and responses. Metadata is enough for cost accounting; the full text is what you need for debugging and compliance — and it's also what makes the logs sensitive.

That sensitivity is the governance twist, and it's why enabling logging is a decision rather than a free win. Those log records now contain raw prompt and completion text — which may include customer PII, secrets a user pasted in, or proprietary data. You have just created a second, highly sensitive copy of your most confidential traffic sitting in a bucket. So enabling logging comes bundled with obligations: lock the destination bucket and log group down with least-privilege IAM (only the people and roles who genuinely need it can read), and set a retention policy so you're not hoarding sensitive text forever. The lab below flips the switch; the warning box after it is the half of the lesson most tutorials skip.

flip the switch once at the account/region level — every later call is captured automatically everyBedrock call prompt + response logging switch put_model_invocation_ logging_configuration CloudWatch Logs S3 bucket textDataDeliveryEnabledfull text vs metadata only raw prompts → lock down + retention not retroactive: only calls made after enabling One switch, two destinations, one sensitive copy. The account-level logging config fans every Bedrock invocation into CloudWatch Logs and S3; textDataDeliveryEnabled decides text-vs-metadata — and turning on text creates a confidential copy you must secure.
🗺️ How to read this diagram

Follow the solid blue arrows left to right: a model call enters the logging switch, which fans out to the two storage destinations on the right.

  • The blue box on the left is every Bedrock call. The solid arrow carries it into the white "logging switch" box — the single put_model_invocation_logging_configuration setting you flip once per account/region.
  • The switch fans out (two blue arrows) to the green boxes: CloudWatch Logs and an S3 bucket. You can send to one or both.
  • The amber box is the textDataDeliveryEnabled flag governing the switch: on = full prompt/response text, off = metadata only. The red box is the payoff caveat — full text means the logs now hold raw prompts, so they must be locked down with least-privilege IAM and a retention policy. The side note reminds you logging is not retroactive.

In short: one platform switch captures every future call to CloudWatch and/or S3 — and enabling text turns that audit trail into sensitive data you own.

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 W14.1
enable_logging.pyimport boto3
bedrock = boto3.client("bedrock", region_name="us-east-1")

# Log every Bedrock invocation (prompt, response, token counts) to CloudWatch + S3.
bedrock.put_model_invocation_logging_configuration(
    loggingConfig={
        "cloudWatchConfig": {
            "logGroupName": "/bedrock/invocations",
            "roleArn": "arn:aws:iam::123456789012:role/bedrock-logging",
        },
        "s3Config": {"bucketName": "bedrock-logs"},
        "textDataDeliveryEnabled": True,
    }
)
print("invocation logging enabled")
▶ How this works

This turns on an audit trail: it tells AWS Bedrock (Amazon's service for calling AI models) to write down a record of every model call — the prompt sent, the answer returned, and how many tokens were used. Without this, an AI call leaves no trace; with it, you can later ask "what did we send, and what came back?". The code doesn't send any prompts itself — it changes a setting on your account.

  1. boto3 is the official Python library for talking to AWS. boto3.client("bedrock", region_name="us-east-1") opens a connection to the Bedrock control API in the US-East-1 region — the part that manages settings (not the part that runs models). That connection object is named bedrock.
  2. put_model_invocation_logging_configuration(...) is the one command that flips logging on. "Put" here means "set/replace the configuration". Everything inside loggingConfig={...} describes where the logs should go.
  3. cloudWatchConfig sends logs to CloudWatch (AWS's monitoring and log service). logGroupName is the named bucket of logs (/bedrock/invocations), and roleArn is the IAM identity AWS assumes to write them — an ARN is just AWS's unique ID for a resource.
  4. s3Config also drops a copy into an S3 bucket (AWS file storage) called bedrock-logs — handy for long-term retention. textDataDeliveryEnabled: True means the actual prompt and response text is included, not just the numbers.

What the output means: It prints invocation logging enabled. From then on, every Bedrock call your account makes gets logged automatically — you don't add logging code to each call. Nothing is logged retroactively; only calls made after this runs.

Try this: The warning box below is the real lesson: those logs contain the raw prompt and answer text, which can be sensitive. Before enabling this for real, decide who can read the bedrock-logs bucket and how long you keep the data.

Logs contain promptsModel-invocation logs include prompt and completion text — potentially sensitive. Lock the S3 bucket and log group down with least-privilege IAM and a retention policy. This is a governance requirement, not an option.

Metrics, alarms & cost expert

Logging tells you what happened; it doesn't wake you up. A perfect audit trail is useless if nobody reads it until Wednesday. The classic failure here is the team that has all the data and still learns about the outage from an angry customer, or learns about the cost blowout from the monthly invoice. Logs are passive — they sit there. What you need next is something active: numbers that update continuously and machinery that shouts when a number goes wrong. That's the job of metrics and alarms.

First separate the three words people blur together. A metric is a number tracked over time — a line on a graph, like "average latency per minute." Bedrock publishes several to CloudWatch on its own, without you instrumenting anything: InvocationLatency (how long calls take), Invocations (how many), and input/output token counts. An alarm is a rule watching one metric that flips between states (OK ↔ ALARM) and can trigger an action when it crosses a line. A budget (AWS Budgets) is the same idea aimed specifically at spend. The mental split: metric = the gauge, alarm = the warning light wired to the gauge, budget = the fuel light.

The design decision that separates a useful alarm from a useless one is how long a problem must persist before it counts. This is what Period and EvaluationPeriods encode, and it's worth understanding rather than copying. Period is the size of each measurement window (300 seconds = a 5-minute bucket); EvaluationPeriods is how many consecutive windows must breach the threshold before the alarm fires. Set both together and you're saying "only page me if latency stays high for two 5-minute windows in a row" — because a single slow window is usually a harmless blip, but sustained slowness is a real incident. Get this wrong in the tight direction and you'll teach your on-call to ignore the pager (alarm fatigue); too loose and you'll sleep through the real thing.

Then there's cost, which is subtler because AWS has no "Bedrock spend right now" metric that updates by the second. You build the cost view yourself from what you do have: the token-count metrics (or the token counts in your invocation logs) multiplied by the model's published per-token price — and remember input and output tokens are priced differently, so you cost each side at its own rate. That gives you a near-real-time spend estimate. AWS Budgets is the safety net underneath it: you set a dollar (or usage) threshold, and when actual or forecast spend crosses it, Budgets sends an alert. The whole point is expressed in one line — runaway spend should page a human, not surface as a surprise on next month's invoice. The lab wires exactly this latency alarm; the same pattern extends to error rate and to your derived cost metric.

Bedrock publishes CloudWatch metrics (InvocationLatency, Invocations, input/output token counts). Alarm on latency and error spikes; build a cost view from token metrics × price. Set AWS Budgets alerts so runaway spend pages you, not the invoice.

Period=300 (5-min windows), EvaluationPeriods=2 — two in a row before it fires InvocationLatency the gauge win 1 win 2 >5s win 3 >5s one breach = still OK 2nd in a row → fire OK ALARM SNS on-call → page Two windows, then it pages. Period=300/EvaluationPeriods=2 means a single slow 5-minute window is ignored, but two consecutive breaches flip the alarm OK→ALARM and fire the SNS on-call action — sustained trouble pages, a blip doesn't.
🗺️ How to read this diagram

Scan left to right in time: the latency metric on the left is chopped into 5-minute windows, and the alarm decides its state from a run of those windows.

  • The blue box is the InvocationLatency metric — the raw gauge CloudWatch fills in on its own. The three small boxes are consecutive 5-minute windows; amber and red ones are breaching (over 5s).
  • A single breaching window (win 2) leaves the alarm in the green OK state — that's EvaluationPeriods=2 suppressing the blip. Only when a second window breaches in a row (win 3) does the arrow drive the state down to the red ALARM box.
  • The payoff is the last hop: flipping to ALARM fires AlarmActions, which notifies the purple SNS on-call topic and pages a human. Sustained slowness reaches a person; a one-window spike never does.

In short: the metric is the gauge, two consecutive breaching windows are the trigger, and the SNS page is what makes the alarm actually wake someone.

Lab W14.2
alarm.pyimport boto3
cw = boto3.client("cloudwatch", region_name="us-east-1")

cw.put_metric_alarm(
    AlarmName="bedrock-latency-high",
    Namespace="AWS/Bedrock",
    MetricName="InvocationLatency",
    Statistic="Average",
    Period=300, EvaluationPeriods=2,
    Threshold=5000.0,                       # ms
    ComparisonOperator="GreaterThanThreshold",
    AlarmActions=["arn:aws:sns:us-east-1:123456789012:oncall"],
)
print("alarm set")
▶ How this works

This creates a CloudWatch alarm — an automatic watchdog. Bedrock already publishes performance numbers ("metrics") to CloudWatch on its own; here we ask CloudWatch to watch one of those numbers and page a human if it gets too high. The metric we watch is InvocationLatency: how long, in milliseconds, model calls are taking. Slow calls usually mean something is wrong.

  1. cw = boto3.client("cloudwatch", ...) opens a connection to the CloudWatch service. put_metric_alarm(...) creates (or overwrites) an alarm named by AlarmName.
  2. Namespace="AWS/Bedrock" and MetricName="InvocationLatency" together point at the exact number to watch — think of Namespace as the folder and MetricName as the file. Statistic="Average" means look at the average latency (not the max or the total).
  3. Period=300, EvaluationPeriods=2 defines the timing: measure over 300-second (5-minute) windows, and only alarm if two windows in a row breach the limit. Requiring two in a row avoids false alarms from a single blip.
  4. Threshold=5000.0 with ComparisonOperator="GreaterThanThreshold" is the rule: fire when average latency goes above 5000 ms (5 seconds). The # ms comment reminds you the unit is milliseconds.
  5. AlarmActions=[...] is what happens when it fires: it notifies an SNS topic (AWS's message/notification hub) named oncall, which can then email, text, or page whoever is on call.

What the output means: It prints alarm set. The alarm now lives in your account and checks latency continuously — you don't keep this script running. It sits OK until latency crosses the threshold for two periods, then flips to ALARM and fires the notification.

Try this: Lower Threshold to something tiny like 1.0 and you'd get paged almost immediately — a quick way to test that the paging path actually works before you rely on it in a real incident.

Governance & the incident runbook expert

Here's the moment governance stops being an abstract word. It's 2am. A budget alert has just fired — spend is 10× normal and climbing. You're half awake, and the only question that matters is: how do I make it stop, right now? If your honest answer is "um, I'd have to find who has AWS access, figure out which service is calling, maybe redeploy something…", you don't have governance — you have hope. Governance is what turns that 2am scramble into a checklist you can execute in ninety seconds. It's the least glamorous part of the chapter and the part that saves you.

Strip away the jargon and governance answers five plain questions about your AI system, each mapping to a concrete AWS mechanism. Who is allowed to call the models? — least-privilege IAM, meaning each role gets exactly the permissions it needs and no more, so a compromised component can't do arbitrary damage. Where does the data physically live? — your region choice (and VPC endpoints for private routing), which is how you satisfy data-residency rules like "EU customer data stays in the EU." What was actually asked and answered? — the invocation log from the previous section, serving as the audit trail. What is the model allowed to say? — Guardrails (from W6) enforcing content policy. And how do we stop everything instantly? — the kill switch. Notice these aren't five products to buy; they're five questions to have an answer for, and a gap in any one is a compliance risk sitting quietly until an auditor or an incident finds it. The table below is that mapping, worth reading as a readiness checklist.

The idea that earns its own emphasis is the kill switch. In any incident — a cost runaway, a prompt-injection attack, a model behaving badly — your first move is not to diagnose, it's to stop the bleeding, then investigate from a safe state. A kill switch is simply the fastest concrete action that halts all model calls, and the discipline is deciding what it is before you need it. On AWS it might be disabling the agent alias (so callers hit a dead endpoint), revoking the IAM role the caller assumes (so calls fail authorization), or flipping a feature flag your application checks. The test of a good kill switch is brutally practical: could a groggy on-call engineer execute it correctly at 2am from their phone? A switch that requires a code change and a deploy is not a kill switch — it's a delay.

This is where the whole chapter clicks together into one loop. Observability detects the problem (the budget alarm pages you). Logging attributes it (you read the invocation log and see one caller is responsible for 95% of the spend). And the runbook's kill switch stops it (you disable that specific caller's alias — surgical, rather than taking the whole service down). Detect → attribute → stop. A runbook is just that loop written down in advance so nobody has to improvise it while the money burns. The exercises below have you build each piece and then assemble exactly this incident response.

Governance is least-privilege IAM per service, data-residency via region choice, guardrails (W6) as policy, and the invocation log as the audit trail. The incident runbook needs a kill switch: the fastest way to stop all model calls (disable the agent alias, revoke the role, or flip a feature flag) when something goes wrong.

a runbook is this loop written down before the incident — not improvised at 2am 1 · detect observability: budget / alarm fires 2 · attribute logging: which caller spent the money? 3 · stop kill switch: stop the bleeding disable alias · revoke role · flag the test: could on-call run it at 2am from a phone? Detect → attribute → stop. Observability detects the breach, the invocation log attributes it to the offending caller, and a concrete kill switch (disable the alias, revoke the role, flip a flag) stops it — the whole chapter as one loop written down in advance.
🗺️ How to read this diagram

Read the three top boxes left to right — they are the incident-response loop in order, each handing off to the next via the blue arrow.

  • Box 1 · detect (blue) is observability doing its job: a budget or alarm fires and tells you something is wrong. Box 2 · attribute (amber) is the invocation log answering who — e.g. one caller responsible for most of the spend.
  • Box 3 · stop (red) is the kill switch: your first move is to halt model calls, not to diagnose. The dashed arrow drops to the green box listing the concrete options — disable the agent alias, revoke the role, or flip a feature flag.
  • The payoff is the note under box 1: a real kill switch passes the "2am from a phone" test. Anything needing a code change and deploy is a delay, not a kill switch.

In short: detect, attribute, stop — the runbook is just that loop decided before the money starts burning.

ConcernAWS mechanism
Who can call modelsleast-privilege IAM policies
Where data livesregion selection + VPC endpoints
What was asked/answeredmodel-invocation logging
Policy enforcementGuardrails (W6)
Stop everything nowdisable agent alias / revoke role

Exercise W14.1 — Operate your project

Context: Operating an AI service is more than deploying it: you must be able to see it, bound its cost, and stop it. This capstone makes an existing project observable and governable, then proves the alarm actually fires.

Your task: For any AWS AI project you built, enable invocation logging, add a latency alarm and a budget alert, and write a one-page incident runbook that names the kill switch — then trigger the alarm with load and confirm it pages.

Requirements:

  • Enable Bedrock model-invocation logging to CloudWatch/S3 on the project
  • Add a CloudWatch latency alarm wired to a real notification (SNS/on-call)
  • Add an AWS Budgets alert so cost overruns are detected, not discovered on the bill
  • Write a one-page incident runbook that names a concrete kill switch (disable alias / revoke role / feature flag)
  • Drive load until the latency alarm breaches and confirm it actually pages
  • Tie it together: logging attributes the cause, the alarm/budget detects, the runbook stops it

💡 Hint: An alarm you have never seen fire is a guess; generate enough load to cross the threshold and watch the page arrive, then check the runbook's kill switch is one you could actually execute at 2am.

🪜 Practice ladder beginner → industry

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

Exercise 1 · Enable model-invocation loggingBeginner

Context: You cannot audit what you do not record. Bedrock's model-invocation logging captures every prompt, response, and token count to CloudWatch and S3 — the foundational audit trail behind cost, quality, and compliance work.

Your task: Use put_model_invocation_logging_configuration to log Bedrock prompts, responses, and token counts to CloudWatch and S3.

Requirements:

  • Call put_model_invocation_logging_configuration on a boto3 bedrock client (the control plane, not bedrock-runtime)
  • Configure a cloudWatchConfig with a log group and the roleArn Bedrock uses to write logs
  • Configure an s3Config destination bucket for the log data
  • Enable text data delivery so prompts/responses (not just metadata) are captured
  • Recognize this is what later powers token/cost accounting and auditing

💡 Hint: This is a one-time account/region configuration on the bedrock control plane; give it both a CloudWatch and an S3 destination plus a role that can write them.

Show solution

Model-invocation logging is the audit trail: it captures prompt, response, and token counts to CloudWatch and S3.

import boto3

bedrock = boto3.client("bedrock", region_name="us-east-1")
bedrock.put_model_invocation_logging_configuration(
    loggingConfig={
        "cloudWatchConfig": {"logGroupName": "/bedrock/invocations",
                             "roleArn": "arn:aws:iam::123:role/bedrock-logs"},
        "s3Config": {"bucketName": "bedrock-logs"},
        "textDataDeliveryEnabled": True,
    },
)
print("logging enabled")
Exercise 2 · Alarm on high latencyIntermediate

Context: A single latency spike shouldn't wake anyone, but sustained slowness should. CloudWatch alarms let you page on InvocationLatency only after it stays high across several evaluation windows, cutting false positives.

Your task: Create a CloudWatch put_metric_alarm on InvocationLatency that fires after two 5-minute windows above 5 seconds.

Requirements:

  • Call put_metric_alarm on a boto3 cloudwatch client for the AWS/Bedrock namespace, metric InvocationLatency
  • Set Period=300 (5 min) and EvaluationPeriods=2 so it needs two sustained windows
  • Set the Threshold in milliseconds (5s = 5000) with ComparisonOperator="GreaterThanThreshold"
  • Wire AlarmActions to an SNS topic (e.g. on-call) so it actually pages
  • Understand two evaluation periods suppress single-spike noise

💡 Hint: Latency is reported in milliseconds, so 5s is 5000.0; the two evaluation periods are what turn a blip into an alarm-worthy trend.

Show solution

Two evaluation periods avoid false positives from a single spike; latency is in milliseconds.

import boto3

cw = boto3.client("cloudwatch", region_name="us-east-1")
cw.put_metric_alarm(
    AlarmName="bedrock-latency-high",
    Namespace="AWS/Bedrock", MetricName="InvocationLatency",
    Statistic="Average", Period=300, EvaluationPeriods=2,
    Threshold=5000.0, ComparisonOperator="GreaterThanThreshold",
    AlarmActions=["arn:aws:sns:us-east-1:123:oncall"],
)
print("alarm created")
Exercise 3 · Cost from logged token countsAdvanced

Context: Invocation logs give you exact input/output token counts; model pricing is quoted per 1K tokens. Multiply and sum and you have real spend for a batch — the basis of any cost dashboard or chargeback.

Your task: Given per-invocation input/output token counts pulled from logs, compute the total spend for a batch at a model's prices. Runs offline.

Requirements:

  • The function takes the log records plus per-1K input and output prices
  • For each record, cost the input and output sides separately at their own per-1K rates
  • Convert token counts to thousands before multiplying by the per-1K price
  • Sum across all records into one total and round sensibly
  • Demonstrate it offline on a small list of {in, out} records

💡 Hint: Input and output are priced differently, so keep the two rates apart; divide each count by 1000 before multiplying, then add every record's cost.

Show solution

Logs give tokens; multiply by per-1K prices and sum to get spend.

def batch_cost(records, in_per_k, out_per_k):
    total = 0.0
    for r in records:
        total += r["in"]/1000*in_per_k + r["out"]/1000*out_per_k
    return round(total, 6)

logs = [{"in": 1200, "out": 300},
        {"in": 800,  "out": 500},
        {"in": 1500, "out": 200}]
print(batch_cost(logs, 0.003, 0.015))   # 0.0261
Exercise 4 · A four-signal health checkExpert

Context: No single metric tells you if a service is healthy. Operators watch latency, error rate, cost/hour, and a quality score together, and roll them into one status where the worst signal dominates — a familiar red/yellow/green rollup.

Your task: Combine latency, error rate, cost/hour, and a quality score into a single status (green/yellow/red) against thresholds. Runs offline.

Requirements:

  • Grade each of the four signals independently into green/yellow/red against its own thresholds
  • Remember quality is inverted — lower is worse — unlike latency, errors, and cost
  • Compute the overall status as the worst of the four signals
  • Return both the overall status and the per-signal breakdown so the cause is visible
  • Demonstrate a green case and a case that goes red on a single bad signal, offline

💡 Hint: The overall status is a max-of-severity: any red makes it red, else any yellow makes it yellow; return the per-signal map so you can see which one tripped.

Show solution

Operate on the four signals together; the worst signal sets the overall status.

def health(latency_ms, error_rate, cost_per_hr, quality):
    checks = {
        "latency": "red" if latency_ms > 5000 else "yellow" if latency_ms > 3000 else "green",
        "errors":  "red" if error_rate > 0.05 else "yellow" if error_rate > 0.01 else "green",
        "cost":    "red" if cost_per_hr > 50 else "yellow" if cost_per_hr > 25 else "green",
        "quality": "red" if quality < 0.7 else "yellow" if quality < 0.85 else "green",
    }
    worst = "red" if "red" in checks.values() else \
            "yellow" if "yellow" in checks.values() else "green"
    return {"status": worst, "signals": checks}

print(health(2000, 0.002, 12, 0.9))     # green
print(health(6000, 0.002, 12, 0.9))     # red (latency)
Exercise 5 · Governance readiness checklistProfessional

Context: Governance turns into concrete controls: who can call (IAM), where data lives (region/VPC), what was asked (logging), what's allowed (guardrails), and how to stop it (kill switch). A gap in any one is a compliance risk, so encode the checklist.

Your task: Encode governance_gaps(controls) that checks the required controls — least-privilege IAM, region/VPC, invocation logging, guardrails, kill switch — and returns the ones that are missing. Runs offline.

Requirements:

  • Define the set of required controls in one place
  • Treat a control as satisfied only when its flag is truthy
  • Return the sorted set difference — the required controls not present/enabled
  • An empty result means ready; a non-empty result names exactly what to fix
  • Demonstrate offline with a controls dict where some are False

💡 Hint: Model it as set arithmetic: required minus the ones that are enabled leaves the gaps; sort the result so the report is stable.

Show solution

Each governance question maps to a concrete control; a gap in any one is a compliance risk.

REQUIRED = {"least_privilege_iam", "region_vpc", "invocation_logging",
            "guardrails", "kill_switch"}

def governance_gaps(controls):
    have = {k for k, v in controls.items() if v}
    return sorted(REQUIRED - have)

controls = {"least_privilege_iam": True, "region_vpc": True,
            "invocation_logging": True, "guardrails": False,
            "kill_switch": False}
gaps = governance_gaps(controls)
print(gaps)   # ['guardrails', 'kill_switch']
print("NOT ready" if gaps else "ready")
Exercise 6 · A cost-runaway incident runbook with a kill switchIndustry scenario

Context: Spend spikes 10× at 2am. A mature response is a runbook, not a scramble: detect the breach via a budget/alarm, attribute it to the offending caller from logs, then trip a concrete kill switch to stop the bleeding.

Your task: Build the cost-runaway incident response: detect via budget/alarm breach, identify the offending caller from logs, and trip a kill switch (disable the agent alias / revoke the role / flip a feature flag). Model the decision offline.

Requirements:

  • Compute how far over budget you are (spend vs. budget) as the trigger
  • Below budget returns a monitor decision — no action
  • Over budget selects a concrete kill switch, e.g. disable_agent_alias when one caller dominates the spend, else revoke_role
  • Attach the follow-up steps (flip the feature flag off, page on-call, review logs)
  • Demonstrate the 10× over-budget, one-caller case offline

💡 Hint: Attribution drives the switch: if a single caller's share dominates, disabling that alias is the surgical stop; otherwise revoke the role, then do the standard follow-ups.

Show solution

Observability detects, logs attribute, and a concrete kill switch stops the bleeding.

def incident(spend_now, budget, top_caller_share):
    over = spend_now / budget
    if over < 1.0:
        return {"action": "monitor", "over_budget_x": round(over, 2)}
    action = "disable_agent_alias" if top_caller_share > 0.8 else "revoke_role"
    return {"action": action,               # the kill switch
            "over_budget_x": round(over, 2),
            "then": "flip feature flag off, page on-call, review logs"}

print(incident(spend_now=1000, budget=100, top_caller_share=0.95))
# over_budget_x 10.0 -> disable_agent_alias (one caller dominates), then flag off

✓ Checkpoint — you can move on when you can…

  • Enable and secure Bedrock model-invocation logging.
  • Create a CloudWatch alarm and a cost/budget alert for AI usage.
  • Name the AWS mechanism for each governance concern.
  • Write an incident runbook with a concrete kill switch.

Knowledge check check yourself

✓ Knowledge check

What does Bedrock model-invocation logging capture, and why is enabling it a governance concern rather than a free win?

Show answer
It records every invocation -- prompt, response, and token counts -- to CloudWatch and/or S3, giving you an audit trail (only for calls made after it's enabled). Because those logs contain raw prompt and completion text, which may be sensitive, you must lock down the bucket/log group with least-privilege IAM and set a retention policy.
✓ Knowledge check

The CloudWatch latency alarm uses Period=300 with EvaluationPeriods=2. What behavior does that combination produce, and what is a 'kill switch' in the incident runbook?

Show answer
It measures average InvocationLatency over 5-minute windows and only fires after two consecutive windows breach the threshold, avoiding false alarms from a single blip. A kill switch is the fastest way to stop all model calls in an incident -- e.g. disable the agent alias, revoke the role, or flip a feature flag.
© 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