1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
//! What a call consumed, and how much of that the provider actually told you.
use serde::{Deserialize, Serialize};
/// How complete a [`Usage`] is.
///
/// Carried alongside the numbers rather than inferred from them, because zero and unknown
/// look the same once they are added up. A total built from partial usage is a total that
/// understates the bill, and nothing downstream can tell.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[non_exhaustive]
pub enum UsageCoverage {
/// Every field was reported.
Exact,
/// The numbers were counted here rather than reported by the provider.
///
/// Between [`UsageCoverage::Exact`] and [`UsageCoverage::Partial`] in this ordering,
/// and it is a different kind of wrong from either. A partial usage understates: the
/// bill is that much or more. An estimate can be wrong in **either** direction, so a
/// total containing one is not a floor and must not be reported as one.
///
/// This variant exists so that a locally counted number can be added up without being
/// folded into `Exact`, which would destroy the one property this type is for.
Estimated,
/// Some fields were reported and some were not.
Partial,
/// Nothing was reported. The call still happened and still cost something.
Absent,
}
impl UsageCoverage {
/// How a coverage is written down, in records and in spans.
///
/// One spelling in one place, for the reason [`crate::Reach::as_str`] has one: two
/// copies of this mapping is two chances for a log line and a report to disagree about
/// what "absent" means.
pub fn as_str(self) -> &'static str {
match self {
UsageCoverage::Exact => "exact",
UsageCoverage::Estimated => "estimated",
UsageCoverage::Partial => "partial",
UsageCoverage::Absent => "absent",
}
}
}
impl std::fmt::Display for UsageCoverage {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
/// The tokens one call consumed.
///
/// Every field is optional because providers differ in what they report, and a missing
/// number is a fact worth keeping. A subscription command line tool usually reports nothing
/// at all, and writing zero in its place turns an unknown cost into a free one.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
#[non_exhaustive]
pub struct Usage {
/// The part of the prompt that was not served from cache.
///
/// Not the size of the prompt. Providers disagree here, and some report a total that
/// includes the cached read. A provider in this crate subtracts so that this field
/// always means the same thing, which is what makes two providers comparable.
pub input_tokens: Option<u64>,
/// Prompt tokens served from cache, billed at a lower rate.
pub cache_read_tokens: Option<u64>,
/// Prompt tokens written to cache, usually billed at a higher rate than input.
pub cache_write_tokens: Option<u64>,
/// Tokens produced, including any the model spent thinking.
///
/// Thinking tokens are billed whether or not they are shown, so they belong in the
/// number you price.
pub output_tokens: Option<u64>,
/// Whether these numbers were counted here rather than reported.
///
/// A flag rather than a fifth optional field, because it is a fact about all four at
/// once. Set it with [`Usage::estimating`], read it through [`Usage::coverage`], and see
/// [`UsageCoverage::Estimated`] for why an estimate is not a floor.
pub estimated: bool,
}
impl Usage {
/// A usage with nothing reported.
///
/// This is the right answer when a provider does not measure. It is not the same as
/// zeros, and [`Usage::coverage`] tells them apart.
pub fn absent() -> Self {
Self::default()
}
/// Records the part of the prompt that was not served from cache.
///
/// Not the whole prompt. If your provider reports a total that includes the cached
/// part, subtract before you get here, or two providers will report different numbers
/// for the same conversation.
#[must_use]
pub fn with_input(mut self, tokens: u64) -> Self {
self.input_tokens = Some(tokens);
self
}
/// Records prompt tokens served from cache.
#[must_use]
pub fn with_cache_read(mut self, tokens: u64) -> Self {
self.cache_read_tokens = Some(tokens);
self
}
/// Records prompt tokens written to cache.
#[must_use]
pub fn with_cache_write(mut self, tokens: u64) -> Self {
self.cache_write_tokens = Some(tokens);
self
}
/// Records tokens produced, thinking included.
#[must_use]
pub fn with_output(mut self, tokens: u64) -> Self {
self.output_tokens = Some(tokens);
self
}
/// Marks these numbers as counted here rather than reported by the provider.
///
/// The case is a subscription command line tool: it answers, it was billed, and it says
/// nothing about tokens. A caller with a token counter can produce a number, and this is
/// how that number is kept apart from one a vendor reported.
///
/// ```
/// # use llmr::{Usage, UsageCoverage};
/// let counted = Usage::absent()
/// .with_input(1_200)
/// .with_cache_read(0)
/// .with_cache_write(0)
/// .with_output(340)
/// .estimating();
///
/// assert_eq!(counted.coverage(), UsageCoverage::Estimated);
/// ```
///
/// **This crate does not count tokens for you and will not.** A tokeniser has to match
/// the vendor's, per model, and one that is close produces numbers that look right and
/// are not, which is the failure every type in this module exists to prevent. Bring your
/// own count, or leave the call [`Usage::absent`].
///
/// Estimating nothing is still nothing: this on an otherwise absent usage leaves the
/// coverage [`UsageCoverage::Absent`], because there is no estimate to be approximate
/// about.
#[must_use]
pub fn estimating(mut self) -> Self {
self.estimated = true;
self
}
/// What an embedding call consumed, when the provider reported its prompt tokens.
///
/// An embedding endpoint reports one number, because one number is all there is: text
/// goes in and a vector comes out, and a vector is not tokens. So the other three fields
/// are set to zero rather than left absent, and this reads as
/// [`UsageCoverage::Exact`].
///
/// **That is a claim, and it is the same claim [`Usage::prompt_tokens`] already makes**:
/// a provider reporting some fields and not others is saying the others did not happen.
/// Here they did not. Left absent instead, every embedding call would read as `Partial`
/// and turn every [`crate::Ledger`] total into a floor forever — an honest-looking
/// hedge about a call that was measured exactly.
///
/// If a vendor does report cached tokens on an embedding call, build the [`Usage`] with
/// the builders instead. This constructor is for the common shape, not for every shape.
pub fn embedding(input_tokens: u64) -> Self {
Usage {
input_tokens: Some(input_tokens),
cache_read_tokens: Some(0),
cache_write_tokens: Some(0),
output_tokens: Some(0),
estimated: false,
}
}
/// How much of this was reported.
pub fn coverage(&self) -> UsageCoverage {
let fields = [
self.input_tokens,
self.cache_read_tokens,
self.cache_write_tokens,
self.output_tokens,
];
let present = fields.iter().filter(|f| f.is_some()).count();
match present {
// Nothing to be approximate about, so the flag changes nothing here.
0 => UsageCoverage::Absent,
// An estimate of three fields out of four is still an estimate, and `Estimated`
// already says the number can be wrong in either direction, which covers the
// field that was never counted. Reporting `Partial` instead would claim the
// total is a floor, and an estimate is not one.
_ if self.estimated => UsageCoverage::Estimated,
n if n == fields.len() => UsageCoverage::Exact,
_ => UsageCoverage::Partial,
}
}
/// Every prompt token, cached or not.
///
/// Returns `None` when no prompt field was reported at all. A missing field among
/// others counts as zero, because a provider that reports two of three is telling you
/// the third did not happen.
pub fn prompt_tokens(&self) -> Option<u64> {
let parts = [
self.input_tokens,
self.cache_read_tokens,
self.cache_write_tokens,
];
if parts.iter().all(Option::is_none) {
return None;
}
Some(parts.iter().map(|p| p.unwrap_or(0)).sum())
}
/// Adds two usages together, field by field.
///
/// A field missing from both stays missing. A field present in either is a number, so
/// summing a run of calls never turns a reported total into an unknown one.
pub fn merge(self, other: Usage) -> Usage {
fn add(a: Option<u64>, b: Option<u64>) -> Option<u64> {
match (a, b) {
(None, None) => None,
(a, b) => Some(a.unwrap_or(0).saturating_add(b.unwrap_or(0))),
}
}
Usage {
input_tokens: add(self.input_tokens, other.input_tokens),
cache_read_tokens: add(self.cache_read_tokens, other.cache_read_tokens),
cache_write_tokens: add(self.cache_write_tokens, other.cache_write_tokens),
output_tokens: add(self.output_tokens, other.output_tokens),
// A measured call added to an estimated one is a number that is partly guessed,
// and the sum has to say so. Estimation spreads on merge, exactly as absence
// does not.
estimated: self.estimated || other.estimated,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn full() -> Usage {
Usage {
input_tokens: Some(100),
cache_read_tokens: Some(900),
cache_write_tokens: Some(50),
output_tokens: Some(20),
estimated: false,
}
}
#[test]
fn nothing_reported_is_absent_rather_than_zero() {
let nothing = Usage::absent();
assert_eq!(nothing.coverage(), UsageCoverage::Absent);
assert_eq!(nothing.prompt_tokens(), None);
assert_eq!(nothing.output_tokens, None);
}
#[test]
fn some_fields_reported_is_partial() {
let partial = Usage {
output_tokens: Some(20),
..Usage::absent()
};
assert_eq!(partial.coverage(), UsageCoverage::Partial);
}
#[test]
fn every_field_reported_is_exact() {
assert_eq!(full().coverage(), UsageCoverage::Exact);
}
#[test]
fn the_prompt_total_counts_the_cached_part() {
assert_eq!(full().prompt_tokens(), Some(1_050));
}
#[test]
fn merging_two_absent_usages_stays_absent() {
let merged = Usage::absent().merge(Usage::absent());
assert_eq!(merged.coverage(), UsageCoverage::Absent);
}
#[test]
fn merging_a_reported_call_with_an_unreported_one_keeps_the_number() {
// A run of ten calls where one provider reports and another does not should not
// report nothing. What is known stays known.
let merged = full().merge(Usage::absent());
assert_eq!(merged.output_tokens, Some(20));
assert_eq!(merged.coverage(), UsageCoverage::Exact);
}
#[test]
fn merging_adds_field_by_field() {
let merged = full().merge(full());
assert_eq!(merged.input_tokens, Some(200));
assert_eq!(merged.output_tokens, Some(40));
}
#[test]
fn a_locally_counted_call_is_estimated_rather_than_exact() {
// Folding a count into `Exact` would destroy the one property this type is for. It
// is a different kind of wrong from `Partial` too: partial understates, an estimate
// can be wrong either way.
let counted = full().estimating();
assert_eq!(counted.coverage(), UsageCoverage::Estimated);
assert_eq!(counted.prompt_tokens(), Some(1_050), "still a number");
}
#[test]
fn an_estimate_of_some_fields_is_still_an_estimate_rather_than_partial() {
// `Partial` claims the figure is a floor, and an estimate is not one whether or not
// every field was counted.
let some = Usage::absent().with_output(20).estimating();
assert_eq!(some.coverage(), UsageCoverage::Estimated);
}
#[test]
fn estimating_nothing_is_still_nothing() {
// There is no estimate to be approximate about, so the flag changes no answer here.
assert_eq!(
Usage::absent().estimating().coverage(),
UsageCoverage::Absent
);
}
#[test]
fn a_measured_call_merged_with_an_estimated_one_is_estimated() {
// The sum is partly guessed and has to say so, or the guess disappears into the
// measurement the first time two calls are added up.
assert_eq!(
full().merge(full().estimating()).coverage(),
UsageCoverage::Estimated
);
assert_eq!(full().merge(full()).coverage(), UsageCoverage::Exact);
}
#[test]
fn the_coverages_are_ordered_best_first() {
// Read by anything comparing two calls. Exact beats an estimate, an estimate beats
// a figure that is short, and all three beat nothing.
assert!(UsageCoverage::Exact < UsageCoverage::Estimated);
assert!(UsageCoverage::Estimated < UsageCoverage::Partial);
assert!(UsageCoverage::Partial < UsageCoverage::Absent);
}
#[test]
fn an_embedding_call_is_measured_exactly_rather_than_partly() {
// One number is all an embedding endpoint has to report, and a vector is not
// tokens. Left absent, the three fields that did not happen would make every
// embedding call `Partial` and every ledger total a floor for good.
let usage = Usage::embedding(1_500);
assert_eq!(usage.coverage(), UsageCoverage::Exact);
assert_eq!(usage.prompt_tokens(), Some(1_500));
assert_eq!(usage.output_tokens, Some(0));
}
#[test]
fn an_embedding_nobody_measured_is_still_absent() {
// The constructor is for a reported number. A provider that got none uses
// `absent`, and zero is not a substitute for it here any more than anywhere else.
assert_eq!(Usage::absent().coverage(), UsageCoverage::Absent);
assert_eq!(Usage::embedding(0).coverage(), UsageCoverage::Exact);
}
}