fix(usage): OpenAI 缺失 input_tokens 时从 total 回推

- 同步与流式两条路径均在 input_tokens 为 0 且 total > output 时用 total - output 兜底
- 补充对应单元测试
This commit is contained in:
fawney19
2026-04-24 22:08:23 +08:00
parent 67b092253d
commit 5b3593038d
3 changed files with 41 additions and 3 deletions

View File

@@ -1822,6 +1822,22 @@ mod tests {
sequence_numbers
}
#[test]
fn openai_usage_derives_missing_input_tokens_from_total() {
let usage = canonical_usage_from_openai_usage(Some(&json!({
"output_tokens": 177,
"total_tokens": 20_612,
"input_tokens_details": {
"cached_tokens": 19_840,
},
})))
.expect("usage should parse");
assert_eq!(usage.input_tokens, 20_435);
assert_eq!(usage.output_tokens, 177);
assert_eq!(usage.cache_read_tokens, 19_840);
}
#[test]
fn openai_cli_client_emitter_emits_doc_like_text_events() {
let mut emitter = OpenAICliClientEmitter::default();

View File

@@ -89,7 +89,7 @@ pub fn resolve_identity(
pub fn canonical_usage_from_openai_usage(value: Option<&Value>) -> Option<CanonicalUsage> {
let usage = value?.as_object()?;
let input_tokens = usage
let mut input_tokens = usage
.get("input_tokens")
.or_else(|| usage.get("prompt_tokens"))
.and_then(Value::as_u64)
@@ -129,6 +129,9 @@ pub fn canonical_usage_from_openai_usage(value: Option<&Value>) -> Option<Canoni
.saturating_add(cache_creation_tokens)
.saturating_add(cache_read_tokens),
);
if input_tokens == 0 && total_tokens > output_tokens {
input_tokens = total_tokens.saturating_sub(output_tokens);
}
Some(CanonicalUsage {
input_tokens,
output_tokens,