Dalam tutorial ini, Anda membuat asisten troubleshooting pengembangan berbasis basis pengetahuan Alibaba Cloud Milvus yang menyimpan referensi API, catatan arsitektur, Runbook, dan postmortem. Anda dapat mencari berdasarkan nama API atau kode kesalahan serta mendapatkan langkah-langkah troubleshooting terstruktur lengkap dengan kutipan sumber.
Ikhtisar solusi
Tutorial ini membangun di atas Buat aplikasi Q&A layanan pelanggan cerdas dengan basis pengetahuan Alibaba Cloud Milvus. Alur end-to-end-nya identik: definisikan tag di Konsol → impor file sumber secara batch berdasarkan tag → publikasikan versi → ambil melalui SDK (dengan filter tag opsional) → minta LLM menghasilkan jawaban dengan kutipan sumber → sajikan halaman Q&A menggunakan Flask. Anda menggunakan kembali kode rekayasa (kb_client.py, upload.py, app.py, templates/index.html, dan start.sh) dari tutorial tersebut, dengan dua modifikasi yang dijelaskan di Langkah 4: Adaptasi kode yang digunakan kembali. Dokumen ini hanya mencakup perubahan yang harus Anda lakukan untuk skenario troubleshooting pengembangan: tag modul, parameter pengambilan untuk istilah eksak, penanganan file sumber HTML, serta pembatasan terhadap perintah berisiko tinggi dan hasil kosong.
Untuk versi pertama, pilih hanya 5 hingga 20 file sumber dari satu sistem saja, lalu perluas cakupannya setelah validasi. Seluruh proses memakan waktu sekitar 20 hingga 30 menit, dan waktu parsing dokumen bergantung pada jumlah serta ukuran file sumber.
Prasyarat
Basis pengetahuan Milvus telah dibuat, dan Anda telah mencatat ID basis pengetahuan (misalnya,
kd-803ae9b10cc31).Pengguna RAM telah dibuat dengan Akun Alibaba Cloud Anda, dengan opsi Use Permanent AccessKey Access dipilih dan kebijakan sistem
AliyunMilvusFullAccessdilampirkan.Titik akhir LLM yang mendukung protokol OpenAI
chat/completionsbeserta Kunci API-nya telah siap.Python 3.8 atau versi lebih baru telah diinstal secara lokal.
Direktori proyek dan lima file yang digunakan kembali (
kb_client.py,upload.py,app.py,templates/index.html, danstart.sh) telah disiapkan dengan mengikuti tutorial yang dirujuk di Ikhtisar solusi.File sumber tidak berisi rahasia, token, atau akun jaringan internal.
Langkah 1: Definisikan tag modul
Di bawah Basic Information pada halaman detail basis pengetahuan, pilih Tags > Manage dan tambahkan tiga tag berikut. Atur tipe bidang masing-masing tag menjadi string. Menu dropdown tipe berada di sisi kanan kotak input nama tag, dengan opsi string, int64, list, float32, dan bool.
| Nama tag | Deskripsi | Contoh nilai |
| module | Sistem atau layanan tempat file sumber berasal | order-service, user-service |
| docType | Jenis dokumen | API, Runbook, ErrorCode, Postmortem |
| version | Versi API atau dokumen | v2 |
Nama tag version memiliki nama yang sama dengan parameter version yang merepresentasikan versi basis pengetahuan yang dipublikasikan dalam permintaan pengambilan, tetapi keduanya tidak saling memengaruhi: tag digunakan dalam tagFilter.field, sedangkan versi pengambilan berada di level teratas permintaan. Jika Anda khawatir terjadi kebingungan, beri nama tag tersebut apiVersion sebagai gantinya.
Kotak dialog manajemen tag disimpan secara keseluruhan. Setelah Anda membuka kotak dialog, daftar tag dimuat secara asinkron. Tunggu hingga semua tag yang ada ditampilkan sebelum menambahkan tag baru dan mengklik OK. Jika tidak, penyimpanan dapat menimpa seluruh isi dengan "daftar kosong + tag baru" dan menghapus definisi tag yang sudah ada.
Tag module merupakan keputusan desain paling kritis dalam skenario ini. Sistem yang berbeda sering kali memiliki path API dengan nama yang persis sama, dan tanpa filter modul, hasilnya akan saling mengganggu. Dalam pengujian kami, user-service dan order-service sama-sama memiliki path GET /api/v2/orders/{orderId}. Saat kami mencari path ini:
| Kondisi pengambilan | Hasil |
| Tanpa filter | File sumber user-service berada di peringkat pertama (relevansi 0.246) — modul yang salah terpilih |
module = order-service | File sumber order-service berada di peringkat pertama, dan file sumber user-service sepenuhnya dikecualikan |
Langkah 2: Siapkan file sumber dan manifes impor
Pada langkah ini, Anda mengumpulkan file sumber, menganotasi masing-masing dalam manifes impor, dan mengonfigurasi cara basis pengetahuan membaginya menjadi chunk.
Letakkan file sumber di direktori
documents/. Format yang didukung meliputi Markdown, HTML, PDF, DOCX, dan TXT. Pertahankan kode kesalahan lengkap, path API, dan nomor versi dalam file sumber — dalam pengujian kami, baik kode kesalahan maupun path lengkap dapat diambil secara eksak.Buat file
documents.jsonldan anotasi setiap file sumber dengan modul, jenis dokumen, dan versinya:{"path": "documents/order-api.md", "metadata": {"module": "order-service", "docType": "API", "version": "v2"}} {"path": "documents/order-timeout-runbook.md", "metadata": {"module": "order-service", "docType": "Runbook", "version": "v2"}} {"path": "documents/order-error-codes.html", "metadata": {"module": "order-service", "docType": "ErrorCode", "version": "v2"}} {"path": "documents/order-postmortem-2026-06.md", "metadata": {"module": "order-service", "docType": "Postmortem", "version": "v2"}}Impor referensi API, Runbook, tabel kode kesalahan, dan postmortem sebagai satu set, dan bedakan menggunakan
docType. Dalam pengujian kami, saat kami menanyakan tentang kode kesalahan, keempat jenis file sumber tersebut ditemukan sekaligus, dan model menghasilkan jawaban lengkap berupa "makna → langkah troubleshooting → kasus historis".
Penanganan file sumber HTML
HTML dapat diunggah dan diurai langsung, dan konten di dalam <table> ikut masuk ke indeks. Dalam pengujian kami, kueri ORD-42901, yang hanya ada dalam tabel kode kesalahan HTML, berhasil menemukan file tersebut secara eksak.
Namun, HTML memiliki granularitas chunk yang lebih kasar dibandingkan Markdown (dalam pengujian ini, satu file HTML berisi dua tabel diurai menjadi 1 chunk). Hal ini terkait dengan cara HTML diurai. Sebelum memilih format file sumber, pahami dua poin berikut:
Blok kode tidak mempertahankan tata letak aslinya. Tag
<pre>dan<code>dikenali sebagai blok, tetapi teks diekstraksi secara rekursif dan digabung dengan spasi, sehingga indentasi, jeda baris, dan pembatas kode semuanya hilang. Untuk file sumber yang berisi banyak kode atau perintah, konversikan ke Markdown sebelum diimpor; jika tidak, potongan kode yang diambil mungkin tidak dapat digunakan langsung.Tabel diindeks secara utuh. Elemen
Kesimpulan: teks deskriptif biasa dan tabel kecil dapat menggunakan HTML secara langsung. Jika Anda memerlukan ketepatan tinggi, pengambilan berdasarkan segmen kode, atau pemisahan tabel besar, lebih baik gunakan Markdown atau file sumber terstruktur, dan setelah impor, lakukan pemeriksaan acak terhadap chunk di halaman Data Management.<table>ditambahkan sebagai segmen independen tunggal dalam bentuk string HTML aslinya dan tidak dibagi per baris, sehingga tabel besar mudah membentuk chunk kasar.
Konfigurasikan kebijakan chunking
Langkah troubleshooting dan contoh kode tidak boleh terpotong di tengah. Di halaman detail basis pengetahuan, klik Create Policy di bagian Processing Policy untuk menyesuaikan granularitas chunk. Satuan panjang segmen maksimum adalah karakter (default 512). Atur menjadi 580–770 karakter (sekitar 384–512 token) agar "satu langkah troubleshooting lengkap" atau "satu contoh kode" berada dalam chunk yang sama. Rasio karakter ke token bervariasi tergantung bahasa dan tokenizer file sumber Anda, jadi gunakan nilai karakter sebagai acuan dasar dan lakukan pemeriksaan acak terhadap chunk setelah impor.
Langkah 3: Konfigurasikan parameter pengambilan dan prompt
Kueri troubleshooting pengembangan umumnya berupa kode kesalahan, path API, dan perintah. Kueri ini termasuk dalam pencocokan istilah eksak, sehingga parameternya sangat berbeda dari skenario lain.
{
"retrieval": {
"page_size": 6,
"candidate_count": 64,
"min_score": 0.05,
"semantic_weight": 0.15,
"enable_query_expansion": false,
"rerank_model_name": "",
"tag_filter": {
"relation": "and",
"conditions": [
{"field": "module", "op": "=", "value": "order-service"}
]
}
},
"scenario": {
"title": "Development docs and troubleshooting assistant",
"system_prompt": "You are a development documentation assistant. Answer only based on the retrieved materials. Preserve API names, error codes, commands, and code as-is. List troubleshooting steps in order and cite them as [Source N]. When write operations or high-risk commands are involved, explicitly state the risk level and require manual confirmation. When the retrieved materials are empty, reply only that the materials are insufficient and warn against performing any change operation.",
"image_enabled": false,
"sample_questions": [
"What are the required parameters for the order query API?",
"In what order should I troubleshoot a connection timeout?",
"In which documents does this error code appear?"
]
}
}Daftar berikut menjelaskan parameter yang disesuaikan untuk skenario ini. Bidang lain dalam contoh tersebut diwariskan dari tutorial yang dirujuk.
semantic_weight: 0.15: Faktor bobot skor akhir. Skor dihitung sebagaiscore ≈ (1-semantic_weight) × keywordScore + semantic_weight × semanticScore(fitur peringkat juga dapat ditambahkan), danmin_scoremelakukan filter pasca pada skor akhir ini. Skenario ini menggunakan 0.15 agar skor kata kunci dari istilah eksak seperti kode kesalahan dan path API mendominasi peringkat. Karenasemantic_weightberinteraksi denganmin_score, sesuaikan keduanya bersama seperti dijelaskan di Sesuaikan semantic_weight dan min_score bersama.enable_query_expansion: false: Ekspansi kueri dinonaktifkan agar kode kesalahan dan nama API tidak ditulis ulang. Dalam pengujian kami, setelah diaktifkan,keywordScoredari kueriORD-50021turun dari 0.283 menjadi 0.226, dan jumlah hasil yang diambil untuk kueriORD-42901turun dari 2 menjadi 1. Biarkan dinonaktifkan untuk pengambilan istilah eksak.tag_filtermenguncimodule: Ini mencegah API dengan nama identik dari sistem berbeda saling mengganggu. Lihat Langkah 1: Definisikan tag modul untuk efeknya. Jika satu titik masuk perlu mencakup beberapa modul, gunakan{"field": "module", "op": "in", "value": ["order-service", "user-service"]}.rerank_model_namekosong berarti penyusunan ulang tidak diaktifkan. Pencocokan eksak untuk kode kesalahan bergantung pada skor kata kunci, sehingga penyusunan ulang memberikan manfaat terbatas.ophanya mendukung=,in, dannot in: Dalam pengujian kami, operator seperti≠,>,≥,<,≤,empty,not empty,start with, danend withdiabaikan diam-diam dan semua data dikembalikan (tanpa laporan kesalahan). Operatorcontainsdannot containshanyalah alias dariin/not in, bukan pencarian substring; memberikan fragmen string menghasilkan 0 hasil. Alias sepertieq,==, danlikemengembalikan400 Unsupported tag filter operator, dan setiap pertanyaan gagal. Setelah mengonfigurasi kondisi filter, bandingkan jumlah total hasil dengan hasil tanpa filter untuk memastikan filter benar-benar berlaku.
Sesuaikan semantic_weight dan min_score bersama
semantic_weight hanya mengubah skor tertimbang akhir; tidak mengubah dua sub-skor (keywordScore dan semanticScore) dalam scoreDetails dari hasil pengambilan. Skor tertimbang kira-kira:
score ≈ semantic_weight × semanticScore + (1 - semantic_weight) × keywordScorePerhatikan dua poin berikut sebelum menyesuaikan:
Menurunkan
semantic_weighttidak selalu menurunkan skor total. Skor total hanya turun jikasemanticScoredari batch hasil lebih tinggi daripadakeywordScore-nya. Inilah alasan dokumen ini juga menurunkanmin_scoremenjadi 0.05. Kedua parameter harus dikalibrasi bersama dengan memeriksascoreDetails.Saat Anda mengatur
Untuk materi pengembangan,semantic_weightke 0, implementasi saat ini tidak lagi menerapkan ambang batasmin_score. Jangan gunakan 0 untuk berarti "pengambilan murni berbasis kata kunci".keywordScorebiasanya jauh lebih rendah daripadasemanticScore(dalam pengujian kami, masing-masing sekitar 0.16–0.28 dan 0.64–0.78), sehingga dalam distribusi ini, menurunkansemantic_weightmenarik skor total ke sisi yang lebih rendah. Ini bukan aturan universal: hanya ketikasemanticScoredari batch hasil lebih tinggi daripadakeywordScore-nya, penurunan bobot akan menurunkan skor total; jika tidak, justru meningkatkannya. Jikamin_scoretidak diturunkan secara bersamaan, bahkan hasil yang cocok secara semantik akan difilter:
| Kueri | semantic_weight=0.15 | semantic_weight=0.7 |
/api/v2/orders/{orderId} | 8 hasil, skor tertinggi 0.269 | 8 hasil, skor tertinggi 0.602 |
kubectl -n order rollout undo deploy/order-api | 4 hasil | 8 hasil |
| Kueri bahasa alami "Mengapa API order belakangan ini melambat secara signifikan?" | 3 hasil | 8 hasil |
Saat Anda menggunakan semantic_weight=0.15, atur min_score menjadi sekitar 0.05 (contoh di atas dikonfigurasi demikian). Jika Anda membiarkan min_score pada 0.15 atau lebih tinggi, recall untuk kueri berbasis perintah dan kueri bahasa alami turun lebih dari separuhnya. Saat menyesuaikan, tetapkan satu parameter dan amati kedua sub-skor melalui scoreDetails sebelum memutuskan.
Batasi perintah berisiko tinggi dan hasil kosong
Asisten troubleshooting mengeluarkan perintah yang dapat dieksekusi secara langsung, sehingga dua hal harus dibatasi.
Perintah berisiko tinggi. Tandai tingkat risiko dan persyaratan konfirmasi dalam tabel di dalam file sumber, dan model akan meneruskannya secara akurat. Dalam pengujian kami, perintah restart yang ditandai "risiko tinggi, konfirmasi dua orang diperlukan" dalam Runbook, saat ditanyakan, dikembalikan oleh model bersama perintah tersebut ditambah pernyataan eksplisit "tingkat risiko tinggi", "konfirmasi dua orang diperlukan", dan "jangan lewati troubleshooting dan jalankan restart secara langsung".
Hasil pengambilan kosong. Kode harus mengintersepsi kasus ini, seperti dijelaskan di Langkah 4: Adaptasi kode yang digunakan kembali.
Langkah 4: Adaptasi kode yang digunakan kembali
Proyek yang digunakan kembali memerlukan dua modifikasi sebelum Anda memverifikasi asisten: fallback hasil kosong di app.py, dan perbaikan kode kembalian di kb_client.py.
Intersepsi hasil pengambilan kosong di app.py
Dalam app.py yang digunakan kembali, fungsi llm_answer() tetap memanggil LLM meskipun hasil pengambilan kosong. Pada titik itu konteksnya adalah string kosong, dan model menjawab sepenuhnya berdasarkan pengetahuannya sendiri. Dalam pengujian kami, saat kami bertanya "Bagaimana cara memulihkan cluster Redis split-brain?", yang tidak tercakup dalam basis pengetahuan, model menghasilkan rencana operasi lengkap dengan parameter dan sama sekali tidak memberikan peringatan. Dalam skenario troubleshooting, pengguna mungkin menyalin perintah tersebut dan menjalankannya langsung di lingkungan produksi, sehingga hal ini harus diintersepsi di tingkat kode:
def llm_answer(question: str, results: list[dict[str, Any]]) -> str:
if not LLM.get("enabled", True):
return "The LLM is not enabled. Review the retrieval results below."
if not results:
return "No relevant document was retrieved from the knowledge base, so no troubleshooting steps can be provided. Do not perform any change operation based on this. We recommend that you contact the module owner."
...Membatasi hal ini hanya melalui prompt tidak dapat diandalkan — bahkan jika Anda menyatakan "beri tahu secara eksplisit ketika materi tidak mencakup pertanyaan", model tetap memberikan jawaban.
Kriteria penerimaan terakhir di Langkah 5: Unggah, publikasikan, dan verifikasi mencakup modifikasi ini.
Perbaiki pemeriksaan kode kembalian di kb_client.py
Dalam kb_client.py yang digunakan kembali, fungsi _check() mengevaluasi getattr(body, "code", 0) != 0. Nilai code dari respons sukses adalah None, sehingga pengambilan sukses dilaporkan sebagai "Failed: None". Ubah pemeriksaan menjadi getattr(body, "code", None) not in (None, 0, "0"). Setelah perbaikan, pengambilan sukses tidak lagi melaporkan "Failed: None".
Langkah 5: Unggah, publikasikan, dan verifikasi
Unggah file sumber.
MetaFieldsberlaku untuk seluruh batch; skrip terlebih dahulu mengelompokkan berdasarkan tag lalu mengirimkan secara batch.python upload.py --manifest documents.jsonlDi halaman Data Management Konsol, pastikan status file sumber adalah Completed. Respons sukses dari API unggah hanya berarti parsing asinkron telah diajukan.
Di halaman Version Management, klik Publish Version. Setelah menyelesaikan wizard, pastikan status versi baru adalah Published.
PentingPaling banyak 3 versi yang dipublikasikan dapat ada secara bersamaan. Setelah batas tercapai, tombol Publish Version akan dinonaktifkan, tetapi halaman tetap menampilkan "Saat ini terdapat N perubahan yang menunggu untuk dipublikasikan". Hapus versi lama yang tidak lagi diperlukan dari catatan versi terlebih dahulu. Dokumen pengembangan sering berubah (setiap rilis mungkin memperbarui referensi API dan Runbook), jadi simpan hanya "versi saat ini + versi historis terbaru".
Jalankan layanan:
python app.pyKirim pertanyaan uji:
curl -sS http://127.0.0.1:7860/api/ask \ -H 'Content-Type: application/json' \ -d '{"question":"In what order should I troubleshoot a connection timeout?"}'Verifikasi terhadap lima kriteria penerimaan berikut:
Halaman terbuka secara normal, dan setelah Anda mengirim pertanyaan, jawaban beserta sumber pengambilan dikembalikan bersamaan.
Setiap
[Source N]dalam jawaban dapat dicocokkan dengan chunk file sumber yang sesuai di bagian sumber di bawahnya.Saat Anda menanyakan kode kesalahan lengkap, semua file sumber tempat kode kesalahan tersebut muncul akan tercantum. Dalam pengujian kami, kueri
ORD-50021berhasil mencantumkan empat file sumber — tabel kode kesalahan, referensi API, Runbook, dan postmortem — lengkap dengan lokasi masing-masing.Saat Anda menanyakan operasi berisiko, jawaban mencakup tingkat risiko dan persyaratan konfirmasi manual.
Saat Anda menanyakan sesuatu yang tidak tercakup dalam basis pengetahuan, responsnya adalah "materi tidak mencukupi" dengan peringatan untuk tidak melakukan operasi perubahan apa pun, dan tidak ada perintah fiktif yang muncul. Uji kasus ini secara praktis.
Catatan untuk skenario pengembangan
Titik masuk per modul — Setelah
tag_filtermenguncimodule, titik masuk hanya menjawab pertanyaan tentang modul tersebut. Pertahankan juga pertanyaan contoh dalam modul yang sama.Istilah eksak lengkap — Pertahankan kode kesalahan lengkap, path API, perintah, dan nomor versi dalam file sumber. Istilah eksak ini merupakan titik masuk utama pengambilan dalam skenario ini.
Penandaan risiko — Tandai tingkat risiko dan persyaratan konfirmasi dalam file sumber. Jangan mengandalkan model untuk menilai perintah mana yang berbahaya.
Tidak ada rahasia dalam unggahan — Jangan unggah rahasia, token, akun jaringan internal, atau string koneksi database produksi. Konten chunk dikirim ke LLM sebagai konteks.
Deskripsi API yang tidak digunakan lagi — Pertahankan deskripsi operasi API yang deprecated. Dalam pengujian kami, setelah referensi API menyatakan "API v1 sudah tidak digunakan dan nama parameternya tidak kompatibel", model tidak mencampurkan parameter lama saat menjawab pertanyaan tentang parameter API baru.
Bidang
tagsdalam hasil pengambilan tidak mencerminkan modul dan versi yang ditulis. Ini adalah bidang yang berbeda dari MetaFields padaAddDocuments, dan tidak ada parameter yang dapat mengaktifkan pengembaliannya. Nilai kosong tidak berarti unggahan gagal. Saat Anda perlu menganotasi modul dan versi dalam jawaban, gabungkan hasil pengambilan dengandocuments.jsonlberdasarkandocumentIddan tambahkan informasi tersebut ke konteks.Penerapan produksi — Jangan terus menggunakan server pengembangan Flask untuk penerapan online. Beralihlah ke server WSGI produksi dan tambahkan manajemen rahasia, otentikasi, audit, dan Pembatasan laju.
FAQ
| Gejala | Penyebab dan solusi |
Pertanyaan mengembalikan 500, dan log menunjukkan 400 Unsupported tag filter operator | op menggunakan alias seperti eq, ==, atau like. Gunakan =, in, atau not in sebagai gantinya. |
| Filter tag ditambahkan, tetapi jumlah hasil persis sama seperti tanpa filter | Operator yang tidak efektif (seperti >, ≥, ≠, atau empty) digunakan. Hanya =, in, dan not in yang berlaku. |
| API dengan nama identik dari sistem lain diambil | Filter module tidak dikonfigurasi, atau nilai filter tidak sesuai dengan nilai yang ditulis saat unggah. |
| Jumlah hasil pengambilan jauh lebih sedikit dari yang diharapkan | Saat semantic_weight rendah, skor keseluruhan ditekan ke bawah, dan dikombinasikan dengan min_score, lebih banyak hasil difilter. Turunkan min_score seperti dijelaskan di Sesuaikan semantic_weight dan min_score bersama. |
| Pengambilan kode kesalahan tidak akurat | Pastikan enable_query_expansion bernilai false. Saat diaktifkan, kode kesalahan mungkin ditulis ulang. |
| Filter tag selalu mengembalikan 0 hasil | Ejaan nama tag tidak sesuai dengan yang ditulis saat unggah (API tidak melaporkan kesalahan dan hanya mengembalikan 0 hasil). Verifikasi di halaman detail basis pengetahuan dengan memilih Tags > Manage. |
| Pertanyaan yang tidak tercakup dalam basis pengetahuan mengembalikan perintah yang tampaknya dapat dieksekusi | llm_answer() tetap memanggil LLM saat hasil pengambilan kosong. Tambahkan fallback hasil kosong seperti dijelaskan di Langkah 4: Adaptasi kode yang digunakan kembali. |
| Detail dalam file sumber HTML tidak dapat diambil | HTML memiliki granularitas chunk yang lebih kasar. Turunkan panjang segmen maksimum. Konversikan HTML yang berisi banyak kode ke Markdown terlebih dahulu. |
| Pengambilan melaporkan "Failed: None" | Pemeriksaan di _check() pada kode yang digunakan kembali salah (getattr(body, "code", 0) != 0); nilai code dari respons sukses adalah None. Terapkan perbaikan di Langkah 4: Adaptasi kode yang digunakan kembali. |
Pengambilan mengembalikan 404 Knowledge base version ... does not exist | Belum ada versi yang dipublikasikan, atau nomor versi yang dikode keras dalam konfigurasi telah dihapus. Gunakan LATEST_PUBLISHED sebagai gantinya. |
Langkah selanjutnya
Perluas cakupan modul demi modul: buat titik masuk independen untuk setiap nilai
module, dan pertahankan pertanyaan contoh dalam modul yang sama.Bersiap untuk produksi: beralih dari server pengembangan Flask ke server WSGI produksi, dan tambahkan manajemen rahasia, otentikasi, audit, dan Pembatasan laju.
Kelola versi seiring perubahan dokumen Anda: publikasikan versi baru saat referensi API atau Runbook diperbarui, dan simpan hanya versi saat ini plus versi historis terbaru.