use serde_json::Value;
use std::time::Duration;
use tracing::debug;
#[derive(Debug, Default, Clone)]
pub struct ProviderMetrics {
pub input_tokens: Option<u32>,
pub output_tokens: Option<u32>,
pub total_tokens: Option<u32>,
pub cost: Option<f64>,
pub model: String,
pub provider_latency: Duration,
pub request_id: Option<String>,
}
impl ProviderMetrics {
pub fn estimate_tokens_from_text(text: &str) -> u32 {
let char_count = text.chars().count();
let estimated_tokens = (char_count as f32 / 4.0).ceil() as u32;
debug!("Estimated {} tokens from {} characters", estimated_tokens, char_count);
estimated_tokens
}
pub fn create_partial_metrics(model: String, accumulated_text: &str) -> Self {
let output_tokens = if !accumulated_text.is_empty() {
Some(Self::estimate_tokens_from_text(accumulated_text))
} else {
None
};
ProviderMetrics {
model,
provider_latency: Duration::from_millis(0),
output_tokens,
..Default::default()
}
}
}
pub trait MetricsExtractor: Send + Sync {
fn extract_metrics(&self, response_body: &Value) -> ProviderMetrics;
fn extract_streaming_metrics(&self, chunk: &str) -> Option<ProviderMetrics> {
if let Some(metrics) = self.try_extract_provider_specific_streaming_metrics(chunk) {
return Some(metrics);
}
self.try_extract_common_streaming_metrics(chunk)
}
fn try_extract_provider_specific_streaming_metrics(&self, _chunk: &str) -> Option<ProviderMetrics> {
None }
fn try_extract_common_streaming_metrics(&self, chunk: &str) -> Option<ProviderMetrics> {
debug!("Attempting common streaming metrics extraction for chunk");
if let Ok(json) = serde_json::from_str::<Value>(chunk) {
if json.get("usage").is_some() {
debug!("Found usage in streaming chunk, extracting metrics");
return Some(self.extract_metrics(&json));
}
let model = json.get("model").and_then(|m| m.as_str()).unwrap_or("unknown").to_string();
let is_llm_response =
json.get("choices").is_some() ||
json.get("completion").is_some() ||
json.get("delta").is_some() ||
json.get("finish_reason").is_some();
if is_llm_response {
debug!("LLM streaming response detected in common handler, creating partial metrics");
return Some(ProviderMetrics {
model,
provider_latency: Duration::from_millis(0),
..Default::default()
});
}
}
debug!("No metrics data found in common streaming handler");
None
}
}
pub fn get_metrics_extractor(provider: &str) -> Box<dyn MetricsExtractor> {
use crate::providers::anthropic::AnthropicMetricsExtractor;
use crate::providers::bedrock::BedrockMetricsExtractor;
use crate::providers::groq::GroqMetricsExtractor;
use crate::providers::openai::OpenAIMetricsExtractor;
match provider {
"anthropic" => Box::new(AnthropicMetricsExtractor),
"bedrock" => Box::new(BedrockMetricsExtractor),
"groq" => Box::new(GroqMetricsExtractor),
"fireworks" => Box::new(OpenAIMetricsExtractor), "together" => Box::new(OpenAIMetricsExtractor), _ => Box::new(OpenAIMetricsExtractor), }
}