LeetLLM
My PlanLearnGlossaryTracksPracticeBlog
LeetLLM

Your go-to resource for mastering AI & LLM systems.

Product

  • Learn
  • Glossary
  • Tracks
  • Practice
  • Blog
  • RSS

Legal

  • Terms of Service
  • Privacy Policy

© 2026 LeetLLM. All rights reserved.

All Topics
Your Progress
0%

0 of 177 articles completed

🛠️Computing Foundations0/9
Git, Shell, Linux for AIDocker for Reproducible AIPython for AI EngineeringNumPy and Tensor ShapesCUDA for ML TrainingMPS & Metal for ML on MacData Structures for AISQL and Data ModelingAlgorithms for ML Engineers
📊Math & Statistics0/8
Gradients and BackpropVectors, Matrices & TensorsLinear Algebra for MLAdam, Momentum, SchedulersProbability for Machine LearningStatistics and UncertaintyDistributions and SamplingHypothesis Tests, Intervals, and pass@k
📚Preparation & Prerequisites0/13
Neural Networks from ScratchCNNs from ScratchTraining & BackpropagationSoftmax, Cross-Entropy & OptimizationRNNs, LSTMs, GRUs, and Sequence ModelingAutoencoders and VAEsThe Transformer Architecture End-to-EndLanguage Modeling & Next TokensFrom GPT to Modern LLMsPrompt Engineering FundamentalsCalling LLM APIs in ProductionFirst AI App End-to-EndThe LLM Lifecycle
🧮ML Algorithms & Evaluation0/11
Linear Regression from ScratchLogistic Regression and MetricsDecision Trees, Forests, and BoostingReinforcement Learning BasicsValidation and LeakageClustering and PCACore Retrieval AlgorithmsDecoding AlgorithmsExperiment Design and A/B TestingPyTorch Training LoopsDataset Pipelines and Data Quality
📦Production ML Systems0/6
Feature Engineering for Production MLBatch and Streaming Feature PipelinesGradient Boosted Trees in ProductionRanking and Recommendation SystemsForecasting and Anomaly DetectionMonitoring Predictive Models
🧪Core LLM Foundations0/8
The Bitter Lesson & ComputeBPE, WordPiece, and SentencePieceStatic to Contextual EmbeddingsPerplexity & Model EvaluationFile Ingestion for AIChunking StrategiesLLM Benchmarks & LimitationsInstruction Tuning & Chat Templates
🧰Applied LLM Engineering0/24
Dimensionality Reduction for EmbeddingsCoT, ToT & Self-Consistency PromptingFunction Calling & Tool UseMCP & Tool Protocol StandardsContext EngineeringPrompt Injection DefenseResponsible AI GovernanceData Labeling and Human FeedbackEvaluating AI AgentsProduction RAG PipelinesHybrid Search: Dense + SparseReranking and Cross-Encoders for RAGRAG Evaluation for Reliable AnswersLLM-as-a-Judge EvaluationBias & Fairness in LLMsHallucination Detection & MitigationLLM Observability & MonitoringExperiment Tracking with MLflow and W&BPrompt Optimization with DSPyModel Versioning & DeploymentSemantic Caching & Cost OptimizationLLM Cost Engineering & Token EconomicsModel Gateways, Routing, and FallbacksDesign an Automated Support Agent
🎓Portfolio Capstones0/9
Capstone: Delivery ETA PredictionCapstone: Product RankingCapstone: Demand ForecastingCapstone: Image Damage ClassifierCapstone: Production ML PipelineCapstone: Document QACapstone: Eval DashboardCapstone: Fine-Tuned ClassifierCapstone: Reproducible ML Study
🧠Transformer Deep Dives0/8
Sentence Embeddings & Contrastive LossEmbedding Similarity & QuantizationScaled Dot-Product AttentionVision Transformers and Image EncodersPositional Encoding: RoPE & ALiBiLayer Normalization: Pre-LN vs Post-LNMechanistic InterpretabilityDecoding Strategies: Greedy to Nucleus
🧬Advanced Training & Adaptation0/16
Scaling Laws & Compute-Optimal TrainingPre-training Data at ScaleBuild GPT from Scratch LabJAX for PyTorch ResearchersContinued Pretraining for Domain ShiftSynthetic Data PipelinesSupervised Fine-Tuning PipelineMixed Precision TrainingDistributed Training: FSDP & ZeROLoRA & Parameter-Efficient TuningReward Modeling from Preference DataRLHF & DPO AlignmentConstitutional AI & Red TeamingRLVR & Verifiable RewardsKnowledge Distillation for LLMsModel Merging and Weight Interpolation
🤖Advanced Agents & Retrieval0/16
Vector DB Internals: HNSW & IVFAdvanced RAG: HyDE & Self-RAGGraphRAG & Knowledge GraphsRAG Security & Access ControlStructured Output GenerationReAct & Plan-and-ExecuteGuardrails & Safety FiltersCode Generation & SandboxingComputer-Use / GUI / Browser AgentsHuman-in-the-Loop Agent ArchitectureAI Coding Workflow with AgentsAgent Memory & PersistenceAgent Failure & RecoveryRecursive Language Models (RLM)Multi-Agent OrchestrationCapstone: Production Agent
⚡Inference & Production Scale0/19
Inference: TTFT, TPS & KV CacheMulti-Query & Grouped-Query AttentionKV Cache & PagedAttentionPrefix Caching and Prompt CachingFlashAttention & Memory EfficiencyContinuous Batching & SchedulingScaling LLM InferenceModel Parallelism for LLM InferenceModel Quantization: GPTQ, AWQ & GGUFLocal LLM DeploymentSLM Specialization & Edge DeploymentSpeculative DecodingLong Context Window ManagementMixture of Experts ArchitectureMamba & State Space ModelsReasoning & Test-Time ComputeAdvanced MLOps & DevOps for AIGPU Serving & AutoscalingA/B Testing for LLMs
🏗️System Design Capstones0/9
Content Moderation SystemCode Completion SystemMulti-Tenant LLM PlatformLLM-Powered Search EngineVision-Language Models & CLIPMultimodal LLM ArchitectureDiffusion Models: Images & TextReal-Time Voice AI AgentReasoning & Test-Time Compute
🎤AI Lab Interviewing0/4
AI Lab Coding Interview: Python SystemsAI Lab System Design InterviewAI Lab Behavioral InterviewAI Lab Technical Presentation
🔬Project Deep Dives0/17
Deep Dive - vLLMDeep Dive - SkyRLDeep Dive - FlashAttentionDeep Dive - FlashInferDeep Dive - DeepGEMMDeep Dive - NCCLDeep Dive - MegatronDeep Dive - DeepSpeedDeep Dive - RayDeep Dive - MLflowDeep Dive - PyTorchDeep Dive - TransformersDeep Dive - SGLangDeep Dive - slimeDeep Dive - DeepEPDeep Dive - TinkerDeep Dive - Light-PEFT
Back to Topics
LearnApplied LLM EngineeringLLM Cost Engineering & Token Economics
⚙️MediumMLOps & Deployment

