pub struct CostModel {
pub input: Decimal,
pub output: Decimal,
pub cache_read: Decimal,
pub cache_write: Decimal,
pub cache_write_long: Option<Decimal>,
pub context_window: u32,
pub max_output: u32,
pub as_of: AsOf,
}Expand description
What one reasoner charges, and how much it can be given.
§Example
use boundgraph_core::{AsOf, CostModel, TokenUsage};
use rust_decimal::Decimal;
let usd = |s: &str| s.parse::<Decimal>().unwrap();
let model = CostModel::new(
usd("5.00"), // input, per million tokens
usd("25.00"), // output
AsOf::new(2026, 6, 24).unwrap(),
)
.with_context_window(1_000_000)
.with_cache_rates(usd("0.50"), usd("6.25"));
// A cached read is a tenth of the uncached rate, and the price says so.
let cold = model.price(&TokenUsage { input: 1_000_000, ..TokenUsage::NONE });
let warm = model.price(&TokenUsage { cache_read: 1_000_000, ..TokenUsage::NONE });
assert_eq!(cold, usd("5.00"));
assert_eq!(warm, usd("0.50"));Fields§
§input: DecimalUS dollars per million uncached prompt tokens.
output: DecimalUS dollars per million completion tokens.
cache_read: DecimalUS dollars per million prompt tokens served from the cache.
Substantially below input — that difference is the
whole point of caching, and collapsing the two would hide it.
cache_write: DecimalUS dollars per million prompt tokens written to the cache.
Above input: a write costs more than not caching at
all, which is why a prefix has to be read back at least twice before
caching it pays for itself.
cache_write_long: Option<Decimal>The same, for a provider’s longer-lived cache tier, if it has one.
context_window: u32The largest prompt this model will accept, in tokens.
A ceiling the compiler checks a flow’s declared context against, so “this step asks for more window than the model has” is a build error rather than a run-time refusal.
max_output: u32The largest completion it will produce, in tokens.
as_of: AsOfThe date these figures were correct on.
Implementations§
Source§impl CostModel
impl CostModel
Sourcepub fn new(input: Decimal, output: Decimal, as_of: AsOf) -> Self
pub fn new(input: Decimal, output: Decimal, as_of: AsOf) -> Self
A model priced per million tokens, with no cache tier.
Cache rates default to the uncached input rate, which is the conservative reading: a provider that has not told us caching is cheaper is priced as though it is not.
Sourcepub fn with_cache_rates(self, read: Decimal, write: Decimal) -> Self
pub fn with_cache_rates(self, read: Decimal, write: Decimal) -> Self
Declare the cache tier’s rates.
Sourcepub fn with_long_cache_write(self, write: Decimal) -> Self
pub fn with_long_cache_write(self, write: Decimal) -> Self
Declare a longer-lived cache tier’s write rate.
Sourcepub fn with_context_window(self, tokens: u32) -> Self
pub fn with_context_window(self, tokens: u32) -> Self
Declare the prompt ceiling.
Sourcepub fn with_max_output(self, tokens: u32) -> Self
pub fn with_max_output(self, tokens: u32) -> Self
Declare the completion ceiling.
Sourcepub fn price(&self, usage: &TokenUsage) -> Decimal
pub fn price(&self, usage: &TokenUsage) -> Decimal
What a call consuming usage costs, in US dollars.
Exact decimal arithmetic throughout: this figure ends up in the ledger and gets summed across a run, and a rounding error that compounds is worse than no figure at all.
Sourcepub fn prices_alike(&self, other: &Self) -> bool
pub fn prices_alike(&self, other: &Self) -> bool
Whether two models would price the same call identically.
What a failover has to satisfy: a fallback that charges differently changes the ceiling the flow was compiled against, so the compiler refuses it (US-0604) rather than letting the proved bound quietly stop being true.