aldohenrique commited on
Commit
7acb074
·
verified ·
1 Parent(s): 7f691ba

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +243 -238
app.py CHANGED
@@ -3,25 +3,161 @@ import os
3
  import json
4
  import re
5
  import gradio as gr
6
- from typing import Dict, Any
 
7
 
8
- # Configuração da API
 
 
 
 
 
 
 
 
 
 
 
 
 
9
  HF_TOKEN = os.getenv("HF_TOKEN")
10
  if not HF_TOKEN:
11
  raise ValueError("Token HF_TOKEN não encontrado nas variáveis de ambiente")
12
 
13
- # Modelos disponíveis via API (testados e funcionais)
14
  MODELS = {
15
  "Phi-3 Mini (Microsoft)": "microsoft/Phi-3-mini-4k-instruct",
16
  "Mistral 7B": "mistralai/Mistral-7B-Instruct-v0.3",
17
  "Zephyr 7B": "HuggingFaceH4/zephyr-7b-beta",
18
- "Llama 3.2 3B (Meta)": "meta-llama/Llama-3.2-3B-Instruct",
19
- "DeepSeek-Coder-V2": "deepseek-ai/DeepSeek-Coder-V2-Lite-Instruct", # Replaced DeepSeek-Code
20
  }
21
-
22
- # Modelo padrão (mais confiável)
23
  DEFAULT_MODEL = "Phi-3 Mini (Microsoft)"
24
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
  class HuggingFaceAPIClient:
26
  def __init__(self, token: str):
27
  self.token = token
@@ -29,11 +165,10 @@ class HuggingFaceAPIClient:
29
  "Authorization": f"Bearer {token}",
30
  "Content-Type": "application/json"
31
  }
32
-
33
- def query_model(self, model_name: str, messages: list, max_tokens: int = 500) -> str:
34
  """Faz requisição para a API do Hugging Face"""
35
  url = f"https://api-inference.huggingface.co/models/{model_name}/v1/chat/completions"
36
-
37
  payload = {
38
  "model": model_name,
39
  "messages": messages,
@@ -42,363 +177,233 @@ class HuggingFaceAPIClient:
42
  "top_p": 0.9,
43
  "stream": False
44
  }
45
-
46
  try:
47
  response = requests.post(url, headers=self.headers, json=payload, timeout=9999)
48
-
49
  if response.status_code == 200:
50
  result = response.json()
51
  return result["choices"][0]["message"]["content"]
52
  else:
53
- # Fallback para API de texto simples se a API de chat não funcionar
54
  return self._fallback_text_generation(model_name, messages, max_tokens)
55
-
56
  except Exception as e:
57
  return f"Erro na API: {str(e)}"
58
-
59
  def _fallback_text_generation(self, model_name: str, messages: list, max_tokens: int) -> str:
60
- """Fallback usando API de geração de texto simples"""
61
  url = f"https://api-inference.huggingface.co/models/{model_name}"
62
-
63
- # Converte mensagens para prompt simples
64
  prompt = self._messages_to_prompt(messages)
65
-
66
  payload = {
67
  "inputs": prompt,
68
  "parameters": {
69
- "max_new_tokens": max_tokens,
70
- "temperature": 0.7,
71
- "top_p": 0.9,
72
- "do_sample": True,
73
- "return_full_text": False
74
  },
75
- "options": {
76
- "wait_for_model": True, # Espera modelo carregar
77
- "use_cache": False
78
- }
79
  }
80
-
81
  try:
82
  response = requests.post(url, headers=self.headers, json=payload, timeout=9999)
83
-
84
  if response.status_code == 200:
85
  result = response.json()
86
  if isinstance(result, list) and len(result) > 0:
87
  generated_text = result[0].get("generated_text", "")
88
- # Limpa o texto gerado
89
  if generated_text:
90
- # Remove o prompt original da resposta
91
  if "Assistente: " in generated_text:
92
  parts = generated_text.split("Assistente: ")
93
- if len(parts) > 1:
94
- return parts[-1].strip()
95
  return generated_text.strip()
96
  return "Resposta vazia"
97
  elif isinstance(result, dict):
98
- if "error" in result:
99
- return f"Erro do modelo: {result['error']}"
100
- elif "generated_text" in result:
101
- return result["generated_text"].strip()
102
  return "Formato de resposta inesperado"
103
- elif response.status_code == 404:
104
- return f" Modelo '{model_name}' não encontrado. Tente outro modelo."
105
- elif response.status_code == 503:
106
- return " Modelo carregando... Aguarde alguns segundos e tente novamente."
107
- elif response.status_code == 429:
108
- return "⚠️ Muitas requisições. Aguarde um momento antes de tentar novamente."
109
- else:
110
- return f"Erro HTTP {response.status_code}: {response.text[:200]}..."
111
-
112
  except requests.Timeout:
113
- return "⏰ Timeout - Modelo demorou muito para responder. Tente novamente."
114
  except Exception as e:
115
  return f"Erro na requisição: {str(e)}"
