9
Knowledge Base RAG
core/knowledge_base.py adalah lapisan retrieval RAG dengan SQLite murni dari stdlib. Korpusnya berisi metadata CVE, CWE, MITRE ATT&CK, dan nuclei template, dan tujuannya agar Analyst berhenti mengarang referensi CVE. FTS5 mengerjakan pencarian leksikal; kolom embedding opsional mempercepat pencocokan semantik fuzzy ketika extra sentence-transformers terpasang. core/knowledge_base.py:1-421
Basis data default ada di ~/.guardian/kb/kb.sqlite3. Direktori induk dibuat saat KnowledgeBase diinstansiasi, dan _connect menerapkan _SCHEMA pada koneksi pertama. core/knowledge_base.py:562 core/knowledge_base.py:165-1773
Dua dataclass membentuk antarmuka data: KBEntry untuk satu record dan KBHit untuk satu hasil retrieval. KBHit.score selalu dinormalkan ke higher-is-better, sehingga pemanggil tidak perlu tahu jalur mana yang menghasilkan skor. core/knowledge_base.py:74-844
Halaman ini hanya membahas penyimpanan dan retrieval. Pemakaian KB oleh agen dijelaskan di Sistem Multi-Agen; pembungkusan hasil retrieval sebagai untrusted input dijelaskan di Keamanan, Scope, dan Sanitasi; daftar seluruh perintah ada di Referensi Perintah CLI.
Skema: entries, fts, dan embeddings
erDiagram
ENTRIES {
TEXT id PK
TEXT kind
TEXT title
TEXT summary
TEXT severity
REAL cvss
TEXT cwe
TEXT refs
TEXT updated
}
FTS {
TEXT id
TEXT title
TEXT summary
TEXT refs
}
EMBEDDINGS {
TEXT id PK
TEXT vec
}
ENTRIES ||--|| FTS : "content=entries"
ENTRIES ||--o| EMBEDDINGS : "id references entries"
%% fig: Skema tiga tabel knowledge base
%% desc: entries menyimpan record kanonik, fts adalah virtual table FTS5 yang mengindeks title/summary/refs, dan embeddings menyimpan vektor float32 opsional per id.entries memakai id TEXT PRIMARY KEY dan index idx_entries_kind pada kolom kind. fts adalah virtual table FTS5 dengan content='entries', content_rowid='rowid', dan tokenizer porter. Tiga trigger — entries_ai, entries_ad, entries_au — menjaga isi fts tetap sinkron pada insert, delete, dan update. core/knowledge_base.py:88-1275
embeddings hanya diisi bila extra terpasang. upsert_embedding memak vektor sebagai float32 little-endian lewat struct.pack, lalu menyimpannya dengan INSERT OR REPLACE. core/knowledge_base.py:236-2436
⚠️
_connectmemanggilsqlite3.connect(self.path)tanpaPRAGMA foreign_keys = ON, sehingga klausaON DELETE CASCADEpadaembeddingshanya berlaku bila pragma itu diaktifkan lebih dulu. Menghapus barisentriestidak otomatis membersihkan embedding-nya. core/knowledge_base.py:172-1777
has_corpus(kind) memberi cek murah sebelum corpus rebuild, sedangkan stats() mengembalikan jumlah baris per kind untuk guardian kb status. core/knowledge_base.py:186-1988
Jalur tulis: upsert
graph LR
A["Iterable[KBEntry]"] --> B["KnowledgeBase.upsert"]
B --> C["susun rows: title 500, summary 5000, refs 20"]
C --> G{"len(rows) == 0?"}
G -->|"ya"| H["return 0"]
G -->|"tidak"| D["INSERT OR REPLACE INTO entries"]
D --> E["trigger entries_ai / entries_au"]
E --> F[("fts FTS5")]
D --> I["return len(rows)"]
style D fill:#336791,color:#fff
style F fill:#336791,color:#fff
style E fill:#f59e0b,color:#000
style I fill:#2d7d46,color:#fff
style H fill:#dc2626,color:#fff
%% fig: Jalur tulis upsert ke entries dan fts
%% desc: upsert menormalkan tiap KBEntry, menulis batch dengan INSERT OR REPLACE, lalu trigger FTS5 menyinkronkan tabel fts.upsert menormalkan setiap KBEntry menjadi tuple kolom. Bila batch kosong, fungsi mengembalikan 0 tanpa menyentuh koneksi; bila tidak, fungsi menulis dalam satu transaksi dan mengembalikan jumlah baris yang ditulis. core/knowledge_base.py:202-2349
| Kolom | Perlakuan di upsert |
|---|---|
title | dipotong ke 500 karakter |
summary | dipotong ke 5000 karakter |
refs | dibatasi 20 URL lalu diserialisasi dengan json.dumps |
cvss | disimpan apa adanya, boleh NULL |
kind | cve / cwe / attck / template |
core/knowledge_base.py:209-22110
upsert memakai INSERT OR REPLACE, jadi id yang sama menimpa baris lama, bukan menggandakannya. Test membuktikannya: dua upsert berurutan dengan id yang sama menyisakan satu baris cwe, dan judul barunya menang di pencarian. tests/test_knowledge_base.py:69-7611
Pemotongan refs ke 20 URL juga diuji langsung. tests/test_knowledge_base.py:218-22312
Korpus bawaan untuk seed ada di _seed_entries(): 15 record, terdiri dari 6 CVE, 5 CWE, dan 4 ATT&CK. cli/commands/kb.py:60-16013
Sanitasi query FTS5
_fts_sanitize membuang semua karakter di luar [A-Za-z0-9_ ], mempertahankan token dengan panjang minimal 2 karakter, membungkus setiap token dengan tanda kutip ganda, lalu menyambungnya dengan OR. Hasil kosong berarti pemanggil melewati FTS dan query langsung mengembalikan list kosong. core/knowledge_base.py:130-14814
Tujuannya menghindari grammar FTS5: tanda kutip, tanda kurung, tanda hubung, dan keyword AND/OR/NOT/NEAR tidak aman dipakai apa adanya. Test memastikan input seperti (injection) OR "foo" dan --; DROP TABLE entries; -- tidak melempar exception. tests/test_knowledge_base.py:107-11115
Query: shortlist FTS5 lalu rerank
sequenceDiagram
participant CLI as guardian kb query
participant Analyst as AnalystAgent
participant KB as KnowledgeBase.query
participant FTS as fts FTS5
participant EMB as embeddings
CLI->>KB: query(text, k, kind)
Analyst->>KB: query(600 char pertama tool + output, k=rag.top_k)
KB->>KB: _fts_sanitize(text)
alt tidak ada token tersisa
KB-->>CLI: []
else ada token
KB->>FTS: SELECT bm25(fts) MATCH ? ORDER BY rank LIMIT k*4
FTS-->>KB: rows
opt reranker tersedia dan shortlist punya embedding
KB->>EMB: SELECT id, vec WHERE id IN shortlist
EMB-->>KB: blob float32
KB->>KB: score = 0.3 * fts + 0.7 * cosine
end
KB-->>Analyst: List[KBHit] ke hits_to_prompt_block
end
%% fig: Siklus satu query KB sampai blok prompt
%% desc: query menyaring teks ke grammar FTS5 yang aman, mengambil shortlist k*4, lalu melakukan rerank cosine bila embedding tersedia.Query SQL mengambil bm25(fts) AS rank, join entries lewat rowid, menambahkan filter e.kind = ? bila kind diberikan, dan mengambil max(k, 1) * 4 baris sebagai shortlist. core/knowledge_base.py:247-29516
| Parameter | Default | Arti |
|---|---|---|
text | – | query bebas; kosong setelah strip() berarti [] |
k | 5 | jumlah hit akhir yang dikembalikan |
kind | None | filter satu jenis korpus |
core/knowledge_base.py:247-25217
bm25 bersifat lower-is-better, jadi skor dibalik dan dipadatkan ke rentang (0, 1] dengan 1.0 / (1.0 + max(bm25, 0.0)). Test memastikan bm25 0.0 mengalahkan 5.0 dan nilai negatif dijepit ke hasil yang sama dengan 0.0. core/knowledge_base.py:321-32518 tests/test_knowledge_base.py:141-14919
Rerank hanya aktif bila _maybe_load_reranker berhasil memuat SentenceTransformer("all-MiniLM-L6-v2"). Model di-cache di level modul, dan kegagalan import apa pun menghasilkan None tanpa mengganggu jalur FTS. core/knowledge_base.py:328-34320
_rerank_with_embeddings mengambil vec dari embeddings untuk id di shortlist, membongkarnya dengan struct.unpack, lalu memblend 0.3 * skor_fts + 0.7 * cosine. Entry tanpa baris embedding tetap membawa skor FTS-nya; bila tidak satu pun shortlist punya embedding, urutan FTS dipertahankan. core/knowledge_base.py:346-38621
Blok referensi untuk prompt
hits_to_prompt_block merender hit sebagai blok berpagar [BEGIN_KB_REFERENCES] dan [END_KB_REFERENCES], satu baris per hit berisi id, kind, severity, cvss bila ada, title, dan summary yang dipotong 300 karakter. Anggaran total dibatasi max_chars=2000, dan hit yang melewati anggaran dilewati. core/knowledge_base.py:404-43122
Analyst memanggilnya lewat _retrieve_kb_block, yang memakai 600 karakter pertama gabungan nama tool dan output sebagai query, serta rag.top_k dengan default 5 sebagai k. Hasilnya dibungkus wrap_untrusted karena teks CVE bisa tercemar di sisi upstream. core/analyst_agent.py:179-20423
Grounding bersifat opt-in: KnowledgeBase() hanya dibuat bila rag.enabled true, dan default-nya false sehingga perilaku Analyst tidak berubah sampai korpus di-seed. core/analyst_agent.py:31-3924
guardian kb query --prompt mencetak blok yang sama apa adanya untuk inspeksi manual, tanpa pembungkusan untrusted; pembungkusan hanya dilakukan oleh Analyst. cli/commands/kb.py:311-31325
Perintah guardian kb
kb_app didaftarkan di aplikasi utama dengan nama kb. cli/main.py:8226
| Subcommand | Opsi | Perilaku |
|---|---|---|
status | – | tabel baris per kind; KB kosong mencetak KB is empty lalu exit 0 |
seed | – | upsert(_seed_entries()) lalu mencetak jumlah baris |
update | --kind, --accept-network, --file | ingest saja; tanpa --file tidak ada unduhan |
query | TEXT, --top/-k, --kind, --prompt | tabel skor/ID/kind/severity/title, atau blok prompt bila --prompt |
cli/commands/kb.py:39-5427 cli/commands/kb.py:163-16828 cli/commands/kb.py:174-22329 cli/commands/kb.py:295-32930
status juga mencetak path basis data yang sedang dibaca, sehingga operator tahu berkas mana yang dipakai. cli/commands/kb.py:39-5427
⚠️
guardian kb update --kind cvetanpa--fileselalu gagal: exit 1 bila--accept-networktidak diberikan, exit 2 bila diberikan. Klien tidak pernah mengunduh feed sendiri; operator mengunduh manual lalu memberi--file. cli/commands/kb.py:193-20831
⚠️ Docstring modul menyebut korpus bawaan di
data/kb_seed/, tetapi direktori itu tidak ada di repository. Isi seed sebenarnya adalah list Python di_seed_entries(). cli/commands/kb.py:11-1832 cli/commands/kb.py:60-16013
update memakai adapter longgar _normalize_payload yang menerima list, items, CVE_Items, atau objects. Item tanpa id dilewati; title jatuh ke name, lalu ke 120 karakter pertama summary, lalu ke id. cli/commands/kb.py:226-28933
Contoh urutan pemakaian dari dokumen fitur v4: kb seed, lalu kb update --kind cve --file ./nvd-2025.json, lalu kb query "log4j JNDI" --top 5, lalu menambahkan rag.enabled: true dan rag.top_k: 5 ke config. docs/V4_FEATURES.md:41-6334
Metrik penerimaan yang ditetapkan untuk A1 adalah CVE-match rate di atas 85% pada 50 finding nuclei yang di-hold-out, dengan baseline tanpa RAG di bawah 50%. docs/V4_FEATURES.md:65-6835