Spaces:
Runtime error
Runtime error
Upload 3 files
Browse files- .env +3 -0
- app.py +185 -0
- requirements.txt +16 -0
.env
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
OPENROUTER_API_KEY=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
|
2 |
+
|
3 |
+
GROQ_API_KEY=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
|
app.py
ADDED
@@ -0,0 +1,185 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import sqlite3
|
3 |
+
import tempfile
|
4 |
+
import pandas as pd
|
5 |
+
from bs4 import BeautifulSoup
|
6 |
+
import requests
|
7 |
+
from langchain_community.document_loaders import PyPDFLoader, WebBaseLoader
|
8 |
+
from langchain.text_splitter import RecursiveCharacterTextSplitter
|
9 |
+
from langchain_community.vectorstores import FAISS
|
10 |
+
from langchain_community.embeddings import HuggingFaceEmbeddings
|
11 |
+
from langchain_openai import ChatOpenAI
|
12 |
+
from langchain.chains import ConversationalRetrievalChain
|
13 |
+
from langchain.memory import ConversationBufferMemory
|
14 |
+
import gradio as gr
|
15 |
+
from langchain_community.document_loaders import PyPDFLoader
|
16 |
+
|
17 |
+
# Configurações
|
18 |
+
embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/all-MiniLM-L6-v2")
|
19 |
+
text_splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=100)
|
20 |
+
|
21 |
+
# Variáveis globais para armazenamento em memória
|
22 |
+
pdf_documents = []
|
23 |
+
vectordb_pdf = None
|
24 |
+
qa_chain_pdf = None
|
25 |
+
|
26 |
+
# Mapeamento de temas para links sobre a prova
|
27 |
+
temas_para_links = {
|
28 |
+
"estruturas de prova": "https://www.florence.edu.br/blog/como-e-dividida-a-prova-do-enem/",
|
29 |
+
"linguagens, códigos e suas tecnologias": "https://www.todamateria.com.br/linguagens-codigos-e-suas-tecnologias/",
|
30 |
+
"Ciências Humanas e suas Tecnologias": "https://www.todamateria.com.br/ciencias-humanas-e-suas-tecnologias/",
|
31 |
+
"Ciências da Natureza e suas Tecnologias": "https://www.todamateria.com.br/ciencias-da-natureza-e-suas-tecnologias/",
|
32 |
+
"Matemática e suas Tecnologias": "https://fia.com.br/blog/matematica-e-suas-tecnologias/",
|
33 |
+
"redação": "https://vestibular.brasilescola.uol.com.br/enem/saiba-tudo-sobre-a-redacao-do-enem.htm"
|
34 |
+
}
|
35 |
+
|
36 |
+
# Inicializa o LLM e a memória
|
37 |
+
llm = ChatOpenAI(
|
38 |
+
model="deepseek/deepseek-r1:free",
|
39 |
+
temperature=0.4,
|
40 |
+
openai_api_key=os.environ.get("OPENROUTER_API_KEY"),
|
41 |
+
openai_api_base="https://openrouter.ai/api/v1"
|
42 |
+
)
|
43 |
+
memoria = ConversationBufferMemory(memory_key="chat_history", return_messages=True, output_key="answer")
|
44 |
+
|
45 |
+
# Cria banco de dados SQLite em memória
|
46 |
+
conn = sqlite3.connect(":memory:")
|
47 |
+
cursor = conn.cursor()
|
48 |
+
cursor.execute('''
|
49 |
+
CREATE TABLE IF NOT EXISTS conversas (
|
50 |
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
51 |
+
aluno TEXT,
|
52 |
+
pergunta TEXT,
|
53 |
+
resposta TEXT,
|
54 |
+
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
|
55 |
+
)
|
56 |
+
''')
|
57 |
+
conn.commit()
|
58 |
+
|
59 |
+
# Funções auxiliares
|
60 |
+
def processar_pdfs(files):
|
61 |
+
global pdf_documents, vectordb_pdf, qa_chain_pdf
|
62 |
+
|
63 |
+
pdf_documents = []
|
64 |
+
if not files:
|
65 |
+
return "⚠️ Nenhum arquivo foi enviado."
|
66 |
+
|
67 |
+
for file in files:
|
68 |
+
try:
|
69 |
+
# Se for NamedString
|
70 |
+
if isinstance(file, gr.FileData) or hasattr(file, "path"):
|
71 |
+
tmp_path = file.path
|
72 |
+
file_name = getattr(file, 'name', 'Arquivo desconhecido')
|
73 |
+
elif isinstance(file, str):
|
74 |
+
tmp_path = file # Assume que o 'file' é um caminho direto
|
75 |
+
file_name = os.path.basename(file)
|
76 |
+
else:
|
77 |
+
return f"❌ Formato de arquivo não suportado: {str(type(file))}"
|
78 |
+
|
79 |
+
if not tmp_path or not os.path.exists(tmp_path):
|
80 |
+
return f"❌ Arquivo {file_name} não encontrado no caminho {tmp_path}"
|
81 |
+
|
82 |
+
# Processa o arquivo PDF
|
83 |
+
loader = PyPDFLoader(tmp_path)
|
84 |
+
docs = loader.load_and_split(text_splitter)
|
85 |
+
pdf_documents.extend(docs)
|
86 |
+
|
87 |
+
except Exception as e:
|
88 |
+
return f"❌ Erro ao processar {file_name if 'file_name' in locals() else 'PDF'}: {str(e)}"
|
89 |
+
|
90 |
+
if pdf_documents:
|
91 |
+
try:
|
92 |
+
vectordb_pdf = FAISS.from_documents(pdf_documents, embeddings)
|
93 |
+
qa_chain_pdf = ConversationalRetrievalChain.from_llm(
|
94 |
+
llm=llm,
|
95 |
+
retriever=vectordb_pdf.as_retriever(),
|
96 |
+
memory=memoria,
|
97 |
+
return_source_documents=True,
|
98 |
+
output_key="answer"
|
99 |
+
)
|
100 |
+
return f"✅ {len(files)} PDF(s) processado(s) - {len(pdf_documents)} trechos!"
|
101 |
+
except Exception as e:
|
102 |
+
return f"❌ Erro ao criar vetores: {str(e)}"
|
103 |
+
return "⚠️ Nenhum dado válido para processar."
|
104 |
+
|
105 |
+
def responder(pergunta, nome):
|
106 |
+
try:
|
107 |
+
# Primeiro tenta encontrar na documentação web
|
108 |
+
link = identificar_tema(pergunta)
|
109 |
+
if link:
|
110 |
+
loader = WebBaseLoader(link)
|
111 |
+
docs = loader.load()
|
112 |
+
if docs:
|
113 |
+
documents = text_splitter.split_documents(docs)
|
114 |
+
vectordb = FAISS.from_documents(documents, embeddings)
|
115 |
+
retriever = vectordb.as_retriever()
|
116 |
+
resultado = ConversationalRetrievalChain.from_llm(
|
117 |
+
llm=llm,
|
118 |
+
retriever=retriever,
|
119 |
+
memory=memoria,
|
120 |
+
return_source_documents=True,
|
121 |
+
output_key="answer"
|
122 |
+
).invoke({"question": pergunta})
|
123 |
+
resposta = resultado["answer"].content if hasattr(resultado["answer"], "content") else str(resultado["answer"])
|
124 |
+
salvar_conversa(nome, pergunta, resposta)
|
125 |
+
return resposta
|
126 |
+
|
127 |
+
# Se não encontrou na web, tenta nos PDFs locais
|
128 |
+
if pdf_documents:
|
129 |
+
resultado = qa_chain_pdf.invoke({"question": pergunta})
|
130 |
+
resposta = resultado["answer"]
|
131 |
+
salvar_conversa(nome, pergunta, resposta)
|
132 |
+
return resposta
|
133 |
+
|
134 |
+
# Fallback para o LLM puro
|
135 |
+
resposta = llm.invoke(pergunta).content
|
136 |
+
salvar_conversa(nome, pergunta, resposta)
|
137 |
+
return resposta
|
138 |
+
|
139 |
+
except Exception as e:
|
140 |
+
return f"❌ Erro ao processar sua pergunta: {str(e)}"
|
141 |
+
|
142 |
+
def resetar_memoria():
|
143 |
+
memoria.clear()
|
144 |
+
return "✅ Memória da conversa foi resetada!"
|
145 |
+
|
146 |
+
def exportar_conversas():
|
147 |
+
df = pd.read_sql_query("SELECT * FROM conversas ORDER BY timestamp DESC", conn)
|
148 |
+
|
149 |
+
# Cria arquivos temporários
|
150 |
+
with tempfile.NamedTemporaryFile(delete=False, suffix=".csv") as tmp_csv:
|
151 |
+
df.to_csv(tmp_csv.name, index=False)
|
152 |
+
|
153 |
+
with tempfile.NamedTemporaryFile(delete=False, suffix=".xlsx") as tmp_xlsx:
|
154 |
+
df.to_excel(tmp_xlsx.name, index=False, engine="openpyxl")
|
155 |
+
|
156 |
+
return [tmp_csv.name, tmp_xlsx.name]
|
157 |
+
|
158 |
+
# Interface Gradio
|
159 |
+
with gr.Blocks(title="Tutor de ENEM - Hugging Face") as app:
|
160 |
+
gr.Markdown("## 🤖 Tutor de ENEM - Busca em Múltiplos PDFs")
|
161 |
+
|
162 |
+
with gr.Tab("📚 Upload de PDFs"):
|
163 |
+
file_input = gr.File(label="Selecione os PDFs", file_count="multiple", file_types=[".pdf"])
|
164 |
+
upload_button = gr.Button("Processar PDFs")
|
165 |
+
upload_status = gr.Textbox(label="Status")
|
166 |
+
upload_button.click(processar_pdfs, inputs=file_input, outputs=upload_status)
|
167 |
+
|
168 |
+
with gr.Tab("💬 Conversar"):
|
169 |
+
with gr.Row():
|
170 |
+
nome = gr.Textbox(label="Seu nome (opcional)", placeholder="Ex: João")
|
171 |
+
pergunta = gr.Textbox(label="Sua dúvida sobre o ENEM", placeholder="Ex: Como se preparar para o ENEM?")
|
172 |
+
resposta = gr.Markdown(value="ℹ️ Aguardando sua pergunta...")
|
173 |
+
|
174 |
+
with gr.Row():
|
175 |
+
botao_enviar = gr.Button("Enviar", variant="primary")
|
176 |
+
botao_resetar = gr.Button("🔁 Resetar Memória")
|
177 |
+
botao_exportar = gr.Button("📤 Exportar Histórico")
|
178 |
+
|
179 |
+
botao_enviar.click(fn=responder, inputs=[pergunta, nome], outputs=resposta)
|
180 |
+
botao_resetar.click(fn=resetar_memoria, outputs=resposta)
|
181 |
+
|
182 |
+
export_files = gr.Files(label="Arquivos exportados")
|
183 |
+
botao_exportar.click(fn=exportar_conversas, outputs=export_files)
|
184 |
+
|
185 |
+
app.launch()
|
requirements.txt
ADDED
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
langchain==0.1.13
|
2 |
+
langchain-community==0.0.29
|
3 |
+
langchain-openai==0.1.1
|
4 |
+
sentence-transformers==2.7.0
|
5 |
+
faiss-cpu==1.7.4
|
6 |
+
python-dotenv==1.0.1
|
7 |
+
gradio==4.24.0
|
8 |
+
pandas==2.2.1
|
9 |
+
openpyxl==3.1.2
|
10 |
+
requests==2.31.0
|
11 |
+
pypdf==4.1.0
|
12 |
+
beautifulsoup4==4.12.3
|
13 |
+
html2text==2020.1.16
|
14 |
+
huggingface-hub==0.22.2
|
15 |
+
tiktoken==0.6.0
|
16 |
+
unstructured==0.13.4
|