wakeupmh commited on
Commit
aaab899
·
1 Parent(s): 269dd87
Files changed (3) hide show
  1. app.py +3 -3
  2. requirements.txt +2 -1
  3. services/model_handler.py +282 -82
app.py CHANGED
@@ -22,7 +22,7 @@ class AutismResearchApp:
22
  Pergunte o que quiser e eu vou analisar os últimos artigos científicos e fornecer uma resposta baseada em evidências.
23
  """)
24
 
25
- def run(self):
26
  """Run the main application loop"""
27
  self._setup_streamlit()
28
 
@@ -49,7 +49,7 @@ class AutismResearchApp:
49
  # Sempre usar o modelo, nunca a resposta padrão
50
  self.model_handler.force_default_response = False
51
 
52
- answer = self.model_handler.generate_answer(query)
53
 
54
  status.write("✨ Resposta gerada! Exibindo resultados...")
55
 
@@ -61,7 +61,7 @@ class AutismResearchApp:
61
 
62
  def main():
63
  app = AutismResearchApp()
64
- app.run()
65
 
66
  if __name__ == "__main__":
67
  main()
 
22
  Pergunte o que quiser e eu vou analisar os últimos artigos científicos e fornecer uma resposta baseada em evidências.
23
  """)
24
 
25
+ async def run(self):
26
  """Run the main application loop"""
27
  self._setup_streamlit()
28
 
 
49
  # Sempre usar o modelo, nunca a resposta padrão
50
  self.model_handler.force_default_response = False
51
 
52
+ answer = await self.model_handler.generate_answer_async(query)
53
 
54
  status.write("✨ Resposta gerada! Exibindo resultados...")
55
 
 
61
 
62
  def main():
63
  app = AutismResearchApp()
64
+ asyncio.run(app.run())
65
 
66
  if __name__ == "__main__":
67
  main()
requirements.txt CHANGED
@@ -9,4 +9,5 @@ agno==1.1.5
9
  pypdf>=3.11.1
10
  watchdog>=2.3.1
11
  sentencepiece>=0.1.99
12
- tenacity>=8.2.2
 
 
9
  pypdf>=3.11.1
10
  watchdog>=2.3.1
11
  sentencepiece>=0.1.99
12
+ tenacity>=8.2.2
13
+ asyncio
services/model_handler.py CHANGED
@@ -9,8 +9,27 @@ from tenacity import retry, stop_after_attempt, wait_exponential
9
  import time
10
  import datetime
11
  import os
 
12
 
13
- MODEL_PATH = "google/flan-t5-large"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
 
15
  # Simple Response class to wrap the model output
16
  class Response:
@@ -56,55 +75,56 @@ class Response:
56
  return self.content if self.content else ""
57
 
58
  def __repr__(self):
59
- return f"Response(content='{self.content}')"
60
 
61
- # Personnalized class for local models
62
  class LocalHuggingFaceModel(Model):
63
- def __init__(self, model, tokenizer, max_length=512):
64
- super().__init__(id="local-huggingface")
65
  self.model = model
66
  self.tokenizer = tokenizer
67
  self.max_length = max_length
 
68
 
69
  async def ainvoke(self, prompt: str, **kwargs) -> str:
70
  """Async invoke method"""
71
  try:
72
- logging.info(f"ainvoke called with prompt: {prompt[:100] if prompt and isinstance(prompt, str) else 'None'}...")
73
  return await self.invoke(prompt, **kwargs)
74
  except Exception as e:
75
- logging.error(f"Error in ainvoke: {str(e)}")
76
  return Response(f"Error in ainvoke: {str(e)}")
77
 
78
  async def ainvoke_stream(self, prompt: str, **kwargs):
79
  """Async streaming invoke method"""
80
  try:
81
- logging.info(f"ainvoke_stream called with prompt: {prompt[:100] if prompt and isinstance(prompt, str) else 'None'}...")
82
  result = await self.invoke(prompt, **kwargs)
83
  yield result
84
  except Exception as e:
85
- logging.error(f"Error in ainvoke_stream: {str(e)}")
86
  yield Response(f"Error in ainvoke_stream: {str(e)}")
87
 
88
  def invoke(self, prompt: str, **kwargs) -> str:
89
  """Synchronous invoke method"""
90
  try:
91
- logging.info(f"Invoking model with prompt: {prompt[:100] if prompt and isinstance(prompt, str) else 'None'}...")
92
 
93
  # Check if prompt is None or empty
94
  if prompt is None:
95
- logging.warning("None prompt provided to invoke method")
96
  return Response("No input provided. Please provide a valid prompt.")
97
 
98
  if not isinstance(prompt, str):
99
- logging.warning(f"Non-string prompt provided: {type(prompt)}")
100
  try:
101
  prompt = str(prompt)
102
- logging.info(f"Converted prompt to string: {prompt[:100]}...")
103
  except:
104
  return Response("Invalid input type. Please provide a string prompt.")
105
 
106
  if not prompt.strip():
107
- logging.warning("Empty prompt provided to invoke method")
108
  return Response("No input provided. Please provide a non-empty prompt.")
109
 
110
  inputs = self.tokenizer(prompt, return_tensors="pt", padding=True)
@@ -124,13 +144,13 @@ class LocalHuggingFaceModel(Model):
124
 
125
  # Check if output is empty
126
  if not decoded_output or not decoded_output.strip():
127
- logging.warning("Model generated empty output")
128
  return Response("The model did not generate any output. Please try with a different prompt.")
129
 
130
- logging.info(f"Model generated output: {decoded_output[:100]}...")
131
  return Response(decoded_output)
132
  except Exception as e:
133
- logging.error(f"Error in local model generation: {str(e)}")
134
  if hasattr(e, 'args') and len(e.args) > 0:
135
  error_message = e.args[0]
136
  else:
@@ -140,11 +160,11 @@ class LocalHuggingFaceModel(Model):
140
  def invoke_stream(self, prompt: str, **kwargs):
141
  """Synchronous streaming invoke method"""
142
  try:
143
- logging.info(f"invoke_stream called with prompt: {prompt[:100] if prompt and isinstance(prompt, str) else 'None'}...")
144
  result = self.invoke(prompt, **kwargs)
145
  yield result
146
  except Exception as e:
147
- logging.error(f"Error in invoke_stream: {str(e)}")
148
  yield Response(f"Error in invoke_stream: {str(e)}")
149
 
150
  def parse_provider_response(self, response: str) -> str:
@@ -159,7 +179,7 @@ class LocalHuggingFaceModel(Model):
159
  """Async response method - required abstract method"""
160
  try:
161
  # Log detalhado de todos os argumentos
162
- logging.info(f"aresponse args: prompt={prompt}, kwargs keys={list(kwargs.keys())}")
163
 
164
  # Extrair o prompt das mensagens se estiverem disponíveis
165
  if prompt is None and 'messages' in kwargs and kwargs['messages']:
@@ -168,32 +188,32 @@ class LocalHuggingFaceModel(Model):
168
  for message in messages:
169
  if hasattr(message, 'role') and message.role == 'user' and hasattr(message, 'content'):
170
  prompt = message.content
171
- logging.info(f"Extracted prompt from user message: {prompt[:100] if prompt and isinstance(prompt, str) else 'None'}")
172
  break
173
 
174
  # Verificar se o prompt está em kwargs['input']
175
  if prompt is None:
176
  if 'input' in kwargs:
177
  prompt = kwargs.get('input')
178
- logging.info(f"Found prompt in kwargs['input']: {prompt[:100] if prompt and isinstance(prompt, str) else 'None'}")
179
 
180
- logging.info(f"aresponse called with prompt: {prompt[:100] if prompt and isinstance(prompt, str) else 'None'}...")
181
 
182
  if not prompt or not isinstance(prompt, str) or not prompt.strip():
183
- logging.warning("Empty or invalid prompt in aresponse")
184
  return Response("No input provided. Please provide a valid prompt.")
185
 
186
  content = await self.ainvoke(prompt, **kwargs)
187
  return content if isinstance(content, Response) else Response(content)
188
  except Exception as e:
189
- logging.error(f"Error in aresponse: {str(e)}")
190
  return Response(f"Error in aresponse: {str(e)}")
191
 
192
  def response(self, prompt=None, **kwargs):
193
  """Synchronous response method - required abstract method"""
194
  try:
195
  # Log detalhado de todos os argumentos
196
- logging.info(f"response args: prompt={prompt}, kwargs keys={list(kwargs.keys())}")
197
 
198
  # Extrair o prompt das mensagens se estiverem disponíveis
199
  if prompt is None and 'messages' in kwargs and kwargs['messages']:
@@ -202,32 +222,32 @@ class LocalHuggingFaceModel(Model):
202
  for message in messages:
203
  if hasattr(message, 'role') and message.role == 'user' and hasattr(message, 'content'):
204
  prompt = message.content
205
- logging.info(f"Extracted prompt from user message: {prompt[:100] if prompt and isinstance(prompt, str) else 'None'}")
206
  break
207
 
208
  # Verificar se o prompt está em kwargs['input']
209
  if prompt is None:
210
  if 'input' in kwargs:
211
  prompt = kwargs.get('input')
212
- logging.info(f"Found prompt in kwargs['input']: {prompt[:100] if prompt and isinstance(prompt, str) else 'None'}")
213
 
214
- logging.info(f"response called with prompt: {prompt[:100] if prompt and isinstance(prompt, str) else 'None'}...")
215
 
216
  if not prompt or not isinstance(prompt, str) or not prompt.strip():
217
- logging.warning("Empty or invalid prompt in response")
218
  return Response("No input provided. Please provide a valid prompt.")
219
 
220
  content = self.invoke(prompt, **kwargs)
221
  return content if isinstance(content, Response) else Response(content)
222
  except Exception as e:
223
- logging.error(f"Error in response: {str(e)}")
224
  return Response(f"Error in response: {str(e)}")
225
 
226
  def response_stream(self, prompt=None, **kwargs):
227
  """Synchronous streaming response method - required abstract method"""
228
  try:
229
  # Log detalhado de todos os argumentos
230
- logging.info(f"response_stream args: prompt={prompt}, kwargs keys={list(kwargs.keys())}")
231
 
232
  # Extrair o prompt das mensagens se estiverem disponíveis
233
  if prompt is None and 'messages' in kwargs and kwargs['messages']:
@@ -236,26 +256,26 @@ class LocalHuggingFaceModel(Model):
236
  for message in messages:
237
  if hasattr(message, 'role') and message.role == 'user' and hasattr(message, 'content'):
238
  prompt = message.content
239
- logging.info(f"Extracted prompt from user message: {prompt[:100] if prompt and isinstance(prompt, str) else 'None'}")
240
  break
241
 
242
  # Verificar se o prompt está em kwargs['input']
243
  if prompt is None:
244
  if 'input' in kwargs:
245
  prompt = kwargs.get('input')
246
- logging.info(f"Found prompt in kwargs['input']: {prompt[:100] if prompt and isinstance(prompt, str) else 'None'}")
247
 
248
- logging.info(f"response_stream called with prompt: {prompt[:100] if prompt and isinstance(prompt, str) else 'None'}...")
249
 
