#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
use std::time::{Duration, Instant};
use emelex::ReasoningExt as _;
use futures::StreamExt;
use rig_core::{agent::Agent, completion::Prompt, prelude::StreamingPrompt};
fn quick_agent(client: &emelex::Client) -> Agent<emelex::CompletionModel> {
client
.agent()
.preamble("Answer in one short sentence.")
.enable_thinking(false)
.build()
}
async fn timed_prompt(agent: &Agent<emelex::CompletionModel>, label: &str, prompt: &str) -> String {
let start = Instant::now();
let answer = agent.prompt(prompt).await.expect("prompt should succeed");
println!(
" [{label}] {:>5.1}s {}",
start.elapsed().as_secs_f32(),
answer
.replace('\n', " ")
.chars()
.take(60)
.collect::<String>()
);
answer
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let model_dir = std::env::args()
.nth(1)
.expect("usage: concurrent <mlx-model-dir>");
println!("== loading the checkpoint twice ==");
let start = Instant::now();
let client_a = emelex::Client::from_path(&model_dir)?;
println!(" client A loaded in {:.1}s", start.elapsed().as_secs_f32());
let start = Instant::now();
let client_b = emelex::Client::from_path(&model_dir)?;
println!(" client B loaded in {:.1}s", start.elapsed().as_secs_f32());
let agent_a = quick_agent(&client_a);
let agent_b = quick_agent(&client_b);
println!("\n== phase A: 4 prompts + 1 stream across two clients ==");
let stream_task = async {
let mut stream = agent_b.stream_prompt("Name three colors.").await;
let mut chunks = 0usize;
while let Some(item) = stream.next().await {
item.expect("stream item should be ok");
chunks += 1;
}
println!(" [B/stream] completed with {chunks} chunks");
};
tokio::join!(
timed_prompt(&agent_a, "A/1", "What is 2 + 2?"),
timed_prompt(&agent_b, "B/1", "What is 10 * 10?"),
timed_prompt(&agent_a, "A/2", "Name a primary color."),
timed_prompt(&agent_b, "B/2", "Name a big city."),
stream_task,
);
println!("\n== phase B: timeout + dropped stream, then recovery ==");
let verbose_agent = client_a
.agent()
.preamble("You are a thorough, long-form writer.")
.enable_thinking(false)
.build();
let cut = tokio::time::timeout(
Duration::from_secs(3),
verbose_agent
.prompt("Write a detailed 1000 word essay about the history of ocean navigation."),
)
.await;
println!(
" timeout fired: {}",
if cut.is_err() {
"yes (future dropped mid-generation)"
} else {
"no (model finished early)"
}
);
let mut stream = agent_a
.stream_prompt("Count from 1 to 200, one number per line.")
.await;
let mut taken = 0usize;
while let Some(item) = stream.next().await {
item.expect("stream item should be ok");
taken += 1;
if taken >= 5 {
break;
}
}
drop(stream);
println!(" stream dropped after {taken} chunks");
timed_prompt(&agent_a, "A/recovery", "Say the word: recovered").await;
println!("\n== phase C: 3 simultaneous prompts, middle one cancelled ==");
let (first, second, third) = tokio::join!(
timed_prompt(&agent_a, "C/1", "What is 1 + 1?"),
async {
let cut = tokio::time::timeout(
Duration::from_millis(300),
agent_a.prompt("Write a long story about dragons."),
)
.await;
println!(
" [C/2] cancelled while queued: {}",
if cut.is_err() { "yes" } else { "no (finished)" }
);
String::new()
},
timed_prompt(&agent_a, "C/3", "What is 3 + 3?"),
);
assert!(!first.is_empty() && !third.is_empty());
drop(second);
println!("\nall phases completed; no client wedged.");
Ok(())
}