Spaces:
Runtime error
Runtime error
Commit
·
cc51976
1
Parent(s):
3c016be
app update
Browse files- Dockerfile +6 -6
- app.py +43 -31
- app/questions.json +0 -0
- index.html +0 -29
- requirements.txt +2 -1
- static/index.html +65 -0
Dockerfile
CHANGED
@@ -4,12 +4,12 @@ WORKDIR /app
|
|
4 |
|
5 |
COPY . /app
|
6 |
|
7 |
-
#
|
8 |
-
RUN apt-get update && apt-get install -y
|
9 |
-
|
10 |
-
|
11 |
-
|
12 |
-
|
13 |
|
14 |
# Define o comando de execução
|
15 |
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
|
|
|
4 |
|
5 |
COPY . /app
|
6 |
|
7 |
+
# Atualiza e instala dependências do sistema
|
8 |
+
RUN apt-get update && apt-get install -y build-essential libssl-dev && rm -rf /var/lib/apt/lists/*
|
9 |
+
|
10 |
+
# Instala dependências Python
|
11 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
12 |
+
RUN pip install --no-cache-dir transformers accelerate sentencepiece torch
|
13 |
|
14 |
# Define o comando de execução
|
15 |
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
|
app.py
CHANGED
@@ -1,18 +1,29 @@
|
|
1 |
-
from fastapi import FastAPI
|
2 |
from transformers import AutoTokenizer, T5ForConditionalGeneration
|
|
|
|
|
3 |
import json
|
4 |
import os
|
5 |
import logging
|
6 |
import time
|
7 |
import gc
|
|
|
8 |
|
9 |
-
# Configura logging
|
10 |
logging.basicConfig(level=logging.INFO)
|
11 |
logger = logging.getLogger(__name__)
|
12 |
|
13 |
app = FastAPI()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
14 |
|
15 |
-
# Carrega
|
16 |
try:
|
17 |
with open("questions.json", "r", encoding="utf-8") as f:
|
18 |
examples = json.load(f)
|
@@ -21,10 +32,10 @@ except FileNotFoundError:
|
|
21 |
examples = []
|
22 |
logger.warning("questions.json não encontrado, usando lista vazia.")
|
23 |
|
24 |
-
# Função para carregar
|
25 |
def get_model():
|
26 |
if not hasattr(get_model, "model_data"):
|
27 |
-
logger.info("Carregando modelo e tokenizer
|
28 |
start_time = time.time()
|
29 |
try:
|
30 |
tokenizer = AutoTokenizer.from_pretrained(
|
@@ -43,14 +54,31 @@ def get_model():
|
|
43 |
get_model.model_data = None
|
44 |
return get_model.model_data
|
45 |
|
46 |
-
# Força
|
47 |
logger.info("Testando carregamento inicial do modelo...")
|
48 |
start_time = time.time()
|
49 |
model_data = get_model()
|
50 |
if model_data:
|
51 |
-
logger.info(f"Modelo e tokenizer inicializados
|
52 |
else:
|
53 |
-
logger.error("Falha na inicialização do modelo.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
54 |
|
55 |
def generate_question_from_prompt(theme, difficulty, example_question=None):
|
56 |
model_data = get_model()
|
@@ -68,15 +96,13 @@ def generate_question_from_prompt(theme, difficulty, example_question=None):
|
|
68 |
f"Explicação: {example_question['explanation']}"
|
69 |
)
|
70 |
prompt = (
|
71 |
-
f"Baseado no exemplo: '{example_text}', gere uma questão
|
72 |
-
f"
|
73 |
-
f"Formato: 'Enunciado clínico: [texto]. Alternativas: A) [opção], B) [opção], C) [opção], D) [opção]. "
|
74 |
f"Gabarito: [letra]. Explicação: [texto].'"
|
75 |
)
|
76 |
else:
|
77 |
prompt = (
|
78 |
-
f"Gere uma questão
|
79 |
-
f"estilo prova de residência médica da USP. Formato: "
|
80 |
f"'Enunciado clínico: [texto]. Alternativas: A) [opção], B) [opção], C) [opção], D) [opção]. "
|
81 |
f"Gabarito: [letra]. Explicação: [texto].'"
|
82 |
)
|
@@ -84,24 +110,7 @@ def generate_question_from_prompt(theme, difficulty, example_question=None):
|
|
84 |
inputs = tokenizer(prompt, return_tensors="pt", padding=True, truncation=True, max_length=512)
|
85 |
outputs = model.generate(**inputs, max_new_tokens=256, temperature=0.7, top_p=0.9)
|
86 |
response = tokenizer.decode(outputs[0], skip_special_tokens=True)
|
87 |
-
|
88 |
-
if len(parts) > 1:
|
89 |
-
question_part = parts[0].replace("Enunciado clínico:", "").strip()
|
90 |
-
options_part = parts[1].split("Gabarito:")[0].strip()
|
91 |
-
answer_part = parts[1].split("Gabarito:")[1].split("Explicação:")[0].strip()
|
92 |
-
explanation_part = parts[1].split("Explicação:")[1].strip() if "Explicação:" in parts[1] else "Explicação padrão"
|
93 |
-
options = [opt.strip() for opt in options_part.split(",")]
|
94 |
-
if len(options) >= 4:
|
95 |
-
result = {
|
96 |
-
"question": f"Enunciado clínico: {question_part}",
|
97 |
-
"options": [f"A) {options[0]}", f"B) {options[1]}", f"C) {options[2]}", f"D) {options[3]}"],
|
98 |
-
"answer": answer_part,
|
99 |
-
"explanation": explanation_part
|
100 |
-
}
|
101 |
-
# Libera memória
|
102 |
-
gc.collect()
|
103 |
-
return result
|
104 |
-
result = {"question": response, "options": [], "answer": "", "explanation": "Explicação padrão"}
|
105 |
gc.collect()
|
106 |
return result
|
107 |
except Exception as e:
|
@@ -110,6 +119,9 @@ def generate_question_from_prompt(theme, difficulty, example_question=None):
|
|
110 |
|
111 |
@app.get("/generate")
|
112 |
async def generate_question(theme: str, difficulty: str):
|
|
|
|
|
|
|
113 |
example = examples[0] if examples else None
|
114 |
return generate_question_from_prompt(theme, difficulty, example)
|
115 |
|
|
|
1 |
+
from fastapi import FastAPI, HTTPException
|
2 |
from transformers import AutoTokenizer, T5ForConditionalGeneration
|
3 |
+
from fastapi.staticfiles import StaticFiles
|
4 |
+
from fastapi.middleware.cors import CORSMiddleware
|
5 |
import json
|
6 |
import os
|
7 |
import logging
|
8 |
import time
|
9 |
import gc
|
10 |
+
import re
|
11 |
|
12 |
+
# Configura logging
|
13 |
logging.basicConfig(level=logging.INFO)
|
14 |
logger = logging.getLogger(__name__)
|
15 |
|
16 |
app = FastAPI()
|
17 |
+
app.mount("/", StaticFiles(directory="static", html=True), name="static")
|
18 |
+
app.add_middleware(
|
19 |
+
CORSMiddleware,
|
20 |
+
allow_origins=["*"],
|
21 |
+
allow_credentials=True,
|
22 |
+
allow_methods=["*"],
|
23 |
+
allow_headers=["*"],
|
24 |
+
)
|
25 |
|
26 |
+
# Carrega questions.json
|
27 |
try:
|
28 |
with open("questions.json", "r", encoding="utf-8") as f:
|
29 |
examples = json.load(f)
|
|
|
32 |
examples = []
|
33 |
logger.warning("questions.json não encontrado, usando lista vazia.")
|
34 |
|
35 |
+
# Função para carregar modelo e tokenizer
|
36 |
def get_model():
|
37 |
if not hasattr(get_model, "model_data"):
|
38 |
+
logger.info("Carregando modelo e tokenizer...")
|
39 |
start_time = time.time()
|
40 |
try:
|
41 |
tokenizer = AutoTokenizer.from_pretrained(
|
|
|
54 |
get_model.model_data = None
|
55 |
return get_model.model_data
|
56 |
|
57 |
+
# Força carregamento inicial
|
58 |
logger.info("Testando carregamento inicial do modelo...")
|
59 |
start_time = time.time()
|
60 |
model_data = get_model()
|
61 |
if model_data:
|
62 |
+
logger.info(f"Modelo e tokenizer inicializados em {time.time() - start_time:.2f} segundos.")
|
63 |
else:
|
64 |
+
logger.error("Falha na inicialização do modelo.")
|
65 |
+
|
66 |
+
def parse_model_output(response):
|
67 |
+
pattern = r"Enunciado clínico: (.*?)\s*Alternativas: (.*?)\s*Gabarito: (.*?)\s*Explicação: (.*)"
|
68 |
+
match = re.match(pattern, response, re.DOTALL)
|
69 |
+
if match:
|
70 |
+
question = match.group(1).strip()
|
71 |
+
options = [opt.strip() for opt in match.group(2).split(",")]
|
72 |
+
answer = match.group(3).strip()
|
73 |
+
explanation = match.group(4).strip()
|
74 |
+
if len(options) >= 4:
|
75 |
+
return {
|
76 |
+
"question": f"Enunciado clínico: {question}",
|
77 |
+
"options": [f"A) {options[0]}", f"B) {options[1]}", f"C) {options[2]}", f"D) {options[3]}"],
|
78 |
+
"answer": answer,
|
79 |
+
"explanation": explanation
|
80 |
+
}
|
81 |
+
return {"question": response, "options": [], "answer": "", "explanation": "Erro no parsing"}
|
82 |
|
83 |
def generate_question_from_prompt(theme, difficulty, example_question=None):
|
84 |
model_data = get_model()
|
|
|
96 |
f"Explicação: {example_question['explanation']}"
|
97 |
)
|
98 |
prompt = (
|
99 |
+
f"Baseado no exemplo: '{example_text}', gere uma questão sobre '{theme}', dificuldade {difficulty}, "
|
100 |
+
f"estilo prova USP. Formato: 'Enunciado clínico: [texto]. Alternativas: A) [opção], B) [opção], C) [opção], D) [opção]. "
|
|
|
101 |
f"Gabarito: [letra]. Explicação: [texto].'"
|
102 |
)
|
103 |
else:
|
104 |
prompt = (
|
105 |
+
f"Gere uma questão sobre '{theme}', dificuldade {difficulty}, estilo prova USP. Formato: "
|
|
|
106 |
f"'Enunciado clínico: [texto]. Alternativas: A) [opção], B) [opção], C) [opção], D) [opção]. "
|
107 |
f"Gabarito: [letra]. Explicação: [texto].'"
|
108 |
)
|
|
|
110 |
inputs = tokenizer(prompt, return_tensors="pt", padding=True, truncation=True, max_length=512)
|
111 |
outputs = model.generate(**inputs, max_new_tokens=256, temperature=0.7, top_p=0.9)
|
112 |
response = tokenizer.decode(outputs[0], skip_special_tokens=True)
|
113 |
+
result = parse_model_output(response)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
114 |
gc.collect()
|
115 |
return result
|
116 |
except Exception as e:
|
|
|
119 |
|
120 |
@app.get("/generate")
|
121 |
async def generate_question(theme: str, difficulty: str):
|
122 |
+
valid_difficulties = ["fácil", "médio", "difícil"]
|
123 |
+
if not theme or difficulty.lower() not in valid_difficulties:
|
124 |
+
raise HTTPException(status_code=400, detail="Tema inválido ou dificuldade deve ser 'fácil', 'médio' ou 'difícil'.")
|
125 |
example = examples[0] if examples else None
|
126 |
return generate_question_from_prompt(theme, difficulty, example)
|
127 |
|
app/questions.json
DELETED
The diff for this file is too large to render.
See raw diff
|
|
index.html
DELETED
@@ -1,29 +0,0 @@
|
|
1 |
-
<!DOCTYPE html>
|
2 |
-
<html lang="pt-br">
|
3 |
-
<head>
|
4 |
-
<title>Simulado USP</title>
|
5 |
-
<style>
|
6 |
-
body { font-family: Arial, sans-serif; margin: 20px; }
|
7 |
-
h1 { color: #2c3e50; }
|
8 |
-
button { padding: 10px 20px; background: #3498db; color: white; border: none; cursor: pointer; }
|
9 |
-
button:hover { background: #2980b9; }
|
10 |
-
#questions { margin-top: 20px; }
|
11 |
-
ul { list-style-type: upper-alpha; padding-left: 20px; }
|
12 |
-
li { margin-bottom: 5px; }
|
13 |
-
.question { border-bottom: 1px solid #ecf0f1; padding-bottom: 10px; margin-bottom: 10px; }
|
14 |
-
</style>
|
15 |
-
</head>
|
16 |
-
<body>
|
17 |
-
<h1>Simulado</h1>
|
18 |
-
<button onclick="loadSimulado()">Carregar</button>
|
19 |
-
<div id="questions"></div>
|
20 |
-
<script>
|
21 |
-
async function loadSimulado() {
|
22 |
-
const response = await fetch('/simulado?num_questions=5');
|
23 |
-
const data = await response.json();
|
24 |
-
const questionsDiv = document.getElementById('questions');
|
25 |
-
questionsDiv.innerHTML = data.simulado.map(q => `<div class="question">${q.question.replace(/\n/g, '<br>')}</div>`).join('');
|
26 |
-
}
|
27 |
-
</script>
|
28 |
-
</body>
|
29 |
-
</html>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
requirements.txt
CHANGED
@@ -3,4 +3,5 @@ uvicorn==0.23.2
|
|
3 |
transformers==4.45.0
|
4 |
accelerate==0.21.0
|
5 |
huggingface_hub
|
6 |
-
sentencepiece
|
|
|
|
3 |
transformers==4.45.0
|
4 |
accelerate==0.21.0
|
5 |
huggingface_hub
|
6 |
+
sentencepiece
|
7 |
+
torch
|
static/index.html
ADDED
@@ -0,0 +1,65 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<!DOCTYPE html>
|
2 |
+
<html lang="pt-br">
|
3 |
+
<head>
|
4 |
+
<title>Simulado USP</title>
|
5 |
+
<style>
|
6 |
+
body { font-family: Arial, sans-serif; margin: 20px; }
|
7 |
+
h1 { color: #2c3e50; }
|
8 |
+
button { padding: 10px 20px; background: #3498db; color: white; border: none; cursor: pointer; }
|
9 |
+
button:hover { background: #2980b9; }
|
10 |
+
#questions { margin-top: 20px; }
|
11 |
+
ul { list-style-type: upper-alpha; padding-left: 20px; }
|
12 |
+
li { margin-bottom: 5px; }
|
13 |
+
.question { border-bottom: 1px solid #ecf0f1; padding-bottom: 10px; margin-bottom: 10px; }
|
14 |
+
.answer { color: #27ae60; }
|
15 |
+
.explanation { font-style: italic; color: #7f8c8d; }
|
16 |
+
.error { color: #e74c3c; }
|
17 |
+
</style>
|
18 |
+
</head>
|
19 |
+
<body>
|
20 |
+
<h1>Simulado</h1>
|
21 |
+
<button onclick="loadSimulado()">Carregar</button>
|
22 |
+
<div id="questions"></div>
|
23 |
+
<script>
|
24 |
+
async function loadSimulado() {
|
25 |
+
console.log('Iniciando carregamento do simulado...');
|
26 |
+
try {
|
27 |
+
const response = await fetch('/simulado?num_questions=5');
|
28 |
+
console.log('Requisição enviada, status:', response.status);
|
29 |
+
if (!response.ok) {
|
30 |
+
throw new Error(`Erro na requisição: ${response.status} ${response.statusText}`);
|
31 |
+
}
|
32 |
+
const data = await response.json();
|
33 |
+
console.log('Dados recebidos:', data);
|
34 |
+
const questionsDiv = document.getElementById('questions');
|
35 |
+
questionsDiv.innerHTML = data.simulado.map((q, index) => `
|
36 |
+
<div class="question">
|
37 |
+
<p><strong>Questão ${index + 1}:</strong> ${q.question.replace(/\n/g, '<br>')}</p>
|
38 |
+
<ul>
|
39 |
+
${q.options.map((opt, i) => `
|
40 |
+
<li>
|
41 |
+
<input type="radio" name="q${index}" value="${opt}" id="q${index}_${i}">
|
42 |
+
<label for="q${index}_${i}">${opt}</label>
|
43 |
+
</li>
|
44 |
+
`).join('')}
|
45 |
+
</ul>
|
46 |
+
<button onclick="showAnswer(${index})">Verificar Resposta</button>
|
47 |
+
<p id="answer${index}" style="display: none;" class="answer">
|
48 |
+
<strong>Gabarito:</strong> ${q.answer}<br>
|
49 |
+
<strong>Explicação:</strong> ${q.explanation.replace(/\n/g, '<br>')}
|
50 |
+
</p>
|
51 |
+
</div>
|
52 |
+
`).join('');
|
53 |
+
} catch (error) {
|
54 |
+
console.error('Erro ao carregar simulado:', error);
|
55 |
+
document.getElementById('questions').innerHTML = `<p class="error">Erro ao carregar simulado: ${error.message}</p>`;
|
56 |
+
}
|
57 |
+
}
|
58 |
+
|
59 |
+
function showAnswer(index) {
|
60 |
+
console.log(`Exibindo resposta para questão ${index + 1}`);
|
61 |
+
document.getElementById(`answer${index}`).style.display = 'block';
|
62 |
+
}
|
63 |
+
</script>
|
64 |
+
</body>
|
65 |
+
</html>
|