LLM Cost Engineering & Token Economics

Build an auditable LLM cost ledger from usage traces, cache decisions, output contracts, offline batch work, and release budget gates.

16 min read
Learning path
Step 77 of 177 in the full curriculum
Semantic Caching & Cost OptimizationModel Gateways, Routing, and Fallbacks

Personalize this lesson

Adapt explanations and teaching visuals to your background and preferred voice.

Your PolicyOps team just promoted a semantic answer cache for its large language model (LLM) support assistant. Safe public access-policy hits can now return an evaluated answer without generating new text. That doesn't make the bill disappear. Live account checks, exception cases, and cache misses still invoke a model, and nobody can defend the next release with "it should be cheaper."

The missing artifact is a cost ledger for one evaluated support release. It starts from provider-reported token usage, accounts for two very different forms of caching, tests output savings against the answer contract, defers only eligible offline work, and produces a promotion decision. The next chapter will turn that budget contract into gateway routing and fallback policy. We won't build a router here.

The accounting boundary

Cost engineering begins after a request decision is known:

DecisionWhat executes?What belongs in this ledger?
Semantic answer hit from the previous lessonStored evaluated answer returnedNo generation charge; record avoided generation separately
Generated answer with provider prompt-cache hitModel still produces a new answerFresh input, cached input, and output token charges
Generated answer without prompt-cache hitModel reads full input and produces a new answerFresh input and output token charges
Offline evaluation batchModel runs later, outside interactive pathBatch-eligible token spend and completion contract

This boundary matters. A semantic answer cache can skip generation. A provider prompt cache reuses matching prefix work but still generates a new output. Treating both as "cache hits" hides both correctness risk and spend.

One support question branches into four spend paths: answer reuse, prefix reuse, shorter output, and batch evaluation.
Same request can change ledger four different ways. Decision determines whether generation disappears, input gets cheaper, output shrinks, or work moves to batch pricing.

Use a dated rate card, not remembered prices

Provider pricing is configuration data. For this lab, the teaching fixture snapshots OpenAI's GPT-5.4 short-context rates from May 31, 2026: standard input costs $2.50 per million tokens, standard cached input costs $0.25 per million tokens, and standard output costs $15.00 per million tokens. OpenAI's pricing page publishes Batch rates as a separate row, so the ledger stores those values explicitly too. Its displayed Batch cached-input rate is $0.13 per million tokens; don't replace the published row with a local "divide every rate by two" rule. This dated fixture teaches the ledger shape; it isn't a permanent price promise. Refresh every production rate card from current provider pricing documentation before forecasting a release.[1]Reference 1OpenAI API Pricinghttps://developers.openai.com/api/docs/pricing

The usage trace, not a character-count estimate, is the source of truth after a model call. A provider can expose total input tokens, the cached subset, and output tokens. The fresh-input charge is the total input minus the cached subset.

For reasoning models such as GPT-5.4, provider-reported output_tokens already includes hidden reasoning tokens billed at the output rate. output_tokens_details.reasoning_tokens is useful diagnostic detail, but adding it to output_tokens again would double-charge the ledger.[2]Reference 2Reasoning modelshttps://developers.openai.com/api/docs/guides/reasoning

