use std::time::Duration;
use dynamo_runtime::protocols::annotated::Annotated;
pub const ANNOTATION_LLM_METRICS: &str = "llm_metrics";
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq)]
pub struct LLMMetricAnnotation {
pub input_tokens: usize,
pub output_tokens: usize,
pub chunk_tokens: usize,
pub cached_tokens: Option<usize>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub prefill_worker_id: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub prefill_dp_rank: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub prefill_worker_type: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub decode_worker_id: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub decode_dp_rank: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub decode_worker_type: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub tokenize_latency: Option<Duration>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub detokenize_total_latency: Option<Duration>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub detokenize_count: Option<u64>,
}
impl LLMMetricAnnotation {
pub fn to_annotation<T>(&self) -> Result<Annotated<T>, serde_json::Error> {
Annotated::from_annotation(ANNOTATION_LLM_METRICS, self)
}
pub fn from_annotation<T>(
annotation: &Annotated<T>,
) -> Result<Option<LLMMetricAnnotation>, Box<dyn std::error::Error>> {
if annotation.event.is_none() {
return Ok(None);
}
if annotation.event.as_ref().unwrap() != ANNOTATION_LLM_METRICS {
return Ok(None);
}
let comments = annotation
.comment
.as_ref()
.ok_or("missing comments block")?;
if comments.len() != 1 {
return Err("malformed comments block - expected exactly 1 comment".into());
}
let metrics: LLMMetricAnnotation = serde_json::from_str(&comments[0])?;
Ok(Some(metrics))
}
}