Abiyasa_Putra_Prasetya/Model LLM/fastapi-llama/model_llmnya.py
2025-06-06 14:55:44 +07:00

183 lines
6.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 **35** sesuai kompleksitas
3. **Untuk soal pilihan ganda (jika ada):**
- Ambil kutipan **1 kalimat yang relevan** dari teks
- Buat pertanyaan dan 4 pilihan jawaban (AD)
- Beri jawaban benar dan bobot antara **12** 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)}")