AI EngineeringZero to ProductionHome·About·What’s new·Contact
Prompt & Context Engineering · Chapter E4

Working with LLMs: APIs, SDKs, Parameters & Open-Source Models

The practical mechanics of driving an LLM: how the request/response actually works, what every parameter does, how SDKs wrap it, and the real decision between a hosted API (like Anthropic's) and an open-source model you run yourself. This is the plumbing under every other chapter.

⏱️ ~1.5 hours🧪 3 labs🎯 Beginner→Tech-lead

Learning objectives

  • Compare closed APIs vs open-source models for a task.
  • Reason about the tradeoffs: capability, cost, control, privacy.
  • Route between models by requirement.
  • Set a model-sourcing policy for a team.
▶ Runnable companionEvery code block in this lesson is also a standalone file under code/pe4-llms-apis-oss/ in the course, with a README on how to run it. Read here, run there — no need to copy-paste.

1 · The landscape essential

Here's a decision you'll be asked to defend in your first architecture review, and "I just used the one everyone uses" is not an answer that survives it. A team ships a feature on a frontier API because it's the fastest path to a demo. It works beautifully. Then the invoice arrives, or legal asks where the customer data went, or a single provider outage takes the whole product down for an afternoon — and suddenly the model you reached for reflexively is the thing being questioned in a postmortem. The choice of where your intelligence comes from is an engineering decision with cost, legal, and reliability consequences, and this chapter is about making it on purpose.

The most useful reframing: you are not choosing a model, you are choosing a supply chain. Think of it like sourcing electricity for a factory. You can buy from the grid (someone else builds the plant, you pay per kilowatt-hour, and you accept that the meter is always running and the utility knows your usage) — that's a closed API. You can build your own generators on-site (huge upfront cost, staff to run them, but the power never leaves your fence and the marginal cost is tiny) — that's self-hosting an open-weight model. Or you can rent a generator someone else maintains but that runs your fuel — a middle path, the managed open option. Nobody argues that on-site generators are morally superior to the grid; you'd match the source to the load. Model sourcing is the same, and treating it as a tribal identity ("we're an open-source shop") instead of a per-workload calculation is the most common mistake here.

The trap worth naming early: people collapse three genuinely different things into one axis. "Open vs closed" is really three questions stacked on top of each other — who runs the hardware, whether you can see and modify the weights, and whether your data leaves your infrastructure. Those usually move together, but not always: a managed open model gives you open weights without running the hardware, and some closed providers offer in-your-VPC deployments that keep data local. Keep the three questions separate in your head and the fog clears. As you read the table below, notice it isn't ranking the options — there is no winner — it's laying out what each one is best at and what each one costs you, because the right pick is entirely a function of which cost you can least afford to pay for the task in front of you.

You can consume LLMs three ways: closed APIs (Claude, GPT — most capable, easiest), open-weight models you host (Llama, Mistral, Qwen — control + privacy), or managed open models (hosted OSS). The right choice is per-task, not ideological.

you are choosing a supply chain, not a model — who runs it, where the data goes your app needs intelligence closed API managed open self-hosted open the grid — buy per kWh provider runs hardware data leaves your fence pay per token a rented generator open weights, hosted middle ground less control on-site generator you run the hardware data never leaves fixed cost + ops Three sources, one spectrum. Left to right you trade away someone-else-runs-it for you-own-it: the closed API is cheapest to start but your data leaves; self-hosting keeps data in-house at a fixed cost you must operate; managed open is the middle path.
🗺️ How to read this diagram

Read it top-down then left-to-right: your app at the top fans out to the three ways it can get an LLM, and each column below spells out what that choice means.

  • The top box is your app's need — "intelligence." The three blue arrows fan out to the three sourcing options, because this is one decision with three answers.
  • The three coloured boxes are the options: the green closed API (like the electricity grid — you buy per call), the amber managed open (a rented generator running open weights), and the purple self-hosted open (your own on-site generator).
  • Read down each column for the tradeoff. Notice the colour-coded lines: the closed API's "data leaves your fence" is red (the cost), while self-hosting's "data never leaves" is green (the payoff) — and the labels move from pay-per-token on the left to fixed-cost-plus-ops on the right.

In short: the same app can be fed by the grid, a rented generator, or your own plant — the right pick depends on which cost (money, ops, or data leaving) you can least afford.

OptionBest atCost of
Closed APIcapability, zero opsper-token fee, data leaves
Self-hosted openprivacy, control, fixed costops + a capability gap
Managed openmiddle groundless control than self-host

2 · The core tradeoffs essential

The reason this decision feels hard is that there is no single "best" — every option is the best at something and the worst at something else. That's not a flaw in the choices; it's the definition of a tradeoff, and the engineers who struggle here are the ones hunting for a universally correct answer that doesn't exist. The skill isn't finding the winner. The skill is naming the axes, then figuring out which one your project cannot afford to lose — because once you know your binding constraint, the decision usually makes itself.

An analogy that keeps people honest: choosing a model source is like choosing where to live relative to your office. Downtown (the frontier API) is the most capable location — everything's close, zero maintenance — but you pay premium rent forever and you're surrounded by strangers. The suburbs (self-hosting) mean a long commute and you fix your own plumbing, but the mortgage is fixed, the yard is yours, and nobody's watching. There is no "correct" place to live; there's only the right place for someone with your budget, commute tolerance, and privacy needs. Anyone who tells you one is objectively better is selling something. The four axes below are just the rent, the commute, the ownership, and the privacy — priced for LLMs.

The common mistake is to weight all four axes equally and average them into a mushy "it depends." They are not equal, and crucially they are not even the same kind of thing. Capability and cost are tradeoffs — dials you can turn, trading a bit of one for a bit of the other. But privacy/residency is often not a dial at all: it's a hard constraint handed to you by a regulator or a contract, a yes/no gate that overrides everything else regardless of how much cheaper or smarter the alternative would be. That asymmetry is why the routing code you'll meet in §3 checks data sensitivity first, before it ever looks at capability or cost. Constraints filter; tradeoffs optimize. Get that order wrong and you'll build a beautifully cost-optimized system that's also illegal. Read the four axes below not as a scorecard to total up, but as a list where you first ask "which of these is non-negotiable for me?" and only then optimize the rest.

Four axes decide it: capability (closed frontier still leads on hard tasks), cost (open wins at high volume if you have GPUs/ops), control (open = you own the model/version), privacy/residency (open = data never leaves). Weight them by your actual constraints.

not all four axes are the same kind of thing — one filters, three optimize CONSTRAINT · filters first privacy / residency yes/no gate — a hard rule regulator or contract says "this data may not leave" — overrides everything else TRADEOFFS · optimize after capability cost control dials you trade against each other then Privacy filters; the rest optimize. Privacy/residency is usually a hard yes/no gate handed to you, so it is checked before capability, cost, and control — which are dials you tune only among the options privacy already allowed.
🗺️ How to read this diagram

Read it left to right: the red box on the left is the one axis that acts differently, and the arrow shows the order — it runs before the blue box on the right.

  • The red "CONSTRAINT" box holds a single axis, privacy/residency. It is drawn as a yes/no gate because it is usually a rule from a regulator or contract — not something you get to weigh, just something you must obey.
  • The blue "TRADEOFFS" box holds the other three axes — capability, cost, control — as three stacked dials. These you can trade against each other: a little less capability for a lot less cost, and so on.
  • The arrow labelled "then" is the payoff: you filter with the constraint first, then optimize the tradeoffs only among whatever options survived. Reverse the order and you can build a beautifully cost-optimized system that is also illegal.

In short: a constraint decides which options are even allowed; the tradeoffs decide which of the allowed ones you pick.

3 · Intermediate — route by requirement intermediate

Once you accept there's no single best source, a scary question follows: does that mean picking one is a judgement call I have to make fresh every time, hoping I remember all four axes? No — and that's the whole point of this section. The move that separates a hobby project from a system is turning the decision from a vibe into an algorithm: a small, explicit function that takes the facts about a task and returns the source, the same way every time, in an order you can defend in a review. When the routing lives in code instead of in someone's head, it's testable, auditable, and it doesn't quietly drift as different developers make different calls on similar tasks.

Think of it like a triage nurse at an emergency room. The nurse doesn't treat patients in the order they walked in, and they don't decide by gut — they run a fixed protocol: check the life-threatening symptoms first (the hard constraints), then severity, then convenience. A chest pain jumps the queue over a sprained ankle every time, because the protocol encodes what must be checked first. Our router is that protocol for LLM requests: it checks the one thing that can override everything — is this data sensitive? — before it ever weighs whether the task is hard or high-volume. The ordering isn't arbitrary; it mirrors §2's insight that a constraint filters before a tradeoff optimizes.

The mistake to avoid: writing the checks in the wrong order and letting a cheaper or smarter option "win" a request that should never have left your infrastructure in the first place. If you check capability before privacy, a hard task on sensitive data routes to the frontier API — and you've just leaked regulated data to optimize a metric you weren't even allowed to optimize. That's why the function below is a cascade of early returns where the first matching rule wins: privacy first as an override, then frontier capability, then cost-at-scale, and finally the simplest default when nothing forces your hand. Read it as a priority list, not a pile of independent ifs — the order is the policy.

choose_source — first matching rule wins; read top to bottom a task arrives 1 · data_sensitive? 2 · needs_frontier? 3 · high_volume & has_gpu_ops? 4 · default (nothing forces it) no ↓ no ↓ no ↓ self-hosted closed API self-hosted closed API ↳ yes → in-house (privacy override) Privacy is checked first, on purpose. The router is a cascade of early returns: data_sensitive is tested before capability or cost, so a hard task on sensitive data still stays in-house instead of leaking to a frontier API.
🗺️ How to read this diagram

Start at the top box (a task arrives) and walk downward through the numbered amber checks. Each check is a yes/no question; the first one that answers "yes" wins and the walk stops there.

  • The amber boxes are the four checks in the exact order the code runs them: data_sensitive → needs_frontier → high_volume & has_gpu_ops → the default.
  • The blue "yes" arrow out of each check points right to a green result box — the source that check returns. The grey dashed "no" arrow drops you down to the next check instead.
  • The payoff is the very first check: data_sensitive sits at the top so privacy acts as an override. Sensitive data is routed in-house before capability or cost ever get a vote — the ordering is the policy.

In short: follow the "no" arrows down until a check says "yes"; whichever fires first picks the source, so the top check wins ties.

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.
Python · choose a source per task (runs)
choose_source.pydef choose_source(needs_frontier, data_sensitive, high_volume, has_gpu_ops):
    if data_sensitive and (has_gpu_ops or not needs_frontier):
        return "self-hosted open (data stays in-house)"
    if needs_frontier:
        return "closed API (capability)"
    if high_volume and has_gpu_ops:
        return "self-hosted open (cost at scale)"
    return "closed API (simplest)"

print(choose_source(True, False, False, False))    # hard task -> API
print(choose_source(False, True, True, True))       # sensitive + volume + ops -> self-host
print(choose_source(False, False, False, False))    # simple -> API
closed API (capability)
self-hosted open (data stays in-house)
closed API (simplest)
▶ How this works

This lab turns "which kind of LLM should I use?" into a small decision function. There are three real options: a closed API (like Claude — most capable, someone else runs it, you pay per use and your data leaves your servers), a self-hosted open model (you run an open-weight model like Llama on your own GPUs — data stays in-house, fixed cost, more work), and the simplest default. The function reads four yes/no facts about your task and returns the recommendation.

  1. The inputs are four true/false flags: needs_frontier (is this a genuinely hard task that needs the most capable model?), data_sensitive (must the data never leave your infrastructure?), high_volume (huge number of calls?), and has_gpu_ops (do you have the GPUs and staff to run a model yourself?).
  2. The checks run top to bottom, and the first match wins. First: if the data is sensitive and you can either self-host or the task isn't that hard, keep it in-house — self-hosted open (data stays in-house). Privacy is checked first because it's usually a hard legal requirement, not a preference.
  3. Next: if needs_frontier: — a genuinely hard task goes to the closed API, because the top hosted models still lead on the hardest reasoning.
  4. Next: if high_volume and has_gpu_ops: — lots of calls and the ability to run your own model means self-hosting saves money at scale.
  5. If none of those fire, the last line returns closed API (simplest) — when nothing forces your hand, the easiest option wins. The three print(...) lines call the function with different fact patterns so you can see each branch in action.

What the output means: Three lines, one per test call. A hard task returns the API; sensitive + high-volume + you-have-ops returns self-hosted; a plain task returns the API as the simplest choice. In short: the logic routes each task to the right source instead of picking one model for everything.

Try this: Change the third call to choose_source(False, True, False, False) (sensitive data, but no GPU ops and not a hard task) and predict the answer before running. It should still say self-hosted — because sensitive data must stay in-house even when you'd rather not run it yourself.

4 · Advanced — the capability/cost frontier advanced

"Self-hosting is cheaper" is one of the most expensive sentences in AI engineering — because it's true right up until it isn't, and the people who say it rarely know which side of the line they're on. A team sees the per-call price of an API, multiplies by their traffic, gets a scary number, and concludes they should run their own model to save money. Sometimes they're right. Often they've forgotten that a GPU rack costs the same whether it serves ten calls a day or ten million, and at their actual volume the "cheap" self-hosted option is burning money on idle hardware. This section gives you the one number that ends the argument: the break-even volume.

Here's the intuition, and it's the same maths as deciding whether to buy a car or take taxis. Taxis (the API) cost you nothing when you're not riding, but every trip has a fare — perfect if you travel occasionally. A car (self-hosting) hits you with a fixed monthly payment whether it leaves the driveway or not, plus a few cents of petrol per trip. If you drive constantly, the car wins; if you barely drive, the taxis win. There's a specific number of trips per month where the two cost exactly the same — that is your break-even, and above it you should own, below it you should rent. Nobody buys a car "because cars are cheaper"; they buy one because they cross the break-even. LLM sourcing is identical: a fixed monthly GPU bill versus a per-call fee, meeting at a crossover volume you can actually compute.

Two subtleties trip people up, so name them now. First, capability and cost are separate questions and you must not let one answer the other: open models have closed most of the gap on common work but still trail the frontier on the hardest reasoning, so "self-hosting is cheaper at our volume" does not mean "self-hosting is good enough for our hardest task" — those are two different tests, and §5's hybrid design exists precisely because the honest answer is often "cheap model for the easy majority, frontier for the hard tail." Second, the break-even only exists if self-hosting is actually cheaper per call in the first place. If the API's per-call price is already at or below your self-hosted marginal cost, no amount of volume will ever let you catch up — the fixed cost never gets paid off, and the right answer is simply "keep using the API." The calculator below computes the crossover and guards that impossible case, turning "self-hosting feels cheaper" into a defensible number.

Open models have closed much of the gap on common tasks but still trail the frontier on the hardest reasoning. The economic crossover: below some volume, an API is cheaper (no fixed GPU/ops cost); above it, self-hosting wins. Know your break-even (Local Models track).

total monthly cost as call volume grows — the lines cross at break-even cost call volume / month → fixed GPU bill API per-call, no fixed cost self-host fixed + tiny marginal break-even API cheaper (avoid fixed bill) self-host cheaper Two cost curves that cross once. The API line starts at zero but climbs fast per call; the self-host line starts high (the fixed GPU bill) but barely rises. Where they meet is the break-even volume — left of it the API wins, right of it self-hosting does.
🗺️ How to read this diagram

It's a cost chart: the horizontal axis is how many calls you make per month, the vertical axis is your total monthly bill. Follow each coloured line left to right as volume grows.

  • The blue "API" line starts at the origin — zero calls, zero cost — but rises steeply, because you pay a fee on every single call and nothing is fixed.
  • The amber "self-host" line starts high, at the dashed "fixed GPU bill" level you owe even at zero calls, but climbs slowly because each extra call adds only a tiny marginal cost.
  • The red dot where the two lines cross is the break-even volume. Left of the dashed vertical line the API is cheaper (you dodge the fixed bill); right of it, self-hosting wins because the per-call saving has finally paid that fixed bill off.

In short: below the crossover, rent (API); above it, own (self-host) — and if the API line were never steeper than the self-host line, they'd never cross and the API always wins.

Python · API vs self-host break-even (runs)
breakeven.pydef breakeven(api_cost_per_call, self_host_fixed_monthly, self_host_marginal):
    # find monthly call volume where self-host becomes cheaper
    denom = api_cost_per_call - self_host_marginal
    if denom <= 0: return "self-host never cheaper at these rates"
    return f"break-even at ~{round(self_host_fixed_monthly/denom):,} calls/month"

print(breakeven(api_cost_per_call=0.01, self_host_fixed_monthly=3000, self_host_marginal=0.0005))
break-even at ~315,789 calls/month
▶ How this works

The previous lab decided which source; this one answers the money question: at what call volume does running your own model become cheaper than paying an API per call? An API charges a fee every single call but has no fixed cost. Self-hosting has a big fixed monthly cost (renting GPUs, whether you use them or not) plus a tiny marginal cost per call. Below some volume the API wins; above it, self-host wins. That tipping point is the break-even.

  1. The three inputs are: api_cost_per_call (what the API charges you each call, e.g. 0.01 = one cent), self_host_fixed_monthly (your flat monthly GPU bill, e.g. 3000 dollars), and self_host_marginal (the tiny extra cost of one more call on your own server, e.g. 0.0005).
  2. denom = api_cost_per_call - self_host_marginal is how much you save per call by self-hosting instead of calling the API (here 0.01 - 0.0005 = 0.0095 per call).
  3. The guard if denom <= 0: handles the case where the API is already as cheap (or cheaper) per call — then self-hosting never catches up, no matter the volume, so it returns a plain-English message instead of a number.
  4. Otherwise self_host_fixed_monthly / denom divides the fixed monthly bill by the per-call saving: "how many calls of savings do I need to pay off that fixed cost?" The round(...) and :, just make it a clean number with comma separators.
  5. The print(breakeven(...)) call passes the values by name (keyword arguments) so the numbers are self-documenting — you can read what each one is without checking the function definition.

What the output means: break-even at ~315,789 calls/month. Below ~316k calls a month the API is cheaper (you avoid the $3,000 fixed bill); above it, self-hosting wins because the per-call saving has covered that fixed cost. This one number is what turns "self-hosting feels cheaper" into an actual, defensible decision.

Try this: Drop the fixed cost to self_host_fixed_monthly=500 and re-run — the break-even falls sharply, because a smaller fixed bill is paid off with far fewer calls. Then set self_host_marginal=0.01 (equal to the API cost) and watch the guard message appear: with no per-call saving, self-hosting can never win.

5 · Professional — hybrid architectures professional

The whole "open vs closed" debate contains a hidden false premise: that you have to pick one. You don't. The most cost-effective production systems in the wild aren't loyal to any single source — they route each request to whichever model can answer it well for the least money, and it turns out that's a different model for different requests. Once you internalize that you're allowed to mix, the agonizing all-or-nothing decision from the earlier sections dissolves into something much easier: send most traffic somewhere cheap, and reserve the expensive frontier for the handful of requests that actually need it.

The reason this works so well comes down to a fact about real traffic that surprises people: the difficulty of your requests is wildly lopsided. Picture a hospital again — the vast majority of visits are routine (a checkup, a prescription refill) that a nurse practitioner handles fine, and only a small tail needs the specialist surgeon whose time costs ten times as much. Staffing the whole hospital with surgeons would be absurd; so would sending every request to the frontier model. If 95% of your queries are "classify this ticket" or "extract this field," a small, cheap, often open model nails them — and because that 95% is where nearly all your volume lives, moving it off the frontier collapses your bill while leaving quality untouched on the 5% that genuinely needs the big model. You're not sacrificing capability; you're spending it only where it's scarce.

The catch — and the reason this is a "professional" tier and not a beginner trick — is that a hybrid only stays sane if the routing and the model-switching are cleanly separated. The mistake is to hardcode "call the cheap model here, call the frontier there" all over your codebase; then every new model, every price change, every provider outage means hunting through business logic to rewire calls. The discipline is an abstraction layer: your code asks for "an answer to this request," and a single provider-agnostic client decides which model gets it, so the router's policy is one thing you can change in one place. That same interface is what §6 turns into a documented policy and what lets the migration in Exercise 6 swap a self-hosted model in behind the scenes without touching a caller. Hold that idea — route freely, but switch behind one door — as you read how the blended cost falls.

Real systems mix sources: a cheap open/small model handles the high-volume easy majority, escalating hard cases to a frontier API; sensitive data routes to a self-hosted model. This captures most of the cost/privacy benefit without sacrificing capability where it matters.

one door in, two models out — split by difficulty request stream provider-agnostic client (router) cheap open model handles the easy 95% frontier API reserved for the hard 5% easy → hard → most traffic is easy → blended cost collapses, quality kept on the hard tail Route freely, switch behind one door. A single provider-agnostic client sends the easy majority to a cheap open model and escalates only the hard tail to a frontier API — so the blended bill drops without losing capability where it counts.
🗺️ How to read this diagram

Read left to right: requests enter on the left, pass through one router in the middle, and leave to one of two models on the right depending on how hard they are.

  • The blue box on the left is the incoming request stream — every query your system receives. It flows into the white centre box, the single provider-agnostic client that decides where each request goes.
  • The two arrows out of the router are the split: the "easy" arrow sends the bulk of traffic to the green cheap open model (the easy 95%), and the "hard" arrow escalates only the difficult tail to the purple frontier API (the hard 5%).
  • The payoff is the lopsided split: because most traffic is easy and lands on the cheap model, the blended cost collapses while the expensive frontier is still there for the few requests that truly need it.

In short: one door in, two models out — send the cheap-to-answer majority to the cheap model, spend the frontier only on the hard minority.

6 · Tech-lead — a model-sourcing policy tech-lead

Everything so far has been about making a good sourcing decision. This section is about the harder problem: making sure the right decision happens every time, on every request, even the ones written by a developer who's never read this chapter. A single engineer weighing the axes carefully is a good start. A team of twenty, each making that call independently under deadline pressure, is a data breach waiting to happen — someone will eventually send restricted data to a closed API "just to unblock the demo," and they won't be wrong to, because nobody told them not to. The job of a lead isn't to make the best individual choice; it's to make the correct choice the default and the wrong one impossible.

The reframing that matters: a sourcing policy is not documentation, it's infrastructure. Compare it to how a building handles fire safety. You don't post a memo saying "please avoid fires and exit calmly" and trust everyone to remember — you install fire doors that physically won't let the blaze spread and exit signs that make the right path obvious under stress. A policy that lives only in a wiki page is the memo; a policy encoded as a gate in your provider-agnostic client is the fire door. When "restricted data may not reach a closed API" is a rule the code enforces, it doesn't matter who's writing the feature or how rushed they are — the wrong route simply cannot execute. That's the difference between a policy people should follow and one they can't help but follow.

A real policy has four moving parts, and it's worth seeing them as a set because leaving any one out reopens a gap. First, a default source so the common, unremarkable request has an obvious home nobody has to think about. Second, a compliance matrix mapping each class of data to the sources it's allowed to touch — this is the part that must be enforced, not suggested, because it's usually a legal obligation rather than a guideline. Third, the break-even threshold from §4, written down so "should we self-host this workload yet?" is answered by a number the whole team shares instead of relitigated every quarter. And fourth, the abstraction layer — the provider-agnostic client from K7/DF4 — that makes swapping a source a config change rather than a code rewrite, which is what keeps the policy adjustable as prices, models, and regulations shift under you. The note and code below turn these four into an auditable gate.

A lead sets the policy: default source, what data may go to which providers (compliance), the break-even threshold for self-hosting, and an abstraction layer so switching sources is config (the provider-agnostic client from K7/DF4).

which data class may reach which source — a green cell = allowed data class ↓ closed API managed open self-hosted public allowed allowed allowed internal blocked allowed allowed restricted blocked blocked allowed the more sensitive the data, the fewer sources it may touch — restricted never leaves your infra The matrix is the contract. Each data class maps to the sources it may touch — and restricted data has exactly one green cell (self-hosted), so a closed API is simply unreachable for it, no matter who writes the feature.
🗺️ How to read this diagram

Read it as a grid: each row is a class of data (getting more sensitive top to bottom) and each column is a possible source. A cell's colour tells you whether that data may go to that source.

  • Green cells mean "allowed" and red cells mean "blocked." Scan across the public row — every cell is green, so public data can go anywhere.
  • The internal row blocks the closed API but allows managed-open and self-hosted; the restricted row blocks everything except self-hosted. The pattern is deliberate: the more sensitive the row, the fewer green cells it has.
  • The payoff is the bottom-left red cell — restricted against closed API. Because that cell is red in the matrix, the code physically cannot route restricted data to a closed API; the rule is enforced, not merely suggested.

In short: one green cell per row = the only sources that data may touch; restricted data's single green cell keeps it in-house by construction.

Sourcing is a compliance + cost decision, not a preferenceWhich models you're allowed to send which data to is often a legal/compliance question, and the cost swing between API and self-host at scale is large. A lead makes this a deliberate, documented policy — and keeps the code provider-agnostic so it can change.

Exercise PE4.1 — Source the models for a system

Context: Sourcing decisions come alive across a mix of workloads with different needs — sensitive extraction, a high-volume classifier, and a hard reasoning task pull in different directions.

Your task: For three workloads (sensitive-data extraction, a high-volume classifier, a hard reasoning task), pick a source per workload, compute the self-host break-even, and write a one-paragraph sourcing policy including what data may leave your infra.

Requirements:

  • Pick a source per workload with the routing logic
  • Compute the self-host break-even volume
  • Write a one-paragraph sourcing policy
  • State explicitly which data classes may leave your infra

💡 Hint: Let privacy decide the sensitive workload before cost or capability, then use the break-even to justify the high-volume one.

🪜 Practice ladder beginner → industry

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

Exercise 1 · Map the three sourcing options to their tradeoffsBeginner

Context: The three sourcing options — closed API, self-hosted open, managed open — trade along the same axes. Sourcing is a compliance and cost decision, not a preference.

Your task: Given what a project cares about most, name the sourcing option that best fits and what it costs you.

Requirements:

  • Cover closed API, self-hosted open, and managed open
  • State what each option is best at
  • State what each option costs you (per-token fee, ops, control)
  • Return a readable summary per option
  • Make clear closed APIs lead on capability but your data leaves

💡 Hint: A lookup keyed by option is enough — the point is that each choice trades capability, privacy, ops, and cost against each other.

Show solution

The sourcing table as a lookup (pure stdlib):

OPTIONS = {
    "closed API":       ("capability, zero ops", "per-token fee, data leaves"),
    "self-hosted open": ("privacy, control, fixed cost", "ops + capability gap"),
    "managed open":     ("middle ground", "less control than self-host"),
}
def describe(option):
    best, cost = OPTIONS[option]
    return f"{option}: best at {best}; you pay in {cost}"

for o in OPTIONS:
    print(describe(o))

Each option trades along the same axes: closed APIs lead on capability with zero ops but your data leaves and you pay per token; self-hosted open keeps data in-house at fixed cost but you own the ops and accept a capability gap. Sourcing is a compliance + cost decision, not a preference.

Exercise 2 · Route a workload to a sourceIntermediate

Context: The routing order matters: privacy is a hard compliance constraint checked first, then frontier capability, then cost at scale. When nothing forces a choice, the simplest option — a closed API — wins.

Your task: Encode the routing function that checks privacy first, then capability, then cost, and run it on three workloads.

Requirements:

  • Check data sensitivity first as a compliance override
  • Force in-house serving for sensitive data
  • Route frontier-capability needs to a closed API
  • Route high-volume-with-ops to self-hosted for cost
  • Default to the simplest option and demonstrate three workloads

💡 Hint: Privacy is evaluated before capability and cost because it is a constraint, not a tradeoff — sensitive data forces in-house serving.

Show solution

The routing decision, exactly as the lesson defines it (pure logic):

def choose_source(needs_frontier, data_sensitive, high_volume, has_gpu_ops):
    if data_sensitive and (has_gpu_ops or not needs_frontier):
        return "self-hosted open (data stays in-house)"
    if needs_frontier:
        return "closed API (capability)"
    if high_volume and has_gpu_ops:
        return "self-hosted open (cost at scale)"
    return "closed API (simplest)"

print(choose_source(True,  False, False, False))  # closed API (capability)
print(choose_source(False, True,  True,  True))   # self-hosted open (in-house)
print(choose_source(False, False, False, False))  # closed API (simplest)

Privacy is evaluated first because it is a hard compliance constraint, not a tradeoff: sensitive data forces in-house serving. Only after that do capability and cost-at-scale decide, and when nothing forces a choice the simplest option (a closed API) wins.

Exercise 3 · Find the self-host break-even volumeAdvanced

Context: Self-hosting has a fixed monthly cost plus a small marginal cost; APIs cost per call. The break-even volume makes "should we self-host?" a number, not an opinion.

Your task: Implement breakeven() — the monthly volume where self-hosting gets cheaper — and interpret the ~316k-calls number.

Requirements:

  • Compute break-even as fixed cost divided by per-call savings
  • Guard the case where marginal cost meets or exceeds the API price
  • Return the break-even volume in calls per month
  • Interpret the ~316k result against a realistic price set
  • State that below break-even the API wins and above it self-host wins

💡 Hint: The denominator is the per-call saving (API price minus marginal); if it is non-positive, self-hosting can never win.

Show solution

The break-even calculator, as the lesson defines it (pure arithmetic):

def breakeven(api_cost_per_call, self_host_fixed_monthly, self_host_marginal):
    denom = api_cost_per_call - self_host_marginal
    if denom <= 0:
        return "self-host never cheaper at these rates"
    return f"break-even at ~{round(self_host_fixed_monthly/denom):,} calls/month"

print(breakeven(api_cost_per_call=0.01, self_host_fixed_monthly=3000,
                self_host_marginal=0.0005))
# break-even at ~315,789 calls/month

Below ~316k calls/month the API's pay-per-use wins; above it the fixed GPU cost amortizes and self-hosting is cheaper. If the marginal cost ever meets or exceeds the API price, self-hosting can never win — the break-even makes "should we self-host?" a number, not an opinion.

Exercise 4 · Design a hybrid: cheap model for easy, frontier for hardExpert

Context: Hybrid architectures route easy, high-volume requests to a cheap (often open) model and hard requests to a frontier API. Most traffic is easy, so this collapses the bill while keeping quality where it matters.

Your task: Model the hybrid router and the blended cost, showing the saving grow as the easy fraction rises.

Requirements:

  • Route easy requests to a cheap model and hard ones to the frontier API
  • Use illustrative per-call prices
  • Compute the blended cost over a mixed stream
  • Compare against sending everything to the frontier model
  • Report the percent saved and note the need for a provider-agnostic client

💡 Hint: The saving tracks the easy fraction — a provider-agnostic client layer is what lets the router send each request wherever it is cheapest to answer well.

Show solution

Hybrid routing with a blended-cost readout (pure stdlib):

COST = {"cheap_open": 0.0005, "frontier_api": 0.01}   # $ per call (illustrative)

def route(difficulty):
    return "frontier_api" if difficulty == "hard" else "cheap_open"

def blended(requests):
    total = sum(COST[route(r)] for r in requests)
    return round(total, 4)

mix = ["easy"] * 95 + ["hard"] * 5
all_frontier = ["hard"] * 100
print("hybrid (95% easy):", blended(mix))          # tiny
print("all frontier     :", blended(all_frontier)) # baseline
print("saving:", round(100*(blended(all_frontier)-blended(mix))/blended(all_frontier)), "%")

Most traffic is easy, so routing it to a cheap open model and reserving the frontier API for the hard tail collapses the bill while keeping quality where it matters. The hybrid needs a provider-agnostic client layer so the router can send each request wherever it is cheapest to answer well.

Exercise 5 · Write a model-sourcing policyProfessional

Context: A sourcing policy is a compliance matrix plus a default plus a break-even threshold, encoded so every route is auditable — not left to a developer's judgement call.

Your task: Implement a policy that maps data classes to allowed sources and refuses to send restricted data to a closed API.

Requirements:

  • Map each data class (public, internal, restricted) to allowed sources
  • Never allow restricted data to leave your infra
  • Honour a desired source when the data class permits it
  • Fall back to an allowed source when the desired one is disallowed
  • Return whether the route was permitted, and demonstrate a blocked case

💡 Hint: The matrix is the contract — a closed API must be unreachable for restricted data, and the fallback should prefer self-hosted.

Show solution

The sourcing policy as an auditable gate (pure logic):

ALLOWED = {
    "public":     {"closed_api", "managed_open", "self_hosted"},
    "internal":   {"managed_open", "self_hosted"},
    "restricted": {"self_hosted"},          # never leaves our infra
}
DEFAULT = "closed_api"

def route_with_policy(data_class, desired):
    allowed = ALLOWED[data_class]
    if desired in allowed:
        return {"source": desired, "ok": True}
    fallback = "self_hosted" if "self_hosted" in allowed else next(iter(allowed))
    return {"source": fallback, "ok": False,
            "note": f"{desired} not allowed for {data_class}; using {fallback}"}

print(route_with_policy("public", "closed_api"))
print(route_with_policy("restricted", "closed_api"))   # blocked -> self_hosted

The policy makes sourcing a documented, enforceable decision: a compliance matrix says which data class may reach which source, a default covers the common case, and restricted data can never be routed to a closed API. Encoding it means every route is auditable, not left to a developer's judgment call.

Exercise 6 · Plan a migration from closed API to self-hosted at scaleIndustry scenario

Context: As lead, a workload has crossed the break-even volume and needs to move in-house. You don't flip a switch — you route everything through an abstraction layer, then shadow and canary the open model against the API baseline so the capability gap is measured, not assumed.

Your task: Plan the migration behind a provider-agnostic layer, decide the cutover, and quantify the cost and capability tradeoff before committing.

Requirements:

  • Quantify API spend versus self-host spend at the given volume
  • Put all calls behind a provider-agnostic client first
  • Shadow then canary the self-hosted model against the API baseline
  • Ramp only if the measured quality gap is acceptable, keeping the API as fallback
  • Return the spend comparison, quality gap, and phased plan

💡 Hint: The break-even proves cost; the shadow/canary comparison against the API baseline is what decides whether the migration actually ships.

Show solution

A migration plan that quantifies the tradeoff before cutover (pure stdlib):

def migration_plan(monthly_calls, api_cost, fixed, marginal, quality_gap):
    api_spend  = monthly_calls * api_cost
    self_spend = fixed + monthly_calls * marginal
    cheaper = self_spend < api_spend
    steps = [
        "1. Put all calls behind a provider-agnostic client (base_url swap)",
        "2. Shadow self-hosted model on live traffic; compare quality offline",
        "3. Canary 5% to self-hosted; watch quality + latency vs API baseline",
        "4. Ramp only if quality gap is acceptable; keep API as fallback via gateway",
    ]
    return {"api_spend": round(api_spend), "self_spend": round(self_spend),
            "self_cheaper": cheaper, "quality_gap": quality_gap, "plan": steps}

import json
print(json.dumps(migration_plan(500_000, 0.01, 3000, 0.0005, quality_gap="~3% on hard set"),
                 indent=2))

A lead does not flip a switch: route everything through an abstraction layer first, then shadow and canary the open model against the API baseline so the capability gap is measured, not assumed. The break-even proves cost, but the quality comparison — with the API kept as gateway fallback — decides whether the migration actually ships.

✓ Checkpoint — you can move on when you can…

  • Compare closed API vs open/self-hosted.
  • Weigh capability/cost/control/privacy.
  • Route by requirement; find the break-even.
  • Set a compliance-aware sourcing policy.

Knowledge check check yourself

✓ Knowledge check

The choose_source logic checks data sensitivity before capability. Why is privacy checked first, and what does the lesson say sourcing fundamentally is?

Show answer
Because keeping sensitive data in-house is usually a hard legal/compliance requirement, not a preference, so it overrides other factors. Model sourcing is a compliance + cost decision, not an ideological one — documented as policy, with provider-agnostic code so sources can change.
✓ Knowledge check

In the API-vs-self-host break-even model, what does the break-even volume represent, and when can self-hosting 'never' win?

Show answer
It's the monthly call volume where the per-call saving (API cost per call minus self-host marginal cost) finally pays off the fixed monthly GPU bill — below it the API is cheaper, above it self-hosting wins. If the API's per-call cost is already ≤ the self-host marginal cost, the saving denominator is ≤ 0 and self-hosting never catches up at any volume.
© 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