Spaces:
Sleeping
Sleeping
Upload 4 files
Browse files- IPLM_QnA_Chatbot.jsonl +29 -0
- README.md +37 -14
- app.py +199 -0
- requirements.txt +6 -0
IPLM_QnA_Chatbot.jsonl
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{"id": "iplm-001", "question": "Apa itu IPLM dan mengapa penting?", "answer": "IPLM (Indeks Pembangunan Literasi Masyarakat) adalah instrumen resmi untuk mengukur capaian literasi masyarakat berbasis kontribusi perpustakaan. Penting karena menjadi Indikator Kinerja Kunci (IKK) urusan perpustakaan sesuai Permendagri 18/2020, sekaligus dasar perumusan kebijakan literasi nasional menuju Indonesia Emas 2045.", "alt_phrases": ["Bisakah jelaskan: Apa itu IPLM dan mengapa penting?", "Saya ingin tahu, apa itu iplm dan mengapa penting?"], "source_doc": "Juknis IPLM 2025 (Pelaksanaan)", "source_locator": "", "last_updated": "2025-09-29", "language": "id", "keywords": "iplm, mengapa, penting?"}
|
| 2 |
+
{"id": "iplm-002", "question": "Siapa saja yang menjadi sasaran IPLM?", "answer": "Sasaran utama adalah: Perpusnas (koordinasi nasional), Perpustakaan Provinsi & Kabupaten/Kota (pelaksana teknis), semua jenis perpustakaan (umum, sekolah, perguruan tinggi, khusus, TBM/komunitas), dan pemangku kepentingan pendidikan, kebudayaan, pembangunan daerah.", "alt_phrases": ["Bisakah jelaskan: Siapa saja yang menjadi sasaran IPLM?", "Saya ingin tahu, siapa saja yang menjadi sasaran iplm?"], "source_doc": "Juknis IPLM 2025 (Pelaksanaan)", "source_locator": "", "last_updated": "2025-09-29", "language": "id", "keywords": "iplm?, menjadi, saja, sasaran, siapa, yang"}
|
| 3 |
+
{"id": "iplm-003", "question": "Bagaimana metode pengumpulan datanya?", "answer": "Sensus penuh untuk perpustakaan jumlah terbatas (umum, perguruan tinggi, khusus OPD/KL). Sampling representatif untuk perpustakaan jumlah besar (SD, SMP, TBM). Semua berbasis daring di platform Perpusnas.", "alt_phrases": ["Bisakah jelaskan: Bagaimana metode pengumpulan datanya?", "Saya ingin tahu, bagaimana metode pengumpulan datanya?"], "source_doc": "Juknis IPLM 2025 (Pelaksanaan)", "source_locator": "", "last_updated": "2025-09-29", "language": "id", "keywords": "bagaimana, datanya?, metode, pengumpulan"}
|
| 4 |
+
{"id": "iplm-004", "question": "Bagaimana data diproses sebelum menghasilkan skor?", "answer": "Tahapan: verifikasi β validasi β transformasi (fungsi Yeo-Johnson) β normalisasi (min-max 0β1) β pembobotan dimensi (kepatuhan 30%, kinerja 70%) β agregasi skor β standardisasi (Z-score) β klasifikasi kategori (sangat tinggi, tinggi, sedang, rendah).", "alt_phrases": ["Bisakah jelaskan: Bagaimana data diproses sebelum menghasilkan skor?", "Saya ingin tahu, bagaimana data diproses sebelum menghasilkan skor?"], "source_doc": "Juknis IPLM 2025 (Pelaksanaan)", "source_locator": "", "last_updated": "2025-09-29", "language": "id", "keywords": "bagaimana, data, diproses, menghasilkan, sebelum, skor?"}
|
| 5 |
+
{"id": "iplm-005", "question": "Mengapa diperlukan transformasi dan normalisasi data?", "answer": "Transformasi: memastikan distribusi data lebih normal, mengurangi pencilan. Normalisasi: menyetarakan skala indikator agar bisa dibandingkan adil.", "alt_phrases": ["Bisakah jelaskan: Mengapa diperlukan transformasi dan normalisasi data?", "Saya ingin tahu, mengapa diperlukan transformasi dan normalisasi data?"], "source_doc": "Juknis IPLM 2025 (Pelaksanaan)", "source_locator": "", "last_updated": "2025-09-29", "language": "id", "keywords": "data?, diperlukan, mengapa, normalisasi, transformasi"}
|
| 6 |
+
{"id": "iplm-006", "question": "Apa saja dimensi IPLM?", "answer": "Kepatuhan (30%): koleksi, tenaga, anggaran. Kinerja (70%): pelayanan, penyelenggaraan/pengelolaan, kerja sama, kegiatan literasi, jumlah pemustaka, inovasi layanan.", "alt_phrases": ["Bisakah jelaskan: Apa saja dimensi IPLM?", "Saya ingin tahu, apa saja dimensi iplm?"], "source_doc": "Peraturan Perpusnas 2025 (Pedoman IPLM)", "source_locator": "", "last_updated": "2025-09-29", "language": "id", "keywords": "dimensi, iplm?, saja"}
|
| 7 |
+
{"id": "iplm-007", "question": "Mengapa bobot kinerja lebih besar daripada kepatuhan?", "answer": "Karena kinerja mencerminkan dampak nyata layanan perpustakaan terhadap literasi masyarakat. Kepatuhan hanya fondasi administratif, sedangkan kinerja adalah hasil dan manfaat langsung.", "alt_phrases": ["Bisakah jelaskan: Mengapa bobot kinerja lebih besar daripada kepatuhan?", "Saya ingin tahu, mengapa bobot kinerja lebih besar daripada kepatuhan?"], "source_doc": "Juknis IPLM 2025 (Pelaksanaan)", "source_locator": "", "last_updated": "2025-09-29", "language": "id", "keywords": "besar, bobot, daripada, kepatuhan?, kinerja, lebih, mengapa"}
|
| 8 |
+
{"id": "iplm-008", "question": "Bagaimana peran tiap level perpustakaan?", "answer": "Perpusnas: metodologi, validasi, analisis nasional, publikasi, bimbingan teknis. Provinsi: kumpulkan & verifikasi data SMA/SMK, SLB, perpustakaan khusus provinsi. Kabupaten/Kota: kumpulkan & verifikasi data perpustakaan umum, SD/SMP, TBM, perpustakaan khusus daerah.", "alt_phrases": ["Bisakah jelaskan: Bagaimana peran tiap level perpustakaan?", "Saya ingin tahu, bagaimana peran tiap level perpustakaan?"], "source_doc": "Juknis IPLM 2025 (Pelaksanaan)", "source_locator": "", "last_updated": "2025-09-29", "language": "id", "keywords": "bagaimana, level, peran, perpustakaan?, tiap"}
|
| 9 |
+
{"id": "iplm-009", "question": "Bagaimana menjamin kualitas data IPLM?", "answer": "Melalui pelatihan operator, supervisi lapangan, validasi silang antar level, berita acara verifikasi & validasi sebelum analisis.", "alt_phrases": ["Bisakah jelaskan: Bagaimana menjamin kualitas data IPLM?", "Saya ingin tahu, bagaimana menjamin kualitas data iplm?"], "source_doc": "Juknis IPLM 2025 (Pelaksanaan)", "source_locator": "", "last_updated": "2025-09-29", "language": "id", "keywords": "bagaimana, data, iplm?, kualitas, menjamin"}
|
| 10 |
+
{"id": "iplm-010", "question": "Apa manfaat IPLM bagi daerah?", "answer": "Menjadi dasar laporan LPPD, menyediakan data objektif untuk kebijakan literasi, mendapatkan rekomendasi program peningkatan literasi, dasar penghargaan/insentif bagi daerah dengan peningkatan signifikan.", "alt_phrases": ["Bisakah jelaskan: Apa manfaat IPLM bagi daerah?", "Saya ingin tahu, apa manfaat iplm bagi daerah?"], "source_doc": "Juknis IPLM 2025 (Pelaksanaan)", "source_locator": "", "last_updated": "2025-09-29", "language": "id", "keywords": "bagi, daerah?, iplm, manfaat"}
|
| 11 |
+
{"id": "iplm-011", "question": "Apakah semua perpustakaan di wilayah kami wajib mengisi instrumen IPLM?", "answer": "Ya, semua unit wajib berpartisipasi. Sensus untuk perpustakaan terbatas, sampling untuk jumlah besar.", "alt_phrases": ["Bisakah jelaskan: Apakah semua perpustakaan di wilayah kami wajib mengisi instrumen IPLM?", "Saya ingin tahu, apakah semua perpustakaan di wilayah kami wajib mengisi instrumen iplm?"], "source_doc": "Juknis IPLM 2025 (Pelaksanaan)", "source_locator": "", "last_updated": "2025-09-29", "language": "id", "keywords": "apakah, instrumen, iplm?, kami, mengisi, perpustakaan, semua, wajib, wilayah"}
|
| 12 |
+
{"id": "iplm-012", "question": "Bagaimana mekanisme jika ada perpustakaan yang tidak mengisi atau terlambat mengirim data?", "answer": "Data dianggap tidak lengkap dan memengaruhi skor daerah. Ketepatan waktu menjadi faktor penting.", "alt_phrases": ["Bisakah jelaskan: Bagaimana mekanisme jika ada perpustakaan yang tidak mengisi atau terlambat mengirim data?", "Saya ingin tahu, bagaimana mekanisme jika ada perpustakaan yang tidak mengisi atau terlambat mengirim data?"], "source_doc": "Juknis IPLM 2025 (Pelaksanaan)", "source_locator": "", "last_updated": "2025-09-29", "language": "id", "keywords": "atau, bagaimana, data?, jika, mekanisme, mengirim, mengisi, perpustakaan, terlambat, tidak, yang"}
|
| 13 |
+
{"id": "iplm-013", "question": "Apakah ada sanksi jika daerah tidak mengirimkan data IPLM?", "answer": "Tidak ada sanksi administratif langsung, tetapi skor daerah rendah dan berdampak pada evaluasi LPPD.", "alt_phrases": ["Bisakah jelaskan: Apakah ada sanksi jika daerah tidak mengirimkan data IPLM?", "Saya ingin tahu, apakah ada sanksi jika daerah tidak mengirimkan data iplm?"], "source_doc": "Juknis IPLM 2025 (Pelaksanaan)", "source_locator": "", "last_updated": "2025-09-29", "language": "id", "keywords": "apakah, daerah, data, iplm?, jika, mengirimkan, sanksi, tidak"}
|
| 14 |
+
{"id": "iplm-014", "question": "Apakah dinas bisa melihat hasil sementara sebelum publikasi nasional?", "answer": "Ya, hasil rekap bisa diakses daerah untuk keperluan internal. Publikasi resmi tetap melalui Perpusnas.", "alt_phrases": ["Bisakah jelaskan: Apakah dinas bisa melihat hasil sementara sebelum publikasi nasional?", "Saya ingin tahu, apakah dinas bisa melihat hasil sementara sebelum publikasi nasional?"], "source_doc": "Juknis IPLM 2025 (Pelaksanaan)", "source_locator": "", "last_updated": "2025-09-29", "language": "id", "keywords": "apakah, bisa, dinas, hasil, melihat, nasional?, publikasi, sebelum, sementara"}
|
| 15 |
+
{"id": "iplm-015", "question": "Apakah ada bimbingan teknis bagi operator di daerah?", "answer": "Ada, Perpusnas menyiapkan pelatihan berjenjang (nasional β provinsi β kabupaten/kota).", "alt_phrases": ["Bisakah jelaskan: Apakah ada bimbingan teknis bagi operator di daerah?", "Saya ingin tahu, apakah ada bimbingan teknis bagi operator di daerah?"], "source_doc": "Juknis IPLM 2025 (Pelaksanaan)", "source_locator": "", "last_updated": "2025-09-29", "language": "id", "keywords": "apakah, bagi, bimbingan, daerah?, operator, teknis"}
|
| 16 |
+
{"id": "iplm-016", "question": "Bagaimana cara menangani perbedaan data antara laporan daerah dan hasil validasi pusat?", "answer": "Dilakukan masa sanggah/rekonsiliasi. Daerah diberi kesempatan klarifikasi/perbaikan sebelum finalisasi skor.", "alt_phrases": ["Bisakah jelaskan: Bagaimana cara menangani perbedaan data antara laporan daerah dan hasil validasi pusat?", "Saya ingin tahu, bagaimana cara menangani perbedaan data antara laporan daerah dan hasil validasi pusat?"], "source_doc": "Juknis IPLM 2025 (Pelaksanaan)", "source_locator": "", "last_updated": "2025-09-29", "language": "id", "keywords": "antara, bagaimana, cara, daerah, data, hasil, laporan, menangani, perbedaan, pusat?, validasi"}
|
| 17 |
+
{"id": "iplm-017", "question": "Apakah hasil IPLM bisa digunakan untuk mengusulkan anggaran daerah?", "answer": "Bisa, IPLM memberikan evidence-based data yang sahih untuk Renstra atau RKPD.", "alt_phrases": ["Bisakah jelaskan: Apakah hasil IPLM bisa digunakan untuk mengusulkan anggaran daerah?", "Saya ingin tahu, apakah hasil iplm bisa digunakan untuk mengusulkan anggaran daerah?"], "source_doc": "Juknis IPLM 2025 (Pelaksanaan)", "source_locator": "", "last_updated": "2025-09-29", "language": "id", "keywords": "anggaran, apakah, bisa, daerah?, digunakan, hasil, iplm, mengusulkan, untuk"}
|
| 18 |
+
{"id": "iplm-018", "question": "Apakah IPLM juga mengukur kontribusi TBM atau perpustakaan komunitas?", "answer": "Ya, TBM dan rumah baca masuk dalam populasi/sampel IPLM untuk mencerminkan ekosistem literasi luas.", "alt_phrases": ["Bisakah jelaskan: Apakah IPLM juga mengukur kontribusi TBM atau perpustakaan komunitas?", "Saya ingin tahu, apakah iplm juga mengukur kontribusi tbm atau perpustakaan komunitas?"], "source_doc": "Juknis IPLM 2025 (Pelaksanaan)", "source_locator": "", "last_updated": "2025-09-29", "language": "id", "keywords": "apakah, atau, iplm, juga, komunitas?, kontribusi, mengukur, perpustakaan"}
|
| 19 |
+
{"id": "iplm-019", "question": "Bagaimana jika data perpustakaan sekolah sulit diperoleh karena kewenangan ada di Dinas Pendidikan?", "answer": "Dinas Perpustakaan perlu koordinasi formal dengan Dinas Pendidikan agar data sekolah bisa dihimpun.", "alt_phrases": ["Bisakah jelaskan: Bagaimana jika data perpustakaan sekolah sulit diperoleh karena kewenangan ada di Dinas Pendidikan?", "Saya ingin tahu, bagaimana jika data perpustakaan sekolah sulit diperoleh karena kewenangan ada di dinas pendidikan?"], "source_doc": "Juknis IPLM 2025 (Pelaksanaan)", "source_locator": "", "last_updated": "2025-09-29", "language": "id", "keywords": "bagaimana, data, dinas, diperoleh, jika, karena, kewenangan, pendidikan?, perpustakaan, sekolah, sulit"}
|
| 20 |
+
{"id": "iplm-020", "question": "Apakah ada insentif bagi daerah yang skornya tinggi?", "answer": "Ya, Perpusnas mempertimbangkan penghargaan/insentif bagi daerah yang datanya lengkap dan kinerjanya meningkat.", "alt_phrases": ["Bisakah jelaskan: Apakah ada insentif bagi daerah yang skornya tinggi?", "Saya ingin tahu, apakah ada insentif bagi daerah yang skornya tinggi?"], "source_doc": "Juknis IPLM 2025 (Pelaksanaan)", "source_locator": "", "last_updated": "2025-09-29", "language": "id", "keywords": "apakah, bagi, daerah, insentif, skornya, tinggi?, yang"}
|
| 21 |
+
{"id": "iplm-021", "question": "Apa dasar hukum terbaru pelaksanaan IPLM?", "answer": "Peraturan Perpusnas Nomor 7 Tahun 2025 tentang Pedoman IPLM, sebagai acuan resmi nasional.", "alt_phrases": ["Bisakah jelaskan: Apa dasar hukum terbaru pelaksanaan IPLM?", "Saya ingin tahu, apa dasar hukum terbaru pelaksanaan iplm?"], "source_doc": "Peraturan Perpusnas 2025 (Pedoman IPLM)", "source_locator": "", "last_updated": "2025-09-29", "language": "id", "keywords": "dasar, hukum, iplm?, pelaksanaan, terbaru"}
|
| 22 |
+
{"id": "iplm-022", "question": "Siapa penyelenggara resmi IPLM?", "answer": "Penyelenggara adalah Perpusnas, dibantu Perpustakaan Provinsi dan Kabupaten/Kota.", "alt_phrases": ["Bisakah jelaskan: Siapa penyelenggara resmi IPLM?", "Saya ingin tahu, siapa penyelenggara resmi iplm?"], "source_doc": "Peraturan Perpusnas 2025 (Pedoman IPLM)", "source_locator": "", "last_updated": "2025-09-29", "language": "id", "keywords": "iplm?, penyelenggara, resmi, siapa"}
|
| 23 |
+
{"id": "iplm-023", "question": "Apa saja tahapan resmi IPLM menurut Peraturan Perpusnas?", "answer": "1) pengumpulan data, 2) verifikasi & validasi, 3) penghitungan (transformasi, normalisasi, bobot), 4) penetapan hasil, 5) pemantauan & evaluasi.", "alt_phrases": ["Bisakah jelaskan: Apa saja tahapan resmi IPLM menurut Peraturan Perpusnas?", "Saya ingin tahu, apa saja tahapan resmi iplm menurut peraturan perpusnas?"], "source_doc": "Peraturan Perpusnas 2025 (Pedoman IPLM)", "source_locator": "", "last_updated": "2025-09-29", "language": "id", "keywords": "iplm, menurut, peraturan, perpusnas?, resmi, saja, tahapan"}
|
| 24 |
+
{"id": "iplm-024", "question": "Bagaimana jika daerah tidak sepakat dengan hasil penghitungan IPLM?", "answer": "Daerah dapat mengajukan keberatan dalam 14 hari setelah menerima hasil awal. Perpusnas akan melakukan penghitungan ulang bersama daerah.", "alt_phrases": ["Bisakah jelaskan: Bagaimana jika daerah tidak sepakat dengan hasil penghitungan IPLM?", "Saya ingin tahu, bagaimana jika daerah tidak sepakat dengan hasil penghitungan iplm?"], "source_doc": "Peraturan Perpusnas 2025 (Pedoman IPLM)", "source_locator": "", "last_updated": "2025-09-29", "language": "id", "keywords": "bagaimana, daerah, dengan, hasil, iplm?, jika, penghitungan, sepakat, tidak"}
|
| 25 |
+
{"id": "iplm-025", "question": "Siapa yang bertanggung jawab mengumpulkan data sekolah dan TBM?", "answer": "Kabupaten/Kota: SD, SMP, desa/kelurahan, kecamatan, TBM, perpustakaan khusus daerah. Provinsi: SMA/SMK, SLB, perpustakaan khusus provinsi. Pusat: perguruan tinggi, perpustakaan khusus K/L, sekolah di bawah Kemenag.", "alt_phrases": ["Bisakah jelaskan: Siapa yang bertanggung jawab mengumpulkan data sekolah dan TBM?", "Saya ingin tahu, siapa yang bertanggung jawab mengumpulkan data sekolah dan tbm?"], "source_doc": "Juknis IPLM 2025 (Pelaksanaan)", "source_locator": "", "last_updated": "2025-09-29", "language": "id", "keywords": "bertanggung, data, jawab, mengumpulkan, sekolah, siapa, tbm?, yang"}
|
| 26 |
+
{"id": "iplm-026", "question": "Apakah ada ketentuan tentang pendanaan IPLM?", "answer": "Ya, pendanaan IPLM bersumber dari APBN. Daerah tetap perlu dukungan administratif/teknis.", "alt_phrases": ["Bisakah jelaskan: Apakah ada ketentuan tentang pendanaan IPLM?", "Saya ingin tahu, apakah ada ketentuan tentang pendanaan iplm?"], "source_doc": "Peraturan Perpusnas 2025 (Pedoman IPLM)", "source_locator": "", "last_updated": "2025-09-29", "language": "id", "keywords": "apakah, iplm?, ketentuan, pendanaan, tentang"}
|
| 27 |
+
{"id": "iplm-027", "question": "Apakah indikator IPLM sama dengan Juknis?", "answer": "Ya, dimensi kepatuhan (koleksi, tenaga) dan kinerja (pelayanan, penyelenggaraan) tetap sama, dengan indikator resmi nasional.", "alt_phrases": ["Bisakah jelaskan: Apakah indikator IPLM sama dengan Juknis?", "Saya ingin tahu, apakah indikator iplm sama dengan juknis?"], "source_doc": "Peraturan Perpusnas 2025 (Pedoman IPLM)", "source_locator": "", "last_updated": "2025-09-29", "language": "id", "keywords": "apakah, dengan, indikator, iplm, juknis?, sama"}
|
| 28 |
+
{"id": "iplm-028", "question": "Bagaimana hasil IPLM digunakan di daerah?", "answer": "Hasil IPLM bisa diakses daerah sebagai bahan evaluasi internal dan perencanaan literasi, serta laporan LPPD.", "alt_phrases": ["Bisakah jelaskan: Bagaimana hasil IPLM digunakan di daerah?", "Saya ingin tahu, bagaimana hasil iplm digunakan di daerah?"], "source_doc": "Juknis IPLM 2025 (Pelaksanaan)", "source_locator": "", "last_updated": "2025-09-29", "language": "id", "keywords": "bagaimana, daerah?, digunakan, hasil, iplm"}
|
| 29 |
+
{"id": "iplm-029", "question": "Apakah ada evaluasi berkala IPLM?", "answer": "Ya, evaluasi dilakukan setiap tahun atau sewaktu-waktu bila diperlukan.", "alt_phrases": ["Bisakah jelaskan: Apakah ada evaluasi berkala IPLM?", "Saya ingin tahu, apakah ada evaluasi berkala iplm?"], "source_doc": "Juknis IPLM 2025 (Pelaksanaan)", "source_locator": "", "last_updated": "2025-09-29", "language": "id", "keywords": "apakah, berkala, evaluasi, iplm?"}
|
README.md
CHANGED
|
@@ -1,14 +1,37 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
# π RAG + LLM Chatbot (Hugging Face Spaces)
|
| 3 |
+
|
| 4 |
+
Chatbot berbasis **RAG (Retrieval-Augmented Generation)** yang membaca **JSONL Q&A** dan menjawab menggunakan **LLM** via **Hugging Face Inference API**.
|
| 5 |
+
|
| 6 |
+
## Struktur Repo
|
| 7 |
+
```
|
| 8 |
+
/
|
| 9 |
+
ββ app.py
|
| 10 |
+
ββ requirements.txt
|
| 11 |
+
ββ IPLM_QnA_Chatbot.jsonl # file JSONL Anda (tiap baris: {"question": "...", "answer": "..."})
|
| 12 |
+
```
|
| 13 |
+
|
| 14 |
+
## Cara Deploy di Hugging Face Spaces
|
| 15 |
+
1. Buat Space baru β pilih **Gradio**.
|
| 16 |
+
2. Unggah `app.py`, `requirements.txt`, dan `IPLM_QnA_Chatbot.jsonl`.
|
| 17 |
+
3. Buka **Settings β Secrets** dan tambahkan:
|
| 18 |
+
- `HF_TOKEN` = User Access Token Anda (scopes default).
|
| 19 |
+
- (Opsional) `LLM_MODEL` (default: `meta-llama/Meta-Llama-3.1-8B-Instruct`)
|
| 20 |
+
- (Opsional) `EMB_MODEL` (default: `sentence-transformers/all-MiniLM-L6-v2`)
|
| 21 |
+
- (Opsional) `HF_CHAT_URL` jika memakai endpoint TGI sendiri.
|
| 22 |
+
4. Jalankan Space. UI siap dipakai.
|
| 23 |
+
|
| 24 |
+
## Format JSONL
|
| 25 |
+
Setiap baris adalah objek JSON:
|
| 26 |
+
```json
|
| 27 |
+
{"question": "Apa itu IPLM?", "answer": "IPLM adalah ... "}
|
| 28 |
+
```
|
| 29 |
+
Alias yang didukung: `pertanyaan/jawaban`, `q/a`.
|
| 30 |
+
|
| 31 |
+
## Catatan
|
| 32 |
+
- Jika `HF_TOKEN` belum diisi, aplikasi tetap berjalan dan retrieval akan muncul, namun LLM akan memberi peringatan (non-generatif).
|
| 33 |
+
- Cache embedding disimpan sebagai `embeddings.pkl` agar startup berikutnya cepat.
|
| 34 |
+
- Anda bisa **upload JSONL** baru dari panel kanan untuk memperbarui basis pengetahuan.
|
| 35 |
+
|
| 36 |
+
## Lisensi
|
| 37 |
+
MIT
|
app.py
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
import os, re, json, pickle, hashlib, requests
|
| 3 |
+
from pathlib import Path
|
| 4 |
+
import gradio as gr
|
| 5 |
+
import pandas as pd
|
| 6 |
+
import numpy as np
|
| 7 |
+
from sklearn.neighbors import NearestNeighbors
|
| 8 |
+
from sentence_transformers import SentenceTransformer
|
| 9 |
+
|
| 10 |
+
# =================== Config ===================
|
| 11 |
+
DATA_PATH = Path(os.getenv("DATA_PATH", "IPLM_QnA_Chatbot.jsonl")) # default filename
|
| 12 |
+
CACHE_EMB = Path("embeddings.pkl")
|
| 13 |
+
CACHE_META = Path("meta.json")
|
| 14 |
+
|
| 15 |
+
# Embedding model for retrieval
|
| 16 |
+
EMB_MODEL = os.getenv("EMB_MODEL", "sentence-transformers/all-MiniLM-L6-v2")
|
| 17 |
+
|
| 18 |
+
# LLM endpoint (HF Inference API / TGI-compatible / OpenAI-compatible route)
|
| 19 |
+
HF_CHAT_URL = os.getenv("HF_CHAT_URL", "https://api-inference.huggingface.co/v1/chat/completions")
|
| 20 |
+
HF_TOKEN = os.getenv("HF_TOKEN", "")
|
| 21 |
+
LLM_MODEL = os.getenv("LLM_MODEL", "meta-llama/Meta-Llama-3.1-8B-Instruct")
|
| 22 |
+
|
| 23 |
+
TOP_K_DEFAULT = int(os.getenv("TOP_K_DEFAULT", "4"))
|
| 24 |
+
TEMPERATURE_DEFAULT = float(os.getenv("TEMPERATURE_DEFAULT", "0.3"))
|
| 25 |
+
MAX_TOKENS = int(os.getenv("MAX_TOKENS", "512"))
|
| 26 |
+
|
| 27 |
+
SYSTEM_PROMPT = os.getenv("SYSTEM_PROMPT",
|
| 28 |
+
"You are an Indonesian librarian assistant. Jawab ringkas, akurat, dan sopan. "
|
| 29 |
+
"Gunakan HANYA informasi dari konteks yang diberikan. Jika konteks tidak memuat jawabannya, "
|
| 30 |
+
"katakan bahwa data tidak tersedia di basis pengetahuan."
|
| 31 |
+
)
|
| 32 |
+
|
| 33 |
+
# =================== Utils ===================
|
| 34 |
+
def norm(s: str) -> str:
|
| 35 |
+
if s is None: return ""
|
| 36 |
+
s = str(s).strip()
|
| 37 |
+
s = re.sub(r"\s+", " ", s)
|
| 38 |
+
return s
|
| 39 |
+
|
| 40 |
+
def dataset_hash(rows: list) -> str:
|
| 41 |
+
m = hashlib.md5()
|
| 42 |
+
for r in rows:
|
| 43 |
+
m.update((norm(r.get("question", "")) + "|" + norm(r.get("answer", ""))).encode("utf-8"))
|
| 44 |
+
return m.hexdigest()
|
| 45 |
+
|
| 46 |
+
def load_jsonl(path: Path) -> list:
|
| 47 |
+
if not path.exists():
|
| 48 |
+
raise FileNotFoundError(f"JSONL tidak ditemukan: {path.resolve()}")
|
| 49 |
+
rows = []
|
| 50 |
+
with path.open("r", encoding="utf-8") as f:
|
| 51 |
+
for line in f:
|
| 52 |
+
line = line.strip()
|
| 53 |
+
if not line: continue
|
| 54 |
+
obj = json.loads(line)
|
| 55 |
+
# support various key names
|
| 56 |
+
q = obj.get("question") or obj.get("pertanyaan") or obj.get("q")
|
| 57 |
+
a = obj.get("answer") or obj.get("jawaban") or obj.get("a")
|
| 58 |
+
if q and a:
|
| 59 |
+
rows.append({"question": norm(q), "answer": norm(a)})
|
| 60 |
+
if not rows:
|
| 61 |
+
raise ValueError("JSONL kosong atau tidak mengandung pasangan 'question'/'answer'.")
|
| 62 |
+
# drop dup by question
|
| 63 |
+
seen = set()
|
| 64 |
+
uniq = []
|
| 65 |
+
for r in rows:
|
| 66 |
+
if r["question"] in seen:
|
| 67 |
+
continue
|
| 68 |
+
seen.add(r["question"])
|
| 69 |
+
uniq.append(r)
|
| 70 |
+
return uniq
|
| 71 |
+
|
| 72 |
+
# =================== Index ===================
|
| 73 |
+
class FAQIndex:
|
| 74 |
+
def __init__(self):
|
| 75 |
+
self.rows = None
|
| 76 |
+
self.model = None
|
| 77 |
+
self.emb = None
|
| 78 |
+
self.nn = None
|
| 79 |
+
|
| 80 |
+
def build(self, rows: list, force=False):
|
| 81 |
+
self.rows = rows
|
| 82 |
+
# try load cache
|
| 83 |
+
if not force and CACHE_EMB.exists() and CACHE_META.exists():
|
| 84 |
+
try:
|
| 85 |
+
meta = json.loads(CACHE_META.read_text(encoding="utf-8"))
|
| 86 |
+
if meta.get("hash") == dataset_hash(rows) and meta.get("emb_model") == EMB_MODEL:
|
| 87 |
+
cached = pickle.loads(CACHE_EMB.read_bytes())
|
| 88 |
+
self.emb = cached["emb"]
|
| 89 |
+
self.nn = cached["nn"]
|
| 90 |
+
if self.model is None:
|
| 91 |
+
self.model = SentenceTransformer(EMB_MODEL)
|
| 92 |
+
return
|
| 93 |
+
except Exception:
|
| 94 |
+
pass
|
| 95 |
+
# build fresh
|
| 96 |
+
self.model = SentenceTransformer(EMB_MODEL)
|
| 97 |
+
# encode "Q: ...\nA: ..." for better grounding
|
| 98 |
+
qas = [f"Q: {r['question']}\nA: {r['answer']}" for r in rows]
|
| 99 |
+
self.emb = self.model.encode(qas, normalize_embeddings=True, convert_to_numpy=True, show_progress_bar=False)
|
| 100 |
+
self.nn = NearestNeighbors(n_neighbors=min(10, len(qas)), metric="cosine").fit(self.emb)
|
| 101 |
+
CACHE_EMB.write_bytes(pickle.dumps({"emb": self.emb, "nn": self.nn}))
|
| 102 |
+
CACHE_META.write_text(json.dumps({"hash": dataset_hash(rows), "emb_model": EMB_MODEL}, ensure_ascii=False))
|
| 103 |
+
|
| 104 |
+
def retrieve(self, query: str, top_k: int = TOP_K_DEFAULT):
|
| 105 |
+
if not query.strip():
|
| 106 |
+
return []
|
| 107 |
+
q_vec = self.model.encode([query], normalize_embeddings=True, convert_to_numpy=True, show_progress_bar=False)
|
| 108 |
+
dists, idxs = self.nn.kneighbors(q_vec, n_neighbors=min(top_k, len(self.rows)))
|
| 109 |
+
sims = 1.0 - dists[0]
|
| 110 |
+
out = []
|
| 111 |
+
for i, sim in zip(idxs[0], sims):
|
| 112 |
+
r = self.rows[int(i)]
|
| 113 |
+
out.append({"question": r["question"], "answer": r["answer"], "score": float(sim)})
|
| 114 |
+
return out
|
| 115 |
+
|
| 116 |
+
# =================== LLM Caller ===================
|
| 117 |
+
def call_hf_chat(messages, temperature=TEMPERATURE_DEFAULT, max_tokens=MAX_TOKENS):
|
| 118 |
+
if not HF_TOKEN:
|
| 119 |
+
# allow non-LLM fallback with a clear message
|
| 120 |
+
return "β οΈ HF_TOKEN belum diatur. Buka Settings β Secrets dan tambahkan HF_TOKEN agar LLM aktif."
|
| 121 |
+
headers = {"Authorization": f"Bearer {HF_TOKEN}"}
|
| 122 |
+
payload = {
|
| 123 |
+
"model": LLM_MODEL,
|
| 124 |
+
"messages": messages,
|
| 125 |
+
"temperature": float(temperature),
|
| 126 |
+
"max_tokens": int(max_tokens),
|
| 127 |
+
"stream": False
|
| 128 |
+
}
|
| 129 |
+
r = requests.post(HF_CHAT_URL, headers=headers, json=payload, timeout=90)
|
| 130 |
+
try:
|
| 131 |
+
r.raise_for_status()
|
| 132 |
+
j = r.json()
|
| 133 |
+
return j["choices"][0]["message"]["content"]
|
| 134 |
+
except Exception as e:
|
| 135 |
+
return f"β Gagal memanggil LLM: {e}\nResp: {r.text[:500]}"
|
| 136 |
+
|
| 137 |
+
# =================== RAG Orchestrator ===================
|
| 138 |
+
def build_context(retrieved):
|
| 139 |
+
blocks = []
|
| 140 |
+
for i, r in enumerate(retrieved, 1):
|
| 141 |
+
blocks.append(f"[DOC {i} | score={r['score']:.2f}]\nQ: {r['question']}\nA: {r['answer']}")
|
| 142 |
+
return "\n\n".join(blocks)
|
| 143 |
+
|
| 144 |
+
def rag_answer(user_msg, top_k=TOP_K_DEFAULT, temperature=TEMPERATURE_DEFAULT):
|
| 145 |
+
hits = faq.retrieve(user_msg, top_k=int(top_k))
|
| 146 |
+
if not hits:
|
| 147 |
+
return "Maaf, saya tidak menemukan referensi di basis pengetahuan Anda."
|
| 148 |
+
context = build_context(hits)
|
| 149 |
+
messages = [
|
| 150 |
+
{"role": "system", "content": SYSTEM_PROMPT},
|
| 151 |
+
{"role": "user", "content": f"KONTEKS:\n{context}\n\nPERTANYAAN:\n{user_msg}\n\nInstruksi: Jawab berbasis KONTEKS. Jika tidak ada di konteks, jawab 'Data tidak tersedia.' "}
|
| 152 |
+
]
|
| 153 |
+
out = call_hf_chat(messages, temperature=float(temperature), max_tokens=MAX_TOKENS)
|
| 154 |
+
bullets = "\n".join([f"- ({h['score']:.2f}) {h['question']}" for h in hits])
|
| 155 |
+
return f"{out}\n\n**Sumber terdekat:**\n{bullets}"
|
| 156 |
+
|
| 157 |
+
# =================== Data load on start ===================
|
| 158 |
+
faq = FAQIndex()
|
| 159 |
+
rows = load_jsonl(DATA_PATH)
|
| 160 |
+
faq.build(rows, force=False)
|
| 161 |
+
|
| 162 |
+
# =================== Upload new JSONL ===================
|
| 163 |
+
def upload_jsonl(file_obj):
|
| 164 |
+
if file_obj is None:
|
| 165 |
+
return gr.update(value="Tidak ada file.")
|
| 166 |
+
tmp = Path(file_obj.name)
|
| 167 |
+
tmp.replace(DATA_PATH)
|
| 168 |
+
if CACHE_EMB.exists(): CACHE_EMB.unlink()
|
| 169 |
+
if CACHE_META.exists(): CACHE_META.unlink()
|
| 170 |
+
global rows, faq
|
| 171 |
+
rows = load_jsonl(DATA_PATH)
|
| 172 |
+
faq = FAQIndex()
|
| 173 |
+
faq.build(rows, force=True)
|
| 174 |
+
return f"β
Basis pengetahuan diperbarui. Total Q&A: {len(rows)}."
|
| 175 |
+
|
| 176 |
+
# =================== UI ===================
|
| 177 |
+
with gr.Blocks(title="RAG + LLM (JSONL)") as demo:
|
| 178 |
+
gr.Markdown("# π RAG + LLM β dari JSONL Q&A\n"
|
| 179 |
+
"Masukkan pertanyaan β sistem mengambil Q&A paling relevan β LLM merangkum/menjawab berdasarkan konteks.")
|
| 180 |
+
with gr.Row():
|
| 181 |
+
with gr.Column(scale=2):
|
| 182 |
+
chat = gr.ChatInterface(
|
| 183 |
+
fn=lambda msg, hist, k, t: rag_answer(msg, top_k=int(k), temperature=float(t)),
|
| 184 |
+
additional_inputs=[
|
| 185 |
+
gr.Slider(1, 10, value=TOP_K_DEFAULT, step=1, label="Top-K dokumen"),
|
| 186 |
+
gr.Slider(0.0, 1.0, value=TEMPERATURE_DEFAULT, step=0.05, label="Temperatur")
|
| 187 |
+
],
|
| 188 |
+
title="Asisten Perpustakaan (RAG)",
|
| 189 |
+
description="Jawab *berdasarkan konteks* dari dokumen JSONL Anda.",
|
| 190 |
+
examples=["Apa itu IPLM?", "Bagaimana perhitungan TGM?", "Apa saja tahap pengolahan data?"]
|
| 191 |
+
)
|
| 192 |
+
with gr.Column(scale=1):
|
| 193 |
+
gr.Markdown("### π Perbarui Basis Data")
|
| 194 |
+
uploader = gr.File(label="Upload JSONL Q&A (keys: question, answer)")
|
| 195 |
+
out = gr.Textbox(label="Status", interactive=False)
|
| 196 |
+
uploader.change(fn=upload_jsonl, inputs=uploader, outputs=out)
|
| 197 |
+
gr.Markdown("Set **HF_TOKEN** di Settings β Secrets untuk mengaktifkan LLM.")
|
| 198 |
+
if __name__ == "__main__":
|
| 199 |
+
demo.launch()
|
requirements.txt
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
gradio==4.44.1
|
| 3 |
+
pandas
|
| 4 |
+
sentence-transformers==2.2.2
|
| 5 |
+
scikit-learn
|
| 6 |
+
requests
|