from fastapi import FastAPI, HTTPException, Request from fastapi.middleware.cors import CORSMiddleware from fastapi.exceptions import RequestValidationError from fastapi.responses import JSONResponse from fastapi.encoders import jsonable_encoder from pydantic import BaseModel import logging, hashlib from groq import Groq logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s") app = FastAPI() app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) client = Groq(api_key="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") @app.exception_handler(RequestValidationError) async def validation_exception_handler(request: Request, exc: RequestValidationError): logging.error(f"Validation error: {exc}") return JSONResponse( status_code=422, content={"detail": jsonable_encoder(exc.errors()), "body": exc.body}, ) class MaterialRequest(BaseModel): content: str question_type: str = "multiple_choice" question_count: int = 5 def potong_konten(text: str, max_chars: int = 5000): if len(text) > max_chars: logging.warning(f"Teks terlalu panjang ({len(text)} karakter), dipotong jadi {max_chars}") return text[:max_chars] return text @app.post("/generate-from-material/") async def generate_from_material(request: MaterialRequest): try: if request.question_count < 1 or request.question_count > 20: raise HTTPException(status_code=400, detail="Jumlah soal harus antara 1-20") if request.question_type not in ["multiple_choice", "essay"]: raise HTTPException(status_code=400, detail="Jenis soal tidak valid. Pilih: multiple_choice atau essay") content_bersih = potong_konten(request.content.strip()) mc_count = request.question_count if request.question_type == "multiple_choice" else 0 essay_count = request.question_count if request.question_type == "essay" else 0 prompt = f""" Buat soal latihan berdasarkan teks materi berikut untuk siswa SD kelas 3. **Instruksi:** 1. Buat total {request.question_count} soal dengan rincian: - Soal pilihan ganda: {mc_count} - Soal isian/essay: {essay_count} 2. Setiap soal harus disertai: - Kutipan 1 kalimat dari teks materi sebagai dasar soal - Jawaban - ✅ Bobot soal antara: - 1-2 untuk soal pilihan ganda - 3-5 untuk soal isian/essay - Gunakan penilaian kompleksitas soal untuk menentukan bobotnya 3. Gunakan bahasa yang sederhana dan sesuai untuk siswa SD kelas 3. 4. Jangan menambahkan informasi di luar materi. **Format Output:** --- **Soal Pilihan Ganda:** 1. Kalimat sumber: "[kutipan kalimat dari teks]" Pertanyaan: [Pertanyaan] A. [Opsi A] B. [Opsi B] C. [Opsi C] D. [Opsi D] Jawaban: [Huruf Opsi] Bobot: [1 atau 2] **Soal Isian:** 1. Kalimat sumber: "[kutipan kalimat dari teks]" Pertanyaan: [Pertanyaan] Jawaban: [Jawaban] Bobot: [3 - 5] --- **Materi:** {content_bersih} """.strip() logging.info("Mengirim prompt ke Groq...") completion = client.chat.completions.create( model="llama-3.1-8b-instant", messages=[{"role": "user", "content": prompt}], temperature=0.7, max_tokens=1500 ) generated_text = completion.choices[0].message.content.strip() return { "generated_questions": generated_text, "question_type": request.question_type, "question_count": request.question_count, "mc_count": mc_count, "essay_count": essay_count } except Exception as e: logging.error(f"Error saat generate: {e}") raise HTTPException(status_code=500, detail=f"Terjadi kesalahan internal: {str(e)}") # Cache Feedback feedback_cache = {} class FeedbackRequest(BaseModel): user_answer: str expected_answer: str @app.post("/generate-feedback/") async def generate_feedback(request: FeedbackRequest): try: user_answer = request.user_answer.strip() expected_answer = request.expected_answer.strip() # Hashing untuk cache prompt_hash = hashlib.sha256(f"{user_answer}|{expected_answer}".encode()).hexdigest() if prompt_hash in feedback_cache: logging.info("Feedback dari cache.") return {"feedback": feedback_cache[prompt_hash]} prompt = f""" Kamu adalah asisten pengajar untuk siswa SD kelas 3. Siswa memberikan jawaban berikut untuk soal isian. **Jawaban Siswa:** {user_answer} **Jawaban Ideal:** {expected_answer} Beri feedback singkat dan membangun, maksimal 2 kalimat. Gunakan bahasa yang mudah dimengerti oleh siswa SD. Jika jawaban siswa salah, berikan petunjuk atau koreksi yang membantu. """ logging.info("Mengirim prompt feedback ke Groq...") completion = client.chat.completions.create( model="llama-3.1-8b-instant", messages=[{"role": "user", "content": prompt}], temperature=0.7, max_tokens=150 ) feedback = completion.choices[0].message.content.strip() feedback_cache[prompt_hash] = feedback return {"feedback": feedback} except Exception as e: logging.error(f"Error saat generate feedback: {e}") raise HTTPException(status_code=500, detail=f"Terjadi kesalahan: {str(e)}")