use std::sync::Arc;
use std::time::{Duration, Instant};
use adk_core::{GenerateContentConfig, Llm, LlmRequest, LlmResponseStream};
use async_trait::async_trait;
use tokio::sync::Mutex;
#[derive(Debug, Clone)]
pub struct LlmCallRecord {
pub request_sent: Instant,
pub response_complete: Instant,
pub round_trip: Duration,
pub model: String,
pub prompt_tokens: Option<u64>,
pub completion_tokens: Option<u64>,
}
#[derive(Debug, Clone)]
pub struct DeterministicConfig {
pub temperature: f32,
pub top_p: f32,
pub seed: Option<i64>,
}
impl Default for DeterministicConfig {
fn default() -> Self {
Self { temperature: 0.0, top_p: 1.0, seed: Some(42) }
}
}
pub struct InstrumentedLlm {
inner: Arc<dyn Llm>,
records: Arc<Mutex<Vec<LlmCallRecord>>>,
deterministic_config: DeterministicConfig,
}
impl InstrumentedLlm {
pub fn new(inner: Arc<dyn Llm>) -> Self {
Self {
inner,
records: Arc::new(Mutex::new(Vec::new())),
deterministic_config: DeterministicConfig::default(),
}
}
pub fn with_config(mut self, config: DeterministicConfig) -> Self {
self.deterministic_config = config;
self
}
pub async fn records(&self) -> Vec<LlmCallRecord> {
self.records.lock().await.clone()
}
pub async fn reset(&self) {
self.records.lock().await.clear();
}
}
#[async_trait]
impl Llm for InstrumentedLlm {
fn name(&self) -> &str {
self.inner.name()
}
async fn generate_content(
&self,
mut req: LlmRequest,
stream: bool,
) -> adk_core::Result<LlmResponseStream> {
let config = req.config.get_or_insert_with(GenerateContentConfig::default);
config.temperature = Some(self.deterministic_config.temperature);
config.top_p = Some(self.deterministic_config.top_p);
if let Some(seed) = self.deterministic_config.seed {
config.seed = Some(seed);
}
let request_sent = Instant::now();
let result = self.inner.generate_content(req, stream).await;
let response_complete = Instant::now();
let record = LlmCallRecord {
request_sent,
response_complete,
round_trip: response_complete.duration_since(request_sent),
model: self.inner.name().to_string(),
prompt_tokens: None,
completion_tokens: None,
};
self.records.lock().await.push(record);
result
}
fn schema_adapter(&self) -> &dyn adk_core::schema_adapter::SchemaAdapter {
self.inner.schema_adapter()
}
}