Build an auditable LLM cost ledger from usage traces, cache decisions, output contracts, offline batch work, and release budget gates.
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.
Cost engineering begins after a request decision is known:
| Decision | What executes? | What belongs in this ledger? |
|---|---|---|
| Semantic answer hit from the previous lesson | Stored evaluated answer returned | No generation charge; record avoided generation separately |
| Generated answer with provider prompt-cache hit | Model still produces a new answer | Fresh input, cached input, and output token charges |
| Generated answer without prompt-cache hit | Model reads full input and produces a new answer | Fresh input and output token charges |
| Offline evaluation batch | Model runs later, outside interactive path | Batch-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.
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]
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]
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}")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.50PolicyOps'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.
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%}")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][1]
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] 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.
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}")1cold_generation=$0.007200
2prefix_cached_generation=$0.004320
3saved_per_generated_answer=$0.002880
4still_generated=TrueThe 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.
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}")1semantic_hit_generation=$0.000000
2semantic_hit_avoided=$0.004320
3prefix_hit_generation=$0.004320
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:
The semantic-hit counterfactual answers "what cost did safe reuse avoid?" Actual spend answers "what appears on this release ledger?"
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)}")1generated_daily_spend=$21.76
2semantic_hit_avoided_daily=$13.82
3request_count=8500Avoided 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.
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:
| Candidate | Intended change | Safe for automatic support? |
|---|---|---|
| Verbose | Full explanation with repetition | Correct but unnecessarily long |
| Concise cited | One decision, one citation, one next step | Candidate for release |
| Truncated | Hard stop before citation and action | Reject |
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)}")1selected=concise_cited
2rejected=truncated
3saved_per_exception=$0.001350
4saved_per_replay_day=$0.68The cheapest candidate is rejected. Cost reductions count only after the output still meets the same correctness and evidence contract.
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] The pricing page publishes Batch token rates explicitly, so the ledger reads that row instead of deriving every category from one multiplier.[1] 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.
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)}")1live_support_batchable=False
2nightly_eval_batchable=True
3nightly_eval_sync=$5.64
4nightly_eval_batch=$2.83An 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.
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}")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
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.
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}")1monthly_forecast=$717.56
2budget_headroom=$32.44
3uniform_volume_growth_headroom=4.5%
4sample_size_passed=True
5quality_passed=True
6budget_passed=TrueThis 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.
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.
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")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_routingThis release did not become cheaper because of one vague "cache" switch:
| Evidence | Correct conclusion |
|---|---|
| Semantic answer hit with valid release scope | New generation was avoided for an already approved answer class |
Provider cached_tokens on a generated answer | Repeated prefix processing was charged at the cached-input rate |
| Concise cited-answer candidate passes evaluation | Output reduction is eligible for release |
| Nightly evaluation classified as noninteractive | Batch discount can be modeled without changing customer latency |
| Every usage trace pins the loaded rate-card identity | Historical spend remains reproducible instead of being silently repriced |
| Monthly forecast and quality check pass | Cost 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.
rate_card_id on every usage trace so historical spend can't be repriced silently.warranty-claim-answer traffic slice with longer cited output. Decide whether it breaks the monthly budget.rate_card_id. Verify that the ledger rejects repricing it against the loaded table.cache_hit metric.rate_card_id on each trace and reject mismatched tables instead of silently repricing.output_tokens_details.reasoning_tokens was added on top of output_tokens, even though total output already includes it.output_tokens once. Store reasoning-token detail only for diagnosis and policy analysis.Answer every question, then check your score. Score above 75% to mark this lesson complete.
9 questions remaining.
Questions and insights from fellow learners.