use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;
pub type RequestId = u64;
#[derive(Clone, Copy, Debug)]
pub struct RequestTag(pub RequestId);
#[derive(Default)]
pub struct RequestIds(AtomicU64);
impl RequestIds {
pub fn next(&self) -> RequestId {
self.0.fetch_add(1, Ordering::Relaxed)
}
}
#[derive(Clone, Debug, serde::Serialize)]
#[serde(tag = "kind", rename_all = "camelCase")]
pub enum ServerEvent {
#[serde(rename_all = "camelCase")]
RequestStarted {
id: RequestId,
at_ms: u64,
method: String,
path: String,
},
#[serde(rename_all = "camelCase")]
RequestRouted {
id: RequestId,
requested: Option<String>,
served: String,
stream: bool,
},
#[serde(rename_all = "camelCase")]
Generated {
id: RequestId,
model: String,
prompt_tokens: u32,
new_tokens: u32,
prefill_seconds: f64,
decode_seconds: f64,
stop_reason: String,
reused_prefix_tokens: u32,
session_slot_evicted: bool,
},
#[serde(rename_all = "camelCase")]
RequestFinished {
id: RequestId,
status: u16,
duration_ms: u32,
#[serde(default, skip_serializing_if = "Option::is_none")]
error: Option<String>,
},
#[serde(rename_all = "camelCase")]
ModelAttached { at_ms: u64, model: String },
#[serde(rename_all = "camelCase")]
ModelDetached { at_ms: u64, model: String },
}
pub trait ServerObserver: Send + Sync {
fn record(&self, event: ServerEvent);
}
pub(crate) fn record<F>(observer: &Option<Arc<dyn ServerObserver>>, build: F)
where
F: FnOnce() -> ServerEvent,
{
if let Some(o) = observer {
o.record(build());
}
}
pub(crate) fn now_ms() -> u64 {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_millis() as u64)
.unwrap_or(0)
}
pub(crate) struct ReportingModel {
inner: Arc<dyn crate::ChatModel>,
observer: Arc<dyn ServerObserver>,
id: RequestId,
}
impl ReportingModel {
pub(crate) fn new(
inner: Arc<dyn crate::ChatModel>,
observer: Arc<dyn ServerObserver>,
id: RequestId,
) -> Self {
Self {
inner,
observer,
id,
}
}
}
impl crate::ChatModel for ReportingModel {
fn tokenizer(&self) -> &tokenizer::MfTokenizer {
self.inner.tokenizer()
}
fn vocab_size(&self) -> usize {
self.inner.vocab_size()
}
fn max_context(&self) -> u32 {
self.inner.max_context()
}
fn model_id(&self) -> &str {
self.inner.model_id()
}
fn model_aliases(&self) -> Vec<String> {
self.inner.model_aliases()
}
fn vision(&self) -> Option<crate::vision::VisionInfo> {
self.inner.vision()
}
fn rate_control(&self) -> runtime::RateControl {
self.inner.rate_control()
}
fn guardrails(&self) -> crate::guardrails::GuardrailConfig {
self.inner.guardrails()
}
fn default_reasoning(&self) -> tokenizer::ReasoningEffort {
self.inner.default_reasoning()
}
fn default_system(&self) -> Option<&str> {
self.inner.default_system()
}
fn supports_embeddings(&self) -> bool {
self.inner.supports_embeddings()
}
fn encode(&self, texts: &[&str]) -> Result<Vec<Vec<f32>>, String> {
self.inner.encode(texts)
}
fn with_producer(
&self,
f: &mut dyn FnMut(
&mut dyn runtime::LogitProducer,
) -> Result<runtime::RawDecodeResult, runtime::RuntimeError>,
) -> Result<runtime::RawDecodeResult, runtime::RuntimeError> {
self.inner.with_producer(f)
}
fn run_completion(
&self,
prompt_ids: &[foundation::TokenId],
config: &runtime::GenerationConfig,
images: Option<&crate::vision::RequestImages>,
cancel: runtime::CancelFlag<'_>,
on_progress: &mut dyn FnMut(runtime::RawDecodeProgress),
) -> Result<runtime::RawDecodeResult, runtime::RuntimeError> {
let result = self
.inner
.run_completion(prompt_ids, config, images, cancel, on_progress);
if let Ok(decode) = &result {
self.observer.record(ServerEvent::Generated {
id: self.id,
model: self.inner.model_id().to_string(),
prompt_tokens: decode.prompt_tokens as u32,
new_tokens: decode.new_tokens as u32,
prefill_seconds: decode.prefill_seconds,
decode_seconds: decode.decode_seconds,
stop_reason: format!("{:?}", decode.reason),
reused_prefix_tokens: decode.reused_prefix_tokens as u32,
session_slot_evicted: decode.session_slot_evicted,
});
}
result
}
}