116
-
117
  def _messages_to_prompt(self, messages: list) -> str:
118
- """Converte mensagens para formato de prompt"""
119
  prompt = ""
120
  for msg in messages:
121
- if msg["role"] == "system":
122
- prompt += f"Sistema: {msg['content']}\n\n"
123
- elif msg["role"] == "user":
124
- prompt += f"Usuário: {msg['content']}\n\n"
125
- elif msg["role"] == "assistant":
126
- prompt += f"Assistente: {msg['content']}\n\n"
127
-
128
  prompt += "Assistente: "
129
  return prompt
130
 
131
  # Inicializar cliente da API
132
  api_client = HuggingFaceAPIClient(HF_TOKEN)
133
 
 
 
 
 
134
  def formatar_resposta_com_codigo(resposta: str) -> str:
135
- """Formata a resposta destacando códigos em blocos separados"""
136
- if not resposta:
137
- return resposta
138
-
139
- # Detecta blocos de código com ```
140
  resposta_formatada = re.sub(
141
  r'```(\w+)?\n(.*?)\n```',
142
  r'<div style="background-color: #f8f9fa; color: #1a1a1a; border: 1px solid #e9ecef; border-radius: 8px; padding: 15px; margin: 10px 0; font-family: Monaco, Consolas, monospace; overflow-x: auto;"><strong style="color: #1a1a1a;">💻 Código:</strong><br><pre style="color: #1a1a1a; margin: 5px 0; white-space: pre-wrap; word-wrap: break-word;"><code>\2</code></pre></div>',
143
- resposta,
144
- flags=re.DOTALL
145
  )
146
-
147
- # Detecta código inline com `
148
  resposta_formatada = re.sub(
149
  r'`([^`]+)`',
150
  r'<code style="background-color: #f1f3f4; color: #1a1a1a; padding: 2px 4px; border-radius: 4px; font-family: Monaco, Consolas, monospace;">\1</code>',
151
  resposta_formatada
152
  )
153
-
154
- # Adiciona quebras de linha para melhor visualização
155
- resposta_formatada = resposta_formatada.replace('\n\n', '<br><br>')
156
  resposta_formatada = resposta_formatada.replace('\n', '<br>')
157
-
158
- # Destaca títulos/seções
159
  resposta_formatada = re.sub(
160
  r'^\*\*(.*?)\*\*',
161
  r'<h3 style="color: #1a1a1a; margin-top: 20px; margin-bottom: 10px;">\1</h3>',
162
- resposta_formatada,
163
- flags=re.MULTILINE
164
  )
165
-
166
  return resposta_formatada
167
 
168
  def responder_como_aldo(pergunta: str, modelo_escolhido: str = DEFAULT_MODEL) -> str:
169
- """Função principal para gerar respostas via API"""
170
  if not pergunta.strip():
171
  return "Por favor, faça uma pergunta."
172
-
173
  try:
174
- # Preparar mensagens
175
- pergunta_completa = f"{pergunta} Não responda nada se a pergunta não for sobre o universo de programação e tecnologia, informe que o Dr. Aldo Henrique só tem domínio em TI. Você é o Professor Dr. Aldo Henrique, foque em explicar e não em só mostrar o resultado. Quando apresentar código, use blocos de código formatados com ```. Sempre responda primeiro a explicação e depois modestre o código."
 
 
 
 
 
 
 
 
 
176
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
177
  messages = [
178
- {
179
- "role": "system",
180
- "content": "Você é o professor Dr. Aldo Henrique, especialista em C, Java, desenvolvimento web e inteligência artificial. Sempre responda primeiro a explicação e depois modestre o código. Responda com clareza, profundidade e tom acadêmico, como um professor experiente. Evite respostas genéricas e forneça exemplos práticos quando aplicável. Foque em explicar e não em só mostrar o resultado. Responda sempre em português brasileiro. Use blocos de código formatados com ``` quando apresentar código. Não responda nada se a pergunta não for sobre o universo de programação e tecnologia."
181
- },
182
- {
183
- "role": "user",
184
- "content": pergunta_completa
185
- }
186
  ]
187
 
188
- # Obter o nome real do modelo
189
  model_name = MODELS.get(modelo_escolhido, MODELS[DEFAULT_MODEL])
 
190
 
191
- # Fazer requisição
192
- resposta = api_client.query_model(model_name, messages, max_tokens=800)
193
-
194
- # Limpar resposta se necessário
195
  if resposta.startswith("Assistente: "):
196
  resposta = resposta.replace("Assistente: ", "")
197
-
198
- # Formatar resposta com código
199
  resposta_formatada = formatar_resposta_com_codigo(resposta.strip())
200
-
201
  return resposta_formatada
202
 
203
  except Exception as e:
204
  return f"Erro ao processar sua pergunta: {str(e)}"
205
 
 
206
  def verificar_modelo_disponivel(model_name: str) -> str:
207
- """Verifica se um modelo está disponível na API"""
208
  try:
