diff --git a/Model LLM/fastapi-llama/model.py b/Model LLM/fastapi-llama/model.py deleted file mode 100644 index 3a0bc1b..0000000 --- a/Model LLM/fastapi-llama/model.py +++ /dev/null @@ -1,219 +0,0 @@ -from fastapi import FastAPI, HTTPException -from fastapi.middleware.cors import CORSMiddleware -from pydantic import BaseModel -import httpx -import logging -import random - -app = FastAPI() - -app.add_middleware( - CORSMiddleware, - allow_origins=["*"], - allow_credentials=True, - allow_methods=["*"], - allow_headers=["*"], -) - -OLLAMA_URL = "http://192.168.60.110:11434/api/generate" - -logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s") - -data_sources = { - "cerita": { - "Malin Kundang": "Malin Kundang adalah seorang anak dari keluarga miskin yang menjadi kaya raya namun menolak mengakui ibunya, hingga akhirnya dikutuk menjadi batu.", - "Bawang Merah Bawang Putih": "Bawang Putih adalah gadis baik hati yang diperlakukan buruk oleh ibu dan saudara tirinya, tetapi kebaikannya membuahkan hasil berkat ikan ajaib.", - "Sangkuriang": "Sangkuriang jatuh cinta pada ibunya, Dayang Sumbi, dan diberi tugas mustahil untuk membangun perahu dalam satu malam. Ia gagal dan akhirnya marah, menendang perahu hingga menjadi Gunung Tangkuban Perahu.", - "Si Kancil": "Si Kancil dengan kecerdikannya berhasil menipu buaya untuk menyeberangi sungai dengan aman.", - "Timun Mas": "Seorang ibu tua mendapatkan anak dari biji timun emas. Namun, anak itu harus melarikan diri dari raksasa jahat yang ingin memakannya." - }, - "pantun": { - "Pantun Nasihat": "Jalan-jalan ke kota Blitar,\nJangan lupa membeli roti.\nRajin belajar sejak pintar,\nAgar sukses di kemudian hari.", - "Pantun Jenaka": "Ke pasar beli ikan teri,\nIkan habis tinggal kepala.\nJangan suka mencuri,\nNanti ketahuan malah celaka." - }, - "puisi": { - "Puisi Alam": "Langit biru membentang luas,\nBurung-burung terbang bebas.\nAngin sepoi menyapu dedaunan,\nAlam indah penuh kedamaian.", - "Puisi Persahabatan": "Sahabat sejati selalu ada,\nDalam suka dan dalam duka.\nBersama kita jalani hari,\nMengukir cerita tak terlupa." - } -} - -@app.post("/generate/") -async def generate_text(): - try: - selected_stories = random.sample(list(data_sources["cerita"].keys()), 3) - selected_pantun = random.choice(list(data_sources["pantun"].keys())) - selected_puisi = random.choice(list(data_sources["puisi"].keys())) - - # PROMPT BAGIAN CERITA - story_prompts = "\n\n".join([ - f"Judul: {story}\nIsi:\n{data_sources['cerita'][story]}" - for story in selected_stories - ]) - - story_prompt_full = f""" -Kamu adalah asisten pengajar untuk siswa SD kelas 3. Berdasarkan teks cerita di bawah ini, buat soal latihan. - -**Instruksi:** -- Untuk setiap cerita, buat: - - 1 soal pilihan ganda (A-D) + jawabannya (Jawaban Benar: X) - - 1 soal isian + jawabannya (Jawaban Ideal: ...) - -Gunakan format berikut: - ---- - -Judul: [judul] -Isi: -[isi teks] - -**Soal Pilihan Ganda:** -1. ... -A. ... -B. ... -C. ... -D. ... -Jawaban Benar: X - -**Soal Isian:** -... -Jawaban Ideal: ... - ---- - -Berikut teks ceritanya: - -{story_prompts} -""" - - # PROMPT BAGIAN PANTUN & PUISI - pantun_prompt = f"Judul: {selected_pantun}\nIsi:\n{data_sources['pantun'][selected_pantun]}" - puisi_prompt = f"Judul: {selected_puisi}\nIsi:\n{data_sources['puisi'][selected_puisi]}" - - pantun_puisi_full = f""" -Kamu adalah asisten pengajar untuk siswa SD kelas 3. Berdasarkan teks pantun dan puisi di bawah ini, buat soal latihan. - -**Instruksi:** -- Untuk setiap teks, buat: - - 1 soal pilihan ganda (A-D) + jawabannya (Jawaban Benar: X) - - 1 soal isian + jawabannya (Jawaban Ideal: ...) - -Gunakan format berikut: - ---- - -Judul: [judul] -Isi: -[isi teks] - -**Soal Pilihan Ganda:** -1. ... -A. ... -B. ... -C. ... -D. ... -Jawaban Benar: X - -**Soal Isian:** -... -Jawaban Ideal: ... - ---- - -Berikut teks pantun dan puisinya: - -{pantun_prompt} - -{puisi_prompt} -""" - - # Siapkan payload untuk kedua request - async with httpx.AsyncClient(timeout=300) as client: - # Request untuk CERITA - res1 = await client.post(OLLAMA_URL, json={ - "model": "llama3.1:latest", - "prompt": story_prompt_full, - "stream": False, - "options": { - "num_predict": 2048 - } - }) - res1.raise_for_status() - response_story = res1.json().get("response", "").strip() - - # Request untuk PANTUN + PUISI - res2 = await client.post(OLLAMA_URL, json={ - "model": "llama3.1:latest", - "prompt": pantun_puisi_full, - "stream": False, - "options": { - "num_predict": 1024 - } - }) - res2.raise_for_status() - response_pantun_puisi = res2.json().get("response", "").strip() - - if not response_story or not response_pantun_puisi: - raise HTTPException(status_code=500, detail="Ollama tidak menghasilkan pertanyaan") - - return { - "selected_stories": [ - {"title": title, "content": data_sources["cerita"][title]} - for title in selected_stories - ], - "selected_pantun": { - "title": selected_pantun, - "content": data_sources["pantun"][selected_pantun] - }, - "selected_puisi": { - "title": selected_puisi, - "content": data_sources["puisi"][selected_puisi] - }, - "generated_questions": response_story + "\n\n" + response_pantun_puisi - } - - except httpx.HTTPStatusError as e: - logging.error(f"HTTP error dari Ollama API: {e.response.text}") - raise HTTPException(status_code=e.response.status_code, detail=e.response.text) - - except Exception as e: - logging.error(f"Terjadi kesalahan: {str(e)}") - raise HTTPException(status_code=500, detail="Terjadi kesalahan internal") - -class FeedbackRequest(BaseModel): - user_answer: str - expected_answer: str - -@app.post("/generate-feedback/") -async def generate_feedback(request: FeedbackRequest): - try: - prompt = f""" -Kamu adalah asisten pengajar untuk siswa SD kelas 3. Siswa memberikan jawaban berikut untuk soal isian. - -**Jawaban Siswa:** {request.user_answer.strip()} -**Jawaban Ideal:** {request.expected_answer.strip()} - -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. -""" - - payload = { - "model": "llama3.1:latest", - "prompt": prompt, - "stream": False - } - - logging.info("Mengirim permintaan feedback ke Ollama...") - async with httpx.AsyncClient(timeout=60) as client: - response = await client.post(OLLAMA_URL, json=payload) - - response.raise_for_status() - result = response.json() - feedback = result.get("response", "").strip() - - if not feedback: - raise HTTPException(status_code=500, detail="Ollama tidak memberikan feedback") - - return {"feedback": feedback} - - except Exception as e: - logging.error(f"Gagal menghasilkan feedback dari Ollama: {e}") - raise HTTPException(status_code=500, detail=f"Terjadi kesalahan: {str(e)}") \ No newline at end of file diff --git a/Model LLM/fastapi-llama/model_llm.py b/Model LLM/fastapi-llama/model_llm.py deleted file mode 100644 index 419b7e8..0000000 --- a/Model LLM/fastapi-llama/model_llm.py +++ /dev/null @@ -1,342 +0,0 @@ -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, re -import httpx - -# Logging -logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s") - -app = FastAPI() - -# CORS -app.add_middleware( - CORSMiddleware, - allow_origins=["*"], - allow_credentials=True, - allow_methods=["*"], - allow_headers=["*"], -) - -# OLLAMA_URL = "http://167.71.212.60:111/api/generate" -OLLAMA_URL = "http://labai.polinema.ac.id:11434/api/generate" - -@app.exception_handler(RequestValidationError) -async def validation_exception_handler(request: Request, exc: RequestValidationError): - return JSONResponse( - status_code=422, - content={"detail": jsonable_encoder(exc.errors()), "body": exc.body}, - ) - -class MaterialRequest(BaseModel): - content: str - question_type: str # hanya 'multiple_choice' atau 'essay' - question_count: int = 5 - -def potong_konten(text: str, max_chars: int = 5000): - return text[:max_chars] if len(text) > max_chars else text - -@app.post("/generate-from-material/") -async def generate_from_material(request: MaterialRequest): - 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 hanya bisa 'multiple_choice' atau 'essay'") - - mc_count = essay_count = 0 - if request.question_type == "multiple_choice": - mc_count = request.question_count - else: - essay_count = request.question_count - - content_bersih = potong_konten(request.content.strip()) - - prompt_generate = f""" -Buat soal latihan berdasarkan teks materi berikut untuk siswa SD kelas 3. - -**Instruksi:** -1. Buat total {request.question_count} soal: - - Pilihan ganda: {mc_count} - - Isian/essay: {essay_count} -2. Setiap soal sertakan: - - Kutipan dari teks (1 kalimat) - - Pertanyaan - - Jawaban - - Bobot default: isi `Bobot: TBD` (nanti akan ditentukan otomatis) -3. Gunakan bahasa yang sederhana dan sesuai untuk siswa SD kelas 3. - -**Format Output:** ---- -**Soal Pilihan Ganda:** -1. Kalimat sumber: "[kutipan]" - Pertanyaan: [pertanyaan] - A. ... - B. ... - C. ... - D. ... - Jawaban: [opsi] - Bobot: TBD - -**Soal Isian:** -1. Kalimat sumber: "[kutipan]" - Pertanyaan: [pertanyaan] - Jawaban: [jawaban] - Bobot: TBD ---- - -**Materi:** -{content_bersih} -""" - - async with httpx.AsyncClient(timeout=300) as client: - res = await client.post(OLLAMA_URL, json={ - "model": "gemma3:12b", - "prompt": prompt_generate, - "stream": False, - "options": {"num_predict": 2048} - }) - res.raise_for_status() - raw_output = res.json().get("response", "").strip() - - prompt_bobot = f""" -Tentukan bobot untuk setiap soal berikut berdasarkan kompleksitas: - -**Panduan Penilaian:** -- Pilihan ganda: - - 1 = mudah - - 2 = agak sulit -- Isian: - - 3 = sedang - - 4 = agak sulit - - 5 = sulit - -Kembalikan soal yang sama, tapi ganti baris "Bobot: TBD" dengan bobot sesuai tingkat kesulitan. Jangan ubah bagian lainnya. - -Soal: -{raw_output} -""".strip() - - async with httpx.AsyncClient(timeout=120) as client: - bobot_res = await client.post(OLLAMA_URL, json={ - "model": "llama3.1:latest", - "prompt": prompt_bobot, - "stream": False - }) - bobot_res.raise_for_status() - final_output = bobot_res.json().get("response", "").strip() - - return { - "generated_questions": final_output, - "question_type": request.question_type, - "question_count": request.question_count, - "mc_count": mc_count, - "essay_count": essay_count - } - -@app.post("/generate-with-bobot/") -async def generate_with_bobot(request: MaterialRequest): - 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 hanya bisa 'multiple_choice' atau 'essay'") - - 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 - - content_bersih = potong_konten(request.content.strip()) - - # Langkah 1: Generate soal mentah dengan Bobot: TBD - prompt_generate = f""" -Buat soal latihan berdasarkan teks berikut untuk siswa SD kelas 3. - -Instruksi: -- Total soal: {request.question_count} -- Jenis soal: - - Pilihan Ganda: {mc_count} - - Isian/Essay: {essay_count} -- Setiap soal berisi: - - Kalimat sumber - - Pertanyaan - - Jawaban - - Bobot: isi "Bobot: TBD" - -Format: ---- -**Soal Pilihan Ganda:** -1. Kalimat sumber: "[kutipan]" - Pertanyaan: ... - A. ... - B. ... - C. ... - D. ... - Jawaban: ... - Bobot: TBD - -**Soal Isian:** -1. Kalimat sumber: "[kutipan]" - Pertanyaan: ... - Jawaban: ... - Bobot: TBD ---- - -Teks: -{content_bersih} -""" - - async with httpx.AsyncClient(timeout=300) as client: - res = await client.post(OLLAMA_URL, json={ - "model": "llama3.1:latest", - "prompt": prompt_generate, - "stream": False - }) - res.raise_for_status() - raw_output = res.json().get("response", "").strip() - - # Langkah 2: Tambahkan bobot - prompt_bobot = f""" -Tentukan bobot untuk setiap soal berdasarkan kompleksitas. - -Panduan Penilaian: -- Pilihan Ganda: - - 1 = mudah - - 2 = agak sulit -- Isian: - - 3 = sedang - - 4 = agak sulit - - 5 = sulit - -Ganti baris "Bobot: TBD" menjadi "Bobot: [1-5]". Jangan ubah bagian lainnya. - -Soal: -{raw_output} -""" - - async with httpx.AsyncClient(timeout=180) as client: - res_bobot = await client.post(OLLAMA_URL, json={ - "model": "llama3.1:latest", - "prompt": prompt_bobot, - "stream": False - }) - res_bobot.raise_for_status() - final_output = res_bobot.json().get("response", "").strip() - - return { - "generated_questions": final_output, - "question_type": request.question_type, - "question_count": request.question_count, - "mc_count": mc_count, - "essay_count": essay_count - } - -class GenerateQuestionsRequest(BaseModel): - content: str - question_count: int - question_type: str # 'multiple_choice', 'essay', atau 'both' - with_bobot: bool = True - -@app.post("/generate-questions/") -async def generate_questions(request: GenerateQuestionsRequest): - mc_count = essay_count = 0 - if request.question_type == "multiple_choice": - mc_count = request.question_count - elif request.question_type == "essay": - essay_count = request.question_count - else: - raise HTTPException(status_code=400, detail="Jenis soal tidak valid.") - - prompt = f""" -Buat soal dari teks berikut untuk siswa SD kelas 3. -Jumlah soal: {request.question_count} -- Pilihan Ganda: {mc_count} -- Isian: {essay_count} - -Setiap soal sertakan: -- Kalimat sumber -- Pertanyaan -- Jawaban -- Bobot: TBD - -Format: -... -Teks: -{potong_konten(request.content)} -""".strip() - - async with httpx.AsyncClient(timeout=300) as client: - result = await client.post(OLLAMA_URL, json={ - "model": "llama3.1:latest", - "prompt": prompt, - "stream": False - }) - result.raise_for_status() - output = result.json().get("response", "").strip() - - if not request.with_bobot: - return { - "generated_questions": output, - "mc_count": mc_count, - "essay_count": essay_count - } - - # Tambah bobot - prompt_bobot = f""" -Tentukan bobot soal berdasarkan kompleksitas. Ganti 'Bobot: TBD' dengan angka 1-5 sesuai panduan. Jangan ubah bagian lainnya. - -Soal: -{output} -""" - async with httpx.AsyncClient(timeout=180) as client: - bobot_result = await client.post(OLLAMA_URL, json={ - "model": "llama3.1:latest", - "prompt": prompt_bobot, - "stream": False - }) - bobot_result.raise_for_status() - final_output = bobot_result.json().get("response", "").strip() - - return { - "generated_questions": final_output, - "mc_count": mc_count, - "essay_count": essay_count - } - -class FeedbackRequest(BaseModel): - user_answer: str - expected_answer: str - -feedback_cache = {} - -@app.post("/generate-feedback/") -async def generate_feedback(request: FeedbackRequest): - user_answer = request.user_answer.strip() - expected_answer = request.expected_answer.strip() - prompt_hash = hashlib.sha256(f"{user_answer}|{expected_answer}".encode()).hexdigest() - - if prompt_hash in feedback_cache: - return {"feedback": feedback_cache[prompt_hash]} - - prompt = f""" -Kamu adalah asisten pengajar SD kelas 3. Siswa memberikan jawaban berikut. - -**Jawaban Siswa:** {user_answer} -**Jawaban Ideal:** {expected_answer} - -Beri feedback singkat dan membangun, maksimal 2 kalimat, dengan bahasa mudah dipahami. -""" - - async with httpx.AsyncClient(timeout=120) as client: - response = await client.post(OLLAMA_URL, json={ - "model": "llama3.1:latest", - "prompt": prompt, - "stream": False - }) - - response.raise_for_status() - feedback = response.json().get("response", "").strip() - feedback_cache[prompt_hash] = feedback - return {"feedback": feedback} diff --git a/Model LLM/fastapi-llama/model_llmku.py b/Model LLM/fastapi-llama/model_llmku.py deleted file mode 100644 index 6739af8..0000000 --- a/Model LLM/fastapi-llama/model_llmku.py +++ /dev/null @@ -1,177 +0,0 @@ -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 -import httpx - -# Logging setup -logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s") - -# FastAPI instance -app = FastAPI() - -# CORS -app.add_middleware( - CORSMiddleware, - allow_origins=["*"], - allow_credentials=True, - allow_methods=["*"], - allow_headers=["*"], -) - -# Ollama endpoint -OLLAMA_URL = "http://labai.polinema.ac.id:11434/api/generate" - -# Error handler -@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}, - ) - -# Input models -class MaterialRequest(BaseModel): - content: str - question_type: str = "multiple_choice" - question_count: int = 5 - -class FeedbackRequest(BaseModel): - user_answer: str - expected_answer: str - -# Utility -feedback_cache = {} - -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", "mixed"]: - raise HTTPException(status_code=400, detail="Jenis soal tidak valid. Pilih: multiple_choice, essay, atau mixed") - - 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 - if request.question_type == "mixed": - mc_count = max(1, request.question_count // 2) - essay_count = request.question_count - mc_count - - content_bersih = potong_konten(request.content.strip()) - - 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 Ollama...") - async with httpx.AsyncClient(timeout=120) as client: - response = await client.post(OLLAMA_URL, json={ - "model": "llama3.1:latest", - "prompt": prompt, - "stream": False, - "options": { - "num_predict": 2048 - } - }) - - response.raise_for_status() - result = response.json() - generated_text = result.get("response", "").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)}") - -@app.post("/generate-feedback/") -async def generate_feedback(request: FeedbackRequest): - try: - user_answer = request.user_answer.strip() - expected_answer = request.expected_answer.strip() - - 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 Ollama...") - async with httpx.AsyncClient(timeout=60) as client: - response = await client.post(OLLAMA_URL, json={ - "model": "llama3.1:latest", - "prompt": prompt, - "stream": False - }) - - response.raise_for_status() - result = response.json() - feedback = result.get("response", "").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)}") \ No newline at end of file diff --git a/Model LLM/fastapi-llama/model_llmnya.py b/Model LLM/fastapi-llama/model_llmnya.py deleted file mode 100644 index f54caf5..0000000 --- a/Model LLM/fastapi-llama/model_llmnya.py +++ /dev/null @@ -1,183 +0,0 @@ -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 -import httpx - -# Logging setup -logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s") - -# FastAPI instance -app = FastAPI() - -# CORS -app.add_middleware( - CORSMiddleware, - allow_origins=["*"], - allow_credentials=True, - allow_methods=["*"], - allow_headers=["*"], -) - -# Ollama endpoint -OLLAMA_URL = "http://labai.polinema.ac.id:11434/api/generate" - -# Error handler -@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}, - ) - -# Input models -class MaterialRequest(BaseModel): - content: str - question_type: str # 'multiple_choice' atau 'essay' - question_count: int = 5 - -class FeedbackRequest(BaseModel): - user_answer: str - expected_answer: str - -# Utility -feedback_cache = {} - -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") - - 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 - - content_bersih = potong_konten(request.content.strip()) - - 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_count} - -2. **Untuk soal isian:** - - Ambil kutipan **minimal dua kalimat yang saling terhubung** dari teks sebagai dasar soal - - Awali dengan: **Bacalah kutipan berikut: "[kutipan]"** - - Buat pertanyaan berdasarkan kutipan tersebut - - Sertakan **jawaban singkat** - - Beri bobot antara **3–5** sesuai kompleksitas - -3. **Untuk soal pilihan ganda (jika ada):** - - Ambil kutipan **1 kalimat yang relevan** dari teks - - Buat pertanyaan dan 4 pilihan jawaban (A–D) - - Beri jawaban benar dan bobot antara **1–2** sesuai tingkat kesulitan - -4. Gunakan bahasa sederhana dan sesuai dengan siswa SD kelas 3. - -5. Jangan menambahkan informasi di luar teks materi. - -**Format Output:** -""" + (""" -**Soal Pilihan Ganda:** -1. Bacalah kutipan berikut: "[2 kalimat atau lebih dari teks]" - Pertanyaan: [Pertanyaan] - A. [Opsi A] - B. [Opsi B] - C. [Opsi C] - D. [Opsi D] - Jawaban: [Huruf Opsi] - Bobot: [1 atau 2] -""" if mc_count > 0 else "") + (""" - -**Soal Isian:** -1. Bacalah kutipan berikut: "[2 kalimat atau lebih dari teks]". [Pertanyaan] - Jawaban: [Jawaban] - Bobot: [3 - 5] -""" if essay_count > 0 else "") + f""" - ---- - -**Materi:** -{content_bersih} -""".strip() - - logging.info("Mengirim prompt ke Ollama...") - async with httpx.AsyncClient(timeout=120) as client: - response = await client.post(OLLAMA_URL, json={ - "model": "llama3.1:latest", - "prompt": prompt, - "stream": False, - "options": { - "num_predict": 2048 - } - }) - - response.raise_for_status() - result = response.json() - generated_text = result.get("response", "").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)}") - -@app.post("/generate-feedback/") -async def generate_feedback(request: FeedbackRequest): - try: - user_answer = request.user_answer.strip() - expected_answer = request.expected_answer.strip() - - 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 Ollama...") - async with httpx.AsyncClient(timeout=60) as client: - response = await client.post(OLLAMA_URL, json={ - "model": "llama3.1:latest", - "prompt": prompt, - "stream": False - }) - - response.raise_for_status() - result = response.json() - feedback = result.get("response", "").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)}") \ No newline at end of file