commit c8f8ce04c48f4a00e2015efa96d8b83e4c3e49bb Author: Rendra Date: Mon Nov 2 10:34:22 2026 +0700 commit pertama diff --git a/ScreenShots/Lab_1.png b/ScreenShots/Lab_1.png new file mode 100644 index 0000000..6ca8698 Binary files /dev/null and b/ScreenShots/Lab_1.png differ diff --git a/ScreenShots/Lab_2.png b/ScreenShots/Lab_2.png new file mode 100644 index 0000000..7009b00 Binary files /dev/null and b/ScreenShots/Lab_2.png differ diff --git a/ScreenShots/Lab_3.png b/ScreenShots/Lab_3.png new file mode 100644 index 0000000..7558e0f Binary files /dev/null and b/ScreenShots/Lab_3.png differ diff --git a/lab-1/Dokumen/panduan_akademik.pdf b/lab-1/Dokumen/panduan_akademik.pdf new file mode 100644 index 0000000..c00af1f --- /dev/null +++ b/lab-1/Dokumen/panduan_akademik.pdf @@ -0,0 +1,93 @@ +%PDF-1.4 +%“Œ‹ž ReportLab Generated PDF document (opensource) +1 0 obj +<< +/F1 2 0 R /F2 3 0 R +>> +endobj +2 0 obj +<< +/BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font +>> +endobj +3 0 obj +<< +/BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding /Name /F2 /Subtype /Type1 /Type /Font +>> +endobj +4 0 obj +<< +/Contents 9 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 8 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +5 0 obj +<< +/Contents 10 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 8 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +6 0 obj +<< +/PageMode /UseNone /Pages 8 0 R /Type /Catalog +>> +endobj +7 0 obj +<< +/Author (\(anonymous\)) /CreationDate (D:20261102012834+00'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20261102012834+00'00') /Producer (ReportLab PDF Library - \(opensource\)) + /Subject (\(unspecified\)) /Title (Panduan Akademik Universitas Contoh) /Trapped /False +>> +endobj +8 0 obj +<< +/Count 2 /Kids [ 4 0 R 5 0 R ] /Type /Pages +>> +endobj +9 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1769 +>> +stream +Gat=+a`?B3&A@rknjmuO)D+LqZ+$.bp<3YVR%#`TWsj[t1fDj`r:ffgZpI,I33WumA``Jbm*dKqdT#mH^1YVau?T33/hKc6a4E;37jNV@%$QRb/,4*4QCR;*h*Ai#S;Ja[QcmNbJ$@SPsKt]>Rr$]M[-DAOR'T!D*I8!'RF-Y0-83tM[Z#BdJA;dVU-Zd6kumlMf`o\@U8Fhdm.!S7T4.:Z/aSDeC^*t';[$SGh?+S6Rns'bH5j!rgM4q&Q+tMM.0)V6ibj_;Q\BHKsXT*V">#hjtjGNA"DDIp91HeLIXmR:gFhmbAqpTh(,L\+S.E.'9mKS)nJo1?VV+AN:7\U#>VJta;/+EeB.qN:9O0_oF9GLbumTuU"8[@MWaa#FF[Sin3)KihQn1#QL-nY&=lu'TqS:tl7eln/0rD1tt\t"U^TK85!**C@@&A4\sBsBHB%6W;mD'2s(]h!p!lGO:/Aed\5n2(a'>#Yt@]jbKsOlMj;'7\tPAB\mmYc4c0*cq5'X<@c_:(R6oN+H%VeXJ)0)ao0-Rn_J=0B$m*;!>\KUb')\VfZhuD$Xj5>bi-A8OLau?X0P[,PL/;=b*K&Q][]^;D_9rQ@@q6ldpk'NVh,-=;>L`FR95XA,gA0R/iPb/Z(_[Dogn_F[-fh+-)"XK?/=^H#r,OjF.;S-P;tI2N16$_1T:/h!*H6""5tBMQk$op1_qCZmEa*lCl`hqY+P=u`2p/Em6D^68-drWkO=&$A@DDeFt?Se6ZL`fibN(X'nUd4RZ!=>9L3h<$HWa#e+/IURY:ZT/>hY)?eJaf<'aZJFKooCon6"R/Bh)S4!J3(f;t>qW=ZmWNlj;Jf!;nqB*m"U2pnY(:,-+?@JG3VD;"^V+9&ck,'rJdSI_^>*R5_OQ0nXBKp$L7+&'=4:N8C\(h_;@`gi4@S`R:LEs"\0%'c:d>fgr]@1UrLT7>_g4bt@_fj+g.rqKfOngTs!eNR5%Z\oFU>NFT4.9^u=.)X[NO(qoeI@l.hj%`c9^7PusY%+,3fGWb8*ij:'/_(!D^endstream +endobj +10 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 736 +>> +stream +Gat='9lJKG&A@7.]OQhVZ/^3GDJ^g8.\NNI[pCD+CJWMsL]&@\^Lq^aP(Gs$OHhenh0JVSc'ndF4@T,GAq5dHDa"j`AuHbIbd0=0:UmDOh^?CHS#^+D[TjShXQHQ7T9W'kPN+bGgL_W8`;t(3ap?o67@=/%,2@:3iEEWgZ_\_NI-rmdr<]J5W`QIZ=*adCEV[%j[UQcc91f^`._/5!5>t48h2\_7%Bkno_f3qV3ij+L.dL&E@i8$a_1jkf]J;bYatFbr(6BTeOMtDVQLpd>e>U)i*B]Cbi*<_mJ#eNK?K-(MIG*%#-D98R9NYn$\n6D/H(V8Otaf:qmn5B)dlB]cBo,m7X9tT=M-Z9Sgg8S43@P.Vfg/:X!^\=<6Pj;&V1%KI_fqVS&G%f`h/7("uK^j(GkbtI/'_[AI1ur&01Q9]P&(-;(^^N0ER\:aJ8IJo?Mr]8Br11/*`m4eL%4@%#/2=$r3rC;nS4%:`,IXh_g]Yf#ZIItb]1s\s;E=A'\E-Vl9;W.MfO$".=S/1?GDXZ+1-%B\"d@kP;U?%:N^dja32*,>SVEl9cdA]"41.ea[lsI,ZgFlF0;t/1=U%^h\ZB;h*'5PV.S*N9Ba5eVaendstream +endobj +xref +0 11 +0000000000 65535 f +0000000061 00000 n +0000000102 00000 n +0000000209 00000 n +0000000321 00000 n +0000000524 00000 n +0000000728 00000 n +0000000796 00000 n +0000001098 00000 n +0000001163 00000 n +0000003023 00000 n +trailer +<< +/ID +[<00caf45b9e105d99b02ef9bd9f5fa72c><00caf45b9e105d99b02ef9bd9f5fa72c>] +% ReportLab generated PDF document -- digest (opensource) + +/Info 7 0 R +/Root 6 0 R +/Size 11 +>> +startxref +3850 +%%EOF diff --git a/lab-1/lab_1.py b/lab-1/lab_1.py new file mode 100644 index 0000000..96952d5 --- /dev/null +++ b/lab-1/lab_1.py @@ -0,0 +1,84 @@ +""" +Lab 1 - Eksperimen Chunking (15 menit) +Hanya butuh library pypdf: pip install pypdf + +Cara jalankan (pilih salah satu): + python lab_1.py -> otomatis pakai PDF pertama di folder dokumen/ + python lab_1.py dokumen/panduan_akademik.pdf -> pakai PDF tertentu + python lab_1.py dokumen/panduan_akademik.pdf --overlap 0.2 +""" + +import argparse +import random +import re +from pathlib import Path + +from pypdf import PdfReader + +UKURAN_LIST = [500, 1000, 1500] + + +def baca_pdf(path): + """Baca seluruh teks dari satu file PDF.""" + reader = PdfReader(str(path)) + halaman = [(p.extract_text() or "") for p in reader.pages] + teks = "\n".join(halaman) + return re.sub(r"[ \t]+", " ", teks).strip() + + +def potong(teks, ukuran=1000, overlap=0.15): + """Potong teks jadi chunk berukuran `ukuran` karakter dengan overlap (0-1).""" + langkah = max(1, int(ukuran * (1 - overlap))) + chunks = [] + for mulai in range(0, len(teks), langkah): + potongan = teks[mulai:mulai + ukuran].strip() + if potongan: + chunks.append(potongan) + if mulai + ukuran >= len(teks): + break + return chunks + + +def cari_pdf_default(): + daftar = sorted(Path("dokumen").glob("*.pdf")) + if not daftar: + raise SystemExit("Tidak ada PDF di folder dokumen/. Taruh PDF di sana atau tulis path-nya.") + return daftar[0] + + +def main(): + p = argparse.ArgumentParser(description="Lab 1: eksperimen chunking") + p.add_argument("pdf", nargs="?", help="Path PDF (kosongkan untuk pakai PDF pertama di dokumen/)") + p.add_argument("--overlap", type=float, default=0.15, help="Overlap 0.10 - 0.20 (default 0.15)") + args = p.parse_args() + + path = Path(args.pdf) if args.pdf else cari_pdf_default() + print(f"File: {path}\n") + + # 1. Baca PDF, cetak 500 karakter pertama + teks = baca_pdf(path) + print(f"=== 1. Hasil baca PDF ({len(teks)} karakter total) ===") + print(teks[:500]) + + # 2. Potong dengan tiga ukuran, catat jumlah chunk + print("\n=== 2. Jumlah chunk per pengaturan ===") + hasil = {} + for ukuran in UKURAN_LIST: + chunks = potong(teks, ukuran, args.overlap) + hasil[ukuran] = chunks + print(f" ukuran={ukuran:<5} overlap={args.overlap:.0%} -> {len(chunks)} chunk") + + # 3. Lihat 3 chunk acak tiap ukuran + print("\n=== 3. Tiga chunk acak per ukuran (terpotong di tengah kalimat/tabel?) ===") + for ukuran, chunks in hasil.items(): + print(f"\n--- ukuran {ukuran} ---") + for c in random.sample(chunks, min(3, len(chunks))): + awal_rapi = c[0].isupper() or c[0].isdigit() + akhir_rapi = c[-1] in ".!?:" + print(f"[awal rapi: {awal_rapi} | akhir rapi: {akhir_rapi}]") + print(c) + print("." * 40) + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/lab-2/Lab_2.py b/lab-2/Lab_2.py new file mode 100644 index 0000000..c6b17f7 --- /dev/null +++ b/lab-2/Lab_2.py @@ -0,0 +1,75 @@ +import os +import chromadb +from chromadb.utils import embedding_functions +from pypdf import PdfReader +from langchain_text_splitters import RecursiveCharacterTextSplitter + +def jalankan_lab2(): + print("--- ๐Ÿงช LAB 2: Membangun Indeks & Pencarian Semantik (Multi-PDF) ---") + + folder_dokumen = "dokumen" + teks_gabungan = "" + + if not os.path.exists(folder_dokumen): + print(f"โŒ Folder '{folder_dokumen}' tidak ditemukan!") + return + + # Mendeteksi otomatis semua file PDF di dalam folder dokumen/ + daftar_pdf = [f for f in os.listdir(folder_dokumen) if f.lower().endswith('.pdf')] + + if not daftar_pdf: + print(f"โŒ Tidak ada file PDF di dalam folder '{folder_dokumen}/'!") + return + + print(f"๐Ÿ“‚ File PDF yang ditemukan: {daftar_pdf}") + + for nama_file in daftar_pdf: + file_path = os.path.join(folder_dokumen, nama_file) + reader = PdfReader(file_path) + teks_file = "" + for halaman in reader.pages: + t = halaman.extract_text() + if t: + teks_file += t + "\n" + + splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=75) + chunks_file = splitter.split_text(teks_file) + print(f" - {nama_file}: menghasilkan {len(chunks_file)} chunk.") + teks_gabungan += teks_file + "\n" + + # Chunking keseluruhan teks dari semua PDF + splitter_utama = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=75) + semua_chunks = splitter_utama.split_text(teks_gabungan) + print(f"\n๐Ÿ“„ Total keseluruhan chunk dari semua PDF: {len(semua_chunks)}") + + # Inisialisasi ChromaDB & Embedding Lokal + client = chromadb.Client() + ef = embedding_functions.SentenceTransformerEmbeddingFunction(model_name="paraphrase-multilingual-MiniLM-L12-v2") + + collection = client.get_or_create_collection(name="rag_lab2_multi", embedding_function=ef) + + # Masukkan chunk ke Vector Database + collection.add( + documents=semua_chunks, + ids=[str(i) for i in range(len(semua_chunks))] + ) + print("โœ… Berhasil menyimpan seluruh indeks ke Vector Database (ChromaDB)!\n") + + # Mengajukan 5 Pertanyaan (Sesuai ketentuan Lab 2) + daftar_pertanyaan = [ + "Apa itu Retrieval-Augmented Generation?", + "Mengapa chunking diperlukan dalam sistem RAG?", + "Bagaimana cara kerja vector database?", + "Jelaskan teknik memecah dokumen agar tidak kehilangan konteks!", + "Bagaimana cara melakukan optimasi sistem RAG?" + ] + + print("--- ๐Ÿ” HASIL PENCARIAN QUERY (5 PERTANYAAN) ---") + for i, q in enumerate(daftar_pertanyaan, 1): + hasil = collection.query(query_texts=[q], n_results=1) + print(f"\nPertanyaan {i}: {q}") + print(f"๐Ÿ’ก Hasil Chunk Relevan: {hasil['documents'][0][0].strip()}") + print("-" * 60) + +if __name__ == "__main__": + jalankan_lab2() \ No newline at end of file diff --git a/lab-2/dokumen/RAG1.pdf b/lab-2/dokumen/RAG1.pdf new file mode 100644 index 0000000..5cfb7f2 Binary files /dev/null and b/lab-2/dokumen/RAG1.pdf differ diff --git a/lab-2/dokumen/RAG2.pdf b/lab-2/dokumen/RAG2.pdf new file mode 100644 index 0000000..6ed9ce6 Binary files /dev/null and b/lab-2/dokumen/RAG2.pdf differ diff --git a/lab-2/dokumen/RAG3.pdf b/lab-2/dokumen/RAG3.pdf new file mode 100644 index 0000000..3ee8fce Binary files /dev/null and b/lab-2/dokumen/RAG3.pdf differ diff --git a/lab-3/Lab_3.py b/lab-3/Lab_3.py new file mode 100644 index 0000000..90b28ad --- /dev/null +++ b/lab-3/Lab_3.py @@ -0,0 +1,66 @@ +import os +import chromadb +from chromadb.utils import embedding_functions +from pypdf import PdfReader +from langchain_text_splitters import RecursiveCharacterTextSplitter + +def jalankan_lab3(): + print("--- ๐Ÿงช LAB 3: RAG Pipeline & Evaluasi Pencarian ---") + + folder_dokumen = "dokumen" + teks_gabungan = "" + + if not os.path.exists(folder_dokumen): + print(f"โŒ Folder '{folder_dokumen}' tidak ditemukan!") + return + + # 1. Otomatis membaca semua file PDF di folder dokumen/ + daftar_pdf = [f for f in os.listdir(folder_dokumen) if f.lower().endswith('.pdf')] + print(f"๐Ÿ“‚ Memuat dokumen: {daftar_pdf}") + + for nama_file in daftar_pdf: + file_path = os.path.join(folder_dokumen, nama_file) + reader = PdfReader(file_path) + for halaman in reader.pages: + t = halaman.extract_text() + if t: + teks_gabungan += t + "\n" + + # 2. Proses Chunking + splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=75) + semua_chunks = splitter.split_text(teks_gabungan) + print(f"๐Ÿ“„ Total chunk yang diindeks: {len(semua_chunks)}") + + # 3. Inisialisasi Vector Database (ChromaDB) & Embedding Lokal + client = chromadb.Client() + ef = embedding_functions.SentenceTransformerEmbeddingFunction(model_name="paraphrase-multilingual-MiniLM-L12-v2") + + # Gunakan collection unik untuk Lab 3 + collection = client.get_or_create_collection(name="rag_lab3_pipeline", embedding_function=ef) + + collection.add( + documents=semua_chunks, + ids=[str(i) for i in range(len(semua_chunks))] + ) + print("โœ… Berhasil membangun indeks untuk RAG Pipeline!\n") + + # 4. Simulasi RAG (Retrieval + Context Generation) + pertanyaan_pengguna = "Bagaimana cara kerja vector database dan apa fungsinya?" + + print(f"โ“ Pertanyaan Pengguna: \"{pertanyaan_pengguna}\"") + + # Melakukan retrieval (pencarian chunk relevan teratas) + hasil_retrieval = collection.query(query_texts=[pertanyaan_pengguna], n_results=2) + konteks_terambil = hasil_retrieval['documents'][0] + + print("\n๐Ÿ” Konteks Relevan yang Ditemukan dari Dokumen:") + for idx, teks_chunk in enumerate(konteks_terambil, 1): + print(f" [Chunk {idx}]: {teks_chunk.strip()}") + print("-" * 40) + + # 5. Simulasi Jawaban Akhir Berdasarkan Konteks (RAG Generation) + print("\n๐Ÿค– Simulasi Jawaban Sistem RAG:") + print("Berdasarkan dokumen yang tersedia, vector database bekerja dengan menyimpan representasi numerik (embedding) dari teks ke dalam ruang multi-dimensi. Ketika ada pertanyaan, sistem mencari jarak terdekat (cosine similarity) untuk mencocokkan informasi yang paling relevan guna menjawab kebutuhan pengguna.") + +if __name__ == "__main__": + jalankan_lab3() \ No newline at end of file diff --git a/lab-3/dokumen/RAG1.pdf b/lab-3/dokumen/RAG1.pdf new file mode 100644 index 0000000..5cfb7f2 Binary files /dev/null and b/lab-3/dokumen/RAG1.pdf differ diff --git a/lab-3/dokumen/RAG2.pdf b/lab-3/dokumen/RAG2.pdf new file mode 100644 index 0000000..6ed9ce6 Binary files /dev/null and b/lab-3/dokumen/RAG2.pdf differ diff --git a/lab-3/dokumen/RAG3.pdf b/lab-3/dokumen/RAG3.pdf new file mode 100644 index 0000000..3ee8fce Binary files /dev/null and b/lab-3/dokumen/RAG3.pdf differ