01-rate-card-and-usage.py
1from collections import defaultdict 2from dataclasses import dataclass, replace 3from decimal import Decimal, ROUND_HALF_UP 4 5MILLION = Decimal("1000000") 6CENT = Decimal("0.01") 7 8def dollars(amount: Decimal) -> Decimal: 9 return amount.quantize(CENT, rounding=ROUND_HALF_UP) 10 11@dataclass(frozen=True) 12class TokenRates: 13 input_per_1m: Decimal 14 cached_input_per_1m: Decimal 15 output_per_1m: Decimal 16 17@dataclass(frozen=True) 18class RateCard: 19 rate_card_id: str 20 standard: TokenRates 21 batch: TokenRates 22 23@dataclass(frozen=True) 24class Usage: 25 input_tokens: int 26 cached_input_tokens: int 27 output_tokens: int 28 reasoning_tokens: int = 0 29 30 def __post_init__(self) -> None: 31 if not 0 <= self.cached_input_tokens <= self.input_tokens: 32 raise ValueError("cached input must be a subset of input tokens") 33 if min(self.input_tokens, self.output_tokens) < 0: 34 raise ValueError("token counts cannot be negative") 35 if not 0 <= self.reasoning_tokens <= self.output_tokens: 36 raise ValueError("reasoning tokens must be a subset of output tokens") 37 38rate_card = RateCard( 39 rate_card_id="openai-gpt-5.4-short-context-2026-05-31", 40 standard=TokenRates( 41 input_per_1m=Decimal("2.50"), 42 cached_input_per_1m=Decimal("0.25"), 43 output_per_1m=Decimal("15.00"), 44 ), 45 batch=TokenRates( 46 input_per_1m=Decimal("1.25"), 47 cached_input_per_1m=Decimal("0.13"), 48 output_per_1m=Decimal("7.50"), 49 ), 50) 51 52print(rate_card.rate_card_id) 53print(f"standard_input_per_1m=${rate_card.standard.input_per_1m}") 54print(f"standard_cached_input_per_1m=${rate_card.standard.cached_input_per_1m}") 55print(f"standard_output_per_1m=${rate_card.standard.output_per_1m}") 56print(f"batch_input_per_1m=${rate_card.batch.input_per_1m}") 57print(f"batch_cached_input_per_1m=${rate_card.batch.cached_input_per_1m}") 58print(f"batch_output_per_1m=${rate_card.batch.output_per_1m}")
Output
1openai-gpt-5.4-short-context-2026-05-31 2standard_input_per_1m=$2.50 3standard_cached_input_per_1m=$0.25 4standard_output_per_1m=$15.00 5batch_input_per_1m=$1.25 6batch_cached_input_per_1m=$0.13 7batch_output_per_1m=$7.50

Price one generated answer

PolicyOps's public-policy-answer path uses a long evaluated instruction prefix and cites policy evidence in every generated response. A cold request reports 1,800 input tokens and 180 output tokens. Split each billable category rather than multiplying one blended token count by one price.

cost=fresh input tokens×fresh input price1,000,000+cached input tokens×cached input price1,000,000+output tokens×output price1,000,000\begin{aligned} \text{cost} = &\frac{\text{fresh input tokens} \times \text{fresh input price}}{1{,}000{,}000} \\ &+ \frac{\text{cached input tokens} \times \text{cached input price}}{1{,}000{,}000} \\ &+ \frac{\text{output tokens} \times \text{output price}}{1{,}000{,}000} \end{aligned}cost=​1,000,000fresh input tokens×fresh input price​+1,000,000cached input tokens×cached input price​+1,000,000output tokens×output price​​
02-price-one-generated-answer.py
1@dataclass(frozen=True) 2class PricedUsage: 3 fresh_input_usd: Decimal 4 cached_input_usd: Decimal 5 output_usd: Decimal 6 7 @property 8 def total_usd(self) -> Decimal: 9 return self.fresh_input_usd + self.cached_input_usd + self.output_usd 10 11def price_usage(usage: Usage, card: RateCard = rate_card, *, batch: bool = False) -> PricedUsage: 12 rates = card.batch if batch else card.standard 13 fresh_input_tokens = usage.input_tokens - usage.cached_input_tokens 14 return PricedUsage( 15 fresh_input_usd=Decimal(fresh_input_tokens) * rates.input_per_1m / MILLION, 16 cached_input_usd=Decimal(usage.cached_input_tokens) * rates.cached_input_per_1m / MILLION, 17 output_usd=Decimal(usage.output_tokens) * rates.output_per_1m / MILLION, 18 ) 19 20cold_policy_answer = Usage(input_tokens=1_800, cached_input_tokens=0, output_tokens=180) 21cold_cost = price_usage(cold_policy_answer) 22 23assert cold_cost.total_usd == Decimal("0.0072") 24assert rate_card.standard.output_per_1m > rate_card.standard.input_per_1m 25 26print(f"fresh_input=${cold_cost.fresh_input_usd:.6f}") 27print(f"output=${cold_cost.output_usd:.6f}") 28print(f"total=${cold_cost.total_usd:.6f}") 29print(f"output_share={(cold_cost.output_usd / cold_cost.total_usd):.1%}")
Output
1fresh_input=$0.004500 2output=$0.002700 3total=$0.007200 4output_share=37.5%

The output token rate is higher in this rate card, although this long-input request still spends more dollars on input overall. That distinction is why both token volume and per-category rate belong in the ledger. Decode is autoregressive and serving systems manage growing KV-cache state during generation; those mechanisms help explain why output can be priced differently, while the current rate card remains the billing authority.[3]Reference 3Efficient Memory Management for Large Language Model Serving with PagedAttention.https://arxiv.org/abs/2309.06180[1]Reference 1OpenAI API Pricinghttps://developers.openai.com/api/docs/pricing

Prefix reuse is still a generated answer

For GPT-5.4, treat prompt-cache eligibility as observed behavior, not a 1,024-token promise. OpenAI's current guidance, checked on July 31, 2026, places GPT-5.4 with GPT-5.5-and-earlier models: the minimum cacheable prefix varies by model from 1,024 to 2,048 tokens, and a prompt just above 1,024 tokens may not cache consistently. Prompt caching still depends on an exact matching prefix at the beginning of the prompt. Count a cache read only when returned usage reports cached_tokens > 0.[4]Reference 4GPT-5.4 Modelhttps://developers.openai.com/api/docs/models/gpt-5.4[5]Reference 5Prompt cachinghttps://developers.openai.com/api/docs/guides/prompt-caching The application design rule is direct: put stable instructions and shared evidence first, then dynamic customer data.

A prefix hit isn't a stored response. The support model still answers the current question and still incurs output spend. The fixture below represents a returned cache read of 1,280 tokens; it doesn't assert that a prompt at any specific threshold will hit.