250
  if not prompt or not isinstance(prompt, str) or not prompt.strip():
251
- logging.warning("Empty or invalid prompt in response_stream")
252
  yield Response("No input provided. Please provide a valid prompt.")
253
  return
254
 
255
  for chunk in self.invoke_stream(prompt, **kwargs):
256
  yield chunk if isinstance(chunk, Response) else Response(chunk)
257
  except Exception as e:
258
- logging.error(f"Error in response_stream: {str(e)}")
259
  yield Response(f"Error in response_stream: {str(e)}")
260
 
261
  def generate(self, prompt: str, **kwargs):
@@ -277,7 +297,7 @@ class LocalHuggingFaceModel(Model):
277
 
278
  return decoded_output
279
  except Exception as e:
280
- logging.error(f"Error in generate method: {str(e)}")
281
  if hasattr(e, 'args') and len(e.args) > 0:
282
  error_message = e.args[0]
283
  else:
@@ -286,17 +306,18 @@ class LocalHuggingFaceModel(Model):
286
 
287
  class ModelHandler:
288
  """
289
- Classe para gerenciar modelos e gerar respostas.
290
  """
291
 
292
  def __init__(self):
293
  """
294
- Inicializa o ModelHandler.
295
  """
296
  self.translator = None
297
  self.researcher = None
298
  self.presenter = None
299
  self.force_default_response = False
 
300
 
301
  # Inicializar modelos
302
  self._load_models()
@@ -360,6 +381,10 @@ Please provide a detailed explanation about the topic, including:
360
  - Recent developments or research
361
  - Real-world implications and applications
362
 
 
 
 
 
363
  Aim to write at least 4-5 paragraphs with detailed information.
364
  Be thorough and informative, covering all important aspects of the topic.
365
  Use clear and accessible language suitable for a general audience.
@@ -388,16 +413,45 @@ Output:"""
388
  else:
389
  logging.error(f"Unknown prompt type: {prompt_type}")
390
  return f"Unknown prompt type: {prompt_type}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
391
 
392
  @staticmethod
393
  @st.cache_resource
394
- def _load_model():
395
- """Load the model and tokenizer with retry logic"""
396
  # Define retry decorator for model loading
397
  @retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=4, max=10))
398
  def load_with_retry(model_name):
399
  try:
400
- logging.info(f"Attempting to load model from {model_name}")
401
 
402
  # Criar diretório de cache se não existir
403
  cache_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "model_cache")
@@ -407,10 +461,10 @@ Output:"""
407
  tokenizer = AutoTokenizer.from_pretrained(model_name, cache_dir=cache_dir)
408
  model = AutoModelForSeq2SeqLM.from_pretrained(model_name, cache_dir=cache_dir)
409
 
410
- logging.info(f"Successfully loaded model from {model_name}")
411
  return model, tokenizer
412
  except Exception as e:
413
- logging.error(f"Error loading model {model_name}: {str(e)}")
414
  raise
415
 
416
  # Lista de modelos para tentar, em ordem de preferência
@@ -421,50 +475,179 @@ Output:"""
421
  try:
422
  return load_with_retry(model_name)
423
  except Exception as e:
424
- logging.error(f"Failed to load {model_name}: {str(e)}")
425
  continue
426
 
427
  # Se todos os modelos falharem, retornar None
428
- logging.error("All models failed to load")
429
  return None, None
430
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
431
  def _load_models(self):
432
- """Carrega os modelos necessários"""
433
- # Inicializar modelo local
434
- base_model = self._initialize_local_model()
 
 
435
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
436
  self.translator = Agent(
437
  name="Translator",
438
  role="You will translate the query to English",
439
- model=base_model,
440
  goal="Translate to English",
441
  instructions=[
442
- "Translate the query to English"
 
 
443
  ]
444
  )
445
 
 
446
  self.researcher = Agent(
447
  name="Researcher",
448
  role="You are a research scholar who specializes in autism research.",
449
- model=base_model,
450
  instructions=[
451
  "You need to understand the context of the question to provide the best answer.",
452
  "Be precise and provide detailed information.",
453
  "You must create an accessible explanation.",
454
  "The content must be for people without autism knowledge.",
455
  "Focus on providing comprehensive information about the topic.",
456
- "Include definition, characteristics, causes, and current understanding."
 
 
 
457
  ],
458
  tools=[
459
- ArxivTools(),
460
- PubmedTools()
461
- ]
 
462
  )
463
 
464
  self.presenter = Agent(
465
  name="Presenter",
466
  role="You are a professional researcher who presents the results of the research.",
467
- model=base_model,
468
  instructions=[
469
  "You are multilingual",
470
  "You must present the results in a clear and engaging manner.",
@@ -472,19 +655,38 @@ Output:"""
472
  "Provide simple explanations of complex concepts.",
473
  "Include a brief conclusion or summary.",
474
  "Add emojis to make the presentation more interactive.",
475
- "Translate the answer to Portuguese."
 
 
476
  ]
477
  )
 
 
478
 
479
- def _initialize_local_model(self):
480
- """Initialize local model as fallback"""
481
- model, tokenizer = self._load_model()
482
 
483
- return LocalHuggingFaceModel(model, tokenizer)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
484
 
485
- def generate_answer(self, query: str) -> str:
486
  """
487
- Gera uma resposta baseada na consulta do usuário.
488
 
489
  Args:
490
  query: A consulta do usuário
@@ -509,7 +711,7 @@ Output:"""
509
  logging.info(f"Translation prompt: {translation_prompt}")
510
 
511
  try:
512
- translation_result = self.translator.run(translation_prompt)
513
  logging.info(f"Translation result type: {type(translation_result)}")
