File size: 13,786 Bytes
9472cf1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
703e66d
9472cf1
 
 
 
 
 
 
703e66d
9472cf1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
703e66d
9472cf1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
# type: ignore
"""

================================================================================

IMPROVED CONTEXT HANDLER - Melhor gerenciamento de contexto para Akira

================================================================================

IMPORTANTE: Este módulo NÃO modifica context_builder.py ou contexto.py!

Ele adiciona uma camada INTELIGENTE de análise de contexto para perguntas curtas.



Função: Resolver o problema de perguntas curtas ("Oq é isso?") perdendo contexto

Preserva: Toda a arquitetura e lógica existente do sistema de contexto

================================================================================

"""

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

try:
    from . import config
except ImportError:
    import modules.config as config


@dataclass
class ContextWeights:
    """Pesos calculados para diferentes tipos de contexto."""
    reply_context: float = 0.0
    quoted_analysis: float = 0.0
    short_term_memory: float = 1.0
    vector_memory: float = 0.7
    
    def to_dict(self) -> Dict[str, float]:
        """Converte para dicionário."""
        return {
            "reply_context": self.reply_context,
            "quoted_analysis": self.quoted_analysis,
            "short_term_memory": self.short_term_memory,
            "vector_memory": self.vector_memory,
        }


@dataclass
class QuestionAnalysis:
    """Análise de uma pergunta."""
    is_short:bool = False  # <= 5 palavras
    is_very_short: bool = False  # <= 2 palavras
    has_pronoun: bool = False  # tem "isso", "aquilo", "ele", etc
    has_reply: bool = False
    needs_context: bool = False  # precisa de contexto extra
    question_type: str = "general"  # "what", "how", "where", "why", "general"


