use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LlmConfig {
pub model: String,
pub max_tokens: usize,
pub temperature: f32,
#[serde(default)]
pub system_prompt: Option<String>,
#[serde(default)]
pub endpoint: Option<String>,
#[serde(default, skip_serializing)]
pub api_key: Option<String>,
}
impl Default for LlmConfig {
fn default() -> Self {
Self {
model: "google/gemini-2.5-flash-lite".to_string(),
max_tokens: 1024,
temperature: 0.0,
system_prompt: None,
endpoint: None,
api_key: None,
}
}
}
impl LlmConfig {
pub fn gemini_flash_lite() -> Self {
Self {
model: "google/gemini-2.5-flash-lite".to_string(),
max_tokens: 1024,
temperature: 0.0,
system_prompt: None,
endpoint: None,
api_key: None,
}
}
pub fn gemini_flash() -> Self {
Self {
model: "google/gemini-2.5-flash".to_string(),
max_tokens: 1024,
temperature: 0.0,
system_prompt: None,
endpoint: None,
api_key: None,
}
}
pub fn haiku() -> Self {
Self {
model: "anthropic/claude-haiku-4.5".to_string(),
max_tokens: 1024,
temperature: 0.0,
system_prompt: None,
endpoint: None,
api_key: None,
}
}
pub fn deepseek() -> Self {
Self {
model: "deepseek/deepseek-v3.2".to_string(),
max_tokens: 1024,
temperature: 0.0,
system_prompt: None,
endpoint: None,
api_key: None,
}
}
pub fn llama3() -> Self {
Self {
model: "meta-llama/llama-3.3-70b-instruct".to_string(),
max_tokens: 1024,
temperature: 0.0,
system_prompt: None,
endpoint: None,
api_key: None,
}
}
pub fn llama4() -> Self {
Self {
model: "meta-llama/llama-4-scout".to_string(),
max_tokens: 1024,
temperature: 0.0,
system_prompt: None,
endpoint: None,
api_key: None,
}
}
pub fn groq(model: &str) -> Self {
Self {
model: model.to_string(),
max_tokens: 1024,
temperature: 0.0,
system_prompt: None,
endpoint: Some("https://api.groq.com/openai/v1/chat/completions".to_string()),
api_key: None,
}
}
pub fn ollama(model: &str) -> Self {
Self {
model: model.to_string(),
max_tokens: 1024,
temperature: 0.0,
system_prompt: None,
endpoint: Some("http://localhost:11434/v1/chat/completions".to_string()),
api_key: Some("ollama".to_string()),
}
}
pub fn with_model(model: &str) -> Self {
Self {
model: model.to_string(),
..Default::default()
}
}
pub fn max_tokens(mut self, tokens: usize) -> Self {
self.max_tokens = tokens;
self
}
pub fn temperature(mut self, temp: f32) -> Self {
self.temperature = temp;
self
}
pub fn system_prompt(mut self, prompt: &str) -> Self {
self.system_prompt = Some(prompt.to_string());
self
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_llm_config_builder() {
let config = LlmConfig::with_model("google/gemini-2.5-flash")
.max_tokens(10)
.temperature(0.0)
.system_prompt("You are an NER assistant.");
assert_eq!(config.model, "google/gemini-2.5-flash");
assert_eq!(config.max_tokens, 10);
assert_eq!(config.temperature, 0.0);
assert_eq!(
config.system_prompt,
Some("You are an NER assistant.".to_string())
);
}
#[test]
fn test_preset_gemini_flash() {
let config = LlmConfig::gemini_flash();
assert_eq!(config.model, "google/gemini-2.5-flash");
assert_eq!(config.max_tokens, 1024);
assert_eq!(config.temperature, 0.0);
}
#[test]
fn test_preset_haiku() {
let config = LlmConfig::haiku();
assert_eq!(config.model, "anthropic/claude-haiku-4.5");
}
#[test]
fn test_preset_deepseek() {
let config = LlmConfig::deepseek();
assert_eq!(config.model, "deepseek/deepseek-v3.2");
}
#[test]
fn test_preset_ollama() {
let config = LlmConfig::ollama("llama3.2:3b");
assert_eq!(config.model, "llama3.2:3b");
assert!(config.endpoint.as_ref().unwrap().contains("localhost"));
assert_eq!(config.api_key.as_deref(), Some("ollama"));
}
#[test]
fn test_default_is_gemini_flash_lite() {
let config = LlmConfig::default();
assert_eq!(config.model, "google/gemini-2.5-flash-lite");
assert_eq!(config.max_tokens, 1024);
}
#[test]
fn test_preset_gemini_flash_lite() {
let config = LlmConfig::gemini_flash_lite();
assert_eq!(config.model, "google/gemini-2.5-flash-lite");
}
}