use std::sync::Arc;
use std::time::Duration;
use tokio::sync::{Semaphore};
#[cfg(not(target_arch = "wasm32"))]
use tokio::time::{sleep, Instant};
#[cfg(target_arch = "wasm32")]
use wasmtimer::std::Instant;
#[cfg(target_arch = "wasm32")]
use wasmtimer::tokio::sleep;
use crate::inference::{InferenceEngine, InferenceResponse, Tool};
pub struct InferenceRequest {
pub agent_id: String,
pub system_prompt: String,
pub user_prompt: String,
pub tools: Vec<Tool>,
pub engine_override: Option<Arc<dyn InferenceEngine>>,
}
#[derive(Debug, Clone)]
pub struct QueueStats {
pub total_requests: usize,
pub total_prompt_tokens: usize,
pub total_completion_tokens: usize,
pub total_tokens: usize,
pub elapsed_time_sec: f64,
pub tokens_per_sec: f64,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct RateLimitConfig {
pub max_concurrent_requests: usize,
pub requests_per_minute: f64,
pub working_hours: Option<(String, String)>,
}
impl Default for RateLimitConfig {
fn default() -> Self {
Self { max_concurrent_requests: 5, requests_per_minute: 0.0, working_hours: None }
}
}
pub struct InferenceQueue {
engine: Arc<dyn InferenceEngine>,
rate_limit: RateLimitConfig,
}
impl InferenceQueue {
pub fn new(engine: Arc<dyn InferenceEngine>, rate_limit: RateLimitConfig) -> Self {
Self { engine, rate_limit }
}
pub async fn process_batch(&self, requests: Vec<InferenceRequest>) -> (Vec<(String, Result<InferenceResponse, String>)>, QueueStats) {
let max_concurrent = self.rate_limit.max_concurrent_requests.max(1);
let semaphore = Arc::new(Semaphore::new(max_concurrent));
let mut handles = Vec::new();
let total_requests = requests.len();
let dispatch_interval = if self.rate_limit.requests_per_minute > 0.0 {
Some(Duration::from_secs_f64(60.0 / self.rate_limit.requests_per_minute))
} else {
None
};
let start_time = Instant::now();
for (index, req) in requests.into_iter().enumerate() {
if let Some((start, end)) = &self.rate_limit.working_hours {
loop {
let now = chrono::Local::now().time();
let start_time = chrono::NaiveTime::parse_from_str(start, "%H:%M").unwrap_or_default();
let end_time = chrono::NaiveTime::parse_from_str(end, "%H:%M").unwrap_or_default();
if now >= start_time && now <= end_time {
break;
}
#[cfg(not(target_arch = "wasm32"))]
sleep(Duration::from_secs(60)).await;
#[cfg(target_arch = "wasm32")]
sleep(Duration::from_secs(60)).await;
}
}
if let Some(interval) = dispatch_interval {
if index > 0 {
#[cfg(not(target_arch = "wasm32"))]
sleep(interval).await;
#[cfg(target_arch = "wasm32")]
sleep(interval).await;
}
}
let engine_clone = req.engine_override.clone().unwrap_or_else(|| Arc::clone(&self.engine));
let sem_clone = Arc::clone(&semaphore);
let agent_id = req.agent_id.clone();
let (tx, rx) = tokio::sync::oneshot::channel();
let fut = async move {
let _permit = sem_clone.acquire().await.unwrap();
println!("Dispatching inference request for Agent {} to provider...", agent_id);
let req_start = Instant::now();
let res = engine_clone.generate_response(&req.system_prompt, &req.user_prompt, req.tools).await;
let elapsed = req_start.elapsed().as_secs_f64();
match &res {
Ok(_) => println!("✅ Received inference response for Agent {} (took {:.2}s)", agent_id, elapsed),
Err(e) => println!("❌ Inference failed for Agent {} (took {:.2}s): {}", agent_id, elapsed, e),
}
let _ = tx.send((agent_id, res));
};
#[cfg(not(target_arch = "wasm32"))]
tokio::spawn(fut);
#[cfg(target_arch = "wasm32")]
wasm_bindgen_futures::spawn_local(fut);
handles.push(rx);
}
let mut results = Vec::new();
let mut total_prompt_tokens = 0;
let mut total_completion_tokens = 0;
let mut total_tokens = 0;
for handle in handles {
if let Ok((agent_id, res)) = handle.await {
if let Ok(response) = &res {
total_prompt_tokens += response.stats.prompt_tokens;
total_completion_tokens += response.stats.completion_tokens;
total_tokens += response.stats.total_tokens;
}
results.push((agent_id, res));
}
}
let elapsed = start_time.elapsed().as_secs_f64();
let tokens_per_sec = if elapsed > 0.0 {
total_tokens as f64 / elapsed
} else {
0.0
};
let stats = QueueStats {
total_requests,
total_prompt_tokens,
total_completion_tokens,
total_tokens,
elapsed_time_sec: elapsed,
tokens_per_sec,
};
(results, stats)
}
}