from fastapi import APIRouter, HTTPException, UploadFile, File, Form
from pydantic import BaseModel
from typing import Optional
import os
from utils.helpers import extract_json_from_text, get_gemini_model
router = APIRouter()
LASALLE_REF_PATH = os.path.join(os.path.dirname(os.path.dirname(__file__)), "la salle portugues.txt")
LASALLE_MATH_REF_PATH = os.path.join(os.path.dirname(os.path.dirname(__file__)), "la salle matematica-raciocinio.txt")
LASALLE_REFERENCE = ""
LASALLE_MATH_REFERENCE = ""
def load_lasalle_reference():
global LASALLE_REFERENCE, LASALLE_MATH_REFERENCE
if os.path.exists(LASALLE_REF_PATH):
try:
with open(LASALLE_REF_PATH, "r", encoding="utf-8") as f:
LASALLE_REFERENCE = f.read().strip()
print(f"✅ [LA SALLE-PT] Referências carregadas ({len(LASALLE_REFERENCE)} caracteres).")
except Exception as e:
print(f"⚠️ [LA SALLE-PT] Erro ao carregar: {e}")
else:
print(f"❌ [LA SALLE-PT] Arquivo de referência não encontrado.")
if os.path.exists(LASALLE_MATH_REF_PATH):
try:
with open(LASALLE_MATH_REF_PATH, "r", encoding="utf-8") as f:
LASALLE_MATH_REFERENCE = f.read().strip()
print(f"✅ [LA SALLE-MATH] Referências carregadas ({len(LASALLE_MATH_REFERENCE)} caracteres).")
except Exception as e:
print(f"⚠️ [LA SALLE-MATH] Erro ao carregar: {e}")
else:
print(f"❌ [LA SALLE-MATH] Arquivo de referência não encontrado.")
load_lasalle_reference()
EDITAL_MAP = {
"portugues": "1. PORTUGUÊS: Análise global do texto. Compreensão e interpretação de textos. Tipologia textual e gêneros textuais. Variedade de textos e adequação de linguagem. Figuras e funções da linguagem. Estruturação do texto e dos parágrafos. Informações literais e inferências. Coesão e coerência textual. Ortografia oficial. Relações entre fonemas e grafias. Acentuação gráfica. Morfologia. Classes de palavras e seu emprego. Flexões de palavras. Significação de palavras e expressões. Estrutura e formação de palavras. Estruturas sintáticas. Concordância nominal e verbal. Regência verbal e nominal. Processos de coordenação e subordinação. Equivalência e transformação de estruturas. Discurso direto e indireto. Colocação pronominal. Crase. Pontuação.",
"matematica": "2. RACIOCÍNIO LÓGICO E MATEMÁTICA: Resolução de problemas de raciocínio matemático: Operações entre números reais. Teoria dos conjuntos. Grandezas diretamente proporcionais e grandezas inversamente proporcionais. Razão e Proporção. Porcentagem. Regra de três simples e composta. Juros simples e compostos. Resolução de equações polinomiais do 1º e 2º grau. Cálculos estatísticos. Média, Moda e Mediana. Análise e interpretação de gráficos e tabelas. Sistema de medidas: comprimento, capacidade, massa e tempo (unidades e transformação de unidades). Resolução de problemas de raciocínio lógico: sentenças abertas; proposições lógicas simples e compostas; conectivos lógicos (conjunção, disjunção, disjunção exclusiva, condicional e bicondicional); negações; número de linhas de uma tabela-verdade; valores lógicos das proposições e construção e interpretação de tabelas-verdade. Raciocínio sequencial, dedução, associação entre elementos (pessoas, objetos, lugares, eventos).",
"legislacao": "3. LEGISLAÇÃO: Lei Orgânica Municipal. Lei Municipal nº 6.055/2006 - Dispõe Sobre o Regime Jurídico e o Estatuto dos Servidores Públicos do Município de São Leopoldo e dá Outras Providências. Lei Municipal nº 5.700/2005 - Reestrutura e Regulamenta o Regime Próprio de Previdência Social dos Servidores Efetivos do Município de São Leopoldo e dá Outras Providências. Lei Municipal nº 6.570/2008 - Estabelece o Plano de Cargos, Carreiras e Vencimentos do Poder Executivo de São Leopoldo e dá Outras Providências. Constituição Federal. Lei de Acesso à informação - Lei nº 12.527/2011. Lei de Improbidade Administrativa - Lei nº 8.429/1992. Lei Geral de Proteção de Dados Pessoais (LGPD) - Lei nº 13.709/2018.",
"informatica": "4. INFORMÁTICA: Microsoft Word 2016 ou superior e versão online - Office 365: Ambiente e componentes do programa: identificação, personalização e configuração. Documentos: criação, abertura, formatação, salvamento, alteração e visualização. Utilização das guias e grupos para formatar e configurar textos. Função Ajuda. Microsoft Excel 2016 ou superior e versão online - Office 365: Ambiente e componentes: identificação e personalização. Células, planilhas e pastas: definição, seleção e manipulação. Utilização de fórmulas envolvendo operações aritméticas e estatísticas; referenciar células em fórmulas; tabelas dinâmica (Pivot Tables); gráficos. Criação, formatação, salvamento, alteração e configuração de planilhas. Uso das guias. Compreensão do significado e resultado das fórmulas. Função Ajuda. Ferramentas e aplicativos de navegação: Google Chrome, Mozilla Firefox e Microsoft Edge em suas versões mais recentes. Identificação do ambiente, características e funcionalidades. Uso de menus, ferramentas, barras de comandos, favoritos e teclas de atalho. Internet e Intranet: Conceituar, identificar, caracterizar e diferenciar Internet e Intranet. Diferenciar acessos em redes locais e globais, acessar plataformas digitais e utilizar mecanismos de busca. Programas de E-mail: Outlook Express, Microsoft Outlook e Gmail em suas versões mais recentes: contas e endereços de e-mail, envio, resposta, encaminhamento, destinatário oculto, anexos, importação e exportação de mensagens, organização da caixa de entrada e uso de menus e atalhos. Sistema Operacional Microsoft Windows 10 ou superior: Área de Trabalho e Menu Iniciar: exibição, organização, classificação, atualização, resolução de tela e gadgets. Acesso e configuração de Documentos, Imagens, Computador, Painel de Controle, Dispositivos e Impressoras, Programas Padrão, Ajuda e Suporte, desligamento e pesquisa de programas e arquivos. Barra de Tarefas, Menu Iniciar e Gerenciador de Tarefas: propriedades, exibição, organização, fechamento e configuração de programas. Janelas, Painel de Controle e Lixeira: navegação, exibição, alteração e organização de arquivos, pastas e bibliotecas. Bibliotecas, arquivos, pastas, ícones e atalhos: localizar, copiar, mover, criar, renomear, excluir, ocultar, criptografar, colar e enviar. Identificação e uso de nomes válidos. Extensões de compactadores; procedimentos, aplicativos e dispositivos para armazenamento de dados e para realização de cópia de segurança (backup); permissões, históricos e pesquisas do Windows; atalhos de teclas gerais. Aspectos de segurança de sistemas computacionais: vírus, antivírus, spyware, antispyware, malware. Noções de computação na nuvem: Google Drive, OneDrive e ambientes de armazenamento online. Noções sobre Ferramentas colaborativas: Google Docs, Google Planilhas, Google Apresentações, Google Formulários. Limites e possibilidades da Inteligência Artificial. Cidadania digital e combate ao cyberbullying. Noções sobre LGPD e Inteligência Artificial: consentimento, transparência e proteção de dados pessoais."
}
class ExplainQuestionRequest(BaseModel):
question_text: Optional[str] = None
options: Optional[list] = None
correct_option: Optional[str] = None
user_option: Optional[str] = None
subject: Optional[str] = "Geral"
chat_metadata: Optional[list] = None
user_message: Optional[str] = None
class ExplainContentRequest(BaseModel):
content_text: str
subject: Optional[str] = "Geral"
chat_metadata: Optional[list] = None
user_message: Optional[str] = None
class FeynmanRequest(BaseModel):
content_text: str
subject: Optional[str] = "Geral"
chat_metadata: Optional[list] = None
user_message: str
class EvaluateDiscursiveRequest(BaseModel):
question_text: str
expected_answer: str
user_answer: str
subject: Optional[str] = "Geral"
chat_metadata: Optional[list] = None
PROFESSOR_PERSONAS = {
"Português": {
"name": "Helena",
"personality": "uma pessoa real, amigável, direta e prática. Ela fala de forma clara, como uma mentora que entende as dificuldades do aluno e foca no que realmente cai na prova. É profissional, mas sem ser robótica."
},
"Matemática e Raciocínio": {
"name": "Ricardo",
"personality": "um mentor prático, bem-humorado e objetivo. Ele é especialista em Matemática e Raciocínio Lógico, focando na resolução de problemas e na simplificação de conceitos complexos de forma natural e motivadora, sem formalismos desnecessários."
},
"Legislação": {
"name": "Marco",
"personality": "um mentor experiente mas totalmente 'pé no chão'. Ele fala de forma simples, sem juridiquês, como um colega de trabalho dando uma dica valiosa."
},
"Informática": {
"name": "Fábio",
"personality": "um cara tech super descontraído. Ele é direto, usa termos do dia a dia e não tem paciência para introduções longas ou textos motivacionais genéricos."
}
}
DEFAULT_PERSONA = {
"name": "Simon",
"personality": "um mentor prático, informal e muito direto. Focado em resultados e sem frescuras."
}
def get_persona_for_subject(subject: str) -> dict:
s = subject.lower()
if any(k in s for k in ['português', 'portuguesa', 'gramática', 'acentuação']):
return PROFESSOR_PERSONAS["Português"]
if any(k in s for k in ['matemática', 'matematica', 'raciocínio', 'raciocinio', 'lógica', 'logica', 'rlm']):
return PROFESSOR_PERSONAS["Matemática e Raciocínio"]
if any(k in s for k in ['legislação', 'lei', 'direito', 'norma']):
return PROFESSOR_PERSONAS["Legislação"]
if any(k in s for k in ['informática', 'computação', 'tech', 'software']):
return PROFESSOR_PERSONAS["Informática"]
return DEFAULT_PERSONA
@router.post("/generate-flashcards")
async def generate_flashcards_endpoint(
file: UploadFile = File(...),
source: str = Form(default="Conteúdo da Apostila"),
count: int = Form(default=10),
style: str = Form(default="normal"),
):
import core.globals as g
if not g.client:
raise HTTPException(status_code=500, detail="Gemini client is not initialized")
try:
raw_bytes = await file.read()
content_text = raw_bytes.decode("utf-8", errors="replace").strip()
if len(content_text) < 30:
raise HTTPException(status_code=400, detail="Conteúdo muito curto para gerar flashcards.")
if len(content_text) > 40000:
content_text = content_text[:40000]
style_instruction = ""
if style == "hardcore":
style_instruction = "FOCO: Exceções, pegadinhas de prova e detalhes minuciosos que costumam gerar confusão."
elif style == "conceptual":
style_instruction = "FOCO: Definições básicas, conceitos fundamentais e a 'essência' teórica."
else:
style_instruction = "FOCO: Equilibrado entre conceitos fundamentais e regras práticas."
prompt = f"""Você é um especialista em preparação para concursos públicos brasileiros.
SUA MISSÃO: Analise o seguinte trecho de apostila e extraia os conceitos mais importantes.
⚠️ REGRAS CRÍTICAS E OBRIGATÓRIAS:
1. Use EXCLUSIVAMENTE as informações contidas no "CONTEÚDO DA APOSTILA" abaixo.
2. NÃO use conhecimentos externos e NÃO adicione exemplos que não estejam no texto.
3. Gere EXATAMENTE {count} flashcards.
4. {style_instruction}
Fonte: "{source}"
CONTEÚDO DA APOSTILA:
{content_text}
INSTRUÇÕES DE FORMATO:
- Cada flashcard deve ter uma PERGUNTA objetiva e uma RESPOSTA clara e direta.
- A resposta deve ser curta e objetiva (1 a 3 frases).
FORMATO DE SAÍDA OBRIGATÓRIO (JSON puro, sem markdown, sem texto extra):
[
{{"pergunta": "...", "resposta": "..."}},
{{"pergunta": "...", "resposta": "..."}}
]"""
print(f"🃏 Gerando flashcards para: {source[:60]} ({len(content_text)} chars)")
model_obj = get_gemini_model("flash")
response = await g.client.generate_content(prompt, model=model_obj)
data = extract_json_from_text(response.text)
if not data:
raise HTTPException(status_code=500, detail="A IA não retornou um JSON válido.")
if isinstance(data, dict) and "flashcards" in data:
data = data["flashcards"]
if not isinstance(data, list):
raise HTTPException(status_code=500, detail="Formato de resposta inesperado da IA.")
cards = [c for c in data if isinstance(c, dict) and c.get("pergunta") and c.get("resposta")]
print(f"✅ {len(cards)} flashcards gerados com sucesso para: {source[:60]}")
return cards
except Exception as e:
print(f"❌ Erro no /generate-flashcards: {e}")
raise HTTPException(status_code=500, detail=str(e))
@router.post("/generate-questions")
async def generate_questions_endpoint(
file: UploadFile = File(...),
source: str = Form(default="Conteúdo da Apostila"),
count: int = Form(default=5),
include_base_text: bool = Form(default=True),
subject: str = Form(default="Português"),
custom_instruction: str = Form(default=""),
question_type: str = Form(default="multiple_choice"),
text_length: str = Form(default="medium"),
):
import core.globals as g
if not g.client:
raise HTTPException(status_code=500, detail="Gemini client is not initialized")
try:
raw_bytes = await file.read()
content_text = raw_bytes.decode("utf-8", errors="replace").strip()
if len(content_text) < 30:
raise HTTPException(status_code=400, detail="Conteúdo muito curto para gerar questões.")
if len(content_text) > 40000:
content_text = content_text[:40000]
banca = "Fundação La Salle"
subj_lower = subject.lower()
is_portuguese = "português" in subj_lower or "portugues" in subj_lower
is_math = any(k in subj_lower for k in ["matemática", "matematica", "raciocínio", "raciocinio", "lógica", "logica", "rlm"])
active_reference = ""
if is_portuguese:
active_reference = LASALLE_REFERENCE
elif is_math:
active_reference = LASALLE_MATH_REFERENCE
math_latex_instruction = ""
if is_math:
math_latex_instruction = """
7. FORMATAÇÃO MATEMÁTICA OBRIGATÓRIA: Toda e qualquer expressão matemática, fórmula, número isolado em contexto de conta, frações ou símbolos lógicos DEVEM estar envoltos em LaTeX.
- Use '$' para fórmulas inline (ex: $2+2=4$).
- Use '$$' para fórmulas em blocos destacados (ex: $\\Delta = b^2 - 4ac$).
- Siga o estilo visual do arquivo de referência (ex: frações com \\frac, raízes com \\sqrt, etc).
"""
base_text_instruction = ""
if question_type == "token_classify":
base_text_instruction = "8. PROIBIDO fornecer texto de apoio. O campo 'texto_base' DEVE ser null."
elif include_base_text:
base_text_instruction = """
8. TEXTO DE APOIO OBRIGATÓRIO: Forneça um 'Texto de Apoio' contextual para as questões.
IMPORTANTE: O texto deve seguir o padrão da banca La Salle, incluindo a numeração de linhas lateral (ex: 01 ..., 02 ...).
Ao se referir a este texto no enunciado das questões, utilize termos naturais como 'texto de apoio', 'texto acima' ou 'fragmento textual', NUNCA utilize termos técnicos ou nomes de chaves JSON.
"""
else:
base_text_instruction = "8. PROIBIDO fornecer texto de apoio. Vá direto para as questões. O campo 'texto_base' no JSON DEVE ser null."
custom_instruction_prompt = ""
if custom_instruction:
custom_instruction_prompt = f"\nINSTRUÇÃO PERSONALIZADA DO USUÁRIO (SIGA À RISCA): {custom_instruction}\n"
relevant_edital = "Geral"
if is_portuguese:
relevant_edital = EDITAL_MAP["portugues"]
elif is_math:
relevant_edital = EDITAL_MAP["matematica"]
elif any(k in subj_lower for k in ["legislação", "lei", "direito"]):
relevant_edital = EDITAL_MAP["legislacao"]
elif any(k in subj_lower for k in ["informática", "computação"]):
relevant_edital = EDITAL_MAP["informatica"]
base_text_null = "null" if (not include_base_text or question_type == "token_classify") else '"Texto (com linhas 01, 02...)"'
if question_type == "token_classify":
if text_length == "short":
length_instruction = "CRIE UMA FRASE CURTA E DIRETA (no máximo 10 palavras)."
elif text_length == "long":
length_instruction = "CRIE UM TEXTO LONGO. OBRIGATÓRIO: Escreva um parágrafo inteiro contendo no mínimo 3 frases completas, com pontos finais, vírgulas e dezenas de palavras no total."
else:
length_instruction = "Crie uma frase de tamanho médio, dinâmica e com vocabulário rico (cerca de 15 a 20 palavras)."
format_instructions = f"""5. TIPO DE QUESTÃO: Análise morfossintática por classificação de tokens.
6. MISSÃO PRINCIPAL: Leia o CONTEÚDO DA APOSTILA enviado e identifique QUAIS CATEGORIAS GRAMATICAIS são o foco do módulo estudado.
- As categorias devem vir EXCLUSIVAMENTE do conteúdo ensinado no módulo.
7. Crie EXATAMENTE {count} questões. Cada questão deve conter:
- {length_instruction} (NÃO copie da apostila, crie textos inéditos).
- TODOS os tokens (palavras e pontuações tratadas como tokens se desejar, ou apenas palavras) do texto.
- SELECIONE ESTRATEGICAMENTE de 3 a 6 palavras do texto que se encaixam nas categorias estudadas para o aluno classificar. Para essas, coloque a classificação correta.
- PARA TODAS AS OUTRAS PALAVRAS que não são o foco do estudo ou que não interessam, OBRIGATORIAMENTE defina a 'classificacao' como 'none'. Assim o sistema saberá quais ignorar.
- O campo 'categorias' com a lista das categorias que o aluno poderá escolher.
8. SINTAXE JSON (CRÍTICO): Retorne um JSON válido. A chave 'tipo' DEVE ser 'token_classify' em cada questão.
"""
json_format = """[
{{
"texto_base": null,
"enunciado": "Classifique as palavras destacadas no texto abaixo de acordo com a categoria estudada.",
"tipo": "token_classify",
"texto_completo": "Insira o texto gerado aqui...",
"tokens": [
{{"id": 1, "texto": "Os", "classificacao": "none"}},
{{"id": 2, "texto": "meus", "classificacao": "none"}},
{{"id": 3, "texto": "dois", "classificacao": "none"}},
{{"id": 4, "texto": "gatos", "classificacao": "substantivo"}},
{{"id": 5, "texto": "pretos", "classificacao": "adjetivo"}},
{{"id": 6, "texto": "comeram", "classificacao": "verbo"}},
{{"id": 7, "texto": "ração", "classificacao": "none"}},
{{"id": 8, "texto": "hoje.", "classificacao": "none"}}
],
"categorias": ["substantivo","verbo","adjetivo","advérbio","preposição","pronome","artigo","numeral","conjunção","interjeição"],
"bancas": [{{"nome": "Fundação La Salle"}}],
"anos": [2024]
}}
]"""
elif question_type == "discursive":
format_instructions = f"""5. Formato Discursivo: Gere EXATAMENTE {count} questões onde o aluno precisará redigir a resposta. Não forneça alternativas. O campo 'itens' DEVE ser vazio ou omitido.
6. A chave 'resposta_esperada' DEVE conter os critérios detalhados para considerar a resposta do aluno correta.
7. A chave 'dica' DEVE conter uma técnica de recuperação ativa (memory retrieval). Use uma metáfora, uma pergunta instigante ou a primeira letra de uma palavra-chave para ativar a memória do aluno, mas NUNCA dê a resposta diretamente.
8. SINTAXE JSON (CRÍTICO): Retorne um JSON válido com a lista de questões geradas, e a chave 'tipo': 'discursiva' em cada uma.
"""
json_format = f"""[
{{
"texto_base": {base_text_null},
"enunciado": "...",
"tipo": "discursiva",
"resposta_esperada": "Os critérios essenciais são X, Y e Z...",
"dica": "Lembre-se da regra do C.A.S.A... O que o 'C' significa no contexto de...",
"bancas": [{{"nome": "{{banca}}"}}],
"anos": [2024]
}}
]"""
else:
format_instructions = f"""5. Quantidade e Itens: Gere EXATAMENTE {count} questões, cada uma com 5 alternativas (A a E). É CRUCIAL que CADA alternativa seja um objeto com "id", "rotulo" e "corpo_clean".
6. Resposta: O campo 'resposta' deve ser o ID da alternativa correta (1 a 5).
7. SINTAXE JSON (CRÍTICO): Retorne um JSON rigorosamente válido."""
json_format = f"""[
{{
"texto_base": {base_text_null},
"enunciado": "...",
"itens": [
{{"id": 1, "rotulo": "A", "corpo_clean": "..."}},
{{"id": 2, "rotulo": "B", "corpo_clean": "..."}},
{{"id": 3, "rotulo": "C", "corpo_clean": "..."}},
{{"id": 4, "rotulo": "D", "corpo_clean": "..."}},
{{"id": 5, "rotulo": "E", "corpo_clean": "..."}}
],
"resposta": 1,
"bancas": [{{"nome": "{{banca}}"}}],
"anos": [2024]
}}
]"""
prompt = f"""Você é um especialista em elaboração de questões de {subject} para concursos públicos brasileiros de NÍVEL MÉDIO.
SUA REFERÊNCIA DE ESTILO (Siga APENAS o ESTILO, VOCABULÁRIO e PADRÃO DE COBRANÇA da Banca Fundação La Salle. PROIBIDO COPIAR O CONTEÚDO):
--- INÍCIO REFERÊNCIA DE ESTILO ---
{active_reference if active_reference else f"Banca foca em {subject} de nível médio."}
--- FIM REFERÊNCIA DE ESTILO ---
LIMITAÇÃO DE ESCOPO (EDITAL PARA A MATÉRIA {subject}):
{relevant_edital}
{custom_instruction_prompt}
⚠️ REGRAS CRÍTICAS E INVIOLÁVEIS (LEIA COM ATENÇÃO):
1. FONTE ÚNICA DE CONTEÚDO: As questões DEVEM ser baseadas EXCLUSIVAMENTE nos conceitos e temas presentes no "CONTEÚDO DA APOSTILA" (fornecido abaixo). Proibido gerar questões sobre temas que estejam no Edital mas NÃO estejam na Apostila.
2. CRIATIVIDADE ABSOLUTA: Proibido copiar enunciados, exemplos ou textos da "REFERÊNCIA DE ESTILO" ou da "APOSTILA". Use a referência apenas para entender COMO a banca pergunta, mas formule cenários e perguntas 100% INÉDITAS.
3. NÍVEL MÉDIO: Todas as questões devem ser de nível médio. Evite complexidades fora do escopo do Edital listado acima.
4. FOCO NA MATÉRIA: Gere questões apenas de {subject}. Se a apostila contiver outros assuntos, ignore-os e foque apenas em {subject}.
{format_instructions}
{math_latex_instruction}
{base_text_instruction}
9. SEM HTML: NÃO use tags HTML.
CONTEÚDO DA APOSTILA (ÚNICA FONTE DE TEMAS PARA AS QUESTÕES):
{content_text}
FORMATO DE SAÍDA OBRIGATÓRIO (JSON puro):
{json_format}
"""
print(f"📝 Gerando questões La Salle ({count} q) | Matéria: {subject} | Fonte: {source[:50]}")
ref_status = "Matemática/RLM" if is_math else ("Português" if is_portuguese else "Inativa (sem referência específica)")
print(f"📡 [DEBUG] Texto Base: {include_base_text} | Referência La Salle: {ref_status}")
model_obj = get_gemini_model("flash")
response = await g.client.generate_content(prompt, model=model_obj)
data = extract_json_from_text(response.text)
if not data:
raise HTTPException(status_code=500, detail="A IA não retornou um JSON de questões válido.")
print(f"✅ Questões geradas com sucesso para: {source[:60]}")
return data
except Exception as e:
print(f"❌ Erro no /generate-questions: {e}")
raise HTTPException(status_code=500, detail=str(e))
@router.post("/explain-question")
async def explain_question_endpoint(request: ExplainQuestionRequest):
import core.globals as g
if not g.client:
raise HTTPException(status_code=500, detail="Gemini client is not initialized")
try:
subject = request.subject or "Geral"
persona = get_persona_for_subject(subject)
model_obj = get_gemini_model("flash")
chat_session = g.client.start_chat(metadata=request.chat_metadata, model=model_obj)
options_text = "\n".join([f"- {opt}" for opt in (request.options or [])])
if request.user_message:
prompt = f"""Você é {persona['name']}, {persona['personality']}.
Responda ao aluno de forma direta e amigável.
MENSAGEM DO ALUNO: "{request.user_message}"
CONTEXTO DA QUESTÃO:
Enunciado: {request.question_text or 'Não fornecido'}
Opções: {options_text}
RESPOSTA DA BANCA: {request.correct_option or 'Não fornecida'}
O QUE O ALUNO MARCOU (ERRO): {request.user_option or 'Não fornecido'}
INSTRUÇÕES:
- Seja sociável, profissional e didático.
- Explique o erro conceitual de forma clara.
- Use um tom de conversa humana, mas educado.
- Responda em Markdown.
- NÃO use tags HTML (como
). Use apenas Markdown padrão.
- Use quebras de linha duplas (\n\n) para parágrafos.
"""
else:
prompt = f"""Você é {persona['name']}, {persona['personality']}.
Você é um mentor especialista em {subject}. O aluno errou uma questão e precisa entender o porquê.
CONTEXTO DA QUESTÃO:
Enunciado: {request.question_text or 'Não fornecido'}
Opções: {options_text}
RESPOSTA DA BANCA: {request.correct_option or 'Não fornecida'}
O QUE O ALUNO MARCOU (ERRO): {request.user_option or 'Não fornecido'}
INSTRUÇÕES:
- Inicie a conversa de forma amigável e acolhedora.
- Seja sociável, profissional e didático.
- Explique o erro conceitual e o assunto central.
- Use um tom de conversa humana, mas educado.
- Responda em Markdown.
- NÃO use tags HTML (como
). Use apenas Markdown padrão.
- Use quebras de linha duplas (\n\n) para parágrafos.
"""
print(f"💡 {persona['name']} explicando questão de {subject}...")
output = await chat_session.send_message(prompt)
return {
"explanation": output.text,
"chat_metadata": chat_session.metadata,
"persona_name": persona['name']
}
except Exception as e:
print(f"❌ Erro no /explain-question: {e}")
raise HTTPException(status_code=500, detail=str(e))
@router.post("/explain-content")
async def explain_content_endpoint(request: ExplainContentRequest):
import core.globals as g
if not g.client:
raise HTTPException(status_code=500, detail="Gemini client is not initialized")
try:
subject = request.subject or "Geral"
persona = get_persona_for_subject(subject)
model_obj = get_gemini_model("flash")
chat_session = g.client.start_chat(metadata=request.chat_metadata, model=model_obj)
if request.user_message:
prompt = f"""Você é {persona['name']}, {persona['personality']}.
Responda à dúvida do aluno sobre o conteúdo abaixo.
MENSAGEM DO ALUNO: "{request.user_message}"
CONTEÚDO DA APOSTILA:
{request.content_text}
INSTRUÇÕES:
- Seja sociável, profissional e didático.
- Use um tom de conversa humana (estilo WhatsApp), mas sempre educado e respeitoso.
- Responda em Markdown.
- NÃO use tags HTML (como
). Use apenas Markdown padrão.
- Use quebras de linha duplas (\n\n) para parágrafos.
"""
else:
prompt = f"""Você é {persona['name']}, {persona['personality']}.
Você é um mentor especialista em {subject}. O aluno está lendo o conteúdo abaixo e quer uma explicação.
CONTEÚDO DA APOSTILA:
{request.content_text}
INSTRUÇÕES:
- Seja sociável, profissional e direto.
- Dê uma visão geral pedagógica e destaque o que é mais importante para provas.
- Use um tom de conversa humana amigável, mas educado.
- Use Markdown.
"""
print(f"💡 {persona['name']} explicando conteúdo de {subject}...")
output = await chat_session.send_message(prompt)
return {
"explanation": output.text,
"chat_metadata": chat_session.metadata,
"persona_name": persona['name']
}
except Exception as e:
print(f"❌ Erro no /explain-content: {e}")
raise HTTPException(status_code=500, detail=str(e))
@router.post("/evaluate-discursive")
async def evaluate_discursive_endpoint(request: EvaluateDiscursiveRequest):
import core.globals as g
from utils.helpers import extract_json_from_text
if not g.client:
raise HTTPException(status_code=500, detail="Gemini client is not initialized")
try:
subject = request.subject or "Geral"
persona = get_persona_for_subject(subject)
model_obj = get_gemini_model("flash")
chat_session = g.client.start_chat(metadata=request.chat_metadata, model=model_obj)
prompt = f"""Você é {persona['name']}, atuando como Professor corretor de questões discursivas.
QUESTÃO:
{request.question_text}
EXPECTATIVA / GABARITO DA BANCA:
{request.expected_answer}
RESPOSTA DO ALUNO:
"{request.user_answer}"
SUA MISSÃO:
1. Avalie a resposta do aluno de forma EXTREMAMENTE OBJETIVA, DIRETA e RIGOROSA. Sem rodeios e sem enrolação.
2. Não tenha pena: se estiver errado, diga que está errado. Se faltar a palavra-chave, diga que está incompleto.
3. Se o aluno ACERTOU, diga algo como: "Correto. [resumo em 1 frase do porquê]". Nada de textos longos.
4. Se o aluno ERROU, diga algo como: "Incorreto. O conceito correto é X, porque Y.".
5. Se o aluno respondeu PARCIALMENTE, diga: "Incompleto. Você esqueceu de mencionar Z.".
6. O feedback deve ter no MÁXIMO 3 linhas. Use texto limpo e conciso.
Você DEVE retornar RIGOROSAMENTE no formato JSON abaixo, sem mais nenhum texto adicional. A chave 'feedback' aceita Markdown:
{{
"status": "correct" | "partial" | "incorrect",
"feedback": "Seu feedback objetivo e direto em Markdown."
}}
"""
print(f"💡 {persona['name']} avaliando discursiva de {subject}...")
output = await chat_session.send_message(prompt)
data = extract_json_from_text(output.text)
if not data or "feedback" not in data or "status" not in data:
data = {
"status": "partial",
"feedback": output.text.replace("```json", "").replace("```", "").strip()
}
return {
"status": data["status"],
"feedback": data["feedback"],
"chat_metadata": chat_session.metadata,
"persona_name": persona['name']
}
except Exception as e:
print(f"❌ Erro no /evaluate-discursive: {e}")
raise HTTPException(status_code=500, detail=str(e))