209
  url = f"https://api-inference.huggingface.co/models/{model_name}"
210
  headers = {"Authorization": f"Bearer {HF_TOKEN}"}
211
-
212
- # Teste simples
213
- payload = {
214
- "inputs": "Hello",
215
- "parameters": {"max_new_tokens": 5}
216
- }
217
-
218
  response = requests.post(url, headers=headers, json=payload, timeout=9999)
219
-
220
- if response.status_code == 200:
221
- return " Disponível"
222
- elif response.status_code == 404:
223
- return "❌ Não encontrado"
224
- elif response.status_code == 503:
225
- return "⏳ Carregando..."
226
- else:
227
- return f"⚠️ Status {response.status_code}"
228
-
229
  except Exception as e:
230
  return f"❌ Erro: {str(e)[:50]}..."
231
 
232
  def testar_todos_modelos():
233
- """Testa todos os modelos disponíveis"""
234
  resultados = []
235
  for nome, modelo in MODELS.items():
236
  status = verificar_modelo_disponivel(modelo)
237
  resultados.append(f"{nome}: {status}")
238
  return "\n".join(resultados)
239
 
240
- # CSS customizado para melhor visualização
241
  css_customizado = """
242
- .gradio-container {
243
- max-width: 1400px !important;
244
- margin: 0 auto;
245
- width: 85%;
246
- }
247
-
248
- .gr-textbox textarea {
249
- font-size: 14px !important;
250
- line-height: 1.5 !important;
251
- }
252
-
253
- .resposta-container {
254
- background-color: #ffffff !important;
255
- color: #1a1a1a !important;
256
- border: 1px solid #e0e0e0 !important;
257
- border-radius: 20px !important;
258
- padding: 20px !important;
259
- margin: 20px 0 !important;
260
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05) !important;
261
- }
262
-
263
- .resposta-container pre code {
264
- color: #1a1a1a !important;
265
- background-color: #f8f9fa !important;
266
- }
267
-
268
- .pergunta-container {
269
- background-color: #f0f8ff !important;
270
- border-radius: 8px !important;
271
- padding: 15px !important;
272
- }
273
-
274
- .titulo-principal {
275
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
276
- color: white !important;
277
- padding: 20px !important;
278
- border-radius: 10px !important;
279
- margin-bottom: 20px !important;
280
- text-align: center !important;
281
- }
282
-
283
- .modelo-dropdown {
284
- margin-bottom: 15px !important;
285
- }
286
  """
287
 
288
  # Interface Gradio
289
- with gr.Blocks(
290
- title="Dr. Aldo Henrique - API Externa",
291
- theme=gr.themes.Soft(),
292
- css=css_customizado
293
- ) as interface:
294
-
295
- # Cabeçalho estilizado
296
  gr.HTML("""
297
  <div class="titulo-principal">
298
- <h1>🤖 Dr. Aldo Henrique - Especialista em TI</h1>
299
- <p style="font-size: 14px; opacity: 0.9;">Dúvidas sobre C, Java, desenvolvimento web, IA e muito mais!</p>
300
  </div>
301
  """)
302
 
303
  with gr.Row():
304
- # Coluna da esquerda - Entrada
305
  with gr.Column(scale=2):
306
  gr.Markdown("### 📝 Faça sua pergunta:")
307
- entrada = gr.Textbox(
308
- label="",
309
- placeholder="Digite sua pergunta aqui.",
310
- lines=6,
311
- elem_classes="pergunta-container"
312
- )
313
-
314
- modelo_select = gr.Dropdown(
315
- choices=list(MODELS.keys()),
316
- value=DEFAULT_MODEL,
317
- label="🧠 Selecione o Modelo de IA",
318
- info="Escolha o modelo para responder sua pergunta",
319
- elem_classes="modelo-dropdown"
320
- )
321
 
322
  with gr.Row():
323
- botao_perguntar = gr.Button(
324
- "🤔 Perguntar ao Dr. Aldo",
325
- variant="primary",
326
- size="lg"
327
- )
328
- botao_testar = gr.Button(
329
- "🔍 Testar Modelos",
330
- variant="secondary"
331
- )
332
-
333
- # Coluna da direita - Saída
334
  with gr.Column(scale=3):
335
  gr.Markdown("### 💬 Resposta do Dr. Aldo Henrique:")
336
- saida = gr.HTML(
337
- label="",
338
- value="<div style='padding: 20px; text-align: center; color: #1a1a1a;'>Aguardando sua pergunta...</div>",
339
- elem_classes="resposta-container"
340
- )
341
-
342
- # Seção expandida para exemplos
343
  with gr.Accordion("📚 Exemplos de Perguntas", open=False):
344
  gr.Examples(
345
  examples=[
346
  ["Como implementar uma lista ligada em C com todas as operações básicas?", DEFAULT_MODEL],
347
- ["Explique a diferença entre == e equals() em Java com exemplos práticos", "Mistral 7B"],
348
- ["Como funciona o algoritmo de machine learning Random Forest?", "Llama 3.2 3B (Meta)"],
349
- ["Mostre como criar uma API REST completa com Spring Boot", "Zephyr 7B"]
350
  ],
351
  inputs=[entrada, modelo_select]
352
  )
