File size: 9,571 Bytes
e982206
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""
Agente de processamento de contexto inicial para sugestão de queries SQL
"""
import logging
import asyncio
from typing import Optional, Dict, Any
from langchain_openai import ChatOpenAI
from langchain_anthropic import ChatAnthropic
from langchain_google_genai import ChatGoogleGenerativeAI
from langchain_community.llms import HuggingFaceEndpoint
from langchain.schema import HumanMessage

from utils.config import (
    TEMPERATURE,
    AVAILABLE_MODELS,
    OPENAI_MODELS,
    ANTHROPIC_MODELS,
    GOOGLE_MODELS,
    REFINEMENT_MODELS
)


class ProcessingAgentManager:
    """
    Gerenciador do agente de processamento de contexto inicial
    """
    
    def __init__(self, model_name: str = "gpt-4o-mini"):
        self.model_name = model_name
        self.llm = None
        self._initialize_llm()
    
    def _initialize_llm(self):
        """Inicializa o modelo LLM baseado no nome fornecido"""
        try:
            # Obtém o ID real do modelo
            model_id = AVAILABLE_MODELS.get(self.model_name, self.model_name)
            
            # Verifica se é modelo de refinamento
            if model_id not in AVAILABLE_MODELS.values():
                model_id = REFINEMENT_MODELS.get(self.model_name, model_id)
            
            # Cria o modelo LLM baseado no provedor
            if model_id in OPENAI_MODELS:
                # Configurações específicas para modelos OpenAI
                if model_id == "o3-mini":
                    # o3-mini não suporta temperature
                    self.llm = ChatOpenAI(model=model_id)
                else:
                    # GPT-4o e GPT-4o-mini suportam temperature
                    self.llm = ChatOpenAI(model=model_id, temperature=TEMPERATURE)
                    
            elif model_id in ANTHROPIC_MODELS:
                # Claude com tool-calling e configurações para rate limiting
                self.llm = ChatAnthropic(
                    model=model_id,
                    temperature=TEMPERATURE,
                    max_tokens=4096,
                    max_retries=2,
                    timeout=60.0
                )

            elif model_id in GOOGLE_MODELS:
                # Gemini com configurações otimizadas
                self.llm = ChatGoogleGenerativeAI(
                    model=model_id,
                    temperature=TEMPERATURE,
                    max_tokens=4096,
                    max_retries=2,
                    timeout=60.0
                )

            else:
                # Modelos HuggingFace (refinement models)
                self.llm = HuggingFaceEndpoint(
                    endpoint_url=f"https://api-inference.huggingface.co/models/{model_id}",
                    temperature=TEMPERATURE,
                    max_new_tokens=1024,
                    timeout=120
                )
                
            logging.info(f"Processing Agent inicializado com modelo {model_id}")
            
        except Exception as e:
            logging.error(f"Erro ao inicializar Processing Agent: {e}")
            # Fallback para GPT-4o-mini
            self.llm = ChatOpenAI(model="gpt-4o-mini", temperature=TEMPERATURE)
            logging.warning("Usando GPT-4o-mini como fallback")
    
    def recreate_llm(self, new_model: str):
        """
        Recria o LLM com novo modelo

        Args:
            new_model: Nome do novo modelo
        """
        old_model = self.model_name
        self.model_name = new_model
        self._initialize_llm()
        logging.info(f"[PROCESSING] Modelo alterado de '{old_model}' para '{new_model}'")
    
    async def process_context(self, context_prompt: str) -> Dict[str, Any]:
        """
        Processa o contexto inicial e retorna sugestão de query
        
        Args:
            context_prompt: Prompt com contexto e pergunta do usuário
            
        Returns:
            Resultado do processamento com pergunta e sugestão de query
        """
        try:
            logging.info(f"[PROCESSING] ===== INICIANDO PROCESSING AGENT =====")
            logging.info(f"[PROCESSING] Modelo utilizado: {self.model_name}")
            logging.info(f"[PROCESSING] Tamanho do contexto: {len(context_prompt)} caracteres")

            # Executa o processamento
            if hasattr(self.llm, 'ainvoke'):
                # Para modelos que suportam async
                logging.info(f"[PROCESSING] Executando chamada assíncrona para {self.model_name}")
                response = await self.llm.ainvoke([HumanMessage(content=context_prompt)])
                output = response.content
            else:
                # Para modelos síncronos, executa em thread
                logging.info(f"[PROCESSING] Executando chamada síncrona para {self.model_name}")
                response = await asyncio.get_event_loop().run_in_executor(
                    None,
                    lambda: self.llm.invoke([HumanMessage(content=context_prompt)])
                )
                output = response.content if hasattr(response, 'content') else str(response)

            logging.info(f"[PROCESSING] Resposta recebida do modelo ({len(output)} caracteres)")

            # Processa a resposta
            processed_result = self._parse_processing_response(output)

            result = {
                "success": True,
                "output": output,
                "processed_question": processed_result.get("question", ""),
                "suggested_query": processed_result.get("query", ""),
                "query_observations": processed_result.get("observations", ""),
                "model_used": self.model_name
            }

            # Log simples do resultado
            if result['suggested_query']:
                logging.info(f"[PROCESSING] ✅ Query SQL extraída com sucesso")
            else:
                logging.warning(f"[PROCESSING] ❌ Nenhuma query SQL foi extraída")

            logging.info(f"[PROCESSING] ===== PROCESSING AGENT CONCLUÍDO =====")
            return result
            
        except Exception as e:
            error_msg = f"Erro no Processing Agent: {e}"
            logging.error(error_msg)
            
            return {
                "success": False,
                "output": error_msg,
                "processed_question": "",
                "suggested_query": "",
                "model_used": self.model_name
            }
    
    def _parse_processing_response(self, response: str) -> Dict[str, str]:
        """
        Extrai query SQL e observações da resposta

        Args:
            response: Resposta do modelo

        Returns:
            Dicionário com query e observações extraídas
        """
        try:
            import re

            query = ""
            observations = ""

            # Primeiro, tenta extrair observações pelo formato esperado
            obs_match = re.search(r'Observações:\s*(.*?)(?:\n|$)', response, re.IGNORECASE)
            if obs_match:
                observations = obs_match.group(1).strip()

            # Agora extrai a query SQL - prioriza blocos de código SQL
            sql_patterns = [
                # Padrão principal: ```sql ... ```
                r'```sql\s*(.*?)\s*```',
                # Padrão alternativo: ``` ... ``` (assumindo que é SQL)
                r'```\s*(WITH.*?)\s*```',
                r'```\s*(SELECT.*?)\s*```',
                # Padrões sem backticks
                r'Opção de querySQL:\s*(WITH.*?)(?=Observações:|$)',
                r'Opção de querySQL:\s*(SELECT.*?)(?=Observações:|$)',
                # Padrões mais gerais
                r'(WITH\s+.*?;)',
                r'(SELECT\s+.*?;)'
            ]

            for pattern in sql_patterns:
                match = re.search(pattern, response, re.DOTALL | re.IGNORECASE)
                if match:
                    query = match.group(1).strip()
                    break

            # Limpa a query final se encontrada
            if query:
                # Remove apenas backticks e mantém formatação original
                query = query.replace('```', '').replace('sql', '').strip()

                # Remove quebras de linha no início e fim, mas mantém formatação interna
                query = query.strip('\n').strip()

            # Se ainda não encontrou observações, tenta padrão mais flexível
            if not observations:
                obs_patterns = [
                    r'Observações:\s*(.*)',
                    r'Observacoes:\s*(.*)',
                ]
                for pattern in obs_patterns:
                    match = re.search(pattern, response, re.IGNORECASE | re.DOTALL)
                    if match:
                        observations = match.group(1).strip()
                        break

            return {
                "question": "",  # Não precisamos da pergunta processada
                "query": query,
                "observations": observations
            }

        except Exception as e:
            logging.error(f"Erro ao extrair query e observações: {e}")
            return {
                "question": "",
                "query": "",
                "observations": ""
            }


def get_default_processing_agent(model_name: str = "gpt-4o-mini") -> ProcessingAgentManager:
    """
    Cria um Processing Agent com configurações padrão
    
    Args:
        model_name: Nome do modelo a usar
        
    Returns:
        ProcessingAgentManager configurado
    """
    return ProcessingAgentManager(model_name)