feat: Added full cost tracking to Coyote and added it to the default right prompt
This commit is contained in:
7 files changed
+237
-25
No files matched your search
+91
-4
@@ -402,6 +402,27 @@ impl TokenUsage {
|
||||
other.cache_read_input_tokens,
|
||||
);
|
||||
}
|
||||
|
||||
/// USD cost of this usage at the given model's prices (USD per 1M
|
||||
/// tokens). The four buckets are disjoint on both Anthropic direct and
|
||||
/// Bedrock Converse (`input_tokens` excludes cache tokens), so the terms
|
||||
/// sum without adjustment. Strict on missing prices: any nonzero
|
||||
/// component without a price yields `None` rather than a silent
|
||||
/// undercount. All-zero usage is also `None` — nothing billed, nothing
|
||||
/// to show.
|
||||
pub fn cost_usd(&self, model: &Model) -> Option<f64> {
|
||||
fn term(tokens: Option<u64>, price: Option<f64>) -> Option<f64> {
|
||||
match tokens.unwrap_or(0) {
|
||||
0 => Some(0.0),
|
||||
tokens => Some(tokens as f64 * price? / 1_000_000.0),
|
||||
}
|
||||
}
|
||||
let total = term(self.input_tokens, model.input_price())?
|
||||
+ term(self.output_tokens, model.output_price())?
|
||||
+ term(self.cache_read_input_tokens, model.cache_read_price())?
|
||||
+ term(self.cache_creation_input_tokens, model.cache_write_price())?;
|
||||
(total > 0.0).then_some(total)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -559,7 +580,7 @@ pub async fn call_chat_completions(
|
||||
ctx.app.config.print_markdown(&text)?;
|
||||
}
|
||||
}
|
||||
ctx.record_token_usage(usage);
|
||||
ctx.record_token_usage(usage, client.model());
|
||||
finish_completion(ctx, text, tool_calls, thinking).await
|
||||
}
|
||||
Err(err) => Err(err),
|
||||
@@ -591,7 +612,7 @@ pub async fn call_chat_completions_streaming(
|
||||
// call's input tokens (reported in message_start), so abort and error
|
||||
// paths must still count toward the session totals.
|
||||
let (text, tool_calls, thinking, usage) = handler.take();
|
||||
ctx.record_token_usage(usage);
|
||||
ctx.record_token_usage(usage, client.model());
|
||||
|
||||
if aborted_ctrld {
|
||||
bail!("Aborted.");
|
||||
@@ -665,7 +686,7 @@ pub async fn call_chat_completions_streaming_quiet(
|
||||
thinking,
|
||||
usage,
|
||||
} = output;
|
||||
ctx.record_token_usage(usage);
|
||||
ctx.record_token_usage(usage, client.model());
|
||||
if abort_signal.aborted() {
|
||||
bail!("Aborted.");
|
||||
}
|
||||
@@ -681,7 +702,7 @@ pub async fn call_chat_completions_streaming_quiet(
|
||||
let send_ret = client.chat_completions_streaming(input, &mut handler).await;
|
||||
|
||||
let (text, tool_calls, thinking, usage) = handler.take();
|
||||
ctx.record_token_usage(usage);
|
||||
ctx.record_token_usage(usage, client.model());
|
||||
|
||||
if abort_signal.aborted() {
|
||||
bail!("Aborted.");
|
||||
@@ -1000,6 +1021,72 @@ mod tests {
|
||||
assert_eq!(total.input_tokens, Some(u64::MAX));
|
||||
}
|
||||
|
||||
fn priced_model(
|
||||
input_price: Option<f64>,
|
||||
output_price: Option<f64>,
|
||||
cache_read_price: Option<f64>,
|
||||
cache_write_price: Option<f64>,
|
||||
) -> Model {
|
||||
let mut data = ModelData::new("test");
|
||||
data.input_price = input_price;
|
||||
data.output_price = output_price;
|
||||
data.cache_read_price = cache_read_price;
|
||||
data.cache_write_price = cache_write_price;
|
||||
Model::from_config("provider", &[data]).remove(0)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_token_usage_cost_usd_sums_all_four_components() {
|
||||
let usage = TokenUsage {
|
||||
input_tokens: Some(1_000_000),
|
||||
output_tokens: Some(500_000),
|
||||
cache_creation_input_tokens: Some(200_000),
|
||||
cache_read_input_tokens: Some(2_000_000),
|
||||
};
|
||||
let model = priced_model(Some(3.0), Some(15.0), Some(0.3), Some(3.75));
|
||||
// 3.0 + 7.5 + 0.6 + 0.75
|
||||
let cost = usage.cost_usd(&model).unwrap();
|
||||
assert!((cost - 11.85).abs() < 1e-9);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_token_usage_cost_usd_is_none_when_a_billed_component_lacks_a_price() {
|
||||
let usage = TokenUsage {
|
||||
input_tokens: Some(100),
|
||||
output_tokens: Some(10),
|
||||
cache_creation_input_tokens: None,
|
||||
cache_read_input_tokens: Some(50),
|
||||
};
|
||||
let model = priced_model(Some(3.0), Some(15.0), None, Some(3.75));
|
||||
assert_eq!(usage.cost_usd(&model), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_token_usage_cost_usd_ignores_missing_prices_for_unbilled_components() {
|
||||
let usage = TokenUsage {
|
||||
input_tokens: Some(1_000_000),
|
||||
output_tokens: Some(1_000_000),
|
||||
cache_creation_input_tokens: Some(0),
|
||||
cache_read_input_tokens: None,
|
||||
};
|
||||
let model = priced_model(Some(3.0), Some(15.0), None, None);
|
||||
let cost = usage.cost_usd(&model).unwrap();
|
||||
assert!((cost - 18.0).abs() < 1e-9);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_token_usage_cost_usd_is_none_for_empty_or_all_zero_usage() {
|
||||
let model = priced_model(Some(3.0), Some(15.0), Some(0.3), Some(3.75));
|
||||
assert_eq!(TokenUsage::default().cost_usd(&model), None);
|
||||
let zero = TokenUsage {
|
||||
input_tokens: Some(0),
|
||||
output_tokens: Some(0),
|
||||
cache_creation_input_tokens: Some(0),
|
||||
cache_read_input_tokens: Some(0),
|
||||
};
|
||||
assert_eq!(zero.cost_usd(&model), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_catch_error_display_json_with_type() {
|
||||
let data = json!({"error": {"type": "invalid_request_error", "message": "Bad request"}});
|
||||
|
||||
@@ -195,6 +195,22 @@ impl Model {
|
||||
self.data.max_output_tokens
|
||||
}
|
||||
|
||||
pub fn input_price(&self) -> Option<f64> {
|
||||
self.data.input_price
|
||||
}
|
||||
|
||||
pub fn output_price(&self) -> Option<f64> {
|
||||
self.data.output_price
|
||||
}
|
||||
|
||||
pub fn cache_read_price(&self) -> Option<f64> {
|
||||
self.data.cache_read_price
|
||||
}
|
||||
|
||||
pub fn cache_write_price(&self) -> Option<f64> {
|
||||
self.data.cache_write_price
|
||||
}
|
||||
|
||||
pub fn supports_function_calling(&self) -> bool {
|
||||
self.data.supports_function_calling
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user