03-account-for-prefix-reuse.py
1prefix_cached_policy_answer = Usage( 2 input_tokens=1_800, 3 cached_input_tokens=1_280, 4 output_tokens=180, 5) 6prefix_cost = price_usage(prefix_cached_policy_answer) 7savings = cold_cost.total_usd - prefix_cost.total_usd 8 9assert prefix_cost.total_usd == Decimal("0.004320") 10assert prefix_cost.output_usd == cold_cost.output_usd 11assert savings == Decimal("0.002880") 12 13print(f"cold_generation=${cold_cost.total_usd:.6f}") 14print(f"prefix_cached_generation=${prefix_cost.total_usd:.6f}") 15print(f"saved_per_generated_answer=${savings:.6f}") 16print(f"still_generated={prefix_cost.output_usd > 0}")
Output
1cold_generation=$0.007200 2prefix_cached_generation=$0.004320 3saved_per_generated_answer=$0.002880 4still_generated=True

Why does a provider prompt-cache hit still have an output charge?

Answer

It reuses processing for an exactly matching input prefix. The model still generates the new response, so output tokens remain billable.

Carry forward the semantic-cache decision

The preceding lesson promoted semantic response reuse only for stable public-policy answers under one evaluated release scope. Cost accounting consumes that decision; it doesn't rebuild its vector index or threshold. A stored-answer hit records zero generated tokens, while a prompt-prefix hit records a newly generated answer at a reduced input cost.

04-separate-answer-hits-from-prefix-hits.py
1@dataclass(frozen=True) 2class RequestOutcome: 3 feature: str 4 decision: str 5 usage: Usage | None 6 counterfactual_usage: Usage | None = None 7 8def generated_cost(outcome: RequestOutcome) -> Decimal: 9 if outcome.usage is None: 10 return Decimal("0") 11 return price_usage(outcome.usage).total_usd 12 13semantic_hit = RequestOutcome( 14 feature="public-policy-answer", 15 decision="SEMANTIC_ANSWER_HIT", 16 usage=None, 17 counterfactual_usage=prefix_cached_policy_answer, 18) 19prefix_hit = RequestOutcome( 20 feature="public-policy-answer", 21 decision="GENERATE_PREFIX_HIT", 22 usage=prefix_cached_policy_answer, 23) 24 25avoided_generation = price_usage(semantic_hit.counterfactual_usage).total_usd 26assert generated_cost(semantic_hit) == Decimal("0") 27assert generated_cost(prefix_hit) == Decimal("0.004320") 28 29print(f"semantic_hit_generation=${generated_cost(semantic_hit):.6f}") 30print(f"semantic_hit_avoided=${avoided_generation:.6f}") 31print(f"prefix_hit_generation=${generated_cost(prefix_hit):.6f}")
Output
1semantic_hit_generation=$0.000000 2semantic_hit_avoided=$0.004320 3prefix_hit_generation=$0.004320
One support question branches into cold generation, prefix reuse, and stored-answer hit billing outcomes.
Prefix reuse is still generation. A stored answer removes generation entirely, so realized spend and avoided spend must stay separate.

Replay a day of traffic

A per-request example is useful, but a release decision needs traffic shape. For a PolicyOps baseline replay day, before testing a shorter exception answer:

  • 3,200 stable policy questions safely hit the semantic answer cache.
  • 1,800 policy questions still generate, but reuse the evaluated prompt prefix.
  • 3,000 live-order questions must generate from current tool evidence.
  • 500 return exceptions generate a longer cited answer with the stable prefix.

The semantic-hit counterfactual answers "what cost did safe reuse avoid?" Actual spend answers "what appears on this release ledger?"

05-price-a-traffic-replay.py
1@dataclass(frozen=True) 2class TrafficSlice: 3 outcome: RequestOutcome 4 requests: int 5 6daily_replay = [ 7 TrafficSlice(semantic_hit, requests=3_200), 8 TrafficSlice(prefix_hit, requests=1_800), 9 TrafficSlice( 10 RequestOutcome( 11 feature="live-order-answer", 12 decision="GENERATE_LIVE_DATA", 13 usage=Usage(input_tokens=900, cached_input_tokens=0, output_tokens=95), 14 ), 15 requests=3_000, 16 ), 17 TrafficSlice( 18 RequestOutcome( 19 feature="access-exception-answer", 20 decision="GENERATE_PREFIX_HIT", 21 usage=Usage(input_tokens=2_200, cached_input_tokens=1_280, output_tokens=220), 22 ), 23 requests=500, 24 ), 25] 26 27actual_daily_spend = sum( 28 generated_cost(slice_.outcome) * slice_.requests 29 for slice_ in daily_replay 30) 31avoided_daily_generation = sum( 32 price_usage(slice_.outcome.counterfactual_usage).total_usd * slice_.requests 33 for slice_ in daily_replay 34 if slice_.outcome.counterfactual_usage is not None 35) 36 37assert actual_daily_spend == Decimal("21.761000") 38assert avoided_daily_generation == Decimal("13.824000") 39 40print(f"generated_daily_spend=${dollars(actual_daily_spend)}") 41print(f"semantic_hit_avoided_daily=${dollars(avoided_daily_generation)}") 42print(f"request_count={sum(slice_.requests for slice_ in daily_replay)}")
Output
1generated_daily_spend=$21.76 2semantic_hit_avoided_daily=$13.82 3request_count=8500

Avoided generation is useful evidence, but it isn't a credit and shouldn't be mixed into actual spend. If an answer hit later fails review, the evaluation consequence matters before its apparent savings.

Reduce output only under an answer contract

Output reductions can matter in a rate card where output is expensive. Blind truncation isn't a cost strategy. PolicyOps's cited support answer must state eligibility, cite the policy evidence, and name the requester's next step. Compare three candidate formats generated on the same input:

CandidateIntended changeSafe for automatic support?
VerboseFull explanation with repetitionCorrect but unnecessarily long
Concise citedOne decision, one citation, one next stepCandidate for release
TruncatedHard stop before citation and actionReject
06-optimize-output-under-contract.py
1@dataclass(frozen=True) 2class AnswerCandidate: 3 label: str 4 output_tokens: int 5 states_decision: bool 6 cites_policy: bool 7 gives_next_step: bool 8 9def satisfies_answer_contract(candidate: AnswerCandidate) -> bool: 10 return ( 11 candidate.states_decision 12 and candidate.cites_policy 13 and candidate.gives_next_step 14 ) 15 16candidates = [ 17 AnswerCandidate("verbose", 220, True, True, True), 18 AnswerCandidate("concise_cited", 130, True, True, True), 19 AnswerCandidate("truncated", 55, True, False, False), 20] 21safe_candidates = [candidate for candidate in candidates if satisfies_answer_contract(candidate)] 22selected_answer = min(safe_candidates, key=lambda candidate: candidate.output_tokens) 23 24verbose_usage = Usage(input_tokens=2_200, cached_input_tokens=1_280, output_tokens=220) 25concise_usage = Usage(input_tokens=2_200, cached_input_tokens=1_280, output_tokens=selected_answer.output_tokens) 26saved_per_exception = price_usage(verbose_usage).total_usd - price_usage(concise_usage).total_usd 27 28assert selected_answer.label == "concise_cited" 29assert not satisfies_answer_contract(candidates[-1]) 30assert saved_per_exception == Decimal("0.001350") 31 32print(f"selected={selected_answer.label}") 33print(f"rejected={candidates[-1].label}") 34print(f"saved_per_exception=${saved_per_exception:.6f}") 35print(f"saved_per_replay_day=${dollars(saved_per_exception * 500)}")
Output
1selected=concise_cited 2rejected=truncated 3saved_per_exception=$0.001350 4saved_per_replay_day=$0.68

The cheapest candidate is rejected. Cost reductions count only after the output still meets the same correctness and evidence contract.

Batch only work that can wait

OpenAI's Batch documentation describes a separate asynchronous workload path with a 50% discount compared with synchronous APIs and a 24-hour completion window.[6]Reference 6OpenAI Batch API Guidehttps://developers.openai.com/api/docs/guides/batch The pricing page publishes Batch token rates explicitly, so the ledger reads that row instead of deriving every category from one multiplier.[1]Reference 1OpenAI API Pricinghttps://developers.openai.com/api/docs/pricing In this fixture, the displayed $0.13 Batch cached-input row means the replay total isn't the exact result of halving every Standard-row decimal locally. That path is useful for nightly regression evaluation of the support release. It isn't appropriate for a customer waiting in a live conversation.

07-separate-interactive-and-batch-work.py
1@dataclass(frozen=True) 2class Workload: 3 name: str 4 interactive: bool 5 deadline_hours: int 6 endpoint_supported: bool 7 daily_requests: int 8 usage: Usage 9 10def batch_eligible(workload: Workload) -> bool: 11 return ( 12 not workload.interactive 13 and workload.deadline_hours >= 24 14 and workload.endpoint_supported 15 ) 16 17live_support = Workload( 18 name="live-support-answer", 19 interactive=True, 20 deadline_hours=0, 21 endpoint_supported=True, 22 daily_requests=3_000, 23 usage=Usage(input_tokens=900, cached_input_tokens=0, output_tokens=95), 24) 25nightly_eval = Workload( 26 name="nightly-release-eval", 27 interactive=False, 28 deadline_hours=24, 29 endpoint_supported=True, 30 daily_requests=2_000, 31 usage=Usage(input_tokens=1_800, cached_input_tokens=1_280, output_tokens=80), 32) 33 34nightly_sync_cost = price_usage(nightly_eval.usage).total_usd * nightly_eval.daily_requests 35nightly_batch_cost = price_usage(nightly_eval.usage, batch=True).total_usd * nightly_eval.daily_requests 36 37assert not batch_eligible(live_support) 38assert batch_eligible(nightly_eval) 39assert nightly_batch_cost == Decimal("2.832800") 40assert nightly_batch_cost < nightly_sync_cost 41 42print(f"live_support_batchable={batch_eligible(live_support)}") 43print(f"nightly_eval_batchable={batch_eligible(nightly_eval)}") 44print(f"nightly_eval_sync=${dollars(nightly_sync_cost)}") 45print(f"nightly_eval_batch=${dollars(nightly_batch_cost)}")
Output
1live_support_batchable=False 2nightly_eval_batchable=True 3nightly_eval_sync=$5.64 4nightly_eval_batch=$2.83

Your nightly evaluation workload is cheaper through Batch. Should the interactive support endpoint use the same path?

Answer

No. The Batch completion window changes latency semantics. Only work that can wait belongs on that path.

Attribute cost to decisions, not models alone

An invoice tells you how much you spent. A release trace tells you why. Each recorded row should name the evaluated release, feature, decision, pricing mode, rate-card identity, token usage, and output-contract evidence. Recording only a model name can't distinguish an unsafe answer-cache promotion from a harmless increase in live-order traffic. Make contract evidence and rate-card identity mandatory on every trace instead of silently repricing old usage with whichever table is loaded now.