353
 
354
- # Status da API (expandível)
355
  with gr.Accordion("🔧 Status da API", open=False):
356
- status_api = gr.Textbox(
357
- label="Status dos Modelos",
358
- interactive=False,
359
- lines=8
360
- )
361
 
362
- # Informações adicionais
363
  with gr.Accordion("ℹ️ Informações", open=False):
364
  gr.Markdown("""
365
  ### Sobre o Dr. Aldo Henrique:
366
  - **Especialidade**: Linguagens C, Java, Desenvolvimento Web, Inteligência Artificial
367
- - **Foco**: Explicações didáticas e exemplos práticos
368
- - **Abordagem**: Acadêmica e profissional
369
-
370
- ### Modelos Disponíveis:
371
- - **Phi-3 Mini**: Modelo compacto e eficiente da Microsoft
372
- - **Llama 3.2 3B**: Versão menor do modelo da Meta
373
- - **Mistral 7B**: Modelo francês de alta qualidade
374
- - **Zephyr 7B**: Versão otimizada para diálogo
375
-
376
  ### Dicas para melhores respostas:
377
- - Seja específico em suas perguntas
378
- - Mencione o contexto (iniciante, intermediário, avançado)
379
- - Peça exemplos práticos quando necessário
380
  """)
381
-
382
  # Eventos
383
- botao_perguntar.click(
384
- fn=responder_como_aldo,
385
- inputs=[entrada, modelo_select],
386
- outputs=saida,
387
- show_progress=True
388
- )
389
-
390
- botao_testar.click(
391
- fn=testar_todos_modelos,
392
- outputs=status_api,
393
- show_progress=True
394
- )
395
 
396
  # Lançar aplicação
397
  if __name__ == "__main__":
398
- print("🚀 Iniciando Dr. Aldo Henrique com Interface Melhorada...")
 
 
 
 
399
  print(f"🔑 Token HF encontrado: {HF_TOKEN[:8]}...")
400
- print("🌐 Interface otimizada para melhor visualização!")
401
- print("💻 Formatação especial para códigos implementada!")
402
 