514
 
515
  # Extrair o conteúdo da resposta
@@ -520,12 +722,11 @@ Output:"""
520
  logging.error("Empty translation result")
521
  return "Desculpe, não foi possível processar sua consulta. Por favor, tente novamente com uma pergunta diferente."
522
 
523
-
524
- # Realizar a pesquisa
525
  research_prompt = self._format_prompt("research", translation_content)
526
  logging.info(f"Research prompt: {research_prompt}")
527
 
528
- research_result = self.researcher.run(research_prompt)
529
  logging.info(f"Research result type: {type(research_result)}")
530
 
531
  # Extrair o conteúdo da pesquisa
@@ -541,16 +742,16 @@ Output:"""
541
  # Tentar novamente com um prompt mais específico
542
  enhanced_prompt = f"""Task: Detailed Research
543
 
544
- Instructions:
545
- Provide a comprehensive explanation about '{translation_content}'.
546
- Include definition, characteristics, causes, and current understanding.
547
- Write at least 4-5 paragraphs with detailed information.
548
- Be thorough and informative, covering all important aspects of the topic.
549
- Use clear and accessible language suitable for a general audience.
550
 
551
- Output:"""
552
  logging.info(f"Enhanced research prompt: {enhanced_prompt}")
553
- research_result = self.researcher.run(enhanced_prompt)
554
  research_content = self._extract_content(research_result)
555
  research_length = len(research_content.strip()) if research_content and isinstance(research_content, str) else 0
556
  logging.info(f"Enhanced research content: {research_content}")
@@ -562,11 +763,11 @@ Output:"""
562
  logging.info("Using default research content")
563
  research_content = self._get_default_research_content(translation_content)
564
 
565
-
566
  presentation_prompt = self._format_prompt("presentation", research_content)
567
  logging.info(f"Presentation prompt: {presentation_prompt}")
568
 
569
- presentation_result = self.presenter.run(presentation_prompt)
570
  logging.info(f"Presentation type: {type(presentation_result)}")
571
 
572
  presentation_content = self._extract_content(presentation_result)
@@ -586,6 +787,5 @@ Output:"""
586
  return f"Desculpe, ocorreu um erro ao processar sua consulta: {str(e)}. Por favor, tente novamente mais tarde."
587
 
588
  except Exception as e:
589
- logging.error(f"Unexpected error in generate_answer: {str(e)}")
590
- return "Desculpe, ocorreu um erro inesperado. Por favor, tente novamente mais tarde."
591
-
 
9
  import time
10
  import datetime
11
  import os
12
+ from typing import Tuple, Optional, Dict, Any, List
13
 
14
+ # Configuração de logging
15
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
16
+ logger = logging.getLogger(__name__)
17
+
18
+ # Configurações dos modelos
19
+ MODEL_CONFIG = {
20
+ "translator": {
21
+ "primary": "facebook/nllb-200-distilled-600M",
22
+ "fallback": "google/flan-t5-base"
23
+ },
24
+ "researcher": {
25
+ "primary": "google/flan-t5-large",
26
+ "fallback": "google/flan-t5-base"
27
+ },
28
+ "presenter": {
29
+ "primary": "bigscience/bloomz-1b7",
30
+ "fallback": "google/flan-t5-base"
31
+ }
32
+ }
33
 
34
  # Simple Response class to wrap the model output
35
  class Response:
 
75
  return self.content if self.content else ""
76
 
77
  def __repr__(self):
78
+ return f"Response(content='{self.content[:50]}{'...' if len(self.content) > 50 else ''}')"
79
 
80
+ # Personalizada classe para modelos locais
81
  class LocalHuggingFaceModel(Model):
82
+ def __init__(self, model, tokenizer, model_id="local-huggingface", max_length=512):
83
+ super().__init__(id=model_id)
84
  self.model = model
85
  self.tokenizer = tokenizer
86
  self.max_length = max_length
87
+ self.model_name = model_id
88
 
89
  async def ainvoke(self, prompt: str, **kwargs) -> str:
90
  """Async invoke method"""
91
  try:
92
+ logging.info(f"[{self.model_name}] ainvoke called with prompt: {prompt[:100] if prompt and isinstance(prompt, str) else 'None'}...")
93
  return await self.invoke(prompt, **kwargs)
94
  except Exception as e:
95
+ logging.error(f"[{self.model_name}] Error in ainvoke: {str(e)}")
96
  return Response(f"Error in ainvoke: {str(e)}")
97
 
98
  async def ainvoke_stream(self, prompt: str, **kwargs):
99
  """Async streaming invoke method"""
100
  try:
101
+ logging.info(f"[{self.model_name}] ainvoke_stream called with prompt: {prompt[:100] if prompt and isinstance(prompt, str) else 'None'}...")
102
  result = await self.invoke(prompt, **kwargs)
103
  yield result
104
  except Exception as e:
105
+ logging.error(f"[{self.model_name}] Error in ainvoke_stream: {str(e)}")
106
  yield Response(f"Error in ainvoke_stream: {str(e)}")
107
 
108
  def invoke(self, prompt: str, **kwargs) -> str:
109
  """Synchronous invoke method"""
110
  try:
111
+ logging.info(f"[{self.model_name}] Invoking model with prompt: {prompt[:100] if prompt and isinstance(prompt, str) else 'None'}...")
112
 
113
  # Check if prompt is None or empty
114
  if prompt is None:
115
+ logging.warning(f"[{self.model_name}] None prompt provided to invoke method")
116
  return Response("No input provided. Please provide a valid prompt.")
117
 
118
  if not isinstance(prompt, str):
