Spaces:
Sleeping
Sleeping
File size: 11,211 Bytes
fbdfc24 3e14b58 fbdfc24 478b91f fbdfc24 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 |
# [file name]: core/router.py
# Add this as the FIRST lines of code (after docstrings)
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent))
import re
import logging
import json
from typing import Dict, List, Optional, Literal, Any
from langchain_openai import ChatOpenAI
from langchain_core.messages import SystemMessage, HumanMessage
from config.settings import settings
from models.state_models import RoutingResult
logger = logging.getLogger(__name__)
class CountryRouter:
def __init__(self):
self.llm = ChatOpenAI(
model=settings.CHAT_MODEL_2,
temperature=0.1,
max_tokens=200
)
async def route_query(self, query: str, conversation_history: List[Dict]) -> RoutingResult:
"""Unified LLM-powered routing"""
try:
# Build conversation context
context = self._build_conversation_context(conversation_history)
# LLM routing prompt
routing_prompt = self._build_routing_prompt(query, context)
logger.info(f"🔀 Routing query: '{query[:50]}...'")
# Call LLM for routing decision
response = await self.llm.ainvoke([SystemMessage(content=routing_prompt)])
routing_result = self._parse_routing_response(response.content)
logger.info(f"🎯 Router decision: {routing_result.country} ({routing_result.confidence})")
return routing_result
except Exception as e:
logger.error(f"Router error: {e}")
# Fallback to unclear
return RoutingResult(
country="unclear",
confidence="low",
method="error_fallback",
explanation=f"Router error: {str(e)}"
)
def _build_routing_prompt(self, query: str, context: str) -> str:
"""Build comprehensive routing prompt"""
return f"""
Vous êtes un routeur intelligent pour un assistant juridique spécialisé dans le droit du Bénin et de Madagascar.
**TÂCHE:** Analyser la requête utilisateur et déterminer la meilleure destination.
**DESTINATIONS POSSIBLES:**
- "benin": Questions juridiques concernant le Bénin (lois, procédures, droits)
- "madagascar": Questions juridiques concernant Madagascar (lois, procédures, droits)
- "assistance_request": Demande pour parler à un avocat humain
- "greeting_small_talk": Salutations, présentations, remerciements (politesse uniquement)
- "conversation_repair": Incompréhension, demande de clarification
- "conversation_summarization": Demande de résumé de la conversation
- "out_of_scope": Questions NON juridiques (café, météo, sports, recettes, etc.)
- "unclear": Intention juridique incertaine
**REQUÊTE:** "{query}"
**CONTEXTE DE CONVERSATION:**
{context}
**RÈGLES DE CLASSIFICATION:**
1. **greeting_small_talk** - UNIQUEMENT pour politesse basique:
- Salutations: "bonjour", "salut", "hello", "bonsoir", "au revoir"
- Présentations brèves: "je m'appelle X", "mon nom est X"
- Remerciements: "merci", "merci beaucoup"
- Politesses simples: "comment ça va", "ça va bien"
- Questions sur l'identité de l'assistant: "qui es-tu", "comment tu t'appelles"
2. **benin** - Pour questions juridiques sur le Bénin:
- Mentions explicites: "bénin", "benin", "béninois"
- Villes: "cotonou", "porto-novo"
- Lois/procédures béninoises
3. **madagascar** - Pour questions juridiques sur Madagascar:
- Mentions explicites: "madagascar", "malgache"
- Villes: "antananarivo", "toamasina"
- Lois/procédures malgaches
4. **assistance_request** - Demande d'aide humaine:
- "parler à un avocat"
- "contacter un avocat"
- "assistance téléphonique"
- "besoin d'aide juridique personnalisée"
5. **conversation_repair** - Problèmes de compréhension:
- "je n'ai pas compris"
- "répète s'il te plaît"
- "explique autrement"
- "qu'est-ce que tu veux dire"
6. **conversation_summarization** - Demande de résumé:
- "résume notre conversation"
- "récapitulatif"
- "qu'avons-nous dit"
7. **out_of_scope** - Questions clairement NON juridiques:
- Météo/Climat: "température à Douala", "il va pleuvoir?"
- Nourriture: "recette de ndolé", "fais-moi un café"
- Sport: "résultat du match", "qui a gagné?"
- Technologie: "comment réparer mon téléphone", "meilleur ordinateur"
- Divertissement: "raconte une blague", "parle-moi de musique"
- Santé non-juridique: "symptômes grippe", "remèdes traditionnels"
- **Règle clé**: AUCUN aspect juridique ou lien avec le droit
8. **unclear** - Questions juridiques MAIS pays/détails manquants:
- "J'ai un problème de divorce" (quel pays?)
- "Comment créer une entreprise" (Bénin ou Madagascar?)
- "Besoin d'aide juridique" (trop vague)
- "Question sur l'héritage" (juridiction non précisée)
- **Règle clé**: Intention juridique évidente MAIS manque de précision sur le pays ou les détails
**EXEMPLES COMPLETS:**
- "Bonjour" → {{"destination": "greeting_small_talk", "confidence": "high", "reasoning": "Salutation simple"}}
- "je m'appelle Thibaut" → {{"destination": "greeting_small_talk", "confidence": "high", "reasoning": "Présentation personnelle"}}
- "comment est-ce que je m'appelle" → {{"destination": "greeting_small_talk", "confidence": "high", "reasoning": "Question personnelle de rappel"}}
- "salut comment ça va" → {{"destination": "greeting_small_talk", "confidence": "high", "reasoning": "Salutation et politesse"}}
- "merci beaucoup" → {{"destination": "greeting_small_talk", "confidence": "high", "reasoning": "Remerciement"}}
- "qui es-tu" → {{"destination": "greeting_small_talk", "confidence": "high", "reasoning": "Question sur l'identité de l'assistant"}}
- "procedure divorce Bénin" → {{"destination": "benin", "confidence": "high", "reasoning": "Question juridique explicite sur le Bénin"}}
- "loi foncière Madagascar" → {{"destination": "madagascar", "confidence": "high", "reasoning": "Question juridique sur Madagascar"}}
- "Je veux parler à un avocat" → {{"destination": "assistance_request", "confidence": "high", "reasoning": "Demande explicite d'assistance humaine"}}
- "Je n'ai pas compris" → {{"destination": "conversation_repair", "confidence": "high", "reasoning": "Demande de clarification"}}
- "résume notre conversation" → {{"destination": "conversation_summarization", "confidence": "high", "reasoning": "Demande de résumé"}}
- "fais-moi un café" → {{"destination": "out_of_scope", "confidence": "high", "reasoning": "Demande sans rapport avec le droit"}}
- "quelle est la météo" → {{"destination": "out_of_scope", "confidence": "high", "reasoning": "Question météorologique, non juridique"}}
- "température à Douala" → {{"destination": "out_of_scope", "confidence": "high", "reasoning": "Question climatique, hors domaine juridique"}}
- "raconte-moi une blague" → {{"destination": "out_of_scope", "confidence": "high", "reasoning": "Demande de divertissement, non juridique"}}
- "J'ai un problème de divorce" → {{"destination": "unclear", "confidence": "medium", "reasoning": "Question juridique mais pays non précisé"}}
- "Comment créer une entreprise" → {{"destination": "unclear", "confidence": "medium", "reasoning": "Question juridique mais juridiction manquante"}}
**IMPORTANT:**
- **out_of_scope**: Questions SANS aucun aspect juridique (météo, sport, nourriture, etc.)
- **unclear**: Questions AVEC intention juridique MAIS manque de précision sur le pays
- Les présentations, salutations et remerciements sont "greeting_small_talk"
- Seules les questions JURIDIQUES avec pays identifié vont vers "benin" ou "madagascar"
**FORMAT DE RÉPONSE:**
Répondez UNIQUEMENT au format JSON valide:
{{
"destination": "benin|madagascar|assistance_request|greeting_small_talk|conversation_repair|conversation_summarization|unclear",
"confidence": "high|medium|low",
"reasoning": "explication brève et claire"
}}
**RÉPONSE:**
"""
def _parse_routing_response(self, response_text: str) -> RoutingResult:
"""Parse LLM routing response"""
try:
# Extract JSON from response
json_match = re.search(r'\{.*\}', response_text, re.DOTALL)
if not json_match:
raise ValueError("No JSON found in response")
result = json.loads(json_match.group())
# Validate required fields
destination = result.get("destination", "unclear")
confidence = result.get("confidence", "low")
reasoning = result.get("reasoning", "No reasoning provided")
# Map destination to RoutingResult country field
valid_destinations = [
"benin", "madagascar", "unclear", "greeting_small_talk",
"conversation_repair", "assistance_request", "conversation_summarization",
"out_of_scope"
]
if destination not in valid_destinations:
logger.warning(f"Invalid destination from LLM: {destination}, defaulting to unclear")
destination = "unclear"
confidence = "low"
reasoning = f"Destination invalide: {destination}"
return RoutingResult(
country=destination,
confidence=confidence,
method="llm_routing",
explanation=reasoning
)
except Exception as e:
logger.error(f"Error parsing routing response: {e}")
logger.error(f"Raw response: {response_text}")
return RoutingResult(
country="unclear",
confidence="low",
method="parse_error",
explanation=f"Parse error: {str(e)}"
)
def _build_conversation_context(self, conversation_history: List[Dict]) -> str:
"""Build conversation context"""
if not conversation_history:
return "Aucun contexte de conversation"
# Get last 6 messages for context
recent_messages = conversation_history[-6:]
context_lines = []
for msg in recent_messages:
role = "Utilisateur" if msg.get("role") in ["user", "human"] else "Assistant"
content = msg.get("content", "")
context_lines.append(f"{role}: {content}")
return "\n".join(context_lines)
async def health_check(self) -> Dict[str, Any]:
"""Router health check"""
try:
# Test with a simple query
test_result = await self.route_query("test", [])
return {
"status": "healthy",
"llm_responding": True,
"last_test_result": test_result.model_dump()
}
except Exception as e:
return {
"status": "unhealthy",
"llm_responding": False,
"error": str(e)
} |