intent recognition added (first implementation)

This commit is contained in:
Priler 2026-01-05 01:22:45 +05:00
parent 0c28304840
commit 5f1f9ce321
20 changed files with 625 additions and 255 deletions

View file

@ -0,0 +1,107 @@
use intent_classifier::{
IntentClassifier, IntentPrediction, IntentError,
TrainingExample, TrainingSource, IntentId
};
use tokio::sync::OnceCell;
use std::path::PathBuf;
use std::fs;
use crate::commands::{self, JCommand, JCommandsList};
use crate::{APP_CONFIG_DIR};
static CLASSIFIER: OnceCell<IntentClassifier> = OnceCell::const_new();
// static COMMANDS_MAP: OnceCell<Vec<JCommandsList>> = OnceCell::const_new();
const TRAINING_CACHE_FILE: &str = "intent_training.json";
const COMMANDS_HASH_FILE: &str = "commands_hash.txt";
pub async fn init(commands: &Vec<JCommandsList>) -> Result<(), String> {
// parse commands first
// let commands = commands::parse_commands()?;
let current_hash = commands::commands_hash(&commands); // regen hash for current commands set
// init classifier
let classifier = IntentClassifier::new().await
.map_err(|e| format!("Failed to init IntentClassifier: {}", e))?;
// check if we can use cached training data
let config_dir = APP_CONFIG_DIR.get().ok_or("Config dir not set")?;
let hash_path = config_dir.join(COMMANDS_HASH_FILE);
let cache_path = config_dir.join(TRAINING_CACHE_FILE);
let should_retrain = if hash_path.exists() && cache_path.exists() {
let stored_hash = fs::read_to_string(&hash_path).unwrap_or_default();
stored_hash.trim() != current_hash
} else {
true
};
if should_retrain {
info!("Training intent classifier with {} commands...", commands.len());
train_classifier(&classifier, &commands).await?;
// save training data and hash
if let Ok(export) = classifier.export_training_data().await {
let _ = fs::write(&cache_path, export);
let _ = fs::write(&hash_path, &current_hash);
info!("Training data cached.");
}
} else {
info!("Loading cached training data...");
if let Ok(data) = fs::read_to_string(&cache_path) {
classifier.import_training_data(&data).await
.map_err(|e| format!("Failed to import training data: {}", e))?;
}
}
// store data
CLASSIFIER.set(classifier).map_err(|_| "Classifier already set")?;
// COMMANDS_MAP.set(commands).map_err(|_| "Commands map already set")?;
Ok(())
}
pub async fn classify(text: &str) -> Result<IntentPrediction, IntentError> {
let classifier = CLASSIFIER.get().expect("IntentClassifier not initialized");
classifier.predict_intent(text).await
}
// get command by intent ID
pub fn get_command(commands: &'static Vec<JCommandsList>, intent_id: &str) -> Option<(&'static PathBuf, &'static JCommand)> {
// let commands = COMMANDS_MAP.get()?;
for assistant_cmd in commands {
for cmd in &assistant_cmd.commands {
if cmd.id == intent_id {
return Some((&assistant_cmd.path, cmd));
}
}
}
None
}
// based on: https://github.com/ciresnave/intent-classifier/blob/main/examples/basic_usage.rs
async fn train_classifier(
classifier: &IntentClassifier,
commands: &Vec<JCommandsList>
) -> Result<(), String> {
for assistant_cmd in commands {
for cmd in &assistant_cmd.commands {
for phrase in &cmd.phrases {
let example = TrainingExample {
text: phrase.clone(),
intent: IntentId::from(cmd.id.as_str()),
confidence: 1.0,
source: TrainingSource::Programmatic,
};
classifier.add_training_example(example).await
.map_err(|e| format!("Failed to add training example: {}", e))?;
}
}
}
Ok(())
}