119
+ logging.warning(f"[{self.model_name}] Non-string prompt provided: {type(prompt)}")
120
  try:
121
  prompt = str(prompt)
122
+ logging.info(f"[{self.model_name}] Converted prompt to string: {prompt[:100]}...")
123
  except:
124
  return Response("Invalid input type. Please provide a string prompt.")
125
 
126
  if not prompt.strip():
127
+ logging.warning(f"[{self.model_name}] Empty prompt provided to invoke method")
128
  return Response("No input provided. Please provide a non-empty prompt.")
129
 
130
  inputs = self.tokenizer(prompt, return_tensors="pt", padding=True)
 
144
 
145
  # Check if output is empty
146
  if not decoded_output or not decoded_output.strip():
147
+ logging.warning(f"[{self.model_name}] Model generated empty output")
148
  return Response("The model did not generate any output. Please try with a different prompt.")
149
 
150
+ logging.info(f"[{self.model_name}] Model generated output: {decoded_output[:100]}...")
151
  return Response(decoded_output)
152
  except Exception as e:
153
+ logging.error(f"[{self.model_name}] Error in local model generation: {str(e)}")
154
  if hasattr(e, 'args') and len(e.args) > 0:
155
  error_message = e.args[0]
156
  else:
 
160
  def invoke_stream(self, prompt: str, **kwargs):
161
  """Synchronous streaming invoke method"""
162
  try:
163
+ logging.info(f"[{self.model_name}] invoke_stream called with prompt: {prompt[:100] if prompt and isinstance(prompt, str) else 'None'}...")
164
  result = self.invoke(prompt, **kwargs)
165
  yield result
166
  except Exception as e:
167
+ logging.error(f"[{self.model_name}] Error in invoke_stream: {str(e)}")
168
  yield Response(f"Error in invoke_stream: {str(e)}")
169
 
170
  def parse_provider_response(self, response: str) -> str:
 
179
  """Async response method - required abstract method"""
180
  try:
181
  # Log detalhado de todos os argumentos
182
+ logging.info(f"[{self.model_name}] aresponse args: prompt={prompt}, kwargs keys={list(kwargs.keys())}")
183
 
184
  # Extrair o prompt das mensagens se estiverem disponíveis
185
  if prompt is None and 'messages' in kwargs and kwargs['messages']:
 
188
  for message in messages:
189
  if hasattr(message, 'role') and message.role == 'user' and hasattr(message, 'content'):
190
  prompt = message.content
191
+ logging.info(f"[{self.model_name}] Extracted prompt from user message: {prompt[:100] if prompt and isinstance(prompt, str) else 'None'}")
192
  break
193
 
194
  # Verificar se o prompt está em kwargs['input']
195
  if prompt is None:
196
  if 'input' in kwargs:
197
  prompt = kwargs.get('input')
198
+ logging.info(f"[{self.model_name}] Found prompt in kwargs['input']: {prompt[:100] if prompt and isinstance(prompt, str) else 'None'}")
199
 
200
+ logging.info(f"[{self.model_name}] aresponse called with prompt: {prompt[:100] if prompt and isinstance(prompt, str) else 'None'}...")
201
 
202
  if not prompt or not isinstance(prompt, str) or not prompt.strip():
203
+ logging.warning(f"[{self.model_name}] Empty or invalid prompt in aresponse")
204
  return Response("No input provided. Please provide a valid prompt.")
205
 
206
  content = await self.ainvoke(prompt, **kwargs)
207
  return content if isinstance(content, Response) else Response(content)
208
  except Exception as e:
209
+ logging.error(f"[{self.model_name}] Error in aresponse: {str(e)}")
210
  return Response(f"Error in aresponse: {str(e)}")
211
 
212
  def response(self, prompt=None, **kwargs):
213
  """Synchronous response method - required abstract method"""
214
  try:
215
  # Log detalhado de todos os argumentos
216
+ logging.info(f"[{self.model_name}] response args: prompt={prompt}, kwargs keys={list(kwargs.keys())}")
217
 
218
  # Extrair o prompt das mensagens se estiverem disponíveis
219
  if prompt is None and 'messages' in kwargs and kwargs['messages']:
 
222
  for message in messages:
223
  if hasattr(message, 'role') and message.role == 'user' and hasattr(message, 'content'):
224
  prompt = message.content
225
+ logging.info(f"[{self.model_name}] Extracted prompt from user message: {prompt[:100] if prompt and isinstance(prompt, str) else 'None'}")
226
  break
227
 
228
  # Verificar se o prompt está em kwargs['input']
229
  if prompt is None:
230
  if 'input' in kwargs:
231
  prompt = kwargs.get('input')
232
+ logging.info(f"[{self.model_name}] Found prompt in kwargs['input']: {prompt[:100] if prompt and isinstance(prompt, str) else 'None'}")
233
 
234
+ logging.info(f"[{self.model_name}] response called with prompt: {prompt[:100] if prompt and isinstance(prompt, str) else 'None'}...")
235
 
236
  if not prompt or not isinstance(prompt, str) or not prompt.strip():
237
+ logging.warning(f"[{self.model_name}] Empty or invalid prompt in response")
238
  return Response("No input provided. Please provide a valid prompt.")
239
 
240
  content = self.invoke(prompt, **kwargs)
241
  return content if isinstance(content, Response) else Response(content)
242
  except Exception as e:
243
+ logging.error(f"[{self.model_name}] Error in response: {str(e)}")
244
  return Response(f"Error in response: {str(e)}")
245
 
246
  def response_stream(self, prompt=None, **kwargs):
247
  """Synchronous streaming response method - required abstract method"""
248
  try:
249
  # Log detalhado de todos os argumentos
