#![allow(missing_docs)]
#![allow(clippy::missing_inline_in_public_items)]
#![allow(clippy::pedantic)]
use std::collections::HashMap;
use std::io::{self, Write};
use serde::{Deserialize, Serialize};
use api_huggingface::*;
use api_huggingface::components::input::InferenceParameters;
use api_huggingface::environment::HuggingFaceEnvironmentImpl;
use api_huggingface::secret::Secret;
#[ derive( Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize ) ]
pub enum QuestionType
{
ContextBased,
GeneralKnowledge,
Factual,
Opinion,
YesNo,
MultipleChoice,
}
impl QuestionType
{
pub fn preferred_model(&self) -> &'static str
{
"moonshotai/Kimi-K2-Instruct-0905:groq"
}
pub fn confidence_threshold(&self) -> f32
{
match self
{
Self::ContextBased => 0.8, Self::GeneralKnowledge => 0.7, Self::Factual => 0.9, Self::Opinion => 0.5, Self::YesNo => 0.8, Self::MultipleChoice => 0.8, }
}
pub fn as_str(&self) -> &'static str
{
match self
{
Self::ContextBased => "Context-based",
Self::GeneralKnowledge => "General Knowledge",
Self::Factual => "Factual",
Self::Opinion => "Opinion",
Self::YesNo => "Yes/No",
Self::MultipleChoice => "Multiple Choice",
}
}
}
#[ derive( Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize ) ]
pub enum DifficultyLevel
{
Easy,
Medium,
Hard,
Expert,
}
impl DifficultyLevel
{
pub fn as_str(&self) -> &'static str
{
match self
{
Self::Easy => "Easy",
Self::Medium => "Medium",
Self::Hard => "Hard",
Self::Expert => "Expert",
}
}
}
#[ derive( Debug, Clone, Serialize, Deserialize ) ]
pub struct Question
{
pub id : String,
pub text : String,
pub question_type : QuestionType,
pub difficulty : DifficultyLevel,
pub context : Option< String >,
pub expected_answer : Option< String >,
pub keywords : Vec< String >,
pub metadata : HashMap< String, String >,
}
impl Question
{
pub fn new(
id : String,
text : String,
question_type : QuestionType,
difficulty : DifficultyLevel,
) -> Self {
Self {
id,
text,
question_type,
difficulty,
context : None,
expected_answer : None,
keywords : Vec::new(),
metadata : HashMap::new(),
}
}
pub fn with_context(mut self, context : String) -> Self
{
self.context = Some(context);
self
}
pub fn with_keywords(mut self, keywords : Vec< String >) -> Self
{
self.keywords = keywords;
self
}
}
#[ derive( Debug, Clone, Serialize, Deserialize ) ]
pub struct Answer
{
pub text : String,
pub confidence : f32,
pub sources : Vec< String >,
pub reasoning : Option< String >,
pub question_id : String,
pub response_time_ms : u64,
}
#[ derive( Debug, Clone, Serialize, Deserialize ) ]
pub struct KnowledgeSource
{
pub id : String,
pub title : String,
pub content : String,
pub source_type : SourceType,
pub reliability_score : f32,
pub last_updated : String,
pub metadata : HashMap< String, String >,
}
impl KnowledgeSource
{
pub fn new(
id : String,
title : String,
content : String,
source_type : SourceType,
reliability_score : f32,
) -> Self {
Self {
id,
title,
content,
source_type,
reliability_score,
last_updated : chrono::Utc::now().format("%Y-%m-%d").to_string(),
metadata : HashMap::new(),
}
}
}
#[ derive( Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize ) ]
pub enum SourceType
{
Document,
WebPage,
Database,
Manual,
Encyclopedia,
FAQ,
}
impl SourceType
{
pub fn reliability_multiplier(&self) -> f32
{
match self
{
Self::Encyclopedia => 0.9,
Self::Manual => 0.85,
Self::Database => 0.8,
Self::Document => 0.75,
Self::WebPage => 0.6,
Self::FAQ => 0.7,
}
}
pub fn as_str(&self) -> &'static str
{
match self
{
Self::Document => "Document",
Self::WebPage => "Web Page",
Self::Database => "Database",
Self::Manual => "Manual",
Self::Encyclopedia => "Encyclopedia",
Self::FAQ => "FAQ",
}
}
}
#[ derive( Debug, Clone, Serialize, Deserialize ) ]
pub struct ConversationTurn
{
pub turn_id : usize,
pub question : Question,
pub answer : Option< Answer >,
pub context_from_previous : Vec< String >,
}
#[ derive( Debug, Clone, Serialize, Deserialize ) ]
pub struct QASession
{
pub session_id : String,
pub turns : Vec< ConversationTurn >,
pub active_context : HashMap< String, String >,
pub user_preferences : UserPreferences,
}
#[ derive( Debug, Clone, Serialize, Deserialize ) ]
pub struct UserPreferences
{
pub preferred_length : AnswerLength,
pub include_sources : bool,
pub include_reasoning : bool,
pub confidence_threshold : f32,
pub preferred_topics : Vec< String >,
}
impl Default for UserPreferences
{
fn default() -> Self
{
Self {
preferred_length : AnswerLength::Standard,
include_sources : false,
include_reasoning : false,
confidence_threshold : 0.6,
preferred_topics : Vec::new(),
}
}
}
#[ derive( Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize ) ]
pub enum AnswerLength
{
Brief, Standard, Detailed, Comprehensive, }
impl AnswerLength
{
pub fn word_count_range(&self) -> (usize, usize)
{
match self
{
Self::Brief => (10, 30),
Self::Standard => (30, 80),
Self::Detailed => (80, 150),
Self::Comprehensive => (150, 300),
}
}
pub fn max_tokens(&self) -> u32
{
match self
{
Self::Brief => 50u32,
Self::Standard => 120u32,
Self::Detailed => 200u32,
Self::Comprehensive => 400u32,
}
}
pub fn as_str(&self) -> &'static str
{
match self
{
Self::Brief => "Brief",
Self::Standard => "Standard",
Self::Detailed => "Detailed",
Self::Comprehensive => "Comprehensive",
}
}
}
#[ derive( Debug ) ]
pub struct QASystem
{
pub client : Client< HuggingFaceEnvironmentImpl >,
pub knowledge_sources : HashMap< String, KnowledgeSource >,
pub active_sessions : HashMap< String, QASession >,
pub default_model : String,
pub accuracy_cache : HashMap< String, f32 >,
}
impl QASystem
{
pub fn new(client : Client< HuggingFaceEnvironmentImpl >) -> Self
{
Self {
client,
knowledge_sources : HashMap::new(),
active_sessions : HashMap::new(),
default_model : "moonshotai/Kimi-K2-Instruct-0905:groq".to_string(),
accuracy_cache : HashMap::new(),
}
}
pub async fn answer_question( &self, question : &Question ) -> Result< Answer, Box< dyn std::error::Error > >
{
let model = question.question_type.preferred_model();
let prompt = Self::build_qa_prompt(question);
let params = InferenceParameters::new()
.with_temperature(0.3) .with_max_new_tokens(Self::get_max_tokens_for_question(question))
.with_top_p(0.8);
let start_time = std::time::Instant::now();
let result = self.client.inference().create_with_parameters(&prompt, model, params).await?;
let response_time = start_time.elapsed().as_millis() as u64;
let generated_text = result.extract_text_or_default( "" );
let answer = Self::parse_answer(&generated_text, question, response_time)?;
Ok(answer)
}
pub fn start_session(&mut self, session_id : String, preferences : UserPreferences) -> &QASession
{
let session = QASession {
session_id : session_id.clone(),
turns : Vec::new(),
active_context : HashMap::new(),
user_preferences : preferences,
};
self.active_sessions.insert(session_id.clone(), session);
self.active_sessions.get(&session_id).unwrap()
}
pub async fn answer_in_session( &mut self, session_id : &str, question : Question ) -> Result< Answer, Box< dyn std::error::Error > >
{
let session_context = if let Some(session) = self.active_sessions.get(session_id)
{
self.build_session_context(session)
} else {
String::new()
};
let mut context_question = question.clone();
if !session_context.is_empty()
{
let prev_context = context_question.context.unwrap_or_default();
context_question.context = Some(format!("{prev_context}\n\nPrevious context : {session_context}"));
}
let answer = self.answer_question(&context_question).await?;
if let Some(session) = self.active_sessions.get_mut(session_id)
{
let turn = ConversationTurn {
turn_id : session.turns.len(),
question : context_question,
answer : Some(answer.clone()),
context_from_previous : Vec::new(), };
session.turns.push(turn);
let turn_num = session.turns.len() - 1;
session.active_context.insert(format!("turn_{turn_num}"), answer.text.clone());
}
Ok(answer)
}
pub fn add_knowledge_source(&mut self, source : KnowledgeSource)
{
self.knowledge_sources.insert(source.id.clone(), source);
}
pub fn search_knowledge_sources(&self, query : &str, max_results : usize) -> Vec< &KnowledgeSource >
{
let mut scored_sources : Vec< (&KnowledgeSource, f32) > = self.knowledge_sources
.values()
.map(|source| {
let relevance = Self::calculate_relevance_score(query, source);
(source, relevance)
})
.collect();
scored_sources.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(core::cmp::Ordering::Equal));
scored_sources.into_iter().take(max_results).map(|(source, _)| source).collect()
}
pub fn evaluate_accuracy(&self, generated_answer : &str, expected_answer : &str) -> f32
{
let generated_lower = generated_answer.to_lowercase();
let expected_lower = expected_answer.to_lowercase();
let generated_words = generated_lower.split_whitespace().collect::< Vec< _ > >();
let expected_words = expected_lower.split_whitespace().collect::< Vec< _ > >();
let common_words = generated_words.iter()
.filter(|word| expected_words.contains(word))
.count();
if expected_words.is_empty()
{
return 0.0;
}
common_words as f32 / expected_words.len() as f32
}
pub fn get_system_stats(&self) -> QASystemStats
{
let total_questions_answered = self.active_sessions.values()
.map(|session| session.turns.len())
.sum();
let avg_confidence = self.active_sessions.values()
.flat_map(|session| session.turns.iter())
.filter_map(|turn| turn.answer.as_ref().map(|a| a.confidence))
.fold((0.0, 0), |(sum, count), conf| (sum + conf, count + 1));
let average_confidence = if avg_confidence.1 > 0
{
avg_confidence.0 / avg_confidence.1 as f32
} else {
0.0
};
QASystemStats {
total_knowledge_sources : self.knowledge_sources.len(),
active_sessions : self.active_sessions.len(),
total_questions_answered,
average_confidence,
cached_accuracies : self.accuracy_cache.len(),
}
}
fn build_qa_prompt(question : &Question) -> String
{
use core::fmt::Write;
let mut prompt = String::new();
if let Some(context) = &question.context
{
let _ = write!(&mut prompt, "Context : {context}\n\n");
}
let _ = writeln!(&mut prompt, "Question : {}", question.text);
match question.question_type
{
QuestionType::ContextBased => prompt.push_str("Answer based on the provided context:"),
QuestionType::GeneralKnowledge => prompt.push_str("Answer based on general knowledge:"),
QuestionType::Factual => prompt.push_str("Provide a factual answer:"),
QuestionType::Opinion => prompt.push_str("Provide a balanced perspective:"),
QuestionType::YesNo => prompt.push_str("Answer with Yes or No and explain briefly:"),
QuestionType::MultipleChoice => prompt.push_str("Choose the best answer and explain:"),
}
prompt
}
fn get_max_tokens_for_question(question : &Question) -> u32
{
match question.difficulty
{
DifficultyLevel::Easy => 100u32,
DifficultyLevel::Medium => 150u32,
DifficultyLevel::Hard => 200u32,
DifficultyLevel::Expert => 300u32,
}
}
fn parse_answer( text : &str, question : &Question, response_time : u64 ) -> Result< Answer, Box< dyn std::error::Error > >
{
let cleaned_text = text.trim().to_string();
let confidence = Self::calculate_confidence_score(&cleaned_text, question);
Ok(Answer {
text : cleaned_text,
confidence,
sources : Vec::new(), reasoning : None, question_id : question.id.clone(),
response_time_ms : response_time,
})
}
fn calculate_confidence_score(answer : &str, question : &Question) -> f32
{
let mut confidence : f32 = 0.5;
if !answer.is_empty()
{
confidence += 0.2;
}
let word_count = answer.split_whitespace().count();
if (5..=100).contains(&word_count)
{
confidence += 0.2;
}
match question.question_type
{
QuestionType::YesNo
if answer.to_lowercase().contains("yes") || answer.to_lowercase().contains("no") =>
{
confidence += 0.1;
},
QuestionType::Factual
if answer.contains('.') => {
confidence += 0.1;
},
_ => {}
}
confidence.min(1.0)
}
fn build_session_context(&self, session : &QASession) -> String
{
let recent_turns = session.turns.iter().rev().take(3).rev();
let context_parts : Vec< String > = recent_turns
.filter_map(|turn| {
let q_text = &turn.question.text;
turn.answer.as_ref().map(|a| {
let a_text = &a.text;
format!("Q: {q_text} A: {a_text}")
})
})
.collect();
context_parts.join("\n")
}
fn calculate_relevance_score(query : &str, source : &KnowledgeSource) -> f32
{
let query_lower = query.to_lowercase();
let query_words = query_lower.split_whitespace().collect::< Vec< _ > >();
let source_text = format!("{} {}", source.title.to_lowercase(), source.content.to_lowercase());
let matches = query_words.iter()
.filter(|word| source_text.contains(*word))
.count();
let base_score = if query_words.is_empty() { 0.0 } else { matches as f32 / query_words.len() as f32 };
base_score * source.source_type.reliability_multiplier() * source.reliability_score
}
}
#[ derive( Debug, Clone, Serialize, Deserialize ) ]
pub struct QASystemStats
{
pub total_knowledge_sources : usize,
pub active_sessions : usize,
pub total_questions_answered : usize,
pub average_confidence : f32,
pub cached_accuracies : usize,
}
#[ derive( Debug ) ]
pub struct QASystemPlatform
{
qa_system : QASystem,
current_session : Option< String >,
stats : PlatformStats,
sample_questions : Vec< Question >,
sample_knowledge : Vec< KnowledgeSource >,
}
#[ derive( Debug, Default ) ]
pub struct PlatformStats
{
questions_asked : usize,
sessions_created : usize,
knowledge_sources_added : usize,
total_response_time_ms : u64,
}
impl QASystemPlatform
{
pub fn new(client : Client< HuggingFaceEnvironmentImpl >) -> Self
{
let mut platform = Self {
qa_system : QASystem::new(client),
current_session : None,
stats : PlatformStats::default(),
sample_questions : Vec::new(),
sample_knowledge : Vec::new(),
};
platform.load_sample_data();
platform
}
fn load_sample_data(&mut self)
{
self.sample_questions = vec![
Question::new(
"q1".to_string(),
"What is the capital of France?".to_string(),
QuestionType::GeneralKnowledge,
DifficultyLevel::Easy,
).with_keywords(vec!["capital".to_string(), "France".to_string()]),
Question::new(
"q2".to_string(),
"Explain the benefits of renewable energy.".to_string(),
QuestionType::ContextBased,
DifficultyLevel::Medium,
).with_context("Renewable energy sources such as solar and wind power offer numerous benefits. They reduce greenhouse gas emissions, provide energy independence, and have lower long-term costs compared to fossil fuels.".to_string())
.with_keywords(vec!["renewable".to_string(), "energy".to_string(), "benefits".to_string()]),
Question::new(
"q3".to_string(),
"Is artificial intelligence beneficial for society?".to_string(),
QuestionType::YesNo,
DifficultyLevel::Hard,
).with_keywords(vec!["AI".to_string(), "society".to_string(), "beneficial".to_string()]),
Question::new(
"q4".to_string(),
"What are the main components of a computer?".to_string(),
QuestionType::Factual,
DifficultyLevel::Medium,
).with_keywords(vec!["computer".to_string(), "components".to_string()]),
Question::new(
"q5".to_string(),
"What do you think about remote work?".to_string(),
QuestionType::Opinion,
DifficultyLevel::Easy,
).with_keywords(vec!["remote".to_string(), "work".to_string(), "opinion".to_string()]),
];
self.sample_knowledge = vec![
KnowledgeSource::new(
"geo1".to_string(),
"Geography Facts".to_string(),
"Paris is the capital and largest city of France. It is located in the north of the country on the River Seine.".to_string(),
SourceType::Encyclopedia,
0.95,
),
KnowledgeSource::new(
"energy1".to_string(),
"Renewable Energy Guide".to_string(),
"Renewable energy technologies harness natural resources like sunlight, wind, and water to generate clean electricity without depleting resources or causing pollution.".to_string(),
SourceType::Manual,
0.9,
),
KnowledgeSource::new(
"tech1".to_string(),
"Computer Components Manual".to_string(),
"A computer typically consists of a CPU (Central Processing Unit), RAM (Random Access Memory), storage devices, motherboard, power supply, and input/output devices like keyboard and monitor.".to_string(),
SourceType::Manual,
0.88,
),
];
for source in self.sample_knowledge.clone()
{
self.qa_system.add_knowledge_source(source);
self.stats.knowledge_sources_added += 1;
}
}
pub async fn run(&mut self) -> Result< (), Box< dyn std::error::Error > >
{
println!("🧠 Intelligent Question-Answering System");
println!("=====================================");
println!();
self.show_help();
loop
{
print!("\n > ");
io::stdout().flush()?;
let mut input = String::new();
io::stdin().read_line(&mut input)?;
let input = input.trim();
if input.is_empty()
{
continue;
}
match input
{
"/help" | "/h" => self.show_help(),
"/quit" | "/q" => {
println!("Thanks for using the QA system!");
break;
}
"/session" => self.create_session_interactive()?,
"/ask" => self.ask_question_interactive().await?,
"/samples" => self.show_sample_questions(),
"/knowledge" => self.manage_knowledge_interactive()?,
"/stats" => self.show_statistics(),
"/export" => self.export_session_interactive()?,
cmd if cmd.starts_with('/') =>
{
println!("❌ Unknown command : {cmd}. Type /help for available commands.");
}
question => {
self.answer_direct_question(question).await?;
}
}
}
Ok(())
}
fn show_help(&self)
{
println!("Available commands:");
println!(" /ask - Ask a question with options");
println!(" /session - Create or manage sessions");
println!(" /samples - Show sample questions");
println!(" /knowledge - Manage knowledge sources");
println!(" /stats - Show system statistics");
println!(" /export - Export session data");
println!(" /help - Show this help");
println!(" /quit - Exit the system");
println!();
println!("You can also type questions directly for quick answers.");
println!("Example : What is machine learning?");
}
async fn answer_direct_question(&mut self, question_text : &str) -> Result< (), Box< dyn std::error::Error > >
{
let q_num = self.stats.questions_asked;
let question = Question::new(
format!("direct_{q_num}"),
question_text.to_string(),
QuestionType::GeneralKnowledge,
DifficultyLevel::Medium,
);
println!("\n🤔 Processing question...");
let answer = if let Some(session_id) = &self.current_session.clone()
{
self.qa_system.answer_in_session(session_id, question).await?
} else {
self.qa_system.answer_question(&question).await?
};
Self::display_answer(&answer);
self.update_stats(&answer);
Ok(())
}
async fn ask_question_interactive(&mut self) -> Result< (), Box< dyn std::error::Error > >
{
println!("\n📝 Ask a Question");
println!("================");
print!("Question : ");
io::stdout().flush()?;
let mut question_text = String::new();
io::stdin().read_line(&mut question_text)?;
let question_text = question_text.trim();
if question_text.is_empty()
{
println!("❌ Question cannot be empty.");
return Ok(());
}
println!("\nQuestion types:");
println!("1. General Knowledge");
println!("2. Context-based");
println!("3. Factual");
println!("4. Opinion");
println!("5. Yes/No");
println!("6. Multiple Choice");
print!("Select type (1-6): ");
io::stdout().flush()?;
let mut type_input = String::new();
io::stdin().read_line(&mut type_input)?;
let question_type = match type_input.trim()
{
"1" => QuestionType::GeneralKnowledge,
"2" => QuestionType::ContextBased,
"3" => QuestionType::Factual,
"4" => QuestionType::Opinion,
"5" => QuestionType::YesNo,
"6" => QuestionType::MultipleChoice,
_ => QuestionType::GeneralKnowledge,
};
println!("\nDifficulty levels:");
println!("1. Easy");
println!("2. Medium");
println!("3. Hard");
println!("4. Expert");
print!("Select difficulty (1-4): ");
io::stdout().flush()?;
let mut diff_input = String::new();
io::stdin().read_line(&mut diff_input)?;
let difficulty = match diff_input.trim()
{
"1" => DifficultyLevel::Easy,
"2" => DifficultyLevel::Medium,
"3" => DifficultyLevel::Hard,
"4" => DifficultyLevel::Expert,
_ => DifficultyLevel::Medium,
};
let mut context = None;
if question_type == QuestionType::ContextBased
{
print!("Context (optional): ");
io::stdout().flush()?;
let mut context_input = String::new();
io::stdin().read_line(&mut context_input)?;
let context_input = context_input.trim();
if !context_input.is_empty()
{
context = Some(context_input.to_string());
}
}
let q_num = self.stats.questions_asked;
let mut question = Question::new(
format!("custom_{q_num}"),
question_text.to_string(),
question_type,
difficulty,
);
if let Some(ctx) = context
{
question = question.with_context(ctx);
}
let q_type_str = question_type.as_str();
let diff_str = difficulty.as_str();
println!("\n🤔 Processing {q_type_str} question with {diff_str} difficulty...");
let answer = if let Some(session_id) = &self.current_session.clone()
{
self.qa_system.answer_in_session(session_id, question).await?
} else {
self.qa_system.answer_question(&question).await?
};
Self::display_answer(&answer);
self.update_stats(&answer);
Ok(())
}
fn create_session_interactive(&mut self) -> Result< (), Box< dyn std::error::Error > >
{
println!("\n🗣️ Session Management");
println!("====================");
if let Some(session_id) = &self.current_session
{
println!("Current session : {session_id}");
println!("1. Continue with current session");
println!("2. Create new session");
println!("3. End current session");
} else {
println!("No active session");
println!("1. Create new session");
println!("2. Continue without session");
}
print!("Select option : ");
io::stdout().flush()?;
let mut option = String::new();
io::stdin().read_line(&mut option)?;
match option.trim()
{
"1" => {
if self.current_session.is_none()
{
self.create_new_session()?;
} else {
println!("✅ Continuing with current session");
}
}
"2" => {
if self.current_session.is_some()
{
self.create_new_session()?;
} else {
println!("✅ Continuing without session (stateless mode)");
}
}
"3" => {
if self.current_session.is_some()
{
println!("✅ Session ended");
self.current_session = None;
}
}
_ => println!("❌ Invalid option"),
}
Ok(())
}
fn create_new_session(&mut self) -> Result< (), Box< dyn std::error::Error > >
{
print!("Session name (optional): ");
io::stdout().flush()?;
let mut name = String::new();
io::stdin().read_line(&mut name)?;
let name = name.trim();
let session_id = if name.is_empty()
{
let session_num = self.stats.sessions_created + 1;
format!("session_{session_num}")
} else {
name.to_string()
};
let preferences = UserPreferences::default();
self.qa_system.start_session(session_id.clone(), preferences);
self.current_session = Some(session_id.clone());
self.stats.sessions_created += 1;
println!("✅ Created session : {session_id}");
Ok(())
}
fn show_sample_questions(&self)
{
println!("\n📚 Sample Questions");
println!("==================");
for (i, question) in self.sample_questions.iter().enumerate()
{
let num = i + 1;
let text = &question.text;
let q_type = question.question_type.as_str();
let diff = question.difficulty.as_str();
println!("{num}. {text} [{q_type}] [{diff}]");
if let Some(context) = &question.context
{
println!(" Context : {context}");
}
}
println!("\nType the question number to ask it, or create your own with /ask");
let max_num = self.sample_questions.len();
print!("Select question (1-{max_num}) or press Enter to skip : ");
io::stdout().flush().unwrap();
let mut input = String::new();
if io::stdin().read_line(&mut input).is_ok()
{
if let Ok(index) = input.trim().parse::< usize >()
{
if index > 0 && index <= self.sample_questions.len()
{
let question = self.sample_questions[index - 1].clone();
tokio::spawn(async move {
let q_text = &question.text;
println!("Selected : {q_text}");
});
}
}
}
}
fn manage_knowledge_interactive(&mut self) -> Result< (), Box< dyn std::error::Error > >
{
println!("\n📖 Knowledge Management");
println!("======================");
println!("1. View knowledge sources");
println!("2. Add knowledge source");
println!("3. Search knowledge sources");
print!("Select option (1-3): ");
io::stdout().flush()?;
let mut option = String::new();
io::stdin().read_line(&mut option)?;
match option.trim()
{
"1" => self.view_knowledge_sources(),
"2" => self.add_knowledge_source_interactive()?,
"3" => self.search_knowledge_interactive()?,
_ => println!("❌ Invalid option"),
}
Ok(())
}
fn view_knowledge_sources(&self)
{
let source_count = self.qa_system.knowledge_sources.len();
println!("\n📚 Knowledge Sources ({source_count}):");
for (i, source) in self.qa_system.knowledge_sources.values().enumerate()
{
let num = i + 1;
let title = &source.title;
let s_type = source.source_type.as_str();
let reliability = source.reliability_score;
println!("{num}. {title} [{s_type}] (Reliability : {reliability:.2})");
println!(" {}", &source.content[..source.content.len().min(100)]);
if source.content.len() > 100
{
println!(" ...");
}
}
}
fn add_knowledge_source_interactive(&mut self) -> Result< (), Box< dyn std::error::Error > >
{
print!("Source title : ");
io::stdout().flush()?;
let mut title = String::new();
io::stdin().read_line(&mut title)?;
let title = title.trim().to_string();
if title.is_empty()
{
println!("❌ Title cannot be empty.");
return Ok(());
}
print!("Source content : ");
io::stdout().flush()?;
let mut content = String::new();
io::stdin().read_line(&mut content)?;
let content = content.trim().to_string();
if content.is_empty()
{
println!("❌ Content cannot be empty.");
return Ok(());
}
println!("Source types:");
println!("1. Document");
println!("2. Web Page");
println!("3. Database");
println!("4. Manual");
println!("5. Encyclopedia");
println!("6. FAQ");
print!("Select source type (1-6): ");
io::stdout().flush()?;
let mut type_input = String::new();
io::stdin().read_line(&mut type_input)?;
let source_type = match type_input.trim()
{
"2" => SourceType::WebPage,
"3" => SourceType::Database,
"4" => SourceType::Manual,
"5" => SourceType::Encyclopedia,
"6" => SourceType::FAQ,
_ => SourceType::Document, };
let source_num = self.stats.knowledge_sources_added;
let source_id = format!("user_{source_num}");
let source = KnowledgeSource::new(
source_id,
title,
content,
source_type,
0.8, );
self.qa_system.add_knowledge_source(source);
self.stats.knowledge_sources_added += 1;
println!("✅ Knowledge source added successfully!");
Ok(())
}
fn search_knowledge_interactive(&mut self) -> Result< (), Box< dyn std::error::Error > >
{
print!("Search query : ");
io::stdout().flush()?;
let mut query = String::new();
io::stdin().read_line(&mut query)?;
let query = query.trim();
if query.is_empty()
{
println!("❌ Query cannot be empty.");
return Ok(());
}
let results = self.qa_system.search_knowledge_sources(query, 5);
if results.is_empty()
{
println!("❌ No relevant knowledge sources found.");
} else {
let result_count = results.len();
println!("\n🔍 Search Results for '{query}' ({result_count} found):");
for (i, source) in results.iter().enumerate()
{
let relevance = QASystem::calculate_relevance_score(query, source);
let num = i + 1;
let title = &source.title;
let s_type = source.source_type.as_str();
println!("{num}. {title} [{s_type}] (Relevance : {relevance:.2})");
println!(" {}", &source.content[..source.content.len().min(150)]);
if source.content.len() > 150
{
println!(" ...");
}
}
}
Ok(())
}
fn show_statistics(&self)
{
let qa_stats = self.qa_system.get_system_stats();
println!("\n📊 System Statistics");
println!("===================");
let q_asked = self.stats.questions_asked;
let s_created = self.stats.sessions_created;
let active_sess = qa_stats.active_sessions;
let k_sources = qa_stats.total_knowledge_sources;
let total_answered = qa_stats.total_questions_answered;
let avg_conf = qa_stats.average_confidence;
println!("Questions Asked : {q_asked}");
println!("Sessions Created : {s_created}");
println!("Active Sessions : {active_sess}");
println!("Knowledge Sources : {k_sources}");
println!("Total Questions Answered : {total_answered}");
println!("Average Confidence : {avg_conf:.2}");
let avg_time = if self.stats.questions_asked > 0
{
self.stats.total_response_time_ms as f64 / self.stats.questions_asked as f64
} else {
0.0
};
println!("Average Response Time : {avg_time:.2}ms");
}
fn export_session_interactive(&mut self) -> Result< (), Box< dyn std::error::Error > >
{
if let Some(session_id) = &self.current_session
{
if let Some(session) = self.qa_system.active_sessions.get(session_id)
{
let json_data = serde_json::to_string_pretty(session)?;
print!("Export filename (press Enter for default): ");
io::stdout().flush()?;
let mut filename = String::new();
io::stdin().read_line(&mut filename)?;
let filename = filename.trim();
let filename = if filename.is_empty()
{
format!("qa_session_{session_id}.json")
} else {
filename.to_string()
};
std::fs::write(&filename, json_data)?;
println!("✅ Session exported to : {filename}");
} else {
println!("❌ Session not found");
}
} else {
println!("❌ No active session to export");
}
Ok(())
}
fn display_answer(answer : &Answer)
{
println!("\n💡 Answer");
println!("========");
let text = &answer.text;
println!("{text}");
println!();
let conf_pct = answer.confidence * 100.0;
let resp_time = answer.response_time_ms;
println!("Confidence : {conf_pct:.1}%");
println!("Response Time : {resp_time}ms");
if !answer.sources.is_empty()
{
let sources = answer.sources.join(", ");
println!("Sources : {sources}");
}
if let Some(reasoning) = &answer.reasoning
{
println!("Reasoning : {reasoning}");
}
}
fn update_stats(&mut self, answer : &Answer)
{
self.stats.questions_asked += 1;
self.stats.total_response_time_ms += answer.response_time_ms;
}
}
#[ tokio::main ]
async fn main() -> Result< (), Box< dyn std::error::Error > >
{
let api_key = std::env::var("HUGGINGFACE_API_KEY")
.or_else(|_| {
use workspace_tools as workspace;
let workspace = workspace::workspace()
.map_err(|_| std::env::VarError::NotPresent)?; let secrets = workspace.load_secrets_from_file("-secrets.sh")
.map_err(|_| std::env::VarError::NotPresent)?; secrets.get("HUGGINGFACE_API_KEY")
.cloned()
.ok_or(std::env::VarError::NotPresent)
})?;
let secret = Secret::new(api_key);
let env = HuggingFaceEnvironmentImpl::build(secret, None)?;
let client = Client::build(env)?;
let mut platform = QASystemPlatform::new(client);
platform.run().await?;
Ok(())
}