class ImprovedContextHandler:
    """

    Gerenciador inteligente de contexto para perguntas curtas.

    

    IMPORTANTE:

    - NÃO substitui o context_builder.py existente

    - Funciona como HELPER para calcular pesos de contexto

    - AUMENTA contexto para perguntas curtas com reply (contrário da lógica antiga)

    """
    
    def __init__(self):
        # Pronomes que indicam necessidade de contexto
        self.context_pronouns = {
            "isso", "aquilo", "este", "esse", "aquele",
            "ele", "ela", "eles", "elas",
            "la", "lo", "las", "los",  # "a la", "o lo"
        }
        
        # Palavras interrogativas
        self.question_words = {
            "what": ["oq", "o que", "oque", "que é"],
            "how": ["como"],
            "where": ["onde", "aonde"],
            "when": ["quando", "que horas"],
            "why": ["porque", "porquê", "por que", "pq"],
            "who": ["quem"],
        }
        
        # Limites de palavras
        self.very_short_threshold = 2  # "Oq é?"
        self.short_threshold = 5  # "Como funciona isso?"
    
    def analyze_question(

        self,

        message: str,

        reply_metadata: Optional[Dict[str, Any]] = None

    ) -> QuestionAnalysis:
        """

        Analisa uma mensagem para determinar necessidade de contexto.

        

        Args:

            message: Mensagem do usuário

            reply_metadata: Metadados de reply (se for reply)

            

        Returns:

            QuestionAnalysis com detalhes da análise

        """
        message_lower = message.lower().strip()
        words = message_lower.split()
        word_count = len(words)
        
        analysis = QuestionAnalysis()
        
        # Classifica tamanho
        analysis.is_very_short = word_count <= self.very_short_threshold
        analysis.is_short = word_count <= self.short_threshold
        
        # Detecta pronomes contextuais
        analysis.has_pronoun = any(
            pronoun in message_lower 
            for pronoun in self.context_pronouns
        )
        
        # Verifica se tem reply
        if reply_metadata:
            analysis.has_reply = reply_metadata.get("is_reply", False)
        
        # Detecta tipo de pergunta
        for q_type, patterns in self.question_words.items():
            if any(pattern in message_lower for pattern in patterns):
                analysis.question_type = q_type
                break
        
        # Determina se precisa de contexto extra
        analysis.needs_context = (
            analysis.is_short and 
            (analysis.has_pronoun or analysis.has_reply)
        )
        
        return analysis
    
    def calculate_context_weights(

        self,

        message: str,

        reply_metadata: Optional[Dict[str, Any]] = None

    ) -> ContextWeights:
        """

        Calcula pesos de contexto de forma inteligente.

        

        LÓGICA INVERTIDA da original:

        - Perguntas curtas COM reply = MAIS contexto de reply

        - Perguntas normais = balanço

        - Sem reply = contexto geral

        

        Args:

            message: Mensagem do usuário

            reply_metadata: Metadados de reply

            

        Returns:

            ContextWeights com pesos calculados

        """
        analysis = self.analyze_question(message, reply_metadata)
        weights = ContextWeights()
        
        # CASO 1: Pergunta MUITO curta COM reply
        # Exemplo: "Oq é isso?" (reply a mensagem sobre Radiohead)
        if analysis.is_very_short and analysis.has_reply:
            weights.reply_context = 1.0  # ✅ MÁXIMO para reply
            weights.quoted_analysis = 0.95  # Analisa profundamente a citação
            weights.short_term_memory = 0.8  # ✅ MANTÉM texto curto + contexto
            weights.vector_memory = 0.3  # Fatos gerais baixo
        
        # CASO 2: Pergunta curta COM reply
        # Exemplo: "Como funciona isso?" (reply a explicação técnica)
        elif analysis.is_short and analysis.has_reply:
            weights.reply_context = 0.9  # Alto para reply
            weights.quoted_analysis = 0.85
            weights.short_term_memory = 0.85  # ✅ MANTÉM texto curto no contexto
            weights.vector_memory = 0.4
        
        # CASO 3: Pergunta curta COM pronome mas SEM reply
        # Exemplo: "Oq é isso?" (sem reply - contexto ambíguo)
        elif analysis.is_short and analysis.has_pronoun:
            weights.reply_context = 0.0  # Sem reply
            weights.quoted_analysis = 0.0
            weights.short_term_memory = 1.0  # Usa histórico recente completo
            weights.vector_memory = 0.8  # Busca memória de fatos
        
        # CASO 4: Pergunta normal COM reply
        # Exemplo: "Você pode explicar melhor esse conceito?" (reply a explicação)
        elif analysis.has_reply:
            weights.reply_context = 0.8
            weights.quoted_analysis = 0.7
            weights.short_term_memory = 0.8
            weights.vector_memory = 0.5
        
        # CASO 5: Pergunta normal SEM reply
        # Exemplo: "Como funciona inteligência artificial?"
        else:
            weights.reply_context = 0.0
            weights.quoted_analysis = 0.0
            weights.short_term_memory = 1.0
            weights.vector_memory = 0.7
        
        return weights
    
    def extract_quoted_content_deep(

        self,

        reply_metadata: Dict[str, Any]

    ) -> str:
        """

        Extrai conteúdo citado de forma profunda.

        Prioriza campos mais completos.

        

        Args:

            reply_metadata: Metadados do reply

            

        Returns:

            Conteúdo completo citado

        """
        # Ordem de prioridade (do mais completo para o menos)
        priority_fields = [
            "mensagem_citada",
            "full_message",
            "quoted_text_original",
            "quoted_text",
            "reply_content",
            "context_hint",
        ]
        
        for field in priority_fields:
            if field in reply_metadata and reply_metadata[field]:
                content = str(reply_metadata[field]).strip()
                if len(content) > 5:  # Ignora conteúdos muito curtos
                    return content
        
        # Fallback: tenta extrair de qualquer campo que pareça mensagem
        for key, value in reply_metadata.items():
            if isinstance(value, str) and len(value) > 10:
                # Verifica se tem palavras comuns de mensagem
                if any(word in value.lower() for word in ["eu", "você", "tu", "ele"]):
                    return value.strip()
        
        return ""
    
    def analyze_quoted_content(

        self,

        quoted_content: str,

        current_message: str

    ) -> Dict[str, Any]:
        """

        Analisa conteúdo citado para entender o contexto.

        

        Args:

            quoted_content: Conteúdo da mensagem citada

            current_message: Mensagem atual do usuário

            

        Returns:

            Análise do conteúdo citado

        """
        if not quoted_content:
            return {"empty": True}
        
        quoted_lower = quoted_content.lower()
        current_lower = current_message.lower()
        
        # Detecta tipo de conteúdo
        content_type = "general"
        if any(w in quoted_lower for w in ["?", "qual", "quando", "onde", "como", "por que"]):
            content_type = "question"
        elif any(w in quoted_lower for w in ["eu", "mim", "meu", "minha"]):
            content_type = "personal"
        elif any(w in quoted_lower for w in ["akira", "bot", "você", "vc"]):
            content_type = "about_bot"
        
        # Extrai keywords principais
        keywords = self._extract_keywords(quoted_content)
        
        # Detecta tom
        tone = "neutral"
        if any(w in quoted_lower for w in ["kkk", "haha", "😂", "🤣"]):
            tone = "humorous"
        elif any(w in quoted_lower for w in ["!!!", "???", "nossa", "eita"]):
            tone = "excited"
        
        # Detecta se há informação técnica/específica
        has_specific_info = any(
            word in quoted_lower 
            for word in ["Estudo", "Academica", "Programação", "Ciência", "política", "País"]
        )
        
        return {
            "content_type": content_type,
            "keywords": keywords,
            "tone": tone,
            "length": len(quoted_content),
            "has_question": "?" in quoted_content,
            "has_specific_info": has_specific_info,
        }
    
    def _extract_keywords(self, text: str, max_keywords: int = 5) -> List[str]:
        """Extrai keywords principais do texto."""
        # Remove stopwords comuns
        stopwords = {
            "o", "a", "de", "da", "do", "em", "para", "com", "por",
            "que", "é", "um", "uma", "os", "as", "dos", "das",
            "e", "ou", "mas", "se", "não", "sim",
        }
        
        words = re.findall(r'\w+', text.lower())
        keywords = [w for w in words if w not in stopwords and len(w) > 3]
        
        # Retorna os primeiros N
        return keywords[:max_keywords]


