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.
This commit is contained in:
Tracewebstudio Dev 2026-06-14 19:24:02 +02:00
parent 1a2781a85f
commit e947e248e2
3 changed files with 87 additions and 15 deletions

View file

@ -16,8 +16,9 @@ use std::sync::Arc;
use config::AppConfig; use config::AppConfig;
use providers::help_center::nxtgauge_help_center_provider::NxtgaugeHelpCenterProvider; 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::litellm_provider::LiteLLMProvider;
use providers::llm::ollama_provider::OllamaAiProvider;
use providers::tickets::nxtgauge_ticket_provider::NxtgaugeTicketProvider; use providers::tickets::nxtgauge_ticket_provider::NxtgaugeTicketProvider;
use providers::llm::ai_provider::AiProvider; use providers::llm::ai_provider::AiProvider;
use retrieval::embeddings::ollama_embedding_provider::OllamaEmbeddingProvider; use retrieval::embeddings::ollama_embedding_provider::OllamaEmbeddingProvider;
@ -38,19 +39,26 @@ async fn main() {
} }
}; };
let ai_provider: Arc<dyn AiProvider> = if cfg.llm_provider == "litellm" { let ai_provider: Arc<dyn AiProvider> = match cfg.llm_provider.as_str() {
info!("Using LiteLLM provider with model {}", cfg.litellm_model); "fake" => {
Arc::new(LiteLLMProvider::new( info!("Using Fake AI provider for testing");
cfg.litellm_base_url.clone(), Arc::new(FakeAiProvider::new()) as Arc<dyn AiProvider>
cfg.litellm_api_key.clone(), }
cfg.litellm_model.clone(), "litellm" => {
)) as Arc<dyn AiProvider> info!("Using LiteLLM provider with model {}", cfg.litellm_model);
} else { Arc::new(LiteLLMProvider::new(
info!("Using Ollama provider with model {}", cfg.ollama_chat_model); cfg.litellm_base_url.clone(),
Arc::new(OllamaAiProvider::new( cfg.litellm_api_key.clone(),
cfg.ollama_base_url.clone(), cfg.litellm_model.clone(),
cfg.ollama_chat_model.clone(), )) as Arc<dyn AiProvider>
)) as Arc<dyn AiProvider> }
_ => {
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<dyn AiProvider>
}
}; };
let _embedding_provider = Arc::new(OllamaEmbeddingProvider::new( let _embedding_provider = Arc::new(OllamaEmbeddingProvider::new(

View file

@ -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<String, String>,
}
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<String, AppError> {
Ok(self.get_response(user_prompt))
}
}

View file

@ -1,3 +1,4 @@
pub mod ai_provider; pub mod ai_provider;
pub mod ollama_provider; pub mod fake_provider;
pub mod litellm_provider; pub mod litellm_provider;
pub mod ollama_provider;