MultiCountryRAG / core /nodes /response_nodes.py
SAAHMATHWORKS
dockerfile 3
478b91f
# [file name]: core/nodes/response_nodes.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 logging
import time
from datetime import datetime
from typing import Dict, Any
from langchain_core.runnables import RunnableConfig
from models.state_models import MultiCountryLegalState
from utils.helpers import dict_to_message_obj, message_obj_to_dict
logger = logging.getLogger(__name__)
class ResponseNodes:
def __init__(self, llm):
self.llm = llm
async def response_generation_node(self, state: MultiCountryLegalState, config: RunnableConfig) -> Dict[str, Any]:
"""Generate appropriate responses based on current state"""
assistance_step = state.assistance_step
# Handle assistance workflow responses
if assistance_step == "collecting_email":
response_content = """
Je vois que vous souhaitez parler à un avocat. Pour vous aider, j'ai besoin de votre adresse email pour que notre équipe puisse vous contacter.
📧 **Veuillez me fournir votre adresse email :**
"""
return {
"messages": [{
"role": "assistant",
"content": response_content,
"meta": {"assistance_step": "collecting_email"}
}],
"supplemental_message": "" # Clear any previous supplemental messages
}
elif assistance_step == "collecting_description":
response_content = f"""
Merci ! Votre email ({state.user_email}) a été enregistré.
📝 **Veuillez maintenant décrire brièvement votre situation :**
- Quelle est votre question juridique ?
- De quel pays s'agit-il ?
- Quel type d'assistance recherchez-vous ?
Cette description aidera notre équipe à mieux vous orienter.
"""
return {
"messages": [{
"role": "assistant",
"content": response_content,
"meta": {"assistance_step": "collecting_description"}
}],
"supplemental_message": "" # Clear any previous supplemental messages
}
elif assistance_step == "confirming_send":
response_content = f"""
📋 **RÉCAPITULATIF DE VOTRE DEMANDE :**
📧 **Email :** {state.user_email}
📝 **Description :** {state.assistance_description}
✅ **Confirmez-vous l'envoi de cette demande à notre équipe juridique ?**
Répondez par :
- **"oui"** pour confirmer et envoyer
- **"non"** pour annuler et modifier
"""
return {
"messages": [{
"role": "assistant",
"content": response_content,
"meta": {"assistance_step": "confirming_send"}
}],
"supplemental_message": "" # Clear any previous supplemental messages
}
else:
# Default LLM response for non-assistance flows
return await self._generate_llm_response(state, config)
async def _generate_llm_response(self, state: MultiCountryLegalState, config: RunnableConfig) -> Dict[str, Any]:
"""Generate LLM-based response for normal conversation flows"""
try:
# Include supplemental message in the response if present
supplemental_message = state.supplemental_message or ""
# Synthesize response using LLM
response_content = await self._synthesize_response(state, supplemental_message)
return {
"messages": [{
"role": "assistant",
"content": response_content,
"meta": {
"timestamp": datetime.now().isoformat(),
"generated_by": "llm"
}
}],
"supplemental_message": "" # Clear after using
}
except Exception as e:
logger.error(f"Error generating LLM response: {str(e)}")
return {
"messages": [{
"role": "assistant",
"content": self._create_error_message(str(e)),
"meta": {"is_error": True}
}],
"supplemental_message": f"Erreur: {str(e)}"
}
async def _synthesize_response(self, state: MultiCountryLegalState, supplemental_message: str = "") -> str:
"""Synthesize final response based on graph execution"""
s = state.model_dump()
# Build context-aware system prompt
system_prompt = self._build_system_prompt(state, supplemental_message)
conversation_messages = self._build_conversation_messages(system_prompt, s.get("messages", []))
# Always use LLM to generate final response
logger.info("🧠 Generating final response with LLM")
ai_resp = await self.llm.ainvoke(conversation_messages)
return ai_resp.content if hasattr(ai_resp, 'content') else str(ai_resp)
def _build_system_prompt(self, state: MultiCountryLegalState, supplemental_message: str = "") -> str:
"""Build context-aware system prompt"""
s = state.model_dump()
base_prompt = """Vous êtes un assistant juridique expert spécialisé dans le droit du Bénin et de Madagascar.
TÂCHE: Fournir une réponse claire, précise et utile à l'utilisateur.
"""
# Add supplemental message if available
if supplemental_message:
base_prompt += f"\nMESSAGE IMPORTANT: {supplemental_message}\n"
# Add legal context if available
country_name = s.get("legal_context", {}).get("jurisdiction", "Unknown")
if country_name != "Unknown":
base_prompt += f"\nCONTEXTE JURIDIQUE: Vous répondez dans le cadre du droit {country_name}.\n"
# Add search results if available
search_results = s.get("search_results", "")
if search_results and "RECHERCHE JURIDIQUE" in search_results:
base_prompt += f"\nINFORMATIONS JURIDIQUES DISPONIBLES:\n{search_results}\n"
base_prompt += """
INSTRUCTIONS POUR LA RÉPONSE JURIDIQUE:
- Basez-vous sur les informations juridiques disponibles
- Citez les articles de loi pertinents si possible
- Soyez précis mais accessible aux non-juristes
- Indiquez si certaines informations manquent
"""
else:
base_prompt += """
INSTRUCTIONS GÉNÉRALES:
- Répondez de manière naturelle et utile
- Adaptez votre ton au contexte de la conversation
- Soyez empathique et professionnel
"""
# Add assistance context if relevant
if s.get("assistance_requested"):
base_prompt += "\nCONTEXTE ASSISTANCE: L'utilisateur a demandé à parler à un avocat.\n"
if s.get("approval_status") == "rejected":
base_prompt += "\nCONTEXTE: La demande d'assistance a été rejetée. Expliquez poliment et proposez des alternatives.\n"
elif s.get("approval_status") == "approved":
base_prompt += "\nCONTEXTE: La demande d'assistance a été approuvée. Confirmez et donnez les prochaines étapes.\n"
return base_prompt
def _build_conversation_messages(self, system_prompt: str, messages: list) -> list:
"""Build conversation messages for LLM"""
from langchain_core.messages import SystemMessage
conversation_messages = [SystemMessage(content=system_prompt)]
# Include recent conversation history (last 6 messages)
recent_messages = messages[-6:] if len(messages) > 6 else messages
# Convert to message objects
conversation_messages.extend(dict_to_message_obj(m) for m in recent_messages)
return conversation_messages
async def human_approval_node(self, state: MultiCountryLegalState, config: RunnableConfig) -> Dict[str, Any]:
"""Handle human approval interrupts"""
logger.info("👨‍⚖️ Human approval node - triggering interrupt")
return {
"approval_status": "pending",
"messages": [{
"role": "assistant",
"content": "⏳ Votre demande d'assistance nécessite une approbation manuelle. Un modérateur va examiner votre demande.",
"meta": {"requires_approval": True}
}],
"supplemental_message": ""
}
async def process_assistance_node(self, state: MultiCountryLegalState, config: RunnableConfig) -> Dict[str, Any]:
"""Process assistance after approval - let LLM generate final message"""
logger.info("📧 Processing assistance request")
return {
"email_status": "sent",
"approval_status": "approved",
"assistance_step": "completed",
"messages": [], # Empty messages so LLM generates the final response
"supplemental_message": "Votre demande d'assistance a été traitée avec succès."
}
def _create_error_message(self, error: str) -> str:
"""Create error message"""
return f"❌ Désolé, une erreur s'est produite: {error}\n\nVeuillez réessayer ou reformuler votre demande."