plainllm 1.2.0

A plain & simple LLM client
Documentation
use serde::{Deserialize, Serialize};
use serde_json::Value;

// ================  Data Structures For LLM  ================
//
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct Message {
    pub role: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub content: Option<String>,
    /// If using tool-calls / function calls
    #[serde(skip_serializing_if = "Option::is_none")]
    pub tool_calls: Option<Vec<FunctionCall>>,
    /// If using tool-calls, track the function call ID
    #[serde(skip_serializing_if = "Option::is_none")]
    pub tool_call_id: Option<String>,
}

impl Message {
    pub fn new(role: &str, content: &str) -> Self {
        Self {
            role: role.to_string(),
            content: Some(content.to_string()),
            tool_calls: None,
            tool_call_id: None,
        }
    }
}

/// A structure representing a single function call from the LLM (tool usage).
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct FunctionCall {
    pub id: String,
    #[serde(rename = "type")]
    pub call_type: String,
    pub function: Function,
}

/// The function descriptor itself (like “weather_lookup”, {args…}).
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct Function {
    pub name: String,
    pub arguments: Value, // JSON of arguments
}

/// Represents a chunk of a streamed LLM response.
#[derive(Deserialize, Debug, Clone)]
pub struct LLMChunkResponse {
    pub id: String,
    pub object: String,
    pub created: u64,
    pub model: String,
    pub choices: Vec<ChunkChoice>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub system_fingerprint: Option<String>,
}

#[derive(Deserialize, Debug, Clone)]
pub struct ChunkChoice {
    pub index: u32,
    pub delta: Delta,
    pub finish_reason: Option<String>,
}

#[derive(Deserialize, Debug, Clone)]
pub struct Delta {
    pub role: Option<String>,
    pub content: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub reasoning_content: Option<String>,
}

/// Represents a complete LLM response (non-streamed or after streaming finishes).
#[derive(Deserialize, Debug, Clone)]
pub struct LLMResponse {
    pub id: String,
    pub object: String,
    pub created: u64,
    pub model: String,
    pub choices: Vec<Choice>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub system_fingerprint: Option<String>,
}

/// Each choice in the response can contain a final `Message`.
#[derive(Deserialize, Debug, Clone)]
pub struct Choice {
    pub index: u32,
    pub message: Option<Message>,
    pub finish_reason: Option<String>,
}

/// HTTP method for calls
#[derive(Debug)]
pub enum Method {
    Get,
    Post,
}

/// Your endpoints for different LLM actions
#[derive(Debug)]
pub enum Endpoint {
    ChatCompletion,
    Responses,
    ListModels,
    PullModel,
    Embedding,
}

impl Endpoint {
    pub fn http_uri(&self) -> &str {
        match self {
            Endpoint::ChatCompletion => "v1/chat/completions",
            Endpoint::Responses => "v1/responses",
            Endpoint::ListModels => "api/tags",
            Endpoint::PullModel => "api/pull",
            Endpoint::Embedding => "api/v0/embeddings",
        }
    }
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Mode {
    ChatCompletion,
    Responses,
}

#[derive(Clone, Debug)]
pub struct LLMConfig {
    pub mode: Mode,
}

impl Default for LLMConfig {
    fn default() -> Self {
        Self {
            mode: Mode::ChatCompletion,
        }
    }
}

//