Upload files to "/"

This commit is contained in:
2026-11-02 01:41:24 -05:00
commit 0d28205930
4 changed files with 480 additions and 0 deletions
+128
View File
@@ -0,0 +1,128 @@
import random
from pathlib import Path
# FUNGSI MEMBACA PDF
def baca_pdf(path):
reader = PdfReader(path)
return "\n".join(
(p.extract_text() or "")
for p in reader.pages
)
# FUNGSI MEMOTONG TEKS (CHUNKING)
def potong(teks, ukuran=800, tumpang=100):
hasil = []
mulai = 0
while mulai < len(teks):
hasil.append(teks[mulai:mulai + ukuran])
mulai += ukuran - tumpang
return [h for h in hasil if h.strip()]
# MENCARI PDF DI FOLDER DOKUMEN
daftar_pdf = list(Path("dokumen").glob("*.pdf"))
if not daftar_pdf:
print("❌ Tidak ada file PDF di folder 'dokumen'! Pastikan Anda membuat folder bernama 'dokumen' dan memasukkan file PDF terkait KAI di dalamnya.")
else:
# Mengambil PDF pertama
pdf = daftar_pdf[0]
# Membaca isi PDF
teks = baca_pdf(pdf)
print(f"\n📄 Nama PDF: {pdf.name}")
print(f"📊 Total karakter: {len(teks)}")
# Menampilkan 500 karakter pertama
print("\n===== 500 KARAKTER PERTAMA =====")
print(teks[:500])
# Eksperimen tiga ukuran chunk
for ukuran in (500, 1000, 1500):
overlap = int(ukuran * 0.1)
chunks = potong(
teks,
ukuran=ukuran,
tumpang=overlap
)
print("\n" + "=" * 50)
print(f"UKURAN CHUNK: {ukuran}")
print(f"OVERLAP: {overlap}")
print(f"JUMLAH CHUNK: {len(chunks)}")
# Mengambil maksimal 3 chunk secara acak
sampel = random.sample(
range(len(chunks)),
min(3, len(chunks))
)
for nomor in sampel:
print(f"\n--- CHUNK {nomor} ---")
print(chunks[nomor])
# LAB 2: EMBEDDING DAN CHROMADB
if daftar_pdf:
# Model embedding multilingual
emb = embedding_functions.SentenceTransformerEmbeddingFunction(
model_name="paraphrase-multilingual-MiniLM-L12-v2"
)
# Membuat database lokal
client = chromadb.PersistentClient(path="./chroma_db")
def bangun_indeks(nama_koleksi, ukuran):
# Menghapus koleksi lama agar tidak tercampur
try:
client.delete_collection(name=nama_koleksi)
except Exception:
pass
# Membuat koleksi baru
koleksi = client.create_collection(
name=nama_koleksi,
embedding_function=emb,
metadata={"hnsw:space": "cosine"}
)
for pdf in daftar_pdf:
teks = baca_pdf(pdf)
overlap = int(ukuran * 0.1)
chunks = potong(
teks,
ukuran=ukuran,
tumpang=overlap
)
if not chunks:
print(pdf.name, "tidak memiliki teks.")
continue
koleksi.upsert(
ids=[
f"{pdf.stem}-{ukuran}-{i}"
for i in range(len(chunks))
],
documents=chunks,
metadatas=[
{
"sumber": pdf.name,
"chunk": i,
"ukuran": ukuran
}
for i in range(len(chunks))
]
)
print(pdf.name, "->", len(chunks), "chunk")
print(f"\n✅ Koleksi '{nama_koleksi}' berhasil dibuat.")
print("Total data dalam koleksi:", koleksi.count())
# Membuat dua koleksi untuk eksperimen
bangun_indeks("pedoman_500", 500)
bangun_indeks("pedoman_1000", 1000)
else:
print("⚠️ Indeks gagal dibuat karena tidak ada file PDF.")
Binary file not shown.
+146
View File
@@ -0,0 +1,146 @@
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)
+206
View File
@@ -0,0 +1,206 @@
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_with_scores(self, query, top_k=5):
"""Mengembalikan daftar dokumen dan skornya berdasarkan 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 scores[:top_k]
def generate_answer(self, query):
"""Tahap Generation: Menggabungkan hasil retrieval dengan format jawaban RAG."""
retrieved_scores = self.retrieve_with_scores(query, top_k=1)
context = (
retrieved_scores[0][1]
if retrieved_scores and retrieved_scores[0][0] > 0
else "Maaf, informasi tidak ditemukan."
)
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, context
# ==========================================
# MODUL EVALUASI RETRIEVAL (Hit@K & MRR)
# ==========================================
# Daftar Uji (Pertanyaan dan kata kunci yang harus ada di dokumen yang diretrubusi)
UJI = [
("Kapan GAPEKA 2026 mulai diberlakukan?", "1 desember 2026"),
("Di mana bisa pesan tiket kereta GAPEKA 2026?", "access by kai"),
("Apa ada perubahan nama kereta api?", "penyederhanaan nama"),
("Apakah ada penambahan titik pemberhentian stasiun?", "titik pemberhentian"),
("Sebutkan rute kereta api baru yang beroperasi!", "ka borobudur"),
]
def cari_peringkat(rag_system, pertanyaan, kunci, k=5):
"""Mencari peringkat dokumen pertama yang mengandung kata kunci."""
hasil = rag_system.retrieve_with_scores(pertanyaan, top_k=k)
kunci_bersih = " ".join(kunci.lower().split())
for peringkat, (score, doc) in enumerate(hasil, start=1):
doc_bersih = " ".join(doc.lower().split())
if kunci_bersih in doc_bersih and score > 0:
return peringkat
return None
def hit_at_k(rag_system, uji_data, k=3):
"""Menghitung metrik Hit@K."""
benar = 0
for pertanyaan, kunci in uji_data:
peringkat = cari_peringkat(rag_system, pertanyaan, kunci, k)
if peringkat is not None:
benar += 1
return benar / len(uji_data)
def hitung_mrr(rag_system, uji_data):
"""Menghitung metrik Mean Reciprocal Rank (MRR@5)."""
total = 0
for pertanyaan, kunci in uji_data:
peringkat = cari_peringkat(rag_system, pertanyaan, kunci, 5)
if peringkat is not None:
total += 1 / peringkat
return total / len(uji_data)
# ==========================================
# 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 Uji Jalankan Pipeline RAG Tunggal
sample_query = "Kapan GAPEKA 2026 mulai diberlakukan dan di mana bisa pesan tiketnya?"
final_answer, sources = rag.generate_answer(sample_query)
print("\n" + final_answer)
# 4. Evaluasi Performa Retrieval (Hit@K & MRR)
print("\n" + "=" * 50)
print("EVALUASI PERFORMA RETRIEVAL KAI GAPEKA 2026")
print("=" * 50)
print(f"Jumlah Pertanyaan Uji : {len(UJI)}")
for k in (1, 3, 5):
nilai_hit = hit_at_k(rag, UJI, k)
print(f"Hit@{k} = {nilai_hit:.2f}")
mrr_score = hitung_mrr(rag, UJI)
print(f"MRR@5 = {mrr_score:.2f}")
print("\nPERTANYAAN YANG GAGAL DITEMUKAN (Jika ada):")
gagal = False
for pertanyaan, kunci in UJI:
peringkat = cari_peringkat(rag, pertanyaan, kunci, 5)
if peringkat is None:
gagal = True
print(f"- {pertanyaan}")
if not gagal:
print("Semua pertanyaan berhasil ditemukan oleh sistem retrieval!")
print("=" * 50)