From e947e248e2e3c0b16f85c8c190ea208ea4096dd0 Mon Sep 17 00:00:00 2001 From: Tracewebstudio Dev Date: Sun, 14 Jun 2026 19:24:02 +0200 Subject: [PATCH] feat: add fake LLM provider for testing Set LLM_PROVIDER=fake to use mock responses without calling real LLM. Useful for API flow tests, action engine tests, UI event tests. --- src/main.rs | 36 ++++++++++------- src/providers/llm/fake_provider.rs | 63 ++++++++++++++++++++++++++++++ src/providers/llm/mod.rs | 3 +- 3 files changed, 87 insertions(+), 15 deletions(-) create mode 100644 src/providers/llm/fake_provider.rs diff --git a/src/main.rs b/src/main.rs index 26fed1d..67c241e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -16,8 +16,9 @@ use std::sync::Arc; use config::AppConfig; use providers::help_center::nxtgauge_help_center_provider::NxtgaugeHelpCenterProvider; -use providers::llm::ollama_provider::OllamaAiProvider; +use providers::llm::fake_provider::FakeAiProvider; use providers::llm::litellm_provider::LiteLLMProvider; +use providers::llm::ollama_provider::OllamaAiProvider; use providers::tickets::nxtgauge_ticket_provider::NxtgaugeTicketProvider; use providers::llm::ai_provider::AiProvider; use retrieval::embeddings::ollama_embedding_provider::OllamaEmbeddingProvider; @@ -38,19 +39,26 @@ async fn main() { } }; - let ai_provider: Arc = if cfg.llm_provider == "litellm" { - info!("Using LiteLLM provider with model {}", cfg.litellm_model); - Arc::new(LiteLLMProvider::new( - cfg.litellm_base_url.clone(), - cfg.litellm_api_key.clone(), - cfg.litellm_model.clone(), - )) as Arc - } else { - info!("Using Ollama provider with model {}", cfg.ollama_chat_model); - Arc::new(OllamaAiProvider::new( - cfg.ollama_base_url.clone(), - cfg.ollama_chat_model.clone(), - )) as Arc + let ai_provider: Arc = match cfg.llm_provider.as_str() { + "fake" => { + info!("Using Fake AI provider for testing"); + Arc::new(FakeAiProvider::new()) as Arc + } + "litellm" => { + info!("Using LiteLLM provider with model {}", cfg.litellm_model); + Arc::new(LiteLLMProvider::new( + cfg.litellm_base_url.clone(), + cfg.litellm_api_key.clone(), + cfg.litellm_model.clone(), + )) as Arc + } + _ => { + info!("Using Ollama provider with model {}", cfg.ollama_chat_model); + Arc::new(OllamaAiProvider::new( + cfg.ollama_base_url.clone(), + cfg.ollama_chat_model.clone(), + )) as Arc + } }; let _embedding_provider = Arc::new(OllamaEmbeddingProvider::new( diff --git a/src/providers/llm/fake_provider.rs b/src/providers/llm/fake_provider.rs new file mode 100644 index 0000000..202c1ac --- /dev/null +++ b/src/providers/llm/fake_provider.rs @@ -0,0 +1,63 @@ +use async_trait::async_trait; + +use crate::{error::AppError, providers::llm::ai_provider::AiProvider}; + +#[derive(Clone)] +pub struct FakeAiProvider { + responses: std::collections::HashMap, +} + +impl FakeAiProvider { + pub fn new() -> Self { + let mut responses = std::collections::HashMap::new(); + responses.insert( + "job description".to_string(), + "## React Developer\n\n**Summary**: We are looking for a skilled React Developer to join our team.\n\n**Responsibilities**:\n- Develop new user-facing features\n- Build reusable components\n- Collaborate with cross-functional teams\n\n**Requirements**:\n- 2+ years of React experience\n- TypeScript proficiency\n- Strong problem-solving skills".to_string(), + ); + responses.insert( + "cover letter".to_string(), + "Dear Hiring Manager,\n\nI am excited to apply for this position. With my experience in React development and passion for creating user-friendly applications, I believe I would be a great fit for your team.\n\nPlease consider my application.\n\nBest regards".to_string(), + ); + responses.insert( + "ticket".to_string(), + "Support ticket created successfully. Our team will respond within 24 hours.".to_string(), + ); + responses.insert( + "help".to_string(), + "I found some helpful articles that might answer your question. You can also contact our support team for further assistance.".to_string(), + ); + responses.insert( + "default".to_string(), + "I'm here to help! Could you please provide more details about what you need?".to_string(), + ); + Self { responses } + } + + pub fn with_response(mut self, trigger: &str, response: &str) -> Self { + self.responses.insert(trigger.to_string(), response.to_string()); + self + } + + fn get_response(&self, prompt: &str) -> String { + let prompt_lower = prompt.to_lowercase(); + for (key, value) in &self.responses { + if prompt_lower.contains(&key.to_lowercase()) { + return value.clone(); + } + } + self.responses.get("default").cloned().unwrap_or_default() + } +} + +impl Default for FakeAiProvider { + fn default() -> Self { + Self::new() + } +} + +#[async_trait] +impl AiProvider for FakeAiProvider { + async fn complete(&self, _system_prompt: &str, user_prompt: &str) -> Result { + Ok(self.get_response(user_prompt)) + } +} diff --git a/src/providers/llm/mod.rs b/src/providers/llm/mod.rs index 9f94b72..04f258f 100644 --- a/src/providers/llm/mod.rs +++ b/src/providers/llm/mod.rs @@ -1,3 +1,4 @@ pub mod ai_provider; -pub mod ollama_provider; +pub mod fake_provider; pub mod litellm_provider; +pub mod ollama_provider;