146 lines
5.5 KiB
Python
146 lines
5.5 KiB
Python
import math
|
||
import re
|
||
from collections import Counter
|
||
|
||
|
||
class KAIGapekaRAG:
|
||
"""Sistem RAG Sederhana berbasis Pure Python untuk topik GAPEKA 2026 PT KAI."""
|
||
|
||
def __init__(self, knowledge_base):
|
||
print("[INFO] Menginisialisasi Basis Pengetahuan KAI Gapeka 2026...")
|
||
self.documents = knowledge_base
|
||
self.vocab = self._build_vocab(self.documents)
|
||
# Pra-penghitungan vektor TF-IDF untuk dokumen lokal
|
||
self.doc_vectors = [
|
||
self._compute_tfidf(doc, self.vocab, self.documents)
|
||
for doc in self.documents
|
||
]
|
||
|
||
def _tokenize(self, text):
|
||
"""Membersihkan teks dan memecahnya menjadi kata-kata (token)."""
|
||
return re.findall(r"\b\w+\b", text.lower())
|
||
|
||
def _build_vocab(self, docs):
|
||
"""Membangun kamus kata unik dari seluruh dokumen."""
|
||
vocab = set()
|
||
for doc in docs:
|
||
vocab.update(self._tokenize(doc))
|
||
return sorted(list(vocab))
|
||
|
||
def _compute_tfidf(self, text, vocab, all_docs):
|
||
"""Menghitung bobot TF-IDF secara manual tanpa library eksternal."""
|
||
tokens = self._tokenize(text)
|
||
tf = Counter(tokens)
|
||
total_tokens = len(tokens) if tokens else 1
|
||
|
||
vector = {}
|
||
for word in vocab:
|
||
# Term Frequency (TF)
|
||
term_freq = tf[word] / total_tokens
|
||
|
||
# Inverse Document Frequency (IDF)
|
||
doc_count = sum(1 for d in all_docs if word in self._tokenize(d))
|
||
idf = (
|
||
math.log(len(all_docs) / (doc_count + 1)) + 1 if doc_count > 0 else 0
|
||
)
|
||
|
||
vector[word] = term_freq * idf
|
||
return vector
|
||
|
||
def _cosine_similarity(self, vec1, vec2):
|
||
"""Menghitung kemiripan kosinus antara dua vektor TF-IDF."""
|
||
intersection = set(vec1.keys()).intersection(set(vec2.keys()))
|
||
numerator = sum(vec1[x] * vec2[x] for x in intersection)
|
||
|
||
sum1 = sum(val ** 2 for val in vec1.values())
|
||
sum2 = sum(val ** 2 for val in vec2.values())
|
||
denominator = math.sqrt(sum1) * math.sqrt(sum2)
|
||
|
||
if denominator == 0:
|
||
return 0.0
|
||
return numerator / denominator
|
||
|
||
def retrieve(self, query, top_k=1):
|
||
"""Mencari dokumen paling relevan menggunakan Cosine Similarity."""
|
||
query_vector = self._compute_tfidf(query, self.vocab, self.documents)
|
||
scores = []
|
||
|
||
for i, doc_vec in enumerate(self.doc_vectors):
|
||
score = self._cosine_similarity(query_vector, doc_vec)
|
||
scores.append((score, self.documents[i]))
|
||
|
||
# Urutkan berdasarkan skor tertinggi
|
||
scores.sort(key=lambda x: x[0], reverse=True)
|
||
return [doc for score, doc in scores[:top_k]]
|
||
|
||
def generate_answer(self, query):
|
||
"""Tahap Generation: Menggabungkan hasil retrieval dengan format jawaban RAG."""
|
||
retrieved_docs = self.retrieve(query, top_k=1)
|
||
context = (
|
||
retrieved_docs[0]
|
||
if retrieved_docs
|
||
else "Maaf, informasi tidak ditemukan."
|
||
)
|
||
|
||
# Template Generator Extractive RAG
|
||
answer = (
|
||
f"==================================================\n"
|
||
f"HASIL GENERASI RAG - PT KERETA API INDONESIA (GAPEKA 2026)\n"
|
||
f"==================================================\n"
|
||
f"Pertanyaan : {query}\n\n"
|
||
f"Konteks Relevan (Retrieved):\n-> {context}\n\n"
|
||
f"Jawaban Sistem:\n"
|
||
f"Berdasarkan informasi resmi kebijakan GAPEKA 2026 PT KAI, "
|
||
f"berikut adalah detail informasinya:\n"
|
||
f'"{context}"\n'
|
||
f"=================================================="
|
||
)
|
||
return answer, retrieved_docs
|
||
|
||
|
||
# ==========================================
|
||
# EKSEKUSI PROGRAM UTAMA
|
||
# ==========================================
|
||
if __name__ == "__main__":
|
||
# 1. Dataset / Basis Pengetahuan Lokal KAI GAPEKA 2026
|
||
kai_kb = [
|
||
(
|
||
"PT Kereta Api Indonesia (KAI) resmi memberlakukan Grafik Perjalanan "
|
||
"Kereta Api (GAPEKA) 2026 mulai tanggal 1 Desember 2026."
|
||
),
|
||
(
|
||
"Tiket perjalanan kereta api untuk jadwal GAPEKA 2026 sudah dapat "
|
||
"dipesan secara online melalui aplikasi Access by KAI."
|
||
),
|
||
(
|
||
"Terdapat penyesuaian serta penyederhanaan nama pada beberapa "
|
||
"perjalanan kereta api agar lebih simpel dan mudah diingat oleh "
|
||
"penumpang."
|
||
),
|
||
(
|
||
"KAI menambah titik pemberhentian di sejumlah stasiun untuk "
|
||
"mendukung mobilitas antarkota dan kawasan aglomerasi."
|
||
),
|
||
(
|
||
"KAI menghadirkan layanan rute baru seperti KA Borobudur "
|
||
"(Lempuyangan–Gambir PP, fakultatif) dan KA Pandawa "
|
||
"(Yogyakarta–Semarang Tawang PP) yang mulai beroperasi pada 1 "
|
||
"Desember 2026."
|
||
),
|
||
]
|
||
|
||
# 2. Inisialisasi Sistem RAG
|
||
rag = KAIGapekaRAG(kai_kb)
|
||
|
||
# 3. Contoh Daftar Pengujian Pertanyaan Tugas RAG
|
||
daftar_pertanyaan = [
|
||
"Kapan GAPEKA 2026 mulai diberlakukan dan di mana bisa pesan tiketnya?",
|
||
"Apa saja rute kereta api baru yang dihadirkan KAI dalam GAPEKA 2026?",
|
||
"Bagaimana kebijakan terkait nama kereta api dalam GAPEKA 2026?"
|
||
]
|
||
|
||
# 4. Menjalankan Pipeline Pengujian
|
||
for idx, sample_query in enumerate(daftar_pertanyaan, 1):
|
||
print(f"\n[PENGUJIAN {idx}]")
|
||
final_answer, sources = rag.generate_answer(sample_query)
|
||
print(final_answer) |