250
+ logging.info(f"[{self.model_name}] response_stream args: prompt={prompt}, kwargs keys={list(kwargs.keys())}")
251
 
252
  # Extrair o prompt das mensagens se estiverem disponíveis
253
  if prompt is None and 'messages' in kwargs and kwargs['messages']:
 
256
  for message in messages:
257
  if hasattr(message, 'role') and message.role == 'user' and hasattr(message, 'content'):
258
  prompt = message.content
259
+ logging.info(f"[{self.model_name}] Extracted prompt from user message: {prompt[:100] if prompt and isinstance(prompt, str) else 'None'}")
260
  break
261
 
262
  # Verificar se o prompt está em kwargs['input']
263
  if prompt is None:
264
  if 'input' in kwargs:
265
  prompt = kwargs.get('input')
266
+ logging.info(f"[{self.model_name}] Found prompt in kwargs['input']: {prompt[:100] if prompt and isinstance(prompt, str) else 'None'}")
267
 
268
+ logging.info(f"[{self.model_name}] response_stream called with prompt: {prompt[:100] if prompt and isinstance(prompt, str) else 'None'}...")
269
 
270
  if not prompt or not isinstance(prompt, str) or not prompt.strip():
271
+ logging.warning(f"[{self.model_name}] Empty or invalid prompt in response_stream")
272
  yield Response("No input provided. Please provide a valid prompt.")
273
  return
274
 
275
  for chunk in self.invoke_stream(prompt, **kwargs):
276
  yield chunk if isinstance(chunk, Response) else Response(chunk)
277
  except Exception as e:
278
+ logging.error(f"[{self.model_name}] Error in response_stream: {str(e)}")
279
  yield Response(f"Error in response_stream: {str(e)}")
280
 
281
  def generate(self, prompt: str, **kwargs):
 
297
 
298
  return decoded_output
299
  except Exception as e:
300
+ logging.error(f"[{self.model_name}] Error in generate method: {str(e)}")
301
  if hasattr(e, 'args') and len(e.args) > 0:
302
  error_message = e.args[0]
303
  else:
 
306
 
307
  class ModelHandler:
308
  """
309
+ Classe para gerenciar múltiplos modelos e gerar respostas.
310
  """
311
 
312
  def __init__(self):
313
  """
314
+ Inicializa o ModelHandler com múltiplos modelos.
315
  """
316
  self.translator = None
317
  self.researcher = None
318
  self.presenter = None
319
  self.force_default_response = False
320
+ self.models = {}
321
 
322
  # Inicializar modelos
323
  self._load_models()
 
381
  - Recent developments or research
382
  - Real-world implications and applications
383
 
384
+ Search for relevant academic papers and medical resources using the provided tools.
385
+ Make sure to include findings from recent research in your response.
386
+ Use ArxivTools and PubmedTools to find the most relevant and up-to-date information.
387
+
388
  Aim to write at least 4-5 paragraphs with detailed information.
389
  Be thorough and informative, covering all important aspects of the topic.
390
  Use clear and accessible language suitable for a general audience.
 
413
  else:
414
  logging.error(f"Unknown prompt type: {prompt_type}")
415
  return f"Unknown prompt type: {prompt_type}"
416
+
417
+ @staticmethod
418
+ def _load_specific_model(model_name: str, purpose: str) -> Tuple[Optional[Any], Optional[Any]]:
419
+ """
420
+ Load a specific model with retry logic
421
+
422
+ Args:
423
+ model_name: The name of the model to load
424
+ purpose: What the model will be used for (logging purposes)
425
+
426
+ Returns:
427
+ A tuple of (model, tokenizer) or (None, None) if loading fails
428
+ """
429
+ try:
430
+ logging.info(f"Attempting to load {purpose} model: {model_name}")
431
+
432
+ # Criar diretório de cache se não existir
433
+ cache_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "model_cache")
434
+ os.makedirs(cache_dir, exist_ok=True)
435
+
436
+ # Carregar modelo e tokenizer
437
+ tokenizer = AutoTokenizer.from_pretrained(model_name, cache_dir=cache_dir)
438
+ model = AutoModelForSeq2SeqLM.from_pretrained(model_name, cache_dir=cache_dir)
439
+
440
+ logging.info(f"Successfully loaded {purpose} model: {model_name}")
441
+ return model, tokenizer
442
+ except Exception as e:
443
+ logging.error(f"Error loading {purpose} model {model_name}: {str(e)}")
444
+ return None, None
445
 
446
  @staticmethod
447
  @st.cache_resource
448
+ def _load_fallback_model():
449
+ """Load a fallback model"""
450
  # Define retry decorator for model loading
451
  @retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=4, max=10))
452
  def load_with_retry(model_name):
453
  try:
454
+ logging.info(f"Attempting to load fallback model from {model_name}")
455
 
456
  # Criar diretório de cache se não existir
457
  cache_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "model_cache")
 
461
  tokenizer = AutoTokenizer.from_pretrained(model_name, cache_dir=cache_dir)
462
  model = AutoModelForSeq2SeqLM.from_pretrained(model_name, cache_dir=cache_dir)
463
 
464
+ logging.info(f"Successfully loaded fallback model from {model_name}")
465
  return model, tokenizer
466
  except Exception as e:
467
+ logging.error(f"Error loading fallback model {model_name}: {str(e)}")
468
  raise
469
 
470
  # Lista de modelos para tentar, em ordem de preferência
 
475
  try:
476
  return load_with_retry(model_name)
477
  except Exception as e:
478
+ logging.error(f"Failed to load fallback model {model_name}: {str(e)}")
479
  continue
480
 
481
  # Se todos os modelos falharem, retornar None
