#![cfg(feature = "llm")]
use ling_ai::LoadedModel;
use std::collections::HashMap;
use std::sync::atomic::{AtomicI64, Ordering};
use std::sync::{Arc, Mutex};
static MODELS: Mutex<Vec<Option<Arc<LoadedModel>>>> = Mutex::new(Vec::new());
fn push_model(model: LoadedModel) -> i64 {
match MODELS.lock() {
Ok(mut g) => {
g.push(Some(Arc::new(model)));
(g.len() - 1) as i64
},
Err(_) => -1,
}
}
fn get_model(id: i64) -> Option<Arc<LoadedModel>> {
let idx = usize::try_from(id).ok()?;
MODELS.lock().ok()?.get(idx)?.clone()
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum JobStatus {
Pending,
ToolCall,
Done,
}
struct Job {
status: Mutex<JobStatus>,
text: Mutex<String>,
tool_name: Mutex<String>,
}
static JOBS: Mutex<Option<HashMap<String, Arc<Job>>>> = Mutex::new(None);
fn jobs() -> std::sync::MutexGuard<'static, Option<HashMap<String, Arc<Job>>>> {
let mut g = JOBS.lock().unwrap();
if g.is_none() {
*g = Some(HashMap::new());
}
g
}
fn new_job_id() -> String {
use rand::RngCore;
let mut buf = [0u8; 16];
rand::rngs::OsRng.fill_bytes(&mut buf);
format!("llm{}", buf.iter().map(|b| format!("{b:02x}")).collect::<String>())
}
pub fn llm_load(gguf_path: &str, tokenizer_path: &str, device_index: i64) -> i64 {
match LoadedModel::load(gguf_path, tokenizer_path, device_index) {
Ok(model) => push_model(model),
Err(_) => -1,
}
}
pub fn llm_generate_start(
handle: i64,
system_prompt: &str,
history: &str,
max_tokens: i64,
temperature: f64,
top_p: f64,
) -> String {
let model = match get_model(handle) {
Some(m) => m,
None => return String::new(),
};
let job_id = new_job_id();
let job = Arc::new(Job {
status: Mutex::new(JobStatus::Pending),
text: Mutex::new(String::new()),
tool_name: Mutex::new(String::new()),
});
jobs().as_mut().unwrap().insert(job_id.clone(), job.clone());
let system_prompt = system_prompt.to_string();
let history = history.to_string();
let max_tokens = max_tokens.max(1) as usize;
let seed = rand::random::<u64>();
std::thread::spawn(move || {
let mut generated = String::new();
model.generate(&system_prompt, &history, max_tokens, temperature, top_p, seed, |piece| {
generated.push_str(piece);
*job.text.lock().unwrap() = generated.clone();
});
*job.status.lock().unwrap() = JobStatus::Done;
});
job_id
}
pub fn llm_generate_poll(job_id: &str) -> String {
match jobs().as_ref().unwrap().get(job_id) {
Some(j) => j.text.lock().unwrap().clone(),
None => String::new(),
}
}
pub fn llm_generate_done(job_id: &str) -> bool {
match jobs().as_ref().unwrap().get(job_id) {
Some(j) => *j.status.lock().unwrap() == JobStatus::Done,
None => true,
}
}
pub fn llm_job_status(job_id: &str) -> String {
match jobs().as_ref().unwrap().get(job_id) {
Some(j) => match *j.status.lock().unwrap() {
JobStatus::Pending => "pending",
JobStatus::ToolCall => "tool_call",
JobStatus::Done => "done",
},
None => "done",
}
.to_string()
}
pub fn llm_job_tool_name(job_id: &str) -> String {
match jobs().as_ref().unwrap().get(job_id) {
Some(j) => j.tool_name.lock().unwrap().clone(),
None => String::new(),
}
}
pub fn llm_job_provide_tool_result(_job_id: &str, _result_text: &str) -> bool {
false
}
pub fn llm_job_cancel(job_id: &str) {
if let Some(j) = jobs().as_ref().unwrap().get(job_id) {
*j.status.lock().unwrap() = JobStatus::Done;
}
}