llmclient 0.3.2

Rust LLM client - Gemini, OpenAI, Claude, Mistral, DeepSeek, Groq
Documentation
#[derive(Debug, Clone)]
pub enum LlmType  {
    GEMINI,
    GPT
}

pub type Triple = (usize, usize, usize);

impl std::fmt::Display for LlmType {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        match self {
            LlmType::GEMINI => write!(f, "GEMINI"),
            LlmType::GPT => write!(f, "GPT"),
        }
    }
}

#[derive(Debug, Clone)]
pub struct LlmReturn {
    pub llm_type: LlmType,
    pub text: String,
    pub finish_reason: String,
    pub usage: Triple,
    pub timing: f64,
    pub citations: Option<String>,
    pub safety_ratings: Option<Vec<String>>,
}

impl LlmReturn {
    pub fn new(llm_type: LlmType, text: String, finish_reason: String, usage: Triple, timing: f64, citations: Option<String>, safety_ratings: Option<Vec<String>>) -> Self {
        LlmReturn { llm_type, text, finish_reason, usage, timing, citations, safety_ratings }
    }
}

impl std::fmt::Display for LlmReturn {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        println!("---------- {} ----------", self.llm_type);
        let _ = writeln!(f, "{}", self.text);
        if !self.finish_reason.is_empty() && self.finish_reason != "STOP" {
            let _ = println!("Finish Reason: {}", self.finish_reason);
        }
        println!("Tokens: Input: {} + Output: {} -> Total: {}",
                 self.usage.0, self.usage.1, self.usage.2);
        println!("Timing: {}", self.timing);
        if let Some(ref citations) = self.citations {
            println!("Citations:\n{}", citations);
        }
        if let Some(ref safety_ratings) = self.safety_ratings {
            println!("Safety Settings: {:?}", safety_ratings);
        }

        Ok(())
    }
}

#[derive(Debug)]
pub struct Message {
    pub role: String,
    pub text: String
}

pub trait LlmMessage {
    fn summarize(&self) -> String;
}

impl LlmMessage for Message {
    fn summarize(&self) -> String {
        "Message".to_string()
    }
}

impl Message {
    /// Supply single role and single part text
    pub fn text(role: &str, text: &str) -> Self {
        Self { role: role.into(), text: text.into() }
    }

    /// Supply single role with multi-string for iparts with single text
    pub fn many_text(role: &str, prompt: &[String]) -> Self {
        let prompt: String = 
            prompt.iter()
                .fold(String::new(), |mut s, p| {
                    s.push_str(if s.is_empty() { "" } else { "\n" });
                    s.push_str(p);

                    s
                });

        Self { role: role.into(), text: prompt }
    }

    /// Supply simple, 'system' text
    pub fn system(system_prompt: &str) -> Vec<Self> {
        vec![Self::text("system", system_prompt)]
    }

    /// Supply multi-parts and single 'system' text
    pub fn multi_part_system(system_prompts: &[String]) -> Vec<Self> {
        vec![Self::many_text("system", system_prompts)]
    }

    /// Supply multi-context 'system' text
    pub fn systems(system_prompts: &[String]) -> Vec<Self> {
        system_prompts.iter()
            .map(|sp| Self::text("system", &sp))
            .collect()
    }

    /// Supply multi-String text with user and model alternating
    pub fn dialogue(prompts: &[String]) -> Vec<Self> {
        prompts.iter()
            .enumerate()
            .map(|(i, p)| {
                let role = if i % 2 == 0 { "user" } else { "llm" };

                Self::text(role, p)
            })
            .collect()
    }
}