08-build-feature-cost-telemetry.py
1@dataclass(frozen=True) 2class LedgerTrace: 3 release_id: str 4 feature: str 5 decision: str 6 requests: int 7 usage: Usage | None 8 rate_card_id: str 9 contract_passed: bool 10 contract_evidence_id: str 11 batch: bool = False 12 13def trace_unit_spend(trace: LedgerTrace, card: RateCard = rate_card) -> Decimal: 14 if trace.rate_card_id != card.rate_card_id: 15 raise ValueError("trace rate card does not match pricing table") 16 if trace.usage is None: 17 return Decimal("0") 18 return price_usage(trace.usage, card, batch=trace.batch).total_usd 19 20def trace_spend(trace: LedgerTrace, card: RateCard = rate_card) -> Decimal: 21 return trace_unit_spend(trace, card) * trace.requests 22 23release_id = "support-release-2026-05-cost-v1" 24optimized_replay = [ 25 TrafficSlice( 26 RequestOutcome( 27 feature=item.outcome.feature, 28 decision=item.outcome.decision, 29 usage=concise_usage if item.outcome.feature == "access-exception-answer" else item.outcome.usage, 30 counterfactual_usage=item.outcome.counterfactual_usage, 31 ), 32 requests=item.requests, 33 ) 34 for item in daily_replay 35] 36traces = [ 37 LedgerTrace( 38 release_id=release_id, 39 feature=item.outcome.feature, 40 decision=item.outcome.decision, 41 requests=item.requests, 42 usage=item.outcome.usage, 43 rate_card_id=rate_card.rate_card_id, 44 contract_passed=True, 45 contract_evidence_id=( 46 "approved-public-policy-cache@v1" 47 if item.outcome.decision == "SEMANTIC_ANSWER_HIT" 48 else "cited-support-answer-v3@canary" 49 ), 50 ) 51 for item in optimized_replay 52] 53traces.append( 54 LedgerTrace( 55 release_id=release_id, 56 feature=nightly_eval.name, 57 decision="BATCH_OFFLINE_EVAL", 58 requests=nightly_eval.daily_requests, 59 usage=nightly_eval.usage, 60 rate_card_id=rate_card.rate_card_id, 61 contract_passed=True, 62 contract_evidence_id="nightly-release-eval@batch-eligible", 63 batch=True, 64 ) 65) 66 67spend_by_feature: defaultdict[str, Decimal] = defaultdict(lambda: Decimal("0")) 68for trace in traces: 69 spend_by_feature[trace.feature] += trace_spend(trace) 70 71total_with_eval = sum(spend_by_feature.values()) 72trace_contracts_complete = all( 73 trace.contract_passed 74 and trace.contract_evidence_id 75 and trace.rate_card_id == rate_card.rate_card_id 76 for trace in traces 77) 78stale_trace = replace(traces[0], rate_card_id="openai-gpt-5.4-short-context-2026-04-30") 79assert spend_by_feature["public-policy-answer"] == Decimal("7.776000") 80assert actual_daily_spend - sum(trace_spend(trace) for trace in traces[:-1]) == Decimal("0.675000") 81assert trace_contracts_complete 82 83for feature in sorted(spend_by_feature): 84 print(f"{feature}=${dollars(spend_by_feature[feature])}") 85print(f"daily_total_with_eval=${dollars(total_with_eval)}") 86print(f"trace_contracts_complete={trace_contracts_complete}") 87try: 88 trace_spend(stale_trace) 89except ValueError as error: 90 print(f"stale_rate_card_rejected={error}")
Output
1access-exception-answer=$2.29 2live-order-answer=$11.03 3nightly-release-eval=$2.83 4public-policy-answer=$7.78 5daily_total_with_eval=$23.92 6trace_contracts_complete=True 7stale_rate_card_rejected=trace rate card does not match pricing table
Cost guardrail flow ranks spend lanes, verifies trace evidence, and gates release decisions.
Rank actual dollars first, then prove them. Cost work can land only when every row carries contract evidence and pinned pricing identity.

Forecast and gate the release

A cost target without quality is an incentive to produce short, wrong answers. A quality target without cost leaves a production team unable to plan capacity or margins. Require both.

The canary report below evaluates the concise cited-answer policy for return exceptions. The replay's request mix is projected across 30 days, including its nightly offline evaluation run.

09-evaluate-a-release-budget.py
1@dataclass(frozen=True) 2class QualityReport: 3 cited_answer_pass_rate: Decimal 4 unsafe_cache_hits: int 5 evaluated_answers: int 6 7@dataclass(frozen=True) 8class PromotionPolicy: 9 monthly_budget_usd: Decimal 10 minimum_cited_answer_pass_rate: Decimal 11 maximum_unsafe_cache_hits: int 12 minimum_evaluated_answers: int 13 14quality = QualityReport( 15 cited_answer_pass_rate=Decimal("0.997"), 16 unsafe_cache_hits=0, 17 evaluated_answers=2_000, 18) 19promotion_policy = PromotionPolicy( 20 monthly_budget_usd=Decimal("750.00"), 21 minimum_cited_answer_pass_rate=Decimal("0.995"), 22 maximum_unsafe_cache_hits=0, 23 minimum_evaluated_answers=2_000, 24) 25monthly_forecast = total_with_eval * Decimal("30") 26budget_headroom = promotion_policy.monthly_budget_usd - monthly_forecast 27uniform_volume_growth_headroom = budget_headroom / monthly_forecast 28 29sample_size_passed = ( 30 quality.evaluated_answers >= promotion_policy.minimum_evaluated_answers 31) 32quality_passed = ( 33 trace_contracts_complete 34 and sample_size_passed 35 and quality.cited_answer_pass_rate >= promotion_policy.minimum_cited_answer_pass_rate 36 and quality.unsafe_cache_hits <= promotion_policy.maximum_unsafe_cache_hits 37) 38budget_passed = monthly_forecast <= promotion_policy.monthly_budget_usd 39 40assert dollars(monthly_forecast) == Decimal("717.56") 41assert dollars(budget_headroom) == Decimal("32.44") 42assert quality_passed and budget_passed 43 44print(f"monthly_forecast=${dollars(monthly_forecast)}") 45print(f"budget_headroom=${dollars(budget_headroom)}") 46print(f"uniform_volume_growth_headroom={uniform_volume_growth_headroom:.1%}") 47print(f"sample_size_passed={sample_size_passed}") 48print(f"quality_passed={quality_passed}") 49print(f"budget_passed={budget_passed}")
Output
1monthly_forecast=$717.56 2budget_headroom=$32.44 3uniform_volume_growth_headroom=4.5% 4sample_size_passed=True 5quality_passed=True 6budget_passed=True

