Status : IMPLEMENTEE
Actuellement, quand l'intent detection donne une confiance "medium" (plusieurs connecteurs possibles), le LLM classifier choisit automatiquement. Il se trompe parfois. Et quand le deep search ne trouve rien, l'utilisateur recoit juste "aucun resultat" sans possibilite de preciser sa demande.
Ajouter un mecanisme de clarification interactive : le systeme pose une question a l'utilisateur dans le chat, attend sa reponse, puis reprend l'execution avec l'info supplementaire.
Avant :
User: "cherche batman"
[LLM classifier choisit automatiquement Jellyfin]
AI: "Resultats Jellyfin..."
Apres :
User: "cherche batman"
AI: "Ou chercher ?
1. Jellyfin - films, series, musique
2. Nextcloud - fichiers, documents
3. GitHub - repos, issues
Reponds avec le numero ou le nom du service."
User: "1"
AI: "Resultats Jellyfin... Batman v Superman, The Batman..."
Avant :
User: "cherche xenomorph awakening sur jellyfin"
[deep search: 0 resultats]
AI: "Aucun resultat trouve."
Apres :
User: "cherche xenomorph awakening sur jellyfin"
[deep search: 0 resultats]
AI: "Aucun resultat pour 'xenomorph awakening' sur Jellyfin.
Suggestions :
1. Chercher 'xenomorph' seul
2. Chercher 'awakening' seul
3. Chercher sur un autre service
Ou tape un autre terme de recherche."
User: "alien"
AI: "Resultats Jellyfin... Alien, Aliens, Alien 3..."
pending_intent sur ai_conversationsALTER TABLE connectors.ai_conversations
ADD COLUMN pending_intent JSONB DEFAULT NULL;
Stocke les donnees necessaires pour reprendre l'execution :
interface PendingIntent {
type: 'clarify_connector' | 'clarify_search';
candidates?: IntentCandidate[]; // pour clarify_connector
originalIntent?: DetectedIntent; // intent d'origine
searchTerms?: string[]; // termes de recherche originaux
connector?: string; // connecteur cible (pour clarify_search)
instanceName?: string;
created_at: string; // pour expiration (ex: 5 min)
}
_processMessage() dans aiChat.tsAu debut : verifier si la conversation a un pending_intent :
// Check for pending clarification BEFORE intent detection
if (conv.pending_intent) {
const pending = conv.pending_intent as PendingIntent;
const age = Date.now() - new Date(pending.created_at).getTime();
if (age < 300000) { // < 5 min
const resolved = resolveClarification(content, pending);
if (resolved) {
// Clear pending
await supabase.update('ai_conversations', { pending_intent: null }, { id: conversationId });
// Execute with resolved intent
// ... (meme code que high confidence)
}
}
// If expired or not resolved, clear and continue normally
await supabase.update('ai_conversations', { pending_intent: null }, { id: conversationId });
}
Dans le bloc medium confidence : au lieu du LLM classifier, sauver les candidats et retourner un message de clarification :
if (intent.confidence === 'medium' && intent.candidates && intent.candidates.length > 0) {
emit({ step: 'clarifying', detail: 'Demande de precision...' });
// Save pending intent
const pending: PendingIntent = {
type: 'clarify_connector',
candidates: intent.candidates,
originalIntent: intent,
created_at: new Date().toISOString(),
};
await supabase.update('ai_conversations', { pending_intent: pending }, { id: conversationId });
// Build clarification message
let clarifyMsg = 'Ou chercher ?\n';
intent.candidates.forEach((c, i) => {
clarifyMsg += ` ${i + 1}. **${c.label}**\n`;
});
clarifyMsg += '\nReponds avec le numero ou le nom du service.';
const assistantMsgResult = await supabase.insert<Message>('ai_messages', {
conversation_id: conversationId, role: 'assistant',
content: clarifyMsg, model: null, duration_ms: Date.now() - startTime
});
return { userMessage, assistantMessage: assistantMsgResult.data! };
}
Apres le deep search echoue : proposer des alternatives :
if (itemCount === 0 && retriesExhausted) {
const pending: PendingIntent = {
type: 'clarify_search',
originalIntent: intent,
searchTerms: intent.searchTerms,
connector: intent.connector,
instanceName: intent.instanceName,
created_at: new Date().toISOString(),
};
await supabase.update('ai_conversations', { pending_intent: pending }, { id: conversationId });
// Build suggestions
let msg = `Aucun resultat pour "${intent.searchTerms?.join(' ')}" sur ${intent.connector}.\n`;
msg += 'Tu peux :\n';
const words = intent.searchTerms || [];
words.forEach((w, i) => { msg += ` ${i + 1}. Chercher "${w}" seul\n`; });
msg += ` ${words.length + 1}. Chercher sur un autre service\n`;
msg += '\nOu tape directement un autre terme de recherche.';
// Save and return
}
resolveClarification()function resolveClarification(
userReply: string,
pending: PendingIntent
): DetectedIntent | null {
const reply = userReply.trim().toLowerCase();
if (pending.type === 'clarify_connector') {
// Check if reply is a number
const num = parseInt(reply, 10);
if (num >= 1 && num <= (pending.candidates?.length || 0)) {
const chosen = pending.candidates![num - 1];
return {
...pending.originalIntent!,
confidence: 'high',
connector: chosen.connector,
endpoint: chosen.endpoint,
instanceName: chosen.instanceName,
queryParams: chosen.queryParams,
};
}
// Check if reply matches a connector name
for (const c of pending.candidates || []) {
if (reply.includes(c.connector)) {
return { ...pending.originalIntent!, confidence: 'high', ...c };
}
}
return null; // Not a clarification reply, treat as new message
}
if (pending.type === 'clarify_search') {
const num = parseInt(reply, 10);
const words = pending.searchTerms || [];
if (num >= 1 && num <= words.length) {
// Retry with single word
return {
...pending.originalIntent!,
confidence: 'high',
searchTerms: [words[num - 1]],
queryParams: { ...pending.originalIntent!.queryParams, searchTerm: words[num - 1] },
};
}
// Otherwise, treat reply as new search term
if (reply.length >= 2 && reply.length <= 100) {
return {
...pending.originalIntent!,
confidence: 'high',
searchTerms: [reply],
queryParams: { ...pending.originalIntent!.queryParams, searchTerm: reply },
};
}
return null;
}
return null;
}
clarifyAjouter un step SSE pour que le frontend sache que c'est une clarification (pas un message normal) :
emit({ step: 'clarify', detail: 'En attente de precision...' });
Le frontend peut afficher ce step differemment (pas de spinner, juste le message).
| Fichier | Action |
|---|---|
connectors-api/migrations/014_pending_intent.sql |
CREER - ALTER TABLE add pending_intent |
connectors-api/src/services/aiChat.ts |
MODIFIER - resolveClarification, modifier medium conf + deep search |
connectors-api/src/services/intentExecutor.ts |
MODIFIER - remonter l'info "0 resultats apres deep search" |
/messages non-SSE (retro-compatible)pending_intent a plus de 5 minutes → ignore, traite comme nouveau messagepending_intent est clear apres utilisation ou expirationresolveClarification() dans aiChat.ts