Spaces:
Sleeping
Sleeping
Update app/services/llm_engine.py
Browse files- app/services/llm_engine.py +65 -65
app/services/llm_engine.py
CHANGED
|
@@ -52,8 +52,9 @@ class LLMEngine:
|
|
| 52 |
print("❌ Semua Token Gagal/Habis.")
|
| 53 |
raise last_error
|
| 54 |
|
| 55 |
-
async def process_user_intent(self, user_text: str, available_skills: list):
|
| 56 |
skills_str = "\n".join([f"- {s}" for s in available_skills])
|
|
|
|
| 57 |
|
| 58 |
system_prompt = f"""
|
| 59 |
ROLE: Kamu adalah 'Router' untuk MORA, sebuah AI Learning Assistant.
|
|
@@ -61,9 +62,10 @@ class LLMEngine:
|
|
| 61 |
|
| 62 |
DAFTAR SKILL TERSEDIA DI DATABASE:
|
| 63 |
{skills_str}
|
|
|
|
| 64 |
|
| 65 |
INSTRUKSI UTAMA:
|
| 66 |
-
Analisis pesan user
|
| 67 |
|
| 68 |
1. ACTION: "START_EXAM"
|
| 69 |
- Trigger: User ingin "tes", "ujian", "uji kemampuan", "soal", atau menyebut topik teknis (SQL, Python, CV, NLP). kalau user tidak menyebut keyword teknis dari subskill maka akan menampilkan list dari sub skill yang ada.
|
|
@@ -79,8 +81,8 @@ class LLMEngine:
|
|
| 79 |
- PENTING: Jangan pilih ini jika user hanya ingin melihat data progress, nilai/laporan.
|
| 80 |
|
| 81 |
3. ACTION: "START_PSYCH_TEST"
|
| 82 |
-
|
| 83 |
-
|
| 84 |
|
| 85 |
3. ACTION: "CHECK_PROGRESS" (FOKUS: PROGRESS / DATA / LAPORAN / NILAI )
|
| 86 |
- Trigger: User ingin melihat HASIL belajarnya, statistiknya, progressnya, atau pencapaiannya sejauh ini.
|
|
@@ -100,22 +102,34 @@ class LLMEngine:
|
|
| 100 |
"detected_skills": ["Nama Skill Database 1", "Nama Skill Database 2"] (Array berisi String nama skill persis dari daftar diatas. Kosongkan jika tidak ada.)
|
| 101 |
}}
|
| 102 |
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 103 |
|
| 104 |
try:
|
| 105 |
-
|
| 106 |
-
messages=
|
| 107 |
-
{"role": "system", "content": system_prompt},
|
| 108 |
-
{"role": "user", "content": user_text}
|
| 109 |
-
],
|
| 110 |
model="llama-3.3-70b-versatile",
|
| 111 |
temperature=0.0,
|
| 112 |
response_format={"type": "json_object"}
|
| 113 |
)
|
|
|
|
|
|
|
|
|
|
| 114 |
return json.loads(response_content)
|
|
|
|
| 115 |
except Exception as e:
|
| 116 |
-
print(f"Error
|
| 117 |
return {"action": "CASUAL_CHAT", "detected_skills": []}
|
| 118 |
-
|
| 119 |
async def generate_question(self, topics: list, level: str):
|
| 120 |
topics_str = ", ".join(topics)
|
| 121 |
prompt = f"""
|
|
@@ -176,70 +190,56 @@ class LLMEngine:
|
|
| 176 |
print(f"ERROR Evaluate answer: {e}")
|
| 177 |
return {"score": 0, "feedback": "Error menilai.", "is_correct": False}
|
| 178 |
|
| 179 |
-
async def casual_chat(self, user_text: str, history: list = [],
|
| 180 |
-
|
| 181 |
-
if dataset_status == "FOUND":
|
| 182 |
-
system_instruction = f"""
|
| 183 |
-
[STATUS: VALID]
|
| 184 |
-
User bertanya tentang topik teknis yang ADA dalam dataset skill kamu: **[{keyword_context}]**.
|
| 185 |
-
|
| 186 |
-
TUGAS:
|
| 187 |
-
1. Jawab pertanyaan user tentang topik tersebut dengan konseptual yang singkat, padat, dan mudah dimengerti.
|
| 188 |
-
2. Fokus jawabanmu HANYA pada keyword tersebut.
|
| 189 |
-
3. Gunakan analogi sederhana jika perlu. Jangan terlalu kaku seperti buku teks, tapi tetap akurat.
|
| 190 |
-
4. Gaya bahasa: Ramah, Suportif, Mentor IT.
|
| 191 |
-
5. Giring user untuk menggunakan fitur belajar seperti tanya tentang skill teknis, Ujian/Tes sub skill, cek progres, rekomendasi belajar.
|
| 192 |
-
"""
|
| 193 |
-
else:
|
| 194 |
-
# SKENARIO 2: Keyword Tidak Ditemukan (TOLAK)
|
| 195 |
-
system_instruction = f"""
|
| 196 |
-
[STATUS: INVALID / OUT OF SCOPE]
|
| 197 |
-
User bertanya tentang topik yang TIDAK ditemukan dalam 'Skill Keywords Dataset' kamu.
|
| 198 |
-
|
| 199 |
-
TUGAS:
|
| 200 |
-
1. **TOLAK** untuk menjawab pertanyaan ini.
|
| 201 |
-
2. Katakan dengan sopan seperti: "Maaf, topik ini tidak ada dalam database skill yang saya pelajari."
|
| 202 |
-
3. JANGAN mencoba menjawab atau menebak, meskipun kamu tahu jawabannya secara umum. Patuhi whitelist dataset.
|
| 203 |
-
5. Tawarkan user untuk menggunakan fitur belajar seperti tanya tentang skill teknis, Ujian/Tes sub skill, cek progres, rekomendasi belajar.
|
| 204 |
-
"""
|
| 205 |
-
|
| 206 |
-
prompt_template = f"""
|
| 207 |
-
[ROLE]
|
| 208 |
-
Namamu MORA. Kamu adalah Mentor & Asisten Teknis Spesialis.
|
| 209 |
|
| 210 |
-
|
| 211 |
-
|
| 212 |
-
|
| 213 |
-
|
| 214 |
-
|
| 215 |
-
- Hindari jawaban yang terlalu panjang (lebih dari 4 kalimat).
|
| 216 |
-
- Jika tidak yakin, katakan "Maaf, itu di luar pengetahuan saya."
|
| 217 |
-
- Jangan buat-buat jawaban untuk topik di luar silabus.
|
| 218 |
-
|
| 219 |
-
[PERSONALITY]
|
| 220 |
-
Ramah, Suportif, Emoji secukupnya.
|
| 221 |
"""
|
| 222 |
-
|
| 223 |
-
system_msg = {
|
| 224 |
-
"role": "system",
|
| 225 |
-
"content": prompt_template
|
| 226 |
-
}
|
| 227 |
|
| 228 |
-
messages = [
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 229 |
for msg in history[-5:]:
|
| 230 |
messages.append({"role": msg['role'], "content": msg['content']})
|
|
|
|
|
|
|
| 231 |
messages.append({"role": "user", "content": user_text})
|
| 232 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 233 |
try:
|
| 234 |
-
|
| 235 |
messages=messages,
|
| 236 |
-
model="llama-3.
|
| 237 |
-
temperature=0.
|
| 238 |
)
|
|
|
|
| 239 |
except Exception as e:
|
| 240 |
-
|
| 241 |
-
return f"Maaf, otak saya sedang error. (Error: {str(e)})"
|
| 242 |
-
|
| 243 |
|
| 244 |
async def analyze_psych_result(self, role: str, traits: list[str]):
|
| 245 |
"""
|
|
|
|
| 52 |
print("❌ Semua Token Gagal/Habis.")
|
| 53 |
raise last_error
|
| 54 |
|
| 55 |
+
async def process_user_intent(self, user_text: str, available_skills: list, user_role: str = "", history: list = []):
|
| 56 |
skills_str = "\n".join([f"- {s}" for s in available_skills])
|
| 57 |
+
role_status_str = "USER BELUM MEMILIKI ROLE (ROLE KOSONG)." if not user_role else f"USER ROLE: {user_role}"
|
| 58 |
|
| 59 |
system_prompt = f"""
|
| 60 |
ROLE: Kamu adalah 'Router' untuk MORA, sebuah AI Learning Assistant.
|
|
|
|
| 62 |
|
| 63 |
DAFTAR SKILL TERSEDIA DI DATABASE:
|
| 64 |
{skills_str}
|
| 65 |
+
STATUS USER SAAT INI: {role_status_str}
|
| 66 |
|
| 67 |
INSTRUKSI UTAMA:
|
| 68 |
+
Analisis pesan user DAN HISTORY PERCAKAPAN untuk menentukan ACTION JSON.
|
| 69 |
|
| 70 |
1. ACTION: "START_EXAM"
|
| 71 |
- Trigger: User ingin "tes", "ujian", "uji kemampuan", "soal", atau menyebut topik teknis (SQL, Python, CV, NLP). kalau user tidak menyebut keyword teknis dari subskill maka akan menampilkan list dari sub skill yang ada.
|
|
|
|
| 81 |
- PENTING: Jangan pilih ini jika user hanya ingin melihat data progress, nilai/laporan.
|
| 82 |
|
| 83 |
3. ACTION: "START_PSYCH_TEST"
|
| 84 |
+
- Trigger: Role Kosong, tanya "karir", "cocok kerja apa" user bingung minat.
|
| 85 |
+
- KONTEKS: Jika di history BOT menawarakan tes minat, dan User jawab "Mau/Ya/Boleh", PILIH INI.
|
| 86 |
|
| 87 |
3. ACTION: "CHECK_PROGRESS" (FOKUS: PROGRESS / DATA / LAPORAN / NILAI )
|
| 88 |
- Trigger: User ingin melihat HASIL belajarnya, statistiknya, progressnya, atau pencapaiannya sejauh ini.
|
|
|
|
| 102 |
"detected_skills": ["Nama Skill Database 1", "Nama Skill Database 2"] (Array berisi String nama skill persis dari daftar diatas. Kosongkan jika tidak ada.)
|
| 103 |
}}
|
| 104 |
"""
|
| 105 |
+
messages = [{"role": "system", "content": system_prompt}]
|
| 106 |
+
|
| 107 |
+
# Masukkan 5 chat terakhir dari history agar AI tau konteks
|
| 108 |
+
# Kita pastikan formatnya dictionary
|
| 109 |
+
for msg in history[-5:]:
|
| 110 |
+
role = msg.get('role') if isinstance(msg, dict) else msg.role
|
| 111 |
+
content = msg.get('content') if isinstance(msg, dict) else msg.content
|
| 112 |
+
messages.append({"role": role, "content": content})
|
| 113 |
+
|
| 114 |
+
# Masukkan pesan user saat ini (paling baru)
|
| 115 |
+
messages.append({"role": "user", "content": user_text})
|
| 116 |
|
| 117 |
try:
|
| 118 |
+
chat_completion = await self.client.chat.completions.create(
|
| 119 |
+
messages=messages, # <--- Kirim list messages yang sudah ada history-nya
|
|
|
|
|
|
|
|
|
|
| 120 |
model="llama-3.3-70b-versatile",
|
| 121 |
temperature=0.0,
|
| 122 |
response_format={"type": "json_object"}
|
| 123 |
)
|
| 124 |
+
|
| 125 |
+
response_content = chat_completion.choices[0].message.content
|
| 126 |
+
print(f"DEBUG AI MAPPING: {response_content}")
|
| 127 |
return json.loads(response_content)
|
| 128 |
+
|
| 129 |
except Exception as e:
|
| 130 |
+
print(f"Error Intent: {e}")
|
| 131 |
return {"action": "CASUAL_CHAT", "detected_skills": []}
|
| 132 |
+
|
| 133 |
async def generate_question(self, topics: list, level: str):
|
| 134 |
topics_str = ", ".join(topics)
|
| 135 |
prompt = f"""
|
|
|
|
| 190 |
print(f"ERROR Evaluate answer: {e}")
|
| 191 |
return {"score": 0, "feedback": "Error menilai.", "is_correct": False}
|
| 192 |
|
| 193 |
+
async def casual_chat(self, user_text: str, history: list = [], is_role_empty: bool = False):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 194 |
|
| 195 |
+
# 1. BASE SYSTEM PROMPT (Identitas Utama)
|
| 196 |
+
# Ini selalu ada, baik role kosong maupun tidak.
|
| 197 |
+
base_system_prompt = """
|
| 198 |
+
Kamu adalah MORA, asisten belajar AI yang ramah, suportif, dan kekinian.
|
| 199 |
+
Jawablah dengan singkat, padat, dan menggunakan emoji sesekali.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 200 |
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 201 |
|
| 202 |
+
messages = [
|
| 203 |
+
{"role": "system", "content": base_system_prompt}
|
| 204 |
+
]
|
| 205 |
+
|
| 206 |
+
# 2. INSERT HISTORY (Normal Flow)
|
| 207 |
+
# Kita tetap masukkan history agar konteks obrolan nyambung
|
| 208 |
for msg in history[-5:]:
|
| 209 |
messages.append({"role": msg['role'], "content": msg['content']})
|
| 210 |
+
|
| 211 |
+
# 3. INSERT USER MESSAGE (Normal Flow)
|
| 212 |
messages.append({"role": "user", "content": user_text})
|
| 213 |
+
|
| 214 |
+
# 4. INJECT RESTRICTION (JIKA ROLE KOSONG)
|
| 215 |
+
# Kita taruh ini DI PALING BAWAH (Setelah user message).
|
| 216 |
+
# Tujuannya: Mengoreksi jika user minta aneh-aneh.
|
| 217 |
+
if is_role_empty:
|
| 218 |
+
restriction_msg = """
|
| 219 |
+
[SYSTEM ALERT - PRIORITY HIGH]
|
| 220 |
+
Status User: ROLE KOSONG (Belum memilih jalur karir).
|
| 221 |
+
|
| 222 |
+
INSTRUKSI RESPON:
|
| 223 |
+
1. JIKA user meminta Soal/Tes/Coding/rekomendasi/progress:
|
| 224 |
+
TOLAK permintaan tersebut dengan halus.
|
| 225 |
+
Katakan: "Wah semangat banget! 🔥 Tapi pilih Role dulu yuk biar jelas arahnya. Mau coba Tes Minat?" tetapi sesuaikan lagi konteksnya
|
| 226 |
+
|
| 227 |
+
2. JIKA user hanya menyapa/curhat:
|
| 228 |
+
Respon normal, tapi selipkan saran untuk memilih Role atau Tes Minat.
|
| 229 |
+
"""
|
| 230 |
+
# Masukkan sebagai role 'system' agar dipatuhi
|
| 231 |
+
messages.append({"role": "system", "content": restriction_msg})
|
| 232 |
+
|
| 233 |
+
# 5. EXECUTE LLM
|
| 234 |
try:
|
| 235 |
+
completion = await self.client.chat.completions.create(
|
| 236 |
messages=messages,
|
| 237 |
+
model="llama-3.1-8b-instant",
|
| 238 |
+
temperature=0.6
|
| 239 |
)
|
| 240 |
+
return completion.choices[0].message.content
|
| 241 |
except Exception as e:
|
| 242 |
+
return f"Maaf, error sistem: {str(e)}"
|
|
|
|
|
|
|
| 243 |
|
| 244 |
async def analyze_psych_result(self, role: str, traits: list[str]):
|
| 245 |
"""
|