This forecast repeats the measured replay day 30 times. Only 4.5% uniform traffic growth would consume the remaining budget, so production planning should also replay low, expected, and high request-volume scenarios. A point forecast that barely passes isn't a comfortable operating margin.

Release gate view combining answer quality and forecast budget.
Promotion needs both receipts. Quality passes safely enough to ship, and budget still passes, but headroom is thin enough that volume growth needs continued monitoring.

Two routes have the same cost per attempt, but one often needs a retry before producing a valid answer. Which cost should the release decision compare?

Answer

Compare cost per successful answer or task, including retries and failed attempts. Cost per request would hide the extra spend caused by lower reliability.

Produce the next policy input

The next lesson will implement model gateway routing and fallbacks. Give it a budget contract, not a half-built router embedded in cost analysis. The contract records what a future route must preserve: citation schema, an approved generated-answer ceiling, and the release evidence behind the limit. The ceiling must be a policy value that the observed maximum fits under, not the observed maximum relabeled as a budget.

Treat that ceiling as a lane-admission artifact for one successful generation, not as a hard bound on total request spend. A failed primary can still bill before timeout, and a contract-preserving fallback bills again. Incident TCO can exceed maximum_generated_answer_usd when attempt costs stack; the gateway lesson tracks request-level attempt spend separately from the per-answer admission ceiling.

10-emit-the-budget-contract.py
1@dataclass(frozen=True) 2class GatewayBudgetContract: 3 release_id: str 4 required_answer_schema: str 5 maximum_generated_answer_usd: Decimal 6 monthly_forecast_usd: Decimal 7 rate_card_id: str 8 status: str 9 10observed_max_generated_cost = max( 11 trace_unit_spend(trace) 12 for trace in traces 13 if trace.usage is not None and not trace.batch 14) 15per_answer_budget_usd = Decimal("0.005000") 16assert observed_max_generated_cost <= per_answer_budget_usd 17 18status = "PROMOTE_COST_POLICY" if quality_passed and budget_passed else "HOLD_RELEASE" 19gateway_contract = GatewayBudgetContract( 20 release_id=release_id, 21 required_answer_schema="cited-support-answer-v3", 22 maximum_generated_answer_usd=per_answer_budget_usd, 23 monthly_forecast_usd=dollars(monthly_forecast), 24 rate_card_id=rate_card.rate_card_id, 25 status=status, 26) 27 28assert gateway_contract.status == "PROMOTE_COST_POLICY" 29assert observed_max_generated_cost == Decimal("0.004570") 30assert gateway_contract.maximum_generated_answer_usd == Decimal("0.005000") 31 32print(f"status={gateway_contract.status}") 33print(f"required_schema={gateway_contract.required_answer_schema}") 34print(f"observed_max_generated_answer=${observed_max_generated_cost:.6f}") 35print(f"max_generated_answer=${gateway_contract.maximum_generated_answer_usd:.6f}") 36print("next_step=apply_contract_in_gateway_routing")
Output
1status=PROMOTE_COST_POLICY 2required_schema=cited-support-answer-v3 3observed_max_generated_answer=$0.004570 4max_generated_answer=$0.005000 5next_step=apply_contract_in_gateway_routing

What the ledger proves

This release did not become cheaper because of one vague "cache" switch:

EvidenceCorrect conclusion
Semantic answer hit with valid release scopeNew generation was avoided for an already approved answer class
Provider cached_tokens on a generated answerRepeated prefix processing was charged at the cached-input rate
Concise cited-answer candidate passes evaluationOutput reduction is eligible for release
Nightly evaluation classified as noninteractiveBatch discount can be modeled without changing customer latency
Every usage trace pins the loaded rate-card identityHistorical spend remains reproducible instead of being silently repriced
Monthly forecast and quality check passCost policy can be promoted and handed to gateway design

Wait for usage traces before claiming a prompt rewrite improved pricing through cached tokens. Count semantic-cache savings only when accepted-hit quality remains valid. Never approve a shorter output merely because it costs less.

Complete the lesson

Mastery Check

Answer every question, then check your score. Score 75% or higher to mark this lesson complete.

1.A generated policy answer reports 1,800 input tokens, 1,280 cached input tokens, and 180 output tokens. Standard rates are 2.50per1Mfreshinputtokens,2.50 per 1M fresh input tokens, 2.50per1Mfreshinputtokens,0.25 per 1M cached input tokens, and $15.00 per 1M output tokens. What total should the ledger charge?

Correct answer: $0.004320, because 520 input tokens are fresh, 1,280 are cached, and 180 output tokens are billed separately.

Fresh input is 1,800 minus 1,280, or 520 tokens. The charges are 520 * 2.50/1,000,000=2.50 / 1,000,000 = 2.50/1,000,000=0.001300, 1,280 * 0.25/1,000,000=0.25 / 1,000,000 = 0.25/1,000,000=0.000320, and 180 * 15.00/1,000,000=15.00 / 1,000,000 = 15.00/1,000,000=0.002700. The total is $0.004320. A prefix cache hit lowers part of the input cost but does not remove output spend.

2.A team wants more GPT-5.4 prompt-cache reads for generated policy answers. Their prompt begins with request-specific session data, then the stable instructions and shared policy evidence. GPT-5.4 belongs to the model group with a cacheable-prefix minimum that varies from 1,024 to 2,048 tokens, so a prompt barely above 1,024 tokens may still miss. Which change and ledger interpretation are correct?

