| 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 <br>). 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 <br>). 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 <br>). 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)) |