403
  interface.launch(
404
  server_name="0.0.0.0",
 
3
  import json
4
  import re
5
  import gradio as gr
6
+ from typing import Dict, Any, List, Optional
7
+ import pickle
8
 
9
+ # --- Novas importações para o RAG ---
10
+ import time
11
+ from bs4 import BeautifulSoup
12
+ from urllib.parse import urljoin, urlparse
13
+ from langchain.text_splitter import RecursiveCharacterTextSplitter
14
+ from langchain.vectorstores import FAISS
15
+ from langchain.embeddings import HuggingFaceEmbeddings
16
+
17
+ # --- Configuração do RAG ---
18
+ BLOG_URL = "https://aldohenrique.com.br/"
19
+ VECTOR_STORE_PATH = "faiss_index_store.pkl"
20
+ PROCESSED_URLS_PATH = "processed_urls.pkl"
21
+
22
+ # --- Configuração da API Hugging Face ---
23
  HF_TOKEN = os.getenv("HF_TOKEN")
24
  if not HF_TOKEN:
25
  raise ValueError("Token HF_TOKEN não encontrado nas variáveis de ambiente")
26
 
 
27
  MODELS = {
28
  "Phi-3 Mini (Microsoft)": "microsoft/Phi-3-mini-4k-instruct",
29
  "Mistral 7B": "mistralai/Mistral-7B-Instruct-v0.3",
30
  "Zephyr 7B": "HuggingFaceH4/zephyr-7b-beta",
31
+ "Llama 3.2 3B (Meta)": "meta-llama/Llama-3.2-3B-Instruct",
32
+ "DeepSeek-Coder-V2": "deepseek-ai/DeepSeek-Coder-V2-Lite-Instruct",
33
  }
 
 
34
  DEFAULT_MODEL = "Phi-3 Mini (Microsoft)"
35
 
36
+ # --- Variáveis Globais para o RAG ---
37
+ vector_store: Optional[FAISS] = None
38
+
39
+ # ==============================================================================
40
+ # SEÇÃO RAG: FUNÇÕES PARA CRAWLING, EMBEDDING E ARMAZENAMENTO
41
+ # ==============================================================================
42
+
43
+ def get_all_blog_links(url: str, processed_urls: set) -> set:
44
+ """Navega pelo blog para encontrar todos os links de posts e páginas."""
45
+ links_to_visit = {url}
46
+ visited_links = set()
47
+
48
+ while links_to_visit:
49
+ current_url = links_to_visit.pop()
50
+ if current_url in visited_links:
51
+ continue
52
+
53
+ try:
54
+ response = requests.get(current_url, timeout=10)
55
+ response.raise_for_status()
56
+ soup = BeautifulSoup(response.content, 'html.parser')
57
+ visited_links.add(current_url)
58
+ print(f"Visitando: {current_url}")
59
+
60
+ for link in soup.find_all('a', href=True):
61
+ href = link['href']
62
+ full_url = urljoin(url, href)
63
+ # Garante que estamos no mesmo domínio e não é um link de âncora
64
+ if urlparse(full_url).netloc == urlparse(url).netloc and full_url not in visited_links:
65
+ links_to_visit.add(full_url)
66
+ except requests.RequestException as e:
67
+ print(f"Erro ao acessar {current_url}: {e}")
68
+
69
+ # Filtra apenas as páginas que parecem ser posts ou páginas de conteúdo
70
+ final_links = {link for link in visited_links if '/tag/' not in link and '/category/' not in link and '?' not in link}
71
+ return final_links
72
+
73
+
74
+ def scrape_text_from_url(url: str) -> str:
75
+ """Extrai o texto principal (de artigos) de uma URL."""
76
+ try:
77
+ response = requests.get(url, timeout=10)
78
+ soup = BeautifulSoup(response.content, 'html.parser')
79
+ # Tenta encontrar a tag <article> ou <main> que geralmente contém o conteúdo principal
80
+ main_content = soup.find('article') or soup.find('main')
81
+ if main_content:
82
+ return main_content.get_text(separator='\n', strip=True)
83
+ return ""
84
+ except Exception as e:
85
+ print(f"Erro ao raspar {url}: {e}")
86
+ return ""
87
+
88
+ def build_and_save_vector_store() -> str:
89
+ """
90
+ Função principal do RAG: raspa o blog, cria chunks, gera embeddings e salva o vector store.
91
+ Esta é a nossa função de "treino".
92
+ """
93
+ global vector_store
94
+ start_time = time.time()
95
+
96
+ print("Iniciando o processo de retreino do RAG...")
97
+ processed_urls = set()
98
+
99
+ # 1. Obter todos os links do blog
100
+ all_links = get_all_blog_links(BLOG_URL, processed_urls)
101
+ print(f"Encontrados {len(all_links)} links para processar.")
102
+
103
+ # 2. Raspar o texto de cada link
104
+ all_texts = [scrape_text_from_url(link) for link in all_links if link not in processed_urls]
105
+ all_texts = [text for text in all_texts if text] # Remove textos vazios
106
+ print(f"Textos extraídos de {len(all_texts)} novas páginas.")
107
+
108
+ if not all_texts:
109
+ return "Nenhum novo conteúdo encontrado para treinar."
110
+
111
+ # 3. Dividir os textos em chunks
112
+ text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=150)
113
+ chunks = text_splitter.create_documents(all_texts)
114
+ print(f"Textos divididos em {len(chunks)} chunks.")
115
+
116
+ # 4. Criar embeddings e o vector store (FAISS)
117
+ print("Carregando modelo de embedding...")
118
+ embeddings_model = HuggingFaceEmbeddings(model_name="sentence-transformers/all-MiniLM-L6-v2")
119
+
120
+ print("Criando o vector store com FAISS...")
121
+ vector_store = FAISS.from_documents(chunks, embeddings_model)
122
+
123
+ # 5. Salvar o vector store e as URLs processadas em disco
124
+ with open(VECTOR_STORE_PATH, "wb") as f:
125
+ pickle.dump(vector_store, f)
126
+
127
+ with open(PROCESSED_URLS_PATH, "wb") as f:
128
+ pickle.dump(all_links, f)
129
+
130
+ end_time = time.time()
131
+ return f"✅ Retreino do RAG concluído em {end_time - start_time:.2f} segundos. {len(chunks)} chunks de texto processados."
132
+
133
+ def load_vector_store():
134
+ """Carrega o vector store do arquivo, se existir."""
135
+ global vector_store
136
+ if os.path.exists(VECTOR_STORE_PATH):
137
+ print(f"Carregando vector store existente de '{VECTOR_STORE_PATH}'...")
138
+ with open(VECTOR_STORE_PATH, "rb") as f:
139
+ vector_store = pickle.load(f)
140
+ print("Vector store carregado com sucesso.")
141
+ else:
142
+ print("Nenhum vector store encontrado. É necessário treinar o modelo.")
143
+ # Inicia o treino automaticamente se não houver um índice
144
+ build_and_save_vector_store()
145
+
146
+ def retrieve_context_from_blog(query: str, k: int = 3) -> str:
147
+ """Busca no vector store por chunks de texto similares à pergunta."""
148
+ if vector_store:
149
+ try:
150
+ results = vector_store.similarity_search(query, k=k)
151
+ context = "\n\n---\n\n".join([doc.page_content for doc in results])
152
+ return context
153
+ except Exception as e:
154
+ return f"Erro ao buscar contexto: {e}"
155
+ return ""
156
+
157
+ # ==============================================================================
158
+ # SEÇÃO API CLIENT: CÓDIGO ORIGINAL PARA CHAMAR A API DO HUGGING FACE
159
+ # ==============================================================================
160
+
161
  class HuggingFaceAPIClient:
162
  def __init__(self, token: str):
163
  self.token = token
 
165
  "Authorization": f"Bearer {token}",
166
  "Content-Type": "application/json"
167
  }
168
+
169
+ def query_model(self, model_name: str, messages: list, max_tokens: int = 1500) -> str:
170
  """Faz requisição para a API do Hugging Face"""
171
  url = f"https://api-inference.huggingface.co/models/{model_name}/v1/chat/completions"
 
172
  payload = {
173
  "model": model_name,
174
  "messages": messages,
 
177
  "top_p": 0.9,
178
  "stream": False
179
  }
 
180
  try:
181
  response = requests.post(url, headers=self.headers, json=payload, timeout=9999)
 
182
  if response.status_code == 200:
183
  result = response.json()
184
  return result["choices"][0]["message"]["content"]
185
  else:
 
186
  return self._fallback_text_generation(model_name, messages, max_tokens)
 
187
  except Exception as e:
188
  return f"Erro na API: {str(e)}"
189
+
190
  def _fallback_text_generation(self, model_name: str, messages: list, max_tokens: int) -> str:
 
191
  url = f"https://api-inference.huggingface.co/models/{model_name}"
 
 
192
  prompt = self._messages_to_prompt(messages)
 
193
  payload = {
194
  "inputs": prompt,
195
  "parameters": {
196
+ "max_new_tokens": max_tokens, "temperature": 0.7, "top_p": 0.9,
197
+ "do_sample": True, "return_full_text": False
 
 
 
198
  },
199
+ "options": {"wait_for_model": True, "use_cache": False}
 
 
 
200
  }
 
201
  try:
202
  response = requests.post(url, headers=self.headers, json=payload, timeout=9999)
 
203
  if response.status_code == 200:
204
  result = response.json()
205
  if isinstance(result, list) and len(result) > 0:
206
  generated_text = result[0].get("generated_text", "")
 
207
  if generated_text:
 
208
  if "Assistente: " in generated_text:
209
  parts = generated_text.split("Assistente: ")
210
+ if len(parts) > 1: return parts[-1].strip()
 
211
  return generated_text.strip()
212
  return "Resposta vazia"
213
  elif isinstance(result, dict):
214
+ if "error" in result: return f"Erro do modelo: {result['error']}"
215
+ elif "generated_text" in result: return result["generated_text"].strip()
 
 
216
  return "Formato de resposta inesperado"
217
+ elif response.status_code == 404: return f"❌ Modelo '{model_name}' não encontrado."
218
+ elif response.status_code == 503: return " Modelo carregando... Tente novamente."
219
+ elif response.status_code == 429: return "⚠️ Muitas requisições. Tente novamente."
220
+ else: return f"Erro HTTP {response.status_code}: {response.text[:200]}..."
 
 
 
 
 
221
  except requests.Timeout:
222
+ return "⏰ Timeout - Modelo demorou muito para responder."
223
  except Exception as e:
224
  return f"Erro na requisição: {str(e)}"
225
+
226
  def _messages_to_prompt(self, messages: list) -> str:
 
227
  prompt = ""
228
  for msg in messages:
229
+ prompt += f"{msg['role'].capitalize()}: {msg['content']}\n\n"
 
 
 
 
 
 
230
  prompt += "Assistente: "
231
  return prompt
232
 
233
  # Inicializar cliente da API
234
  api_client = HuggingFaceAPIClient(HF_TOKEN)
235
 
236
+ # ==============================================================================
237
+ # SEÇÃO PRINCIPAL: LÓGICA DO CHATBOT E GRADIO
238
+ # ==============================================================================
239
+
240
  def formatar_resposta_com_codigo(resposta: str) -> str:
241
+ """Formata a resposta destacando códigos em blocos separados."""
242
+ if not resposta: return resposta
 
 
 
243
  resposta_formatada = re.sub(
244
  r'```(\w+)?\n(.*?)\n```',
245
  r'<div style="background-color: #f8f9fa; color: #1a1a1a; border: 1px solid #e9ecef; border-radius: 8px; padding: 15px; margin: 10px 0; font-family: Monaco, Consolas, monospace; overflow-x: auto;"><strong style="color: #1a1a1a;">💻 Código:</strong><br><pre style="color: #1a1a1a; margin: 5px 0; white-space: pre-wrap; word-wrap: break-word;"><code>\2</code></pre></div>',
246
+ resposta, flags=re.DOTALL
 
247
  )
 
 
248
  resposta_formatada = re.sub(
249
  r'`([^`]+)`',
250
  r'<code style="background-color: #f1f3f4; color: #1a1a1a; padding: 2px 4px; border-radius: 4px; font-family: Monaco, Consolas, monospace;">\1</code>',
251
  resposta_formatada
252
  )
 
 
 
253
  resposta_formatada = resposta_formatada.replace('\n', '<br>')
 
 
