mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-10-09 03:20:56 +00:00
Merge pull request #691 from fabro-sh/fix/modal-reasoning-token-usage
fix(llm): decode Modal reasoning token usage
This commit is contained in:
commit
66b282d966
1 changed files with 46 additions and 0 deletions
|
|
@ -306,6 +306,10 @@ pub(super) struct ApiUsage {
|
|||
pub prompt_cache_hit_tokens: Option<i64>,
|
||||
#[serde(default)]
|
||||
pub completion_tokens_details: Option<CompletionTokensDetails>,
|
||||
/// Modal reports reasoning tokens directly on `usage` instead of nesting
|
||||
/// them under `completion_tokens_details`.
|
||||
#[serde(default)]
|
||||
pub reasoning_tokens: Option<i64>,
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
|
|
@ -328,6 +332,9 @@ impl ApiUsage {
|
|||
/// cache-write detail tokens are subtracted out of `input_tokens`, and
|
||||
/// reasoning tokens out of `output_tokens`, mirroring the
|
||||
/// `openai_responses` convention.
|
||||
///
|
||||
/// Nested detail fields win over the flat `prompt_cache_hit_tokens` and
|
||||
/// `reasoning_tokens` spellings that some providers send instead.
|
||||
pub(super) fn token_counts(&self) -> TokenCounts {
|
||||
let cached_detail = self
|
||||
.prompt_tokens_details
|
||||
|
|
@ -344,6 +351,7 @@ impl ApiUsage {
|
|||
.completion_tokens_details
|
||||
.as_ref()
|
||||
.and_then(|d| d.reasoning_tokens)
|
||||
.or(self.reasoning_tokens)
|
||||
.unwrap_or(0);
|
||||
let (uncached_input, cached) =
|
||||
split_inclusive_token_total(self.prompt_tokens, cached_detail);
|
||||
|
|
@ -560,6 +568,44 @@ mod tests {
|
|||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn token_counts_accept_modal_reasoning_tokens_field() {
|
||||
let usage: ApiUsage = serde_json::from_value(serde_json::json!({
|
||||
"prompt_tokens": 116,
|
||||
"completion_tokens": 66,
|
||||
"reasoning_tokens": 54
|
||||
}))
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(usage.token_counts(), TokenCounts {
|
||||
input_tokens: 116,
|
||||
output_tokens: 12,
|
||||
reasoning_tokens: 54,
|
||||
..TokenCounts::default()
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn token_counts_prefer_nested_reasoning_detail_over_top_level() {
|
||||
let both_spellings: ApiUsage = serde_json::from_value(serde_json::json!({
|
||||
"prompt_tokens": 10,
|
||||
"completion_tokens": 66,
|
||||
"completion_tokens_details": {"reasoning_tokens": 20},
|
||||
"reasoning_tokens": 54
|
||||
}))
|
||||
.unwrap();
|
||||
assert_eq!(both_spellings.token_counts().reasoning_tokens, 20);
|
||||
|
||||
let empty_detail: ApiUsage = serde_json::from_value(serde_json::json!({
|
||||
"prompt_tokens": 10,
|
||||
"completion_tokens": 66,
|
||||
"completion_tokens_details": {},
|
||||
"reasoning_tokens": 54
|
||||
}))
|
||||
.unwrap();
|
||||
assert_eq!(empty_detail.token_counts().reasoning_tokens, 54);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reasoning_accepts_provider_and_openrouter_spellings() {
|
||||
let provider_response: ApiResponse = serde_json::from_value(serde_json::json!({
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue