* docs(model-catalog): explain meter, band, channel and When Add a Concepts section to the model-catalog AGENTS.md with worked key breakdowns, doc comments on the undocumented litellm-cost price types, and a cost AGENTS.md that points to the concepts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(model-catalog): write concepts as nested lists Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(model-catalog): spell out band rules and channel fallbacks Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
5.4 KiB
Concepts
Every price in a row lowers to one Price { meter, channel, when } and its UnitPrice. These keys come from the FlatKey::parse tests
-
input_cost_per_token- meter
Input(Token(Text)), channelOnline, tierStandard, bandBase
- meter
-
output_cost_per_token_above_272k_tokens_priority- meter
Output(Token(Text)), channelOnline _prioritysets tierPriority_above_272k_tokenssets bandOverPromptTokens(272000)
- meter
-
cache_creation_input_token_cost_above_1hr_above_200k_tokens- meter
CacheWrite { Text, OneHour }, where_above_1hris the cache TTL, not a band _above_200k_tokenssets bandOverPromptTokens(200000)- channel
Online, tierStandard
- meter
-
ocr_cost_per_page_batches- meter
Feature(OcrPage), tierStandard, bandBase _batchessets channelBatch
- meter
-
Meter is what gets counted, such as text input tokens, reasoning tokens, an image of a given size or a web search
- A variation only one meter has stays inside it (cache TTL, image size, resolution,
_above_Ns_interval,max_results_range)
- A variation only one meter has stays inside it (cache TTL, image size, resolution,
-
UnitPriceis USD for one unit of the meter, such as one token, image, page or second -
Band is a range of prompt sizes, starting where
_above_<N>_tokensor_above_<N>k_tokenssaysPrices::bandpicks one band from prompt tokens alone, the highest edge passed wins, andBand::Baseis below every edge- The whole call moves, so every token meter, output included, bills all its tokens at that band's rate
Billing::band_edgesays whether a prompt exactly at the edge is in the band (exclusive by default, inclusive for xAI)- A band only another tier or the batch channel prices never moves the call
-
Whenis the request's condition, its service tier and band- A missing price falls back from
(tier, band)to(standard, band),(tier, base), then(standard, base)
- A missing price falls back from
-
Channelis the route whose price list the call reads, and each falls back differentlyOnlinefalls back acrossWhenas aboveBatch(_batches) uses bands but no service tier, and a missing price is half the online rateDecisions(_decisions, OpenAI/v1/decisions) overrides single online fields, and a missing price is the full online rate
-
Off-peak and
Adjustmentsare never prices- Off-peak swaps in other token rates while a UTC window is open
Adjustmentsmultiplies a priced call for region, residency, geo orspeed: fast
-
BillingonPricesholds row attributes that change how its prices apply (text unit, band edge, cache-read fallback, wall clock, web search unit, tier scope)lower.rsderives it from the row and itslitellm_provider, solitellm-costholds no provider list
Layout
wire/ reads one entry as written. EntryWire::read makes one pass over the entry's keys and puts every non-null key in exactly one place, which is an attribute, a flat price parsed by FlatKey::parse, a price block, a supports_* capability or unknown. A known key with a bad value becomes an Error that names the key, and the rest of the entry is still read
lower.rs is the only code that knows both the wire types and litellm-cost. It lowers an EntryWire into Prices, Adjustments and Model and checks the rules that span keys, such as one price per meter and condition, contiguous tiers, no suffix inside a block, and off-peak needing hours and prices. Lowering only goes one way. Nothing turns Prices back into JSON, since the raw fields are kept
catalog.rs reads the document, indexes aliases and runs the checks across entries. A bad entry keeps its raw fields and its errors are listed in Loaded.errors. Deciding what to do with them is the caller's job. Catalog::written gives back every top-level key and value as the document wrote them, in document order. Catalog::served gives back every model and alias as Python serves it, without sample_spec and fallback_generalizations
store/ is the catalog's in-memory store and the only place that indexes model names. Its rules are in src/store/AGENTS.md
layers.rs, resolve/, model_info.rs and register.rs hold types only. The functions over them land with their first caller
Catalog errors use one opaque Error with typed causes and JSON Pointer accessors (path, key, field). Error::rejects alone decides whether an error fails the document, rejects its row or neither, and ErrorKind is the stable name hosts report it by
Prices
The price vocabulary lives in litellm-cost, and only this crate knows JSON key names
A new N, image size or resolution in an existing key shape parses with no Rust change. A new unit, token kind, feature or tier needs a variant in litellm-cost plus its spelling in wire/flat_key.rs and FLAT_KEY_PATTERN in schema.rs
Prices::band and Prices::rate are pinned to Python's _get_token_base_cost by tests/price_lookup_reference.json. After a billing change in Python, regenerate it from the repo root with PYTHONPATH=. python litellm-rust/crates/model-catalog/tests/generate_price_lookup_reference.py
Tests
BUNDLED_REGISTRY embeds the root model_prices_and_context_window.json, so the tests over it fail a JSON change with a bad value or a key no type reads
Catalog::problems is the one definition of a mergeable cost map. The bundled test asserts it is empty, and the cost map guard workflow runs it on a pull request's JSON through cargo run --bin check-registry <path>