diff --git a/Guia1/src/__pycache__/__init__.cpython-314.pyc b/Guia1/src/__pycache__/__init__.cpython-314.pyc new file mode 100644 index 0000000..999c127 Binary files /dev/null and b/Guia1/src/__pycache__/__init__.cpython-314.pyc differ diff --git a/Guia1/src/config/__pycache__/__init__.cpython-314.pyc b/Guia1/src/config/__pycache__/__init__.cpython-314.pyc new file mode 100644 index 0000000..8d1423d Binary files /dev/null and b/Guia1/src/config/__pycache__/__init__.cpython-314.pyc differ diff --git a/Guia1/src/config/__pycache__/settings.cpython-314.pyc b/Guia1/src/config/__pycache__/settings.cpython-314.pyc new file mode 100644 index 0000000..45b0e64 Binary files /dev/null and b/Guia1/src/config/__pycache__/settings.cpython-314.pyc differ diff --git a/Guia1/src/models/__pycache__/__init__.cpython-314.pyc b/Guia1/src/models/__pycache__/__init__.cpython-314.pyc new file mode 100644 index 0000000..96ae7fb Binary files /dev/null and b/Guia1/src/models/__pycache__/__init__.cpython-314.pyc differ diff --git a/Guia1/src/models/__pycache__/record.cpython-314.pyc b/Guia1/src/models/__pycache__/record.cpython-314.pyc new file mode 100644 index 0000000..4814a5e Binary files /dev/null and b/Guia1/src/models/__pycache__/record.cpython-314.pyc differ diff --git a/Guia1/src/repositories/__pycache__/__init__.cpython-314.pyc b/Guia1/src/repositories/__pycache__/__init__.cpython-314.pyc new file mode 100644 index 0000000..ab190b0 Binary files /dev/null and b/Guia1/src/repositories/__pycache__/__init__.cpython-314.pyc differ diff --git a/Guia1/src/repositories/__pycache__/abstract_repository.cpython-314.pyc b/Guia1/src/repositories/__pycache__/abstract_repository.cpython-314.pyc new file mode 100644 index 0000000..dccf22f Binary files /dev/null and b/Guia1/src/repositories/__pycache__/abstract_repository.cpython-314.pyc differ diff --git a/Guia1/src/repositories/__pycache__/record_repository.cpython-314.pyc b/Guia1/src/repositories/__pycache__/record_repository.cpython-314.pyc new file mode 100644 index 0000000..9269d19 Binary files /dev/null and b/Guia1/src/repositories/__pycache__/record_repository.cpython-314.pyc differ diff --git a/Guia1/src/services/__pycache__/__init__.cpython-314.pyc b/Guia1/src/services/__pycache__/__init__.cpython-314.pyc new file mode 100644 index 0000000..40bae60 Binary files /dev/null and b/Guia1/src/services/__pycache__/__init__.cpython-314.pyc differ diff --git a/Guia1/src/services/__pycache__/record_service.cpython-314.pyc b/Guia1/src/services/__pycache__/record_service.cpython-314.pyc new file mode 100644 index 0000000..5a2c760 Binary files /dev/null and b/Guia1/src/services/__pycache__/record_service.cpython-314.pyc differ diff --git a/Guia1/src/utils/__pycache__/__init__.cpython-314.pyc b/Guia1/src/utils/__pycache__/__init__.cpython-314.pyc new file mode 100644 index 0000000..32964a2 Binary files /dev/null and b/Guia1/src/utils/__pycache__/__init__.cpython-314.pyc differ diff --git a/Guia1/src/utils/__pycache__/file_loader.cpython-314.pyc b/Guia1/src/utils/__pycache__/file_loader.cpython-314.pyc new file mode 100644 index 0000000..44e2c03 Binary files /dev/null and b/Guia1/src/utils/__pycache__/file_loader.cpython-314.pyc differ diff --git a/Guia1/tests/__pycache__/__init__.cpython-314.pyc b/Guia1/tests/__pycache__/__init__.cpython-314.pyc new file mode 100644 index 0000000..edb1c8f Binary files /dev/null and b/Guia1/tests/__pycache__/__init__.cpython-314.pyc differ diff --git a/Guia1/tests/__pycache__/test_runner.cpython-314-pytest-9.0.3.pyc b/Guia1/tests/__pycache__/test_runner.cpython-314-pytest-9.0.3.pyc new file mode 100644 index 0000000..a80cdcc Binary files /dev/null and b/Guia1/tests/__pycache__/test_runner.cpython-314-pytest-9.0.3.pyc differ diff --git a/Guia3/src/alternativa.py b/Guia3/src/alternativa.py index 4dde61f..cdae2b5 100644 --- a/Guia3/src/alternativa.py +++ b/Guia3/src/alternativa.py @@ -1,4 +1,7 @@ from typing import List, Tuple, Dict class Alternativa: - pass \ No newline at end of file + def __init__(self, texto: str, correta: bool, explicacao: str = None): + self.texto = texto + self.correta = correta + self.explicacao = explicacao \ No newline at end of file diff --git a/Guia3/src/pergunta.py b/Guia3/src/pergunta.py index 5b3763d..34e4b74 100644 --- a/Guia3/src/pergunta.py +++ b/Guia3/src/pergunta.py @@ -1,4 +1,19 @@ from typing import List, Tuple, Dict +from abc import ABC, abstractmethod -class Pergunta: - pass \ No newline at end of file +class Pergunta(ABC): + def __init__(self, texto: str, explicacao_geral = None): + self._texto = texto + self._explicacao_geral = explicacao_geral + + @abstractmethod + def validar_resposta(self, resposta) -> bool: + pass + + @abstractmethod + def get_explicacao(self) -> str: + pass + + @abstractmethod + def get_tipo(self) -> str: + pass \ No newline at end of file diff --git a/Guia3/src/perguntadiscursiva.py b/Guia3/src/perguntadiscursiva.py index f4c26af..274f02b 100644 --- a/Guia3/src/perguntadiscursiva.py +++ b/Guia3/src/perguntadiscursiva.py @@ -1,4 +1,18 @@ from typing import List, Tuple, Dict +from pergunta import Pergunta -class PerguntaDiscursiva: - pass \ No newline at end of file +class PerguntaDiscursiva(Pergunta): + def __init__(self, texto, explicacao_geral = None, resposta_esperada: str = None): + super().__init__(texto, explicacao_geral) + self.resposta_esperada = resposta_esperada + + def validar_resposta(self, texto: str): + if self.resposta_esperada is None: + return False + return texto.strip().lower() == self.resposta_esperada.strip().lower() + + def get_explicacao(self) -> str: + return self.explicacao_geral + + def get_tipo(self): + return "discursiva" \ No newline at end of file diff --git a/Guia3/src/perguntamultiplaescolha.py b/Guia3/src/perguntamultiplaescolha.py index bcbe94d..75a6368 100644 --- a/Guia3/src/perguntamultiplaescolha.py +++ b/Guia3/src/perguntamultiplaescolha.py @@ -1,4 +1,63 @@ from typing import List, Tuple, Dict +from pergunta import Pergunta -class PerguntaMultiplaEscolha: - pass \ No newline at end of file +class PerguntaMultiplaEscolha(Pergunta): + def __init__(self, texto: str, alternativas: List[Tuple[str, bool]], explicacao_geral = None): + super().__init__(texto, explicacao_geral) + self.alternativas = alternativas + self.explicacao_geral = explicacao_geral + + def _obter_texto_correta(self, alternativa): + if isinstance(alternativa, tuple): + return alternativa[0], alternativa[1] + if isinstance(alternativa, dict): + return alternativa.get("texto"), alternativa.get("correta", False) + if hasattr(alternativa, "texto") and hasattr(alternativa, "correta"): + return alternativa.texto, alternativa.correta + return None, False + + def validar_resposta(self, resposta) -> bool: + if isinstance(resposta, int): + if 0 <= resposta < len(self.alternativas): + _, correta = self._obter_texto_correta(self.alternativas[resposta]) + return correta + return False + + if isinstance(resposta, str): + for alternativa in self.alternativas: + texto, correta = self._obter_texto_correta(alternativa) + if texto == resposta: + return correta + return False + + if isinstance(resposta, (list, tuple, set)): + selecionadas = set() + for item in resposta: + if isinstance(item, int): + if 0 <= item < len(self.alternativas): + texto, _ = self._obter_texto_correta(self.alternativas[item]) + selecionadas.add(texto) + else: + return False + elif isinstance(item, str): + selecionadas.add(item) + else: + return False + + corretas = {texto for alternativa in self.alternativas if (texto := self._obter_texto_correta(alternativa)[0]) is not None and self._obter_texto_correta(alternativa)[1]} + return selecionadas == corretas + + return False + + def get_alternativa_correta(self): + for alt in self.alternativas: + _, correta = self._obter_texto_correta(alt) + if correta: + return alt + return None + + def get_explicacao(self) -> str: + return self.explicacao_geral + + def get_tipo(self) -> str: + return "multipla_escolha" diff --git a/Guia3/src/questionario.py b/Guia3/src/questionario.py index 7525582..36b8f79 100644 --- a/Guia3/src/questionario.py +++ b/Guia3/src/questionario.py @@ -1,4 +1,13 @@ -from typing import List, Tuple, Dict +from typing import List +from src.tentativaquestionario import TentativaQuestionario class Questionario: - pass + def __init__(self, titulo: str): + self.titulo = titulo + self.perguntas: List = [] + + def adicionar_pergunta(self, pergunta) -> None: + self.perguntas.append(pergunta) + + def criar_attempt(self, usuario: str): + return TentativaQuestionario(questionario = self, usuario = usuario) \ No newline at end of file diff --git a/Guia3/src/resposta.py b/Guia3/src/resposta.py index 846d771..a6c5a3b 100644 --- a/Guia3/src/resposta.py +++ b/Guia3/src/resposta.py @@ -1,4 +1,11 @@ +from abc import ABC from typing import List, Tuple, Dict -class Resposta: - pass \ No newline at end of file +class Resposta(ABC): + def __init__(self,pergunta, esta_correta: bool, pontuacao_obtida: float): + self.pergunta = pergunta + self.esta_correta = esta_correta + self.pontuacao_obtida = pontuacao_obtida + + def calcular_pontuacao(self): + pass \ No newline at end of file diff --git a/Guia3/src/respostadiscursiva.py b/Guia3/src/respostadiscursiva.py index 4ea6dbb..7ee4d84 100644 --- a/Guia3/src/respostadiscursiva.py +++ b/Guia3/src/respostadiscursiva.py @@ -1,4 +1,11 @@ from typing import List, Tuple, Dict +from src.pergunta import Pergunta class RespostaDiscursiva: - pass \ No newline at end of file + def __init__(self, pergunta, texto_resposta: str): + self.pergunta = pergunta + self.texto_resposta = texto_resposta + self.esta_correta = pergunta.validar_resposta(texto_resposta) + + def calcular_pontuacao(self) -> float: + return 1.0 if self.esta_correta else 0.0 \ No newline at end of file diff --git a/Guia3/src/respostaobjetiva.py b/Guia3/src/respostaobjetiva.py index 72ed2d0..c78ae52 100644 --- a/Guia3/src/respostaobjetiva.py +++ b/Guia3/src/respostaobjetiva.py @@ -1,4 +1,11 @@ from typing import List, Tuple, Dict class RespostaObjetiva: - pass \ No newline at end of file + def __init__(self, pergunta, indice_escolhido: int): + self.pergunta = pergunta + self.indice_escolhido = indice_escolhido + self.alternativa_selecionada = pergunta.alternativas[indice_escolhido] + self.esta_correta = pergunta.validar_resposta(indice_escolhido) + + def calcular_pontuacao(self) -> float: + return 1.0 if self.esta_correta else 0.0 \ No newline at end of file diff --git a/Guia3/src/tentativaquestionario.py b/Guia3/src/tentativaquestionario.py index 9947dd1..047ad40 100644 --- a/Guia3/src/tentativaquestionario.py +++ b/Guia3/src/tentativaquestionario.py @@ -1,4 +1,38 @@ -from typing import List, Tuple, Dict +from typing import List, Tuple +from datetime import datetime +from src.perguntamultiplaescolha import PerguntaMultiplaEscolha +from src.respostaobjetiva import RespostaObjetiva +from src.respostadiscursiva import RespostaDiscursiva class TentativaQuestionario: - pass \ No newline at end of file + def __init__(self, questionario, usuario: str): + self.questionario = questionario + self.usuario = usuario + self.data_inicio = datetime.now() + self.data_fim = None + self.respostas: List = [] + self._finalizado: bool = False + + def registrar_resposta(self, indice_pergunta: int, valor) -> None: + pergunta = self.questionario.perguntas[indice_pergunta] + + if isinstance(pergunta, PerguntaMultiplaEscolha): + resposta = RespostaObjetiva(pergunta = pergunta, indice_escolhido=valor) + else: + resposta = RespostaDiscursiva(pergunta = pergunta, texto_resposta=valor) + + self.respostas.append(resposta) + + def calcular_pontuacao(self) -> float: + return sum(r.calcular_pontuacao() for r in self.respostas) + + def finalizar(self) -> Tuple[float, str]: + self._finalizado = True + self.data_fim = datetime.now() + pontuacao = self.calcular_pontuacao() + total = len(self.questionario.perguntas) + feedback = f"Você acertou {pontuacao:.0f} de {total} perguntas." + return pontuacao, feedback + + def is_finalizado(self) -> bool: + return self._finalizado \ No newline at end of file diff --git a/Guia4/.gitignore b/Guia4/.gitignore new file mode 100644 index 0000000..4b3bf0c --- /dev/null +++ b/Guia4/.gitignore @@ -0,0 +1,13 @@ +__pycache__/ +*.pyc +*.pyo +*.pyd +venv/ +.venv/ +.venv +env/ +.env +.pytest_cache/ +.pytest_cache +.coverage +htmlcov/ diff --git a/Guia4/README.md b/Guia4/README.md new file mode 100644 index 0000000..ca95454 --- /dev/null +++ b/Guia4/README.md @@ -0,0 +1,173 @@ +# Guia 4 — Serviço LLM + +## Contexto +Você faz parte da equipe responsável por desenvolver um **Sistema de Quiz** educativo. +O sistema deve permitir a criação de quizzes com perguntas de múltipla escolha ou **discursivas**, validação de respostas e cálculo de pontuação. + +Agora, **perguntas discursivas** deverão ser corrigidas por um serviço de LLM (Groq) via API. +Sua missão é implementar/completar as classes seguindo o **diagrama UML** complementar e as regras abaixo. + +--- + +## Diagrama UML + +### 1. Diagrama Principal (mantido exatamente como estava) + +```mermaid +classDiagram + direction TB + class Pergunta { + <> + -String texto + -String? explicacao_geral + +validar_resposta(resposta) boolean + +get_explicacao() String + +get_tipo() String + } + class PerguntaMultiplaEscolha { + -List~Alternativa~ alternativas + +validar_resposta(int indice) boolean + +get_alternativa_correta() Alternativa + } + class PerguntaDiscursiva { + -String? resposta_esperada + +validar_resposta(String texto) boolean + } + class Alternativa { + +String texto + +boolean correta + +String? explicacao + } + class Resposta { + <> + -Pergunta pergunta + -boolean esta_correta + -float pontuacao_obtida + +calcular_pontuacao() float + } + class RespostaObjetiva { + -int indice_escolhido + -Alternativa? alternativa_selecionada + } + class RespostaDiscursiva { + -String texto_resposta + } + class Questionario { + -String titulo + -List~Pergunta~ perguntas + +adicionar_pergunta(Pergunta p) + +criar_attempt(String usuario) QuizAttempt + } + class TentativaQuestionario { + -Questionario questionario + -String usuario + -DateTime? data_inicio + -DateTime? data_fim + -List~Resposta~ respostas + +registrar_resposta(int indice_pergunta, Object valor) + +finalizar() Tuple~float, String~ + +calcular_pontuacao() float + +is_finalizado() boolean + } + + %% Relacionamentos + Pergunta <|-- PerguntaMultiplaEscolha + Pergunta <|-- PerguntaDiscursiva + Resposta <|-- RespostaObjetiva + Resposta <|-- RespostaDiscursiva + + PerguntaMultiplaEscolha "1" *-- "2..*" Alternativa + Questionario "1" *-- "0..*" Pergunta + Questionario "1" --> "0..*" TentativaQuestionario + TentativaQuestionario "1" *-- "0..*" Resposta + Resposta "1" --> "1" Pergunta +``` + +### 2. Diagrama Complementar — Integração com LLM (Novo) + +```mermaid +classDiagram + direction TB + + class LLMService { + <> + -String api_key + -String model + -String base_url + +__init__(api_key: str, model: str = "llama3-70b-8192") + +corrigir_resposta(pergunta: PerguntaDiscursiva, resposta_aluno: str) -> Dict + -_fazer_chamada_api(prompt: str) -> str + -_tratar_erro(e: Exception) -> None + } + + class Correcao { + <> + +corrigir_discursiva(pergunta: PerguntaDiscursiva, resposta_aluno: str, service: LLMService = None) -> Dict + +criar_prompt_correcao(pergunta: PerguntaDiscursiva, resposta_aluno: str) -> str + } + + LLMService o-- Correcao : "é usado por" +``` + +--- + +## Nova Descrição das Classes + +### LLMService (Classe de Serviço) +Responsável pela **conexão direta** com o serviço Groq. + +**Responsabilidades:** +- Guardar a API Key (deve ser carregada via variável de ambiente `GROQ_API_KEY`) +- Realizar as chamadas HTTP para a API do Groq +- **Tratar todos os erros** (timeout, rate limit, autenticação, JSON inválido, etc.) internamente +- Montar o prompt adequado para correção de questões discursivas +- Retornar apenas o resultado limpo (nunca expor detalhes da API para o resto da aplicação) + +**Métodos principais:** +- `corrigir_resposta(pergunta: PerguntaDiscursiva, resposta_aluno: str) → Dict` + - Retorna dicionário com: `{"correta": bool, "pontuacao": float, "feedback": str, "explicacao": str}` + +### CorrecaoUtil (Classe Utilitária) +Classe de alto nível que a aplicação deve usar. + +**Responsabilidades:** +- Abstrair o uso do `LLMService` +- Criar o prompt de forma inteligente +- Fornecer interface simples e limpa para o resto do sistema +- Possibilitar uso de mock em testes + +**Método principal:** +- `corrigir_discursiva(...)` + +--- + +## Regras de Implementação Importantes + +1. **Não modificar** as classes existentes do diagrama principal. +2. A classe `PerguntaDiscursiva` **não deve** conhecer o `LLMService` diretamente. +3. Toda correção de discursiva deve passar pela `Correcao`. +4. O `LLMService` deve: + - Usar a biblioteca `groq` (ou `requests`) + - Tratar erros internamente + - Ter fallback ou mensagem clara em caso de falha na API +5. A API Key **nunca** deve ficar hard-coded. + +--- + +## Como preparar o ambiente + +```bash +# 1. Criar venv +python -m venv .venv + +# 2. Ativar +# Windows +.\.venv\Scripts\activate +# Linux/macOS +source .venv/bin/activate + +# 3. Instalar dependências +pip install -r requirements.txt +``` + +--- \ No newline at end of file diff --git a/Guia4/main.py b/Guia4/main.py new file mode 100644 index 0000000..0185df4 --- /dev/null +++ b/Guia4/main.py @@ -0,0 +1,29 @@ +from src.perguntadiscursiva import PerguntaDiscursiva +from src.correcao import Correcao + +def main(): + perguntas_e_respostas = [ + (PerguntaDiscursiva("O que é POO?", resposta_esperada="Programação Orientada a Objetos"), "Programação Orientada a Objetos"), + (PerguntaDiscursiva("O que é POO?", resposta_esperada="Programação Orientada a Objetos"), "Banco de dados"), + (PerguntaDiscursiva("O que significa CPU?", resposta_esperada="Central Processing Unit"), "Central Processing Unit"), + (PerguntaDiscursiva("O que significa CPU?", resposta_esperada="Central Processing Unit"), "Memória RAM"), + (PerguntaDiscursiva("Explique encapsulamento."), "É quando escondemos os detalhes internos de uma classe."), + (PerguntaDiscursiva("Explique herança."), "É quando uma classe filha herda atributos e métodos da classe mãe."), + (PerguntaDiscursiva("Sigla CPU", resposta_esperada="Central Processing Unit"), "Central Processing Unit"), + ] + + for i, (pergunta, resposta_aluno) in enumerate(perguntas_e_respostas, 1): + print(f"=== Pergunta {i} ===") + print(f"Pergunta: {pergunta.texto}") + print(f"Resposta: {resposta_aluno}") + + resultado = Correcao.corrigir_discursiva(pergunta, resposta_aluno) + + print(f"Correta: {resultado['correta']}") + print(f"Pontuação: {resultado['pontuacao']}") + print(f"Feedback: {resultado['feedback']}") + print(f"Explicação:{resultado['explicacao']}") + print() + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/Guia4/requirements.txt b/Guia4/requirements.txt new file mode 100644 index 0000000..a0ca34d --- /dev/null +++ b/Guia4/requirements.txt @@ -0,0 +1,4 @@ +pytest>=8.0.0 +pytest-cov>=4.0.0 +groq +python-dotenv \ No newline at end of file diff --git a/Guia4/src/__init__.py b/Guia4/src/__init__.py new file mode 100644 index 0000000..5435a81 --- /dev/null +++ b/Guia4/src/__init__.py @@ -0,0 +1,11 @@ +from .alternativa import Alternativa +from .pergunta import Pergunta +from .perguntadiscursiva import PerguntaDiscursiva +from .perguntamultiplaescolha import PerguntaMultiplaEscolha +from .questionario import Questionario +from .resposta import Resposta +from .respostadiscursiva import RespostaDiscursiva +from .respostaobjetiva import RespostaObjetiva +from .tentativaquestionario import TentativaQuestionario +from .llmservice import LLMService +from .correcao import Correcao \ No newline at end of file diff --git a/Guia4/src/alternativa.py b/Guia4/src/alternativa.py new file mode 100644 index 0000000..cdae2b5 --- /dev/null +++ b/Guia4/src/alternativa.py @@ -0,0 +1,7 @@ +from typing import List, Tuple, Dict + +class Alternativa: + def __init__(self, texto: str, correta: bool, explicacao: str = None): + self.texto = texto + self.correta = correta + self.explicacao = explicacao \ No newline at end of file diff --git a/Guia4/src/correcao.py b/Guia4/src/correcao.py new file mode 100644 index 0000000..9a1ee3f --- /dev/null +++ b/Guia4/src/correcao.py @@ -0,0 +1,26 @@ +from src.llmservice import LLMService + +class Correcao: + + @staticmethod + def corrigir_discursiva(pergunta, resposta_aluno: str, service: LLMService = None) -> dict: + if service is None: + service = LLMService() + return service.corrigir_resposta(pergunta, resposta_aluno) + + @staticmethod + def criar_prompt_correcao(pergunta, resposta_aluno: str) -> str: + esperada = getattr(pergunta, 'resposta_esperada', None) + partes = [ + "Você é um corretor de questões discursivas.", + f"Pergunta: {pergunta.texto}", + f"Resposta do aluno: {resposta_aluno}", + ] + if esperada: + partes.append(f"Resposta esperada: {esperada}") + partes.append( + 'Avalie a resposta e retorne APENAS um JSON válido com as chaves: ' + '"correta" (bool), "pontuacao" (float entre 0.0 e 1.0), ' + '"feedback" (str) e "explicacao" (str). Sem texto adicional.' + ) + return "\n".join(partes) \ No newline at end of file diff --git a/Guia4/src/llmservice.py b/Guia4/src/llmservice.py new file mode 100644 index 0000000..f8758a7 --- /dev/null +++ b/Guia4/src/llmservice.py @@ -0,0 +1,56 @@ +from typing import List, Tuple, Dict +import os +from groq import Groq + + +class LLMService: + def __init__(self, api_key: str = None, model: str = "llama-3.3-70b-versatile"): + self.api_key = api_key or os.environ.get("O PRIMEIRO MODO DE TESTE É CONFIGURAR SUA CHAVE API PARA VARIÁVEL LOCAL. A SEGUNDA FORMA É SUBSTITURI TODO ESSE TEXTO ESCRITO AQUI POR SUA CHAVE API") + self.model = model + self._client = Groq(api_key=self.api_key) + + def corrigir_resposta(self, pergunta, resposta_aluno: str) -> dict: + prompt = self._montar_prompt(pergunta, resposta_aluno) + resposta_bruta = self._fazer_chamada_api(prompt) + try: + import json + return json.loads(resposta_bruta) + except Exception as e: + self._tratar_erro(e) + return { + "correta": False, + "pontuacao": 0.0, + "feedback": "Não foi possível avaliar a resposta.", + "explicacao": str(e) + } + + def _montar_prompt(self, pergunta, resposta_aluno: str) -> str: + esperada = getattr(pergunta, 'resposta_esperada', None) + partes = [ + "Você é um corretor de questões discursivas.", + f"Pergunta: {pergunta.texto}", + f"Resposta do aluno: {resposta_aluno}", + ] + if esperada: + partes.append(f"Resposta esperada: {esperada}") + partes.append( + 'Avalie a resposta e retorne APENAS um JSON válido com as chaves: ' + '"correta" (bool), "pontuacao" (float entre 0.0 e 1.0), ' + '"feedback" (str) e "explicacao" (str). Sem texto adicional.' + ) + return "\n".join(partes) + + def _fazer_chamada_api(self, prompt: str) -> str: + try: + completion = self._client.chat.completions.create( + model=self.model, + messages=[{"role": "user", "content": prompt}], + temperature=0.2, + ) + return completion.choices[0].message.content + except Exception as e: + self._tratar_erro(e) + return '{"correta": false, "pontuacao": 0.0, "feedback": "Erro na chamada à API.", "explicacao": ""}' + + def _tratar_erro(self, e: Exception) -> None: + print(f"[LLMService] Erro: {type(e).__name__}: {e}") \ No newline at end of file diff --git a/Guia4/src/pergunta.py b/Guia4/src/pergunta.py new file mode 100644 index 0000000..34e4b74 --- /dev/null +++ b/Guia4/src/pergunta.py @@ -0,0 +1,19 @@ +from typing import List, Tuple, Dict +from abc import ABC, abstractmethod + +class Pergunta(ABC): + def __init__(self, texto: str, explicacao_geral = None): + self._texto = texto + self._explicacao_geral = explicacao_geral + + @abstractmethod + def validar_resposta(self, resposta) -> bool: + pass + + @abstractmethod + def get_explicacao(self) -> str: + pass + + @abstractmethod + def get_tipo(self) -> str: + pass \ No newline at end of file diff --git a/Guia4/src/perguntadiscursiva.py b/Guia4/src/perguntadiscursiva.py new file mode 100644 index 0000000..127c1fb --- /dev/null +++ b/Guia4/src/perguntadiscursiva.py @@ -0,0 +1,22 @@ +from typing import List, Tuple, Dict +from src.pergunta import Pergunta + +class PerguntaDiscursiva(Pergunta): + def __init__(self, texto, explicacao_geral=None, resposta_esperada: str = None): + super().__init__(texto, explicacao_geral) + self.resposta_esperada = resposta_esperada + + @property + def texto(self): + return self._texto + + def validar_resposta(self, texto: str): + if self.resposta_esperada is None: + return False + return texto.strip().lower() == self.resposta_esperada.strip().lower() + + def get_explicacao(self) -> str: + return self._explicacao_geral + + def get_tipo(self): + return "discursiva" \ No newline at end of file diff --git a/Guia4/src/perguntamultiplaescolha.py b/Guia4/src/perguntamultiplaescolha.py new file mode 100644 index 0000000..16300ca --- /dev/null +++ b/Guia4/src/perguntamultiplaescolha.py @@ -0,0 +1,65 @@ +from typing import List, Tuple, Dict +#from pergunta import Pergunta +from src.pergunta import Pergunta +from src.alternativa import Alternativa + +class PerguntaMultiplaEscolha(Pergunta): + def __init__(self, texto: str, alternativas: List[Tuple[str, bool]], explicacao_geral = None): + super().__init__(texto, explicacao_geral) + self.alternativas = alternativas + self.explicacao_geral = explicacao_geral + + def _obter_texto_correta(self, alternativa): + if isinstance(alternativa, tuple): + return alternativa[0], alternativa[1] + if isinstance(alternativa, dict): + return alternativa.get("texto"), alternativa.get("correta", False) + if hasattr(alternativa, "texto") and hasattr(alternativa, "correta"): + return alternativa.texto, alternativa.correta + return None, False + + def validar_resposta(self, resposta) -> bool: + if isinstance(resposta, int): + if 0 <= resposta < len(self.alternativas): + _, correta = self._obter_texto_correta(self.alternativas[resposta]) + return correta + return False + + if isinstance(resposta, str): + for alternativa in self.alternativas: + texto, correta = self._obter_texto_correta(alternativa) + if texto == resposta: + return correta + return False + + if isinstance(resposta, (list, tuple, set)): + selecionadas = set() + for item in resposta: + if isinstance(item, int): + if 0 <= item < len(self.alternativas): + texto, _ = self._obter_texto_correta(self.alternativas[item]) + selecionadas.add(texto) + else: + return False + elif isinstance(item, str): + selecionadas.add(item) + else: + return False + + corretas = {texto for alternativa in self.alternativas if (texto := self._obter_texto_correta(alternativa)[0]) is not None and self._obter_texto_correta(alternativa)[1]} + return selecionadas == corretas + + return False + + def get_alternativa_correta(self): + for alt in self.alternativas: + _, correta = self._obter_texto_correta(alt) + if correta: + return alt + return None + + def get_explicacao(self) -> str: + return self.explicacao_geral + + def get_tipo(self) -> str: + return "multipla_escolha" \ No newline at end of file diff --git a/Guia4/src/questionario.py b/Guia4/src/questionario.py new file mode 100644 index 0000000..36b8f79 --- /dev/null +++ b/Guia4/src/questionario.py @@ -0,0 +1,13 @@ +from typing import List +from src.tentativaquestionario import TentativaQuestionario + +class Questionario: + def __init__(self, titulo: str): + self.titulo = titulo + self.perguntas: List = [] + + def adicionar_pergunta(self, pergunta) -> None: + self.perguntas.append(pergunta) + + def criar_attempt(self, usuario: str): + return TentativaQuestionario(questionario = self, usuario = usuario) \ No newline at end of file diff --git a/Guia4/src/resposta.py b/Guia4/src/resposta.py new file mode 100644 index 0000000..a6c5a3b --- /dev/null +++ b/Guia4/src/resposta.py @@ -0,0 +1,11 @@ +from abc import ABC +from typing import List, Tuple, Dict + +class Resposta(ABC): + def __init__(self,pergunta, esta_correta: bool, pontuacao_obtida: float): + self.pergunta = pergunta + self.esta_correta = esta_correta + self.pontuacao_obtida = pontuacao_obtida + + def calcular_pontuacao(self): + pass \ No newline at end of file diff --git a/Guia4/src/respostadiscursiva.py b/Guia4/src/respostadiscursiva.py new file mode 100644 index 0000000..34f2715 --- /dev/null +++ b/Guia4/src/respostadiscursiva.py @@ -0,0 +1,14 @@ +from typing import List, Tuple, Dict +from src.pergunta import Pergunta +from src.resposta import Resposta + +from src.resposta import Resposta + +class RespostaDiscursiva(Resposta): + def __init__(self, pergunta, texto_resposta: str): + self.texto_resposta = texto_resposta + esta_correta = pergunta.validar_resposta(texto_resposta) + super().__init__(pergunta, esta_correta, 1.0 if esta_correta else 0.0) + + def calcular_pontuacao(self) -> float: + return 1.0 if self.esta_correta else 0.0 \ No newline at end of file diff --git a/Guia4/src/respostaobjetiva.py b/Guia4/src/respostaobjetiva.py new file mode 100644 index 0000000..e916311 --- /dev/null +++ b/Guia4/src/respostaobjetiva.py @@ -0,0 +1,12 @@ +from typing import List, Tuple, Dict +from src.resposta import Resposta + +class RespostaObjetiva(Resposta): + def __init__(self, pergunta, indice_escolhido: int): + self.indice_escolhido = indice_escolhido + self.alternativa_selecionada = pergunta.alternativas[indice_escolhido] + esta_correta = pergunta.validar_resposta(indice_escolhido) + super().__init__(pergunta, esta_correta, 1.0 if esta_correta else 0.0) + + def calcular_pontuacao(self) -> float: + return 1.0 if self.esta_correta else 0.0 \ No newline at end of file diff --git a/Guia4/src/tentativaquestionario.py b/Guia4/src/tentativaquestionario.py new file mode 100644 index 0000000..047ad40 --- /dev/null +++ b/Guia4/src/tentativaquestionario.py @@ -0,0 +1,38 @@ +from typing import List, Tuple +from datetime import datetime +from src.perguntamultiplaescolha import PerguntaMultiplaEscolha +from src.respostaobjetiva import RespostaObjetiva +from src.respostadiscursiva import RespostaDiscursiva + +class TentativaQuestionario: + def __init__(self, questionario, usuario: str): + self.questionario = questionario + self.usuario = usuario + self.data_inicio = datetime.now() + self.data_fim = None + self.respostas: List = [] + self._finalizado: bool = False + + def registrar_resposta(self, indice_pergunta: int, valor) -> None: + pergunta = self.questionario.perguntas[indice_pergunta] + + if isinstance(pergunta, PerguntaMultiplaEscolha): + resposta = RespostaObjetiva(pergunta = pergunta, indice_escolhido=valor) + else: + resposta = RespostaDiscursiva(pergunta = pergunta, texto_resposta=valor) + + self.respostas.append(resposta) + + def calcular_pontuacao(self) -> float: + return sum(r.calcular_pontuacao() for r in self.respostas) + + def finalizar(self) -> Tuple[float, str]: + self._finalizado = True + self.data_fim = datetime.now() + pontuacao = self.calcular_pontuacao() + total = len(self.questionario.perguntas) + feedback = f"Você acertou {pontuacao:.0f} de {total} perguntas." + return pontuacao, feedback + + def is_finalizado(self) -> bool: + return self._finalizado \ No newline at end of file diff --git a/Guia4/tests/__init__.py b/Guia4/tests/__init__.py new file mode 100644 index 0000000..e69de29