482
+ logging.error("All fallback models failed to load")
483
  return None, None
484
 
485
+ def _get_default_research_content(self, topic):
486
+ """
487
+ Gera conteúdo de pesquisa padrão quando não for possível gerar com o modelo.
488
+
489
+ Args:
490
+ topic: O tópico da pesquisa
491
+
492
+ Returns:
493
+ Conteúdo de pesquisa padrão
494
+ """
495
+ return f"""
496
+ # Research on {topic}
497
+
498
+ ## Definition and Key Characteristics
499
+
500
+ {topic} is a subject of significant interest in various fields. While detailed information is currently limited in our system, we understand that it encompasses several key characteristics and has important implications.
501
+
502
+ ## Current Understanding
503
+
504
+ Research on {topic} continues to evolve, with new findings emerging regularly. The current understanding suggests multiple dimensions to consider when approaching this topic.
505
+
506
+ ## Applications and Implications
507
+
508
+ The study of {topic} has several real-world applications and implications that affect various sectors including healthcare, education, and social services.
509
+
510
+ ## Conclusion
511
+
512
+ While our current information on {topic} is limited, it represents an important area for continued research and understanding. For more detailed information, consulting specialized literature and experts is recommended.
513
+ """
514
+
515
  def _load_models(self):
516
+ """Carrega múltiplos modelos para diferentes propósitos"""
517
+ # Carregar modelo de tradução
518
+ translator_model, translator_tokenizer = self._load_specific_model(
519
+ MODEL_CONFIG["translator"]["primary"], "translator"
520
+ )
521
 
522
+ # Carregar modelo de pesquisa
523
+ researcher_model, researcher_tokenizer = self._load_specific_model(
524
+ MODEL_CONFIG["researcher"]["primary"], "researcher"
525
+ )
526
+
527
+ # Carregar modelo de apresentação
528
+ presenter_model, presenter_tokenizer = self._load_specific_model(
529
+ MODEL_CONFIG["presenter"]["primary"], "presenter"
530
+ )
531
+
532
+ # Carregar modelo de fallback
533
+ fallback_model, fallback_tokenizer = self._load_fallback_model()
534
+
535
+ # Criar modelos locais
536
+ if translator_model and translator_tokenizer:
537
+ self.models["translator"] = LocalHuggingFaceModel(
538
+ translator_model,
539
+ translator_tokenizer,
540
+ model_id=MODEL_CONFIG["translator"]["primary"]
541
+ )
542
+ else:
543
+ # Tentar carregar o modelo fallback para tradutor
544
+ fallback_translator, fallback_translator_tokenizer = self._load_specific_model(
545
+ MODEL_CONFIG["translator"]["fallback"], "translator fallback"
546
+ )
547
+
548
+ if fallback_translator and fallback_translator_tokenizer:
549
+ self.models["translator"] = LocalHuggingFaceModel(
550
+ fallback_translator,
551
+ fallback_translator_tokenizer,
552
+ model_id=MODEL_CONFIG["translator"]["fallback"]
553
+ )
554
+ else:
555
+ self.models["translator"] = LocalHuggingFaceModel(
556
+ fallback_model,
557
+ fallback_tokenizer,
558
+ model_id="fallback-model"
559
+ )
560
+
561
+ if researcher_model and researcher_tokenizer:
562
+ self.models["researcher"] = LocalHuggingFaceModel(
563
+ researcher_model,
564
+ researcher_tokenizer,
565
+ model_id=MODEL_CONFIG["researcher"]["primary"]
566
+ )
567
+ else:
568
+ # Tentar carregar o modelo fallback para pesquisador
569
+ fallback_researcher, fallback_researcher_tokenizer = self._load_specific_model(
570
+ MODEL_CONFIG["researcher"]["fallback"], "researcher fallback"
571
+ )
572
+
573
+ if fallback_researcher and fallback_researcher_tokenizer:
574
+ self.models["researcher"] = LocalHuggingFaceModel(
575
+ fallback_researcher,
576
+ fallback_researcher_tokenizer,
577
+ model_id=MODEL_CONFIG["researcher"]["fallback"]
578
+ )
579
+ else:
580
+ self.models["researcher"] = LocalHuggingFaceModel(
581
+ fallback_model,
582
+ fallback_tokenizer,
583
+ model_id="fallback-model"
584
+ )
585
+
586
+ if presenter_model and presenter_tokenizer:
587
+ self.models["presenter"] = LocalHuggingFaceModel(
588
+ presenter_model,
589
+ presenter_tokenizer,
590
+ model_id=MODEL_CONFIG["presenter"]["primary"]
591
+ )
592
+ else:
593
+ # Tentar carregar o modelo fallback para apresentador
594
+ fallback_presenter, fallback_presenter_tokenizer = self._load_specific_model(
595
+ MODEL_CONFIG["presenter"]["fallback"], "presenter fallback"
596
+ )
597
+
598
+ if fallback_presenter and fallback_presenter_tokenizer:
599
+ self.models["presenter"] = LocalHuggingFaceModel(
600
+ fallback_presenter,
601
+ fallback_presenter_tokenizer,
602
+ model_id=MODEL_CONFIG["presenter"]["fallback"]
603
+ )
604
+ else:
605
+ self.models["presenter"] = LocalHuggingFaceModel(
606
+ fallback_model,
607
+ fallback_tokenizer,
608
+ model_id="fallback-model"
609
+ )
610
+
611
+ # Configurar agentes com seus respectivos modelos
612
  self.translator = Agent(
613
  name="Translator",
614
  role="You will translate the query to English",
615
+ model=self.models["translator"],
616
  goal="Translate to English",
617
  instructions=[
618
+ "Translate the query to English",
619
+ "Preserve all key information from the original query",
620
+ "Return only the translated text without additional comments"
621
  ]
622
  )
