Module 1 · Why AI Engineering Is Engineering ⏱ 19 min

Your First Cost & Latency Budget

By the end of this lesson you will be able to:
  • Estimate the token count of a prompt or response from its character length
  • Compute a dollar cost from input tokens, output tokens, and per-thousand-token prices
  • Build a budget that reasons about cost and latency before a single model call is written

The most common way teams discover the price of an AI feature is the invoice at the end of the month. A feature ships, traffic grows, and a number that looked negligible in testing turns into a bill that sinks the quarter. Latency tells the same story in miniature: a request that felt instant at low load suddenly times out under real traffic, because nobody multiplied one request by a thousand.

Both disasters share one cause: the cost and the latency were never estimated before the model was wired in. This lesson fixes that. You will write a few small functions that turn a prompt and an expected answer into a dollar figure and a millisecond figure, using nothing but arithmetic, before a single model call exists. The model's price changes every quarter; the budgeting method does not.

flowchart TD
  A["input chars"] --> IT["input tokens = ceil(chars / 4)"]
  B["output chars"] --> OT["output tokens = ceil(chars / 4)"]
  IT --> C["cost = inTok * inPrice + outTok * outPrice"]
  OT --> C
  C --> D["dollars"]
  style C fill:#8b5cf6,color:#fff
  style D fill:#166534,color:#fff
A cost estimate is just arithmetic: characters become tokens, tokens meet prices, prices become dollars.

Two ingredients, easily confused

A language-model bill has two ingredients, and mixing them up is the first mistake. Input tokens are what you send in: the prompt, the retrieved documents, the conversation history. Output tokens are what the model generates back. Providers price them separately, and output almost always costs more, because producing text is harder work for the model than reading it.

To turn characters into tokens you need a token estimate. The durable rule of thumb is that one token is roughly four characters of English text - close enough for a budget, never precise enough for a final invoice. Real tokenizers split on sub-words and punctuation, so the exact count differs; but for planning, four characters per token is the estimate the whole field leans on.

A token estimate from character length: rough, but good enough to plan a budget. Press Run.
function estimateTokens(text) {
  return Math.ceil(text.length / 4);
}

// Rough token counts for budgeting, not exact billing.
console.log(estimateTokens("The quick brown fox"));   // 19 chars -> 5 tokens
console.log(estimateTokens("a"));                       // 1 char  -> 1 token

Cost is now arithmetic

Estimating cost is arithmetic from here. Multiply the input tokens by the input price and the output tokens by the output price, then add the two. The shape of the function matters far more than any specific number: prices are arguments you pass in, not facts hard-coded into your code, so when a provider changes them you update one input rather than editing every call site.

Notice where the money hides. A long prompt is paid once, but a long response is paid on a more expensive rate and is the part your users actually wait for. Doubling the prompt length is cheap; doubling the output length is expensive in both dollars and seconds. That asymmetry is the single most useful thing to internalise about model budgets.

Cost = input term + output term. Prices are passed in, so your code never hard-codes a rate. Run it.
function estimateTokens(text) {
  return Math.ceil(text.length / 4);
}

// rates are dollars per 1000 tokens - you pass YOUR provider's current price.
function estimateCost(inputChars, outputChars, rates) {
  const inTok = estimateTokens(inputChars);
  const outTok = estimateTokens(outputChars);
  return inTok / 1000 * rates.inputPer1k
       + outTok / 1000 * rates.outputPer1k;
}

const rates = { inputPer1k: 1, outputPer1k: 2 };   // example rates only
console.log(estimateCost(4000, 4000, rates));       // 1.0 + 2.0 = 3.0

From one request to a monthly bill

A per-request estimate only matters once you scale it up. Take the cost of a single call, multiply by the calls you expect each day, then by thirty: that is the monthly number that lands on an invoice. A feature that costs a tenth of a cent per call looks free in testing, but at a million calls a day it is three thousand dollars a month.

The same arithmetic exposes the levers you can pull. Cut the output length in half and the monthly bill roughly halves with it. Cache the answer to a common prompt and you pay once instead of a thousand times. Downsize to a cheaper model and the per-token price drops. None of those levers is visible until the per-request estimate is in hand.

flowchart LR
  P["Read the prompt: one pass"] --> G["Generate output: one token at a time"]
  G --> L["Latency grows with output length"]
  style G fill:#b45309,color:#fff
  style L fill:#b91c1c,color:#fff
