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 158 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/8
Capstone: Delivery ETA PredictionCapstone: Product RankingCapstone: Demand ForecastingCapstone: Image Damage ClassifierCapstone: Production ML PipelineCapstone: Document QACapstone: Eval DashboardCapstone: Fine-Tuned Classifier
🧠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/15
Scaling Laws & Compute-Optimal TrainingPre-training Data at ScaleBuild GPT from Scratch LabContinued 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
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.

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

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. 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

OpenAI's documented prompt caching applies automatically for prompts of at least 1,024 tokens, depends on exact matching at the beginning of the prompt, and reports the cached prefix count under cached_tokens in the response usage details.[4]Reference 4Prompt 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.

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

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. 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.[5]Reference 5OpenAI 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

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. 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. 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.

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.

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.

Mastery check

Key concepts

  • Version rate cards because provider prices and discount rules change.
  • Price provider-reported fresh input, cached input, and output separately.
  • Reasoning tokens are already included in billed output tokens; don't add them twice.
  • A semantic response hit can skip generation; a prompt-prefix hit can't.
  • Output reduction is valid only after the answer schema and evidence requirements still pass.
  • Batch is a workload-class decision: asynchronous evaluation can wait; live support can't.
  • Store explicit rates for each provider pricing mode instead of assuming every discount composes arithmetically.
  • Pin rate_card_id on every usage trace so historical spend can't be repriced silently.
  • A release promotion needs both a quality check and a budget check.

Practice tasks

  1. Add a new warranty-claim-answer traffic slice with longer cited output. Decide whether it breaks the monthly budget.
  2. Change the rate card to a newly verified provider/model rate card and rerun the ledger. Explain which request class moves the forecast most.
  3. Change one stored trace to a stale rate_card_id. Verify that the ledger rejects repricing it against the loaded table.
  4. Add one unsafe semantic answer hit to the quality report. Verify that apparent savings don't allow promotion.
  5. Increase every traffic slice by 5%. Verify that the monthly forecast now crosses the $750 budget.
  6. Replace the fixed replay with exported usage traces from an application you control, keeping rate-card identity and release identity explicit.

Evaluation rubric

  • Foundational: Computes a generated request charge from fresh input, cached input, and output categories.
  • Foundational: Uses total output tokens for billing while keeping reasoning-token detail diagnostic.
  • Foundational: Explains why answer reuse and prefix reuse can't share one cache_hit metric.
  • Intermediate: Attributes spend by feature and decision, including avoided generation without treating it as actual spend.
  • Intermediate: Tests an output optimization against a required answer contract.
  • Advanced: Rejects historical usage when its pinned rate-card identity doesn't match the loaded pricing table.
  • Advanced: Promotes only when a dated rate card, measured traffic replay, quality report, and budget policy all pass.
  • Advanced: Hands a budget contract to the routing layer without prematurely implementing routing here.

Self-check questions

Common pitfalls

Estimating the bill from characters

  • Symptom: Forecast misses invoice even when request count is correct.
  • Cause: Character length or visible response length was treated as provider usage.
  • Fix: Store billable usage categories returned by the provider and price them with a versioned rate card.

Calling all reuse a cache hit

  • Symptom: Team believes generations were skipped, but output spend remains high.
  • Cause: Prefix-cache reuse was confused with stored-answer reuse.
  • Fix: Record separate decisions and cost semantics for each layer.

Cutting output below correctness

  • Symptom: Spend falls while support answers omit evidence or next actions.
  • Cause: Output cap was promoted without evaluating the required answer schema.
  • Fix: Make the quality contract a hard promotion gate.

Batching interactive requests

  • Symptom: Forecast looks excellent while customer response latency becomes unacceptable.
  • Cause: A discount was modeled without preserving completion-time requirements.
  • Fix: Batch only explicitly asynchronous work such as nightly evaluation.

Deriving every pricing mode from one multiplier

  • Symptom: Forecast drifts from provider billing even though token counts are correct.
  • Cause: Standard, Batch, or other processing-mode prices were inferred from one discount instead of loaded from the provider's dated table.
  • Fix: Version explicit rates for every pricing mode used by the release and refresh them from current provider documentation.

Repricing historical traces with the current table

  • Symptom: The same saved trace produces a different historical spend number after a rate-card refresh.
  • Cause: Usage rows stored token categories but omitted the rate-card identity used for the original ledger decision.
  • Fix: Pin rate_card_id on each trace and reject mismatched tables instead of silently repricing.

Double-counting reasoning tokens

  • Symptom: Ledger output spend is higher than provider billing for a reasoning model.
  • Cause: output_tokens_details.reasoning_tokens was added on top of output_tokens, even though total output already includes it.
  • Fix: Price output_tokens once. Store reasoning-token detail only for diagnosis and policy analysis.
Complete the lesson

Mastery Check

Answer every question, then check your score. Score above 75% 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?
2.A team wants more provider prompt-cache hits for generated policy answers. Their prompt begins with request-specific session data, then the stable instructions and shared policy evidence. The provider cache requires an exact matching prefix of at least 1,024 tokens and reports cached prefix tokens. Which change and ledger interpretation are correct?
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?
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?
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?
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?
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?
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?
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?

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

Reasoning models

OpenAI · 2026

Efficient Memory Management for Large Language Model Serving with PagedAttention.

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

Prompt caching

OpenAI · 2026

OpenAI Batch API Guide

OpenAI · 2026

Discussion

Questions and insights from fellow learners.

Discussion loads when you reach this section.