File size: 9,347 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
# [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."