Reading the prompt is one pass; generating the answer is one token at a time - so output length drives latency.

Latency follows the same logic, in time

Latency follows the same logic but measured in time. Reading a long prompt is largely a single parallel pass; generating the output is sequential, one token at a time. So the part of the request your user actually feels - the wait for the answer to stream back - scales with the output length multiplied by how long the model takes per output token. A model that emits thirty tokens a second takes a full second to produce a thirty-token reply, prompt aside.

For both cost and latency, plan against traffic, not a single call. Multiply one request's cost by your real request volume, and reason in percentiles - the median request and the slow ninety-fifth - rather than the average, because a few huge requests dominate the average and quietly hide the worst cases.

Latency mirrors cost: read the prompt once, then pay per output token. Output dominates the wait. Run it.
function estimateTokens(text) {
  return Math.ceil(text.length / 4);
}

// latency = prompt read (one pass) + output generated one token at a time
function estimateLatencyMs(inputChars, outputChars, msPerInputToken, msPerOutputToken) {
  const inTok = estimateTokens(inputChars);
  const outTok = estimateTokens(outputChars);
  return inTok * msPerInputToken + outTok * msPerOutputToken;
}

// example rates only: reading is cheap, generation dominates
console.log(estimateLatencyMs(4000, 4000, 1, 30));  // 1000*1 + 1000*30 = 31000 ms
flowchart TD
  S["Estimate tokens per request"] --> C["Multiply by your real per-token price"]
  C --> T["Apply real traffic: p50 and p95, not the average"]
  T --> Q["Is the monthly bill acceptable?"]
  Q -->|"yes"| SHIP["Ship it"]
  Q -->|"no"| CUT["Trim output, cache, or downsize the model"]
  style SHIP fill:#1e7f3d,color:#fff
  style CUT fill:#b45309,color:#fff
The budgeting workflow: estimate, multiply by real traffic at p50 and p95, then decide before you ship.
Exercise

What does this print? Tokens are estimated as the character count divided by 4, rounded up.

function estimateTokens(text) {
  return Math.ceil(text.length / 4);
}
console.log(estimateTokens("abcdefgh"));
Exercise

Write estimateCost(inputChars, outputChars, rates) that returns the dollar cost: input tokens times rates.inputPer1k plus output tokens times rates.outputPer1k, each divided by 1000. Tokens are Math.ceil(chars / 4). Pass rates in - never hard-code a price.

function estimateCost(inputChars, outputChars, rates) {
  // tokens = ceil(chars / 4); cost = inTok/1000*inputPer1k + outTok/1000*outputPer1k
}
Exercise

This estimateCost has the missing-output bug: it charges only for input tokens and drops the output term entirely, so any request with a real reply is under-priced. Add the output term - output tokens times rates.outputPer1k over 1000 - so both halves of the bill are counted.

function estimateCost(inputChars, outputChars, rates) {
  const inTok = Math.ceil(inputChars / 4);
  return inTok / 1000 * rates.inputPer1k;
}
Exercise

Why does a model provider typically charge MORE per token for output than for input?

Exercise

Write withinBudget(inputChars, outputChars, rates, maxDollars) that returns true if the estimated cost of the request is at or below maxDollars, and false otherwise. This is the gate you would call before sending a request: estimate first, spend second. Tokens are Math.ceil(chars / 4).

function withinBudget(inputChars, outputChars, rates, maxDollars) {
  // estimate the cost, then compare to maxDollars
}

Recap

  • A model bill has two ingredients: input tokens (what you send) and output tokens (what comes back), and output is usually priced higher.
  • Estimate tokens from characters with the durable rule ceil(chars / 4) - rough, but good enough to plan against.
  • Cost = input tokens times the input price plus output tokens times the output price, each over 1000. Pass prices in as arguments; never hard-code a rate.
  • Latency is dominated by output: the prompt is read in one pass, but the answer is generated one token at a time.
  • Plan against real traffic at p50 and p95, not the average, so a few huge requests cannot hide the worst cases.
  • Always estimate input and output separately; dropping the output term is the classic budgeting bug.

Next you will meet the broader shape of an AI system - the deterministic components that surround a single model call and turn it into something you can ship.

Checkpoint quiz

Your estimated monthly bill is far smaller than the real invoice. What is the most likely single cause?

Why should a cost or latency budget be planned against the 95th-percentile request rather than the average?

Go deeper — technical resources