Assistant
Overview
The Assistant service powers the intelligent chatbot that handles visitor inquiries across the Sutomo platform. It processes natural language questions in multiple languages, matches them against known intents, and returns relevant answers.
The system uses a hybrid matching pipeline: ML classification (Rubix ML) → exact database match → fuzzy similarity scoring. This ensures both speed and accuracy while continuously learning from real user interactions.
Why It Exists
- Provide instant self-service answers to common visitor questions (school hours, registration, fees, etc.)
- Support multi-language inquiries (Indonesian, English, and others via Google Translate)
- Continuously improve through automated training data collection
- Reduce admin workload by handling repetitive questions
Architecture
Methods
Chatbot (Orchestrator)
| Method | Description |
|---|---|
handleUserMessage(dialog, message) | Main entry point — translates, normalizes, matches intent, returns reply |
getOrCreateDialog(fingerprint, user) | Get or create a dialog session |
ensureGreeting(dialog, greeting) | Insert greeting message if dialog is empty |
resetDialog(dialog) | Clear all messages in a dialog |
getDefaultLanguage() | Read default assistant language from settings |
isLlmEnabled() | Check if LLM feature is enabled |
DialogManager
| Method | Description |
|---|---|
getOrCreateFingerprint() | Get or generate a persistent visitor fingerprint (session + cookie) |
getOrCreateDialog(fingerprint, user) | Find existing dialog or create new one |
getMessages(dialog) | Get all messages in chat format |
ensureGreeting(dialog, greeting) | Add greeting if first interaction |
resetDialog(dialog) | Delete all messages and reset state |
Normalizer
| Method | Description |
|---|---|
normalizeQuestion(question) | Static — lowercases, removes punctuation, collapses whitespace |
tokenize(text, language) | Split, stem, remove stopwords, normalize synonyms |
stemToken(token, language) | Indonesian affix removal (prefixes & suffixes) |
isStopword(token, language) | Check against ID/EN stopword lists |
normalizeSynonym(token, language) | Map synonyms to canonical form (e.g., "mulai" → "jam") |
MlClassifier (Rubix ML)
| Method | Description |
|---|---|
train() | Train Softmax Classifier on questions + confirmed training logs |
predict(normalizedQuestion) | Predict intent with confidence score |
isTrained() | Check if model file exists |
getTrainingSampleCount() | Count available training samples |
SimilarityScorer
| Method | Description |
|---|---|
scoreSimilarity(a, b, language) | Composite score combining Jaccard, Overlap, Levenshtein, and Trigram Cosine |
jaccardSimilarity(a, b) | Token set intersection over union |
overlapSimilarity(a, b) | Twice intersection over total set size |
levenshteinSimilarity(a, b) | Character-level edit distance |
trigramCosineSimilarity(a, b) | N-gram (3-char) cosine similarity |
Translator
| Method | Description |
|---|---|
translateIntoDefaultLanguage(message, defaultLang) | Detect source language and translate to default |
translateFromDefaultLanguage(text, defaultLang, targetLang) | Translate reply back to user's language |
looksEnglish(text) | Heuristic check if text is English |
guessSourceLanguageFromScript(text) | Detect CJK, Arabic, Cyrillic scripts |
simpleDictionaryTranslateEnToId(text) | Fallback dictionary-based EN→ID translation |
Matching Pipeline
The following flowchart shows how a user message is processed through the hybrid matching pipeline:
How to Use
The Assistant service is used through the Chatbot class, which acts as the main orchestrator:
php
use App\Services\Assistant\Chatbot;
class YourController
{
public function __construct(
private readonly Chatbot $chatbot,
) {}
public function handle(Request $request): array
{
$fingerprint = $this->chatbot->getOrCreateFingerprint();
$dialog = $this->chatbot->getOrCreateDialog($fingerprint, $request->user());
$this->chatbot->ensureGreeting($dialog, 'Hello! How can I help you?');
[$reply, $intentCode, $matchInfo] = $this->chatbot->handleUserMessage(
$dialog,
$request->input('message'),
);
return [
'reply' => $reply,
'intent' => $intentCode,
'match' => $matchInfo,
];
}
}Training the ML Model
php
use App\Services\Assistant\MlClassifier;
$classifier = app(MlClassifier::class);
$classifier->train(); // trains on all active questions + confirmed logsKey Files
app/Services/Assistant/
├── Chatbot.php # Main orchestrator — handles messages, coordinates pipeline
├── DialogManager.php # Session management, fingerprinting, dialog CRUD
├── Normalizer.php # Text preprocessing: stemming, stopwords, synonyms
├── MlClassifier.php # Rubix ML Softmax Classifier — train & predict
├── SimilarityScorer.php # Composite text similarity (Jaccard, Overlap, Levenshtein, Trigram)
└── Translator.php # Multi-language translation via Google Translate
app/Models/Assistant/
├── BotDialog/BotDialog.php # Dialog session model
├── BotDialogMessage/BotDialogMessage.php # Individual messages in a dialog
├── ChatbotIntent/ChatbotIntent.php # Intent definitions
├── ChatbotQuestion/ChatbotQuestion.php # Question variations per intent
├── ChatbotAnswer/ChatbotAnswer.php # Answer content per intent
└── TrainingLog/TrainingLog.php # Training data from interactions
database/migrations/v1_4_0/
├── 005_create_otps_table.php # Assistant-related tables
└── ...