623
 
624
+ # Configurar o agente de pesquisa com as ferramentas ArxivTools e PubmedTools
625
  self.researcher = Agent(
626
  name="Researcher",
627
  role="You are a research scholar who specializes in autism research.",
628
+ model=self.models["researcher"],
629
  instructions=[
630
  "You need to understand the context of the question to provide the best answer.",
631
  "Be precise and provide detailed information.",
632
  "You must create an accessible explanation.",
633
  "The content must be for people without autism knowledge.",
634
  "Focus on providing comprehensive information about the topic.",
635
+ "Include definition, characteristics, causes, and current understanding.",
636
+ "ALWAYS use the provided tools (ArxivTools and PubmedTools) to search for relevant information.",
637
+ "Cite specific papers and studies in your response when appropriate.",
638
+ "When using tools, specify the search query clearly in your thoughts before making the call."
639
  ],
640
  tools=[
641
+ ArxivTools(), # Usar ferramentas ArxivTools
642
+ PubmedTools() # Usar ferramentas PubmedTools
643
+ ],
644
+ verbose=True # Ativar modo verbose para depuração
645
  )
646
 
647
  self.presenter = Agent(
648
  name="Presenter",
649
  role="You are a professional researcher who presents the results of the research.",
650
+ model=self.models["presenter"],
651
  instructions=[
652
  "You are multilingual",
653
  "You must present the results in a clear and engaging manner.",
 
655
  "Provide simple explanations of complex concepts.",
656
  "Include a brief conclusion or summary.",
657
  "Add emojis to make the presentation more interactive.",
658
+ "Translate the answer to Portuguese.",
659
+ "Maintain any citations or references from the research in your presentation.",
660
+ "Do not add fictional information not present in the research."
661
  ]
662
  )
663
+
664
+ logging.info("Models and agents loaded successfully.")
665
 
666
+ async def _run_with_tools(self, agent, prompt, max_steps=5):
667
+ """
668
+ Executa um agente com suporte a ferramentas e gerencia a execução.
669
 
670
+ Args:
671
+ agent: O agente a ser executado
672
+ prompt: O prompt a ser enviado para o agente
673
+ max_steps: Número máximo de passos para execução
674
+
675
+ Returns:
676
+ O resultado da execução do agente
677
+ """
678
+ try:
679
+ logging.info(f"Running agent {agent.name} with tools")
680
+ result = await agent.arun(prompt, max_steps=max_steps)
681
+ logging.info(f"Agent {agent.name} execution complete")
682
+ return result
683
+ except Exception as e:
684
+ logging.error(f"Error during agent {agent.name} execution: {str(e)}")
685
+ return f"Error during {agent.name} execution: {str(e)}"
686
 
687
+ async def generate_answer_async(self, query: str) -> str:
688
  """
689
+ Gera uma resposta baseada na consulta do usuário usando execução assíncrona.
690
 
691
  Args:
692
  query: A consulta do usuário
 
711
  logging.info(f"Translation prompt: {translation_prompt}")
712
 
713
  try:
714
+ translation_result = await self.translator.arun(translation_prompt)
715
  logging.info(f"Translation result type: {type(translation_result)}")
716
 
717
  # Extrair o conteúdo da resposta
 
722
  logging.error("Empty translation result")
723
  return "Desculpe, não foi possível processar sua consulta. Por favor, tente novamente com uma pergunta diferente."
724
 
725
+ # Realizar a pesquisa com ferramentas
 
726
  research_prompt = self._format_prompt("research", translation_content)
727
  logging.info(f"Research prompt: {research_prompt}")
728
 
729
+ research_result = await self._run_with_tools(self.researcher, research_prompt)
730
  logging.info(f"Research result type: {type(research_result)}")
731
 
732
  # Extrair o conteúdo da pesquisa
 
742
  # Tentar novamente com um prompt mais específico
743
  enhanced_prompt = f"""Task: Detailed Research
744
 
745
+ Instructions:
746
+ Provide a comprehensive explanation about '{translation_content}'.
747
+ Include definition, characteristics, causes, and current understanding.
748
+ Write at least 4-5 paragraphs with detailed information.
749
+ Be thorough and informative, covering all important aspects of the topic.
750
+ Use clear and accessible language suitable for a general audience.
751
 
752
+ Output:"""
753
  logging.info(f"Enhanced research prompt: {enhanced_prompt}")
754
+ research_result = await self._run_with_tools(self.researcher, enhanced_prompt)
755
  research_content = self._extract_content(research_result)
756
  research_length = len(research_content.strip()) if research_content and isinstance(research_content, str) else 0
757
  logging.info(f"Enhanced research content: {research_content}")
 
763
  logging.info("Using default research content")
764
  research_content = self._get_default_research_content(translation_content)
765
 
766
+ # Gerar a apresentação
767
  presentation_prompt = self._format_prompt("presentation", research_content)
768
  logging.info(f"Presentation prompt: {presentation_prompt}")
769
 
770
+ presentation_result = await self.presenter.arun(presentation_prompt)
771
  logging.info(f"Presentation type: {type(presentation_result)}")
772
 
773
  presentation_content = self._extract_content(presentation_result)
 
787
  return f"Desculpe, ocorreu um erro ao processar sua consulta: {str(e)}. Por favor, tente novamente mais tarde."
788
 
789
  except Exception as e:
790
+ logging.error(f"Unexpected error in generate_answer_async: {str(e)}")
791
+ return "Desculpe, ocorreu um erro inesperado. Por favor, tente novamente mais tarde."