Correct answer: Move stable instructions and shared evidence to the beginning; a hit discounts cached input tokens but the new output remains billable.

Provider prompt caching applies to an exact matching beginning of the prompt, so stable repeated material should come first and dynamic session data should follow it. For GPT-5.4, treat returned cached_tokens as proof of a cache read rather than promising a hit at exactly 1,024 tokens. A prompt-cache hit is still a generated answer: cached input tokens are discounted, but output tokens remain billable.

3.In a replay, 3,200 stable public-policy requests are approved semantic answer-cache hits. No model call is made for those requests. The counterfactual generated answer would have cost $0.004320 each if it had used the provider prefix cache. What should the release ledger report for that slice?

Correct answer: 0.00actualgeneratedspendand0.00 actual generated spend and 0.00actualgeneratedspendand13.824 avoided generation, because the stored answers skipped new model calls.

A semantic answer-cache hit returns an already evaluated stored answer, so it has no generated-token charge in actual spend. The counterfactual avoided generation is reported separately: 3,200 * 0.004320=0.004320 = 0.004320=13.824. Mixing avoided cost into actual spend would make the ledger misleading.

4.PolicyOps compares three access-exception formats on the same input. The answer contract requires a decision, a policy citation, and a next step. Verbose has all three and 220 output tokens; concise_cited has all three and 130 output tokens; truncated has only the decision and 55 output tokens. Which format is eligible as the cost-saving release candidate?

Correct answer: concise_cited, because it is the shortest candidate that still satisfies all three contract requirements.

Cost reduction counts only among candidates that still pass the answer contract. The truncated answer is cheaper but lacks the required citation and next step. The concise_cited answer is shorter than verbose while preserving all required evidence and action fields.

5.Two workloads use the same model endpoint. Live support is interactive with a 0-hour deadline; nightly release evaluation is noninteractive with a 24-hour deadline. The endpoint supports Batch, and the rate card lists explicit Batch prices, including cached input at $0.13 per 1M tokens. How should the ledger model them?

Correct answer: Use Standard pricing for live support; use Batch pricing for nightly evaluation and read the explicit Batch rows from the rate card.

Batch is appropriate only for work that can wait, so the interactive support endpoint stays on Standard pricing despite endpoint support. The nightly evaluation workload is noninteractive with a 24-hour deadline, so it can use Batch. The ledger should load the published Batch rows explicitly rather than deriving every rate from a local discount rule.

6.A saved trace for a release has rate_card_id openai-gpt-5.4-short-context-2026-05-31. The loaded pricing table has rate_card_id openai-gpt-5.4-short-context-2026-06-30. The token counts are present and the model family name is the same. What should the ledger do before computing historical spend?

Correct answer: Reject the trace or require an explicit repricing path, because the pinned rate-card identity does not match the loaded table.

Provider prices and pricing-mode rows can change. A historical trace must be priced with the rate card identity recorded for that ledger decision, or repriced only through an explicit process. Silently applying the loaded table would make old spend non-reproducible.

7.A canary release has complete contract traces and a daily total with nightly eval of 23.9188.Thepromotionpolicyallows23.9188. The promotion policy allows 23.9188.Thepromotionpolicyallows750 per 30-day month, requires cited-answer pass rate at least 0.995, and allows 0 unsafe cache hits. The quality report shows pass rate 0.997 and 1 unsafe cache hit. What should happen?

Correct answer: Hold the release, because the $717.56 forecast is under budget but the unsafe cache hit fails the quality check.

The 30-day forecast is 23.9188 * 30 = 717.564, rounded to $717.56, so the budget check passes. The quality check fails because the policy allows at most 0 unsafe cache hits and the report has 1. Promotion requires both checks, and avoided generation should not be subtracted from actual spend.

8.A GPT-5.4 response reports output_tokens=1,186 and output_tokens_details.reasoning_tokens=1,024. Which output-token count should the cost ledger price?

Correct answer: Price 1,186 output tokens once; the 1,024 reasoning tokens are already included in that total.

Reasoning tokens are billed as output tokens and are included in the provider's total output_tokens count. The detail field explains the composition of that total; adding it again would double-count the same usage.

9.After a release passes quality and budget gates, the next routing layer needs a cost input but routing has not been implemented. Which artifact should the cost ledger emit?

Correct answer: A budget contract with required answer schema, maximum generated-answer spend, monthly forecast, rate_card_id, and status.

The cost analysis should hand routing a budget contract, not a half-built router. Its generated-answer limit must be an approved policy ceiling that observed costs fit under, alongside the answer schema, monthly forecast, rate-card identity, and promotion status.

9 questions remaining.

Next Step
Continue to Model Gateways, Routing, and Fallbacks

You now have a measured budget contract for each generated support answer. Next you'll apply it when selecting lanes and safe fallbacks.

PreviousSemantic Caching & Cost Optimization
Share this article
XFacebookLinkedInBlueskyRedditHacker NewsEmail
References

OpenAI API Pricing

OpenAI · 2026

https://developers.openai.com/api/docs/pricing

Reasoning models

OpenAI · 2026

https://developers.openai.com/api/docs/guides/reasoning

Efficient Memory Management for Large Language Model Serving with PagedAttention.

Kwon, W., et al. · 2023 · SOSP 2023

https://arxiv.org/abs/2309.06180

GPT-5.4 Model

OpenAI · 2026

https://developers.openai.com/api/docs/models/gpt-5.4

Prompt caching

OpenAI · 2026

https://developers.openai.com/api/docs/guides/prompt-caching

OpenAI Batch API Guide

OpenAI · 2026

https://developers.openai.com/api/docs/guides/batch

Discussion

Questions and insights from fellow learners.

Discussion loads when you reach this section.