File size: 16,603 Bytes
9f203f4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f80e242
9f203f4
 
 
 
 
 
 
 
 
 
 
f80e242
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9f203f4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f80e242
 
9f203f4
 
 
 
 
f80e242
9f203f4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4386026
 
 
9f203f4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4386026
 
 
9f203f4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4386026
 
 
9f203f4
 
 
 
 
 
f80e242
 
 
 
 
 
 
 
 
 
 
 
 
9f203f4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6f12b05
 
 
 
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
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
# ────────────────────────────── memo/retrieval.py ──────────────────────────────
"""
Context Retrieval and Enhancement

Handles intelligent context retrieval, enhancement decisions,
and input optimization for natural conversation flow.
"""

import re
from typing import List, Dict, Any, Tuple, Optional

from utils.logger import get_logger
from utils.rag.embeddings import EmbeddingClient
from memo.context import cosine_similarity, semantic_context

logger = get_logger("RETRIEVAL_MANAGER", __name__)

class RetrievalManager:
    """
    Manages context retrieval and enhancement for conversations.
    """
    
    def __init__(self, memory_system, embedder: EmbeddingClient):
        self.memory_system = memory_system
        self.embedder = embedder
    
    async def get_smart_context(self, user_id: str, question: str, 
                              nvidia_rotator=None, project_id: Optional[str] = None,
                              conversation_mode: str = "chat") -> Tuple[str, str, Dict[str, Any]]:
        """
        Get intelligent context for conversation with enhanced memory planning.
        
        Args:
            user_id: User identifier
            question: Current question/instruction
            nvidia_rotator: NVIDIA API rotator for AI enhancement
            project_id: Project context
            conversation_mode: "chat" or "report"
            
        Returns:
            Tuple of (recent_context, semantic_context, metadata)
        """
        try:
            # Use the new memory planning system from core memory
            return await self.memory_system.get_smart_context(
                user_id, question, nvidia_rotator, project_id, conversation_mode
            )
            
        except Exception as e:
            logger.error(f"[RETRIEVAL_MANAGER] Smart context failed: {e}")
            # Fallback to legacy approach
            try:
                return await self._get_legacy_smart_context(
                    user_id, question, nvidia_rotator, project_id, conversation_mode
                )
            except Exception as fallback_error:
                logger.error(f"[RETRIEVAL_MANAGER] Legacy fallback also failed: {fallback_error}")
                return "", "", {"error": str(e)}
    
    async def _get_legacy_smart_context(self, user_id: str, question: str, 
                                      nvidia_rotator=None, project_id: Optional[str] = None,
                                      conversation_mode: str = "chat") -> Tuple[str, str, Dict[str, Any]]:
        """Legacy smart context retrieval as fallback"""
        try:
            # Check for conversation session continuity
            from memo.sessions import get_session_manager
            session_manager = get_session_manager()
            session_info = session_manager.get_or_create_session(user_id, question, conversation_mode)
            
            # Get enhanced context based on conversation state
            if session_info["is_continuation"]:
                recent_context, semantic_context = await self._get_continuation_context(
                    user_id, question, session_info, nvidia_rotator, project_id
                )
            else:
                recent_context, semantic_context = await self._get_fresh_context(
                    user_id, question, nvidia_rotator, project_id
                )
            
            # Enhance question/instructions with context if beneficial
            enhanced_input, context_used = await self._enhance_input_with_context(
                question, recent_context, semantic_context, nvidia_rotator, conversation_mode
            )
            
            # Update session tracking
            session_manager.update_session(user_id, question, enhanced_input, context_used)
            
            # Prepare metadata
            metadata = {
                "session_id": session_info["session_id"],
                "is_continuation": session_info["is_continuation"],
                "context_enhanced": context_used,
                "enhanced_input": enhanced_input,
                "conversation_depth": session_info["depth"],
                "last_activity": session_info["last_activity"],
                "legacy_mode": True
            }
            
            return recent_context, semantic_context, metadata
            
        except Exception as e:
            logger.error(f"[RETRIEVAL_MANAGER] Legacy smart context failed: {e}")
            return "", "", {"error": str(e)}
    
    async def _get_continuation_context(self, user_id: str, question: str, 
                                      session_info: Dict[str, Any], nvidia_rotator, 
                                      project_id: Optional[str]) -> Tuple[str, str]:
        """Get context for conversation continuation"""
        try:
            # Use enhanced context retrieval with focus on recent conversation
            if self.memory_system.is_enhanced_available():
                recent_context, semantic_context = await self.memory_system.get_conversation_context(
                    user_id, question, project_id
                )
            else:
                # Fallback to legacy with enhanced selection
                recent_memories = self.memory_system.recent(user_id, 5)  # More recent for continuation
                rest_memories = self.memory_system.rest(user_id, 5)
                
                recent_context = ""
                if recent_memories and nvidia_rotator:
                    try:
                        from memo.nvidia import related_recent_context
                        recent_context = await related_recent_context(question, recent_memories, nvidia_rotator)
                    except Exception as e:
                        logger.warning(f"[RETRIEVAL_MANAGER] NVIDIA recent context failed: {e}")
                        recent_context = await semantic_context(question, recent_memories, self.embedder, 3)
                
                semantic_context = ""
                if rest_memories:
                    semantic_context = await semantic_context(question, rest_memories, self.embedder, 5)
            
            return recent_context, semantic_context
            
        except Exception as e:
            logger.error(f"[RETRIEVAL_MANAGER] Continuation context failed: {e}")
            return "", ""
    
    async def _get_fresh_context(self, user_id: str, question: str, 
                               nvidia_rotator, project_id: Optional[str]) -> Tuple[str, str]:
        """Get context for fresh conversation or context switch"""
        try:
            # Use standard context retrieval
            if self.memory_system.is_enhanced_available():
                recent_context, semantic_context = await self.memory_system.get_conversation_context(
                    user_id, question, project_id
                )
            else:
                # Legacy fallback
                recent_memories = self.memory_system.recent(user_id, 3)
                rest_memories = self.memory_system.rest(user_id, 3)
                
                recent_context = await semantic_context(question, recent_memories, self.embedder, 2)
                semantic_context = await semantic_context(question, rest_memories, self.embedder, 3)
            
            return recent_context, semantic_context
            
        except Exception as e:
            logger.error(f"[RETRIEVAL_MANAGER] Fresh context failed: {e}")
            return "", ""
    
    async def _enhance_input_with_context(self, original_input: str, recent_context: str, 
                                        semantic_context: str, nvidia_rotator, 
                                        conversation_mode: str) -> Tuple[str, bool]:
        """Enhance input with relevant context if beneficial"""
        try:
            # Determine if enhancement would be beneficial
            should_enhance = await self._should_enhance_input(
                original_input, recent_context, semantic_context, nvidia_rotator
            )
            
            if not should_enhance:
                return original_input, False
            
            # Enhance based on conversation mode
            if conversation_mode == "chat":
                return await self._enhance_question(original_input, recent_context, semantic_context, nvidia_rotator)
            else:  # report mode
                return await self._enhance_instructions(original_input, recent_context, semantic_context, nvidia_rotator)
                
        except Exception as e:
            logger.warning(f"[RETRIEVAL_MANAGER] Input enhancement failed: {e}")
            return original_input, False
    
    async def _should_enhance_input(self, original_input: str, recent_context: str, 
                                  semantic_context: str, nvidia_rotator) -> bool:
        """Determine if input should be enhanced with context"""
        try:
            # Don't enhance if no context available
            if not recent_context and not semantic_context:
                return False
            
            # Don't enhance very specific questions that seem complete
            if len(original_input.split()) > 20:  # Long, detailed questions
                return False
            
            # Don't enhance if input already contains context indicators
            context_indicators = ["based on", "from our", "as we discussed", "following up", "regarding"]
            if any(indicator in original_input.lower() for indicator in context_indicators):
                return False
            
            # Use NVIDIA to determine if enhancement would be helpful
            if nvidia_rotator:
                try:
                    from utils.api.router import generate_answer_with_model
                    
                    sys_prompt = """You are an expert at determining if a user's question would benefit from additional context.

Given a user's question and available context, determine if enhancing the question with context would:
1. Make the answer more relevant and helpful
2. Provide better continuity in conversation
3. Not make the question unnecessarily complex

Respond with only "YES" or "NO"."""
                    
                    user_prompt = f"""USER QUESTION: {original_input}

AVAILABLE CONTEXT:
Recent: {recent_context[:200]}...
Semantic: {semantic_context[:200]}...

Should this question be enhanced with context?"""
                    
                    # Use Qwen for better context enhancement reasoning
                    from utils.api.router import qwen_chat_completion
                    response = await qwen_chat_completion(sys_prompt, user_prompt, nvidia_rotator)
                    
                    return "YES" in response.upper()
                    
                except Exception as e:
                    logger.warning(f"[RETRIEVAL_MANAGER] Enhancement decision failed: {e}")
            
            # Fallback: enhance if we have substantial context
            total_context_length = len(recent_context) + len(semantic_context)
            return total_context_length > 100
            
        except Exception as e:
            logger.warning(f"[RETRIEVAL_MANAGER] Enhancement decision failed: {e}")
            return False
    
    async def _enhance_question(self, question: str, recent_context: str, 
                              semantic_context: str, nvidia_rotator) -> Tuple[str, bool]:
        """Enhance question with context"""
        try:
            from utils.api.router import generate_answer_with_model
            
            sys_prompt = """You are an expert at enhancing user questions with relevant conversation context.

Given a user's question and relevant context, create an enhanced question that:
1. Incorporates the context naturally and seamlessly
2. Maintains the user's original intent
3. Provides better context for answering
4. Flows naturally and doesn't sound forced

Return ONLY the enhanced question, no meta-commentary."""
            
            context_text = ""
            if recent_context:
                context_text += f"Recent conversation:\n{recent_context}\n\n"
            if semantic_context:
                context_text += f"Related information:\n{semantic_context}\n\n"
            
            user_prompt = f"""ORIGINAL QUESTION: {question}

RELEVANT CONTEXT:
{context_text}

Create an enhanced version that incorporates this context naturally."""
            
            # Use Qwen for better question enhancement reasoning
            from utils.api.router import qwen_chat_completion
            enhanced_question = await qwen_chat_completion(sys_prompt, user_prompt, nvidia_rotator)
            
            return enhanced_question.strip(), True
            
        except Exception as e:
            logger.warning(f"[RETRIEVAL_MANAGER] Question enhancement failed: {e}")
            return question, False
    
    async def _enhance_instructions(self, instructions: str, recent_context: str, 
                                  semantic_context: str, nvidia_rotator) -> Tuple[str, bool]:
        """Enhance report instructions with context"""
        try:
            from utils.api.router import generate_answer_with_model
            
            sys_prompt = """You are an expert at enhancing report instructions with relevant conversation context.

Given report instructions and relevant context, create enhanced instructions that:
1. Incorporates the context naturally and seamlessly
2. Maintains the user's original intent for the report
3. Provides better context for generating a comprehensive report
4. Flows naturally and doesn't sound forced

Return ONLY the enhanced instructions, no meta-commentary."""
            
            context_text = ""
            if recent_context:
                context_text += f"Recent conversation:\n{recent_context}\n\n"
            if semantic_context:
                context_text += f"Related information:\n{semantic_context}\n\n"
            
            user_prompt = f"""ORIGINAL REPORT INSTRUCTIONS: {instructions}

RELEVANT CONTEXT:
{context_text}

Create an enhanced version that incorporates this context naturally."""
            
            # Use Qwen for better instruction enhancement reasoning
            from utils.api.router import qwen_chat_completion
            enhanced_instructions = await qwen_chat_completion(sys_prompt, user_prompt, nvidia_rotator)
            
            return enhanced_instructions.strip(), True
            
        except Exception as e:
            logger.warning(f"[RETRIEVAL_MANAGER] Instructions enhancement failed: {e}")
            return instructions, False
    
    async def get_enhancement_context(self, user_id: str, question: str, 
                                    nvidia_rotator=None, project_id: Optional[str] = None) -> Tuple[str, str, Dict[str, Any]]:
        """Get context specifically optimized for enhancement requests"""
        try:
            # Use the core memory system's enhancement context method
            return await self.memory_system.get_enhancement_context(
                user_id, question, nvidia_rotator, project_id
            )
            
        except Exception as e:
            logger.error(f"[RETRIEVAL_MANAGER] Enhancement context failed: {e}")
            return "", "", {"error": str(e)}


# ────────────────────────────── Global Instance ──────────────────────────────

_retrieval_manager: Optional[RetrievalManager] = None

def get_retrieval_manager(memory_system=None, embedder: EmbeddingClient = None) -> RetrievalManager:
    """Get the global retrieval manager instance"""
    global _retrieval_manager
    
    if _retrieval_manager is None:
        if not memory_system:
            from memo.core import get_memory_system
            memory_system = get_memory_system()
        if not embedder:
            from utils.rag.embeddings import EmbeddingClient
            embedder = EmbeddingClient()
        
        _retrieval_manager = RetrievalManager(memory_system, embedder)
        logger.info("[RETRIEVAL_MANAGER] Global retrieval manager initialized")
    
    return _retrieval_manager

# def reset_retrieval_manager():
#     """Reset the global retrieval manager (for testing)"""
#     global _retrieval_manager
#     _retrieval_manager = None