254
  resposta_formatada = re.sub(
255
  r'^\*\*(.*?)\*\*',
256
  r'<h3 style="color: #1a1a1a; margin-top: 20px; margin-bottom: 10px;">\1</h3>',
257
+ resposta_formatada, flags=re.MULTILINE
 
258
  )
 
259
  return resposta_formatada
260
 
261
  def responder_como_aldo(pergunta: str, modelo_escolhido: str = DEFAULT_MODEL) -> str:
262
+ """Função principal para gerar respostas, agora com RAG."""
263
  if not pergunta.strip():
264
  return "Por favor, faça uma pergunta."
265
+
266
  try:
267
+ # --- ETAPA DE RAG ---
268
+ print(f"Buscando contexto para a pergunta: '{pergunta[:50]}...'")
269
+ contexto_blog = retrieve_context_from_blog(pergunta)
270
+
271
+ # Montar o prompt do sistema com o contexto do RAG
272
+ system_prompt = (
273
+ "Você é o professor Dr. Aldo Henrique, especialista em C, Java, desenvolvimento web e inteligência artificial. "
274
+ "Responda com clareza, profundidade e tom acadêmico. Foque em explicar e não em só mostrar o resultado. "
275
+ "Responda sempre em português brasileiro. Use blocos de código formatados com ```. "
276
+ "Não responda nada se a pergunta não for sobre o universo de programação e tecnologia."
277
+ )
278
 
279
+ # Montar prompt do usuário, injetando o contexto do blog
280
+ if contexto_blog:
281
+ pergunta_completa = (
282
+ "Com base no seguinte contexto extraído do seu blog, responda à pergunta do usuário.\n\n"
283
+ "--- CONTEXTO DO BLOG ---\n"
284
+ f"{contexto_blog}\n"
285
+ "--- FIM DO CONTEXTO ---\n\n"
286
+ f"PERGUNTA DO USUÁRIO: {pergunta}"
287
+ )
288
+ print("Contexto encontrado e injetado no prompt.")
289
+ else:
290
+ pergunta_completa = f"{pergunta} Não responda nada se a pergunta não for sobre o universo de programação e tecnologia, informe que o Dr. Aldo Henrique só tem domínio em TI. Você é o Professor Dr. Aldo Henrique, foque em explicar e não em só mostrar o resultado. Quando apresentar código, use blocos de código formatados com ```. Sempre responda primeiro a explicação e depois modestre o código."
291
+ print("Nenhum contexto relevante encontrado no blog, usando prompt padrão.")
292
+
293
  messages = [
294
+ {"role": "system", "content": system_prompt},
295
+ {"role": "user", "content": pergunta_completa}
 
 
 
 
 
 
296
  ]
297
 
 
298
  model_name = MODELS.get(modelo_escolhido, MODELS[DEFAULT_MODEL])
299
+ resposta = api_client.query_model(model_name, messages)
300
 
 
 
 
 
301
  if resposta.startswith("Assistente: "):
302
  resposta = resposta.replace("Assistente: ", "")
303
+
 
304
  resposta_formatada = formatar_resposta_com_codigo(resposta.strip())
 
305
  return resposta_formatada
306
 
307
  except Exception as e:
308
  return f"Erro ao processar sua pergunta: {str(e)}"
309
 
310
+ # Funções de teste (inalteradas)
311
  def verificar_modelo_disponivel(model_name: str) -> str:
 
312
  try:
313
  url = f"https://api-inference.huggingface.co/models/{model_name}"
314
  headers = {"Authorization": f"Bearer {HF_TOKEN}"}
315
+ payload = {"inputs": "Hello", "parameters": {"max_new_tokens": 5}}
 
 
 
 
 
 
316
  response = requests.post(url, headers=headers, json=payload, timeout=9999)
317
+ if response.status_code == 200: return "✅ Disponível"
318
+ elif response.status_code == 404: return "❌ Não encontrado"
319
+ elif response.status_code == 503: return " Carregando..."
320
+ else: return f"⚠️ Status {response.status_code}"
 
 
 
 
 
 
321
  except Exception as e:
322
  return f"❌ Erro: {str(e)[:50]}..."
323
 
324
  def testar_todos_modelos():
 
325
  resultados = []
326
  for nome, modelo in MODELS.items():
327
  status = verificar_modelo_disponivel(modelo)
328
  resultados.append(f"{nome}: {status}")
329
  return "\n".join(resultados)
330
 
331
+ # CSS (inalterado)
332
  css_customizado = """
333
+ .gradio-container { max-width: 1400px !important; margin: 0 auto; width: 85%; }
334
+ .gr-textbox textarea { font-size: 14px !important; line-height: 1.5 !important; }
335
+ .resposta-container { background-color: #ffffff !important; color: #1a1a1a !important; border: 1px solid #e0e0e0 !important; border-radius: 20px !important; padding: 20px !important; margin: 20px 0 !important; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05) !important; }
336
+ .resposta-container pre code { color: #1a1a1a !important; background-color: #f8f9fa !important; }
337
+ .pergunta-container { background-color: #f0f8ff !important; border-radius: 8px !important; padding: 15px !important; }
338
+ .titulo-principal { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important; color: white !important; padding: 20px !important; border-radius: 10px !important; margin-bottom: 20px !important; text-align: center !important; }
339
+ .modelo-dropdown { margin-bottom: 15px !important; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
340
  """