# ============================================================
# FUNÇÕES DE CONVENIÊNCIA
# ============================================================

_handler_instance: Optional[ImprovedContextHandler] = None


def get_context_handler() -> ImprovedContextHandler:
    """Retorna instância singleton do handler."""
    global _handler_instance
    if _handler_instance is None:
        _handler_instance = ImprovedContextHandler()
    return _handler_instance


def calculate_smart_context_weights(

    message: str,

    reply_metadata: Optional[Dict[str, Any]] = None

) -> Dict[str, float]:
    """

    Função helper para calcular pesos de contexto inteligentemente.

    

    Args:

        message: Mensagem do usuário

        reply_metadata: Metadados de reply

        

    Returns:

        Dict com pesos de contexto

    """
    handler = get_context_handler()
    weights = handler.calculate_context_weights(message, reply_metadata)
    return weights.to_dict()


# ============================================================
# EXEMPLO DE USO
# ============================================================

if __name__ == "__main__":
    # Teste básico
    handler = ImprovedContextHandler()
    
    test_cases = [
        # (mensagem, tem_reply, descrição)
        ("Oq é isso?", True, "Pergunta muito curta com reply"),
        ("Como funciona isso?", True, "Pergunta curta com reply"),
        ("Oq é isso?", False, "Pergunta curta SEM reply (ambígua)"),
        ("Você pode explicar melhor esse conceito?", True, "Pergunta normal com reply"),
        ("Como funciona inteligência artificial?", False, "Pergunta normal sem reply"),
    ]
    
    print("=== TESTE DE PESOS DE CONTEXTO ===\n")
    
    for message, has_reply, description in test_cases:
        print(f"Caso: {description}")
        print(f"Mensagem: \"{message}\"")
        print(f"Tem reply: {has_reply}")
        
        reply_meta = {"is_reply": has_reply} if has_reply else None
        weights = handler.calculate_context_weights(message, reply_meta)
        
        print(f"Pesos calculados:")
        print(f"  - Reply context: {weights.reply_context:.2f}")
        print(f"  - Quoted analysis: {weights.quoted_analysis:.2f}")
        print(f"  - Short-term memory: {weights.short_term_memory:.2f}")
        print(f"  - Vector memory: {weights.vector_memory:.2f}")
        print()