Skip to main content

velesdb_memory/context/
insights.rs

1//! Savings accounting: tokens (always) and money (only when a pricing table
2//! is injected).
3//!
4//! Money is integer micro-units of one currency (1 unit = 10⁻⁶ of the
5//! currency's major unit, e.g. `1_000_000` micros = 1 EUR) — no floats, no
6//! rounding drift. Prices are **injected and versioned**, never hardcoded:
7//! the compiler works fully without a pricing table, reporting tokens only.
8//!
9//! The token figures are *local estimates* (see
10//! [`super::estimator::HeuristicEstimator`]), not the provider's exact count,
11//! nor billed tokens, nor cache-read tokens — four different numbers a caller
12//! must not conflate. The insights report says which one it carries.
13
14use std::collections::BTreeMap;
15
16use schemars::JsonSchema;
17use serde::{Deserialize, Serialize};
18
19/// Pricing of one model, in integer micro-units per **million** input tokens
20/// (e.g. a `3 USD / 1M tokens` rate is `3_000_000`).
21#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema)]
22#[schemars(transform = crate::schema::strip_int_formats)]
23pub struct ModelPricing {
24    /// Micro-units of the table's currency per million input tokens.
25    pub input_micros_per_million_tokens: u64,
26}
27
28/// A versioned, caller-injected pricing table.
29#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
30pub struct PricingTable {
31    /// Caller-side version tag of this table (recorded in insights so a cost
32    /// figure is always traceable to the prices that produced it).
33    pub version: String,
34    /// ISO-4217 currency code of every rate in the table (e.g. `"EUR"`).
35    pub currency: String,
36    /// Rates keyed by model name (a `BTreeMap` so iteration — and therefore
37    /// every serialized output — is deterministically ordered).
38    pub models: BTreeMap<String, ModelPricing>,
39}
40
41impl PricingTable {
42    /// The cost of `tokens` input tokens on `model`, in micro-units of
43    /// [`Self::currency`] — `None` when the model has no rate in the table.
44    ///
45    /// Saturates instead of overflowing: with realistic rates (≤ 10⁹ micros
46    /// per million tokens) saturation is unreachable, and a saturated figure
47    /// is still the safer answer than a wrapped one.
48    #[must_use]
49    pub fn cost_micros(&self, model: &str, tokens: u64) -> Option<u64> {
50        let rate = self.models.get(model)?.input_micros_per_million_tokens;
51        Some(tokens.saturating_mul(rate) / 1_000_000)
52    }
53}
54
55/// Token and cost savings of one compilation.
56#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)]
57#[schemars(transform = crate::schema::strip_int_formats)]
58pub struct CompilationInsights {
59    /// Estimated tokens of all input fragments combined.
60    pub tokens_in: u64,
61    /// Estimated tokens of the assembled output.
62    pub tokens_out: u64,
63    /// `tokens_in − tokens_out` (saturating) — the local *estimate* of what
64    /// this compilation avoided sending; not billed tokens.
65    pub tokens_saved: u64,
66    /// Tokens saved attributed to the rule that saved them, keyed by rule id
67    /// (`BTreeMap` for deterministic serialization order).
68    pub tokens_saved_by_rule: BTreeMap<String, u64>,
69    /// Estimated cost avoided, in micro-units of [`Self::currency`] — only
70    /// when a pricing table was injected *and* it prices the target model.
71    #[serde(default, skip_serializing_if = "Option::is_none")]
72    pub estimated_cost_saved_micros: Option<u64>,
73    /// Currency of the cost figure, from the pricing table.
74    #[serde(default, skip_serializing_if = "Option::is_none")]
75    pub currency: Option<String>,
76    /// Version tag of the pricing table that produced the cost figure.
77    #[serde(default, skip_serializing_if = "Option::is_none")]
78    pub pricing_version: Option<String>,
79}
80
81#[cfg(test)]
82#[path = "insights_tests.rs"]
83mod tests;