341
 
342
  # Interface Gradio
343
+ with gr.Blocks(title="Dr. Aldo Henrique - API Externa", theme=gr.themes.Soft(), css=css_customizado) as interface:
 
 
 
 
 
 
344
  gr.HTML("""
345
  <div class="titulo-principal">
346
+ <h1>🤖 Dr. Aldo Henrique - Especialista em TI (com RAG)</h1>
347
+ <p style="font-size: 14px; opacity: 0.9;">Conhecimento enriquecido com o conteúdo do <a href="https://aldohenrique.com.br/" style="color: white; text-decoration: underline;">Blog do Prof. Dr. Aldo Henrique</a></p>
348
  </div>
349
  """)
350
 
351
  with gr.Row():
 
352
  with gr.Column(scale=2):
353
  gr.Markdown("### 📝 Faça sua pergunta:")
354
+ entrada = gr.Textbox(label="", placeholder="Digite sua pergunta aqui.", lines=6, elem_classes="pergunta-container")
355
+ modelo_select = gr.Dropdown(choices=list(MODELS.keys()), value=DEFAULT_MODEL, label="🧠 Selecione o Modelo de IA", info="Escolha o modelo para responder", elem_classes="modelo-dropdown")
 
 
 
 
 
 
 
 
 
 
 
 
356
 
357
  with gr.Row():
358
+ botao_perguntar = gr.Button("🤔 Perguntar ao Dr. Aldo", variant="primary", size="lg")
359
+ botao_testar = gr.Button("🔍 Testar Modelos", variant="secondary")
360
+
 
 
 
 
 
 
 
 
361
  with gr.Column(scale=3):
362
  gr.Markdown("### 💬 Resposta do Dr. Aldo Henrique:")
363
+ saida = gr.HTML(label="", value="<div style='padding: 20px; text-align: center; color: #1a1a1a;'>Aguardando sua pergunta...</div>", elem_classes="resposta-container")
364
+
365
+ # --- NOVA SEÇÃO PARA CONTROLE DO RAG ---
366
+ with gr.Accordion("⚙️ Controle do Conhecimento (RAG)", open=False):
367
+ status_rag = gr.Textbox(label="Status do Retreino", interactive=False)
368
+ botao_retreinar = gr.Button("🔄 Atualizar Conhecimento do Blog", variant="stop")
369
+
370
  with gr.Accordion("📚 Exemplos de Perguntas", open=False):
371
  gr.Examples(
372
  examples=[
373
  ["Como implementar uma lista ligada em C com todas as operações básicas?", DEFAULT_MODEL],
374
+ ["Qual a sua opinião sobre o uso de ponteiros em C++ moderno, baseada no seu blog?", "Mistral 7B"],
375
+ ["Resuma o que você escreveu sobre machine learning no seu blog.", "Llama 3.2 3B (Meta)"],
 
376
  ],
377
  inputs=[entrada, modelo_select]
378
  )
379
 
 
380
  with gr.Accordion("🔧 Status da API", open=False):
381
+ status_api = gr.Textbox(label="Status dos Modelos", interactive=False, lines=8)
 
 
 
 
382
 
 
383
  with gr.Accordion("ℹ️ Informações", open=False):
384
  gr.Markdown("""
385
  ### Sobre o Dr. Aldo Henrique:
386
  - **Especialidade**: Linguagens C, Java, Desenvolvimento Web, Inteligência Artificial
387
+ - **Conhecimento Adicional**: Conteúdo do blog aldohenrique.com.br
 
 
 
 
 
 
 
 
388
  ### Dicas para melhores respostas:
389
+ - Faça perguntas específicas sobre o conteúdo do blog para ver o RAG em ação!
390
+ - Peça resumos ou opiniões sobre temas que o professor aborda.
 
391
  """)
392
+
393
  # Eventos
394
+ botao_perguntar.click(fn=responder_como_aldo, inputs=[entrada, modelo_select], outputs=saida, show_progress=True)
395
+ botao_testar.click(fn=testar_todos_modelos, outputs=status_api, show_progress=True)
396
+ botao_retreinar.click(fn=build_and_save_vector_store, outputs=status_rag, show_progress=True)
 
 
 
 
 
 
 
 
 
397
 
398
  # Lançar aplicação
399
  if __name__ == "__main__":
400
+ print("🚀 Iniciando Dr. Aldo Henrique com RAG...")
401
+
402
+ # Carrega ou constrói o vector store na inicialização
403
+ load_vector_store()
404
+
405
  print(f"🔑 Token HF encontrado: {HF_TOKEN[:8]}...")
406
+ print("🌐 Interface pronta!")
 
407
 
408
  interface.launch(
409
  server_name="0.0.0.0",