12
Pengujian dan Evaluasi
Halaman ini menjelaskan harness evals/ yang mengukur apakah Guardian membaik antar rilis, perubahan prompt agen, pembaruan knowledge base, dan penambahan wrapper tool. Harness itu dibagi menjadi tiga tier independen: fixture parser, integrasi workflow, dan grounding analyst. Semua skor dihasilkan oleh primitif di evals/scoring.py yang mengembalikan dataclass, bukan mencetak keluaran, sehingga hasilnya bisa diagregasi atau diserialisasi ke JSON oleh lapisan test. evals/scoring.py:1-111
graph TD
IN["fixtures/<tool>/<case>.input.txt"] --> DF["discover_fixtures()"]
EXP["fixtures/<tool>/<case>.expected.json"] --> DF
DF --> FX["Fixture: id = tool::case"]
FX --> TP["test_parser_fixture"]
TP --> PT["BaseTool.parse_output"]
PT --> AS["assert_subset()"]
AS --> OK["lulus atau AssertionError berisi path"]
CASE["EVAL_CASES (WorkflowEvalCase)"] --> TW["test_workflow_finds_expected"]
TW --> SB["score_binary()"]
SB --> BS["BinaryClassificationScore"]
BS --> M1["precision / recall / f1"]
J1["datasets/*.jsonl"] --> AG["test_analyst_grounding"]
AG --> HS["score_hallucinations()"]
HS --> M2["HallucinationScore: hallucination_rate"]
AG --> CS["score_cost()"]
CS --> M3["CostEfficiencyScore: cost_per_finding"]
style OK fill:#2d7d46,color:#fff
style M1 fill:#8b5cf6,color:#fff
style M2 fill:#8b5cf6,color:#fff
style M3 fill:#8b5cf6,color:#fff
style CASE fill:#f59e0b,color:#000
style J1 fill:#f59e0b,color:#000
style TW fill:#dc2626,color:#fff
style AG fill:#dc2626,color:#fff
%% fig: Alur fixture dan dataset sampai skor
%% desc: Fixture parser mengalir ke parse_output dan assert_subset, sedangkan tier workflow serta tier agent memakai scorer di evals/scoring.py untuk menghasilkan presisi, recall, F1, dan laju halusinasi.Tiga tingkat eval
| Tier | Yang diukur | Biaya | Marker | Berkas |
|---|---|---|---|---|
| Unit (Tier 1) | kebenaran parse_output pada golden fixture | gratis | default | evals/test_parser_fixtures.py |
| Workflow (Tier 2) | finding end-to-end terhadap aplikasi rentan ber-docker | docker | integration | evals/test_workflow_integration.py |
| Agent (Tier 3) | laju halusinasi, presisi/recall finding, validitas CVSS, biaya | token API | agent_eval | evals/test_analyst_grounding.py |
Tier 1: fixture parser
Satu fixture adalah pasangan berkas di subdirektori bersaudara: evals/fixtures/<tool_name>/<case_name>.input.txt berisi stdout mentah tool, dan evals/fixtures/<tool_name>/<case_name>.expected.json berisi dict hasil parse yang diharapkan. Menambah fixture untuk tool baru adalah jalur termurah untuk memperluas cakupan eval, karena tidak ada kode yang perlu diubah. evals/fixtures_loader.py:1-163
discover_fixtures menelusuri evals/fixtures/ dan mengembalikan setiap pasangan; pasangan yang kehilangan berkas expected.json dilewati. Setiap Fixture mengekspos id berbentuk f"{self.tool}::{self.case}" yang dipakai sebagai id parametrize. evals/fixtures_loader.py:28-614
Contohnya, evals/fixtures/nuclei/two_findings.expected.json hanya menuliskan kunci yang wajib cocok: vulnerabilities, count, dan by_severity. evals/fixtures/nuclei/two_findings.expected.json:1-85
Perbandingan dilakukan oleh assert_subset: setiap kunci di expected harus muncul di actual, list dibandingkan secara posisional, dict secara rekursif, dan nilai lain harus sama persis. Ketidakcocokan melempar AssertionError yang menyertakan path, misalnya f"{path}: actual list shorter ({len(actual)}) than expected ({len(expected)})". evals/fixtures_loader.py:64-916
test_parser_fixtures.py memanggil discover_fixtures() sekali saat import, lalu memparametrisasi test_parser_fixture atas seluruh fixture dengan ids=[f.id for f in _FIXTURES]. evals/test_parser_fixtures.py:23-427
Kelas tool diresolusi lewat TOOL_REGISTRY di core.tool_agent, dipisah pada : menjadi module dan class. Tool yang tidak terdaftar membuat test di-skip dengan pesan f"tool {tool_name} not in registry". Instans dibuat dengan tool_cls.__new__(tool_cls) dan tool.config = {}, karena parser tidak boleh bergantung pada config untuk membaca stdout-nya sendiri. evals/test_parser_fixtures.py:26-528
Tier 2: integrasi workflow
Setiap WorkflowEvalCase mendeklarasikan compose_file (fragmen docker-compose.yml), workflow (nama workflow Guardian), expected_findings (identifier yang wajib muncul), min_findings (batas bawah jumlah mentah), dan timeout_seconds dengan nilai default 600. evals/test_workflow_integration.py:30-389
EVAL_CASES saat ini memuat tiga seed case: dvwa_web_pentest dengan min_findings=3, juiceshop_web_full dengan min_findings=5, dan dvga_graphql dengan min_findings=2. evals/test_workflow_integration.py:44-6610
Test ini memakai marker integration, memanggil pytest.importorskip("docker"), lalu pytest.skip dengan pesan bahwa scaffolding siap tetapi orkestrasi compose_file belum disambungkan. evals/test_workflow_integration.py:69-8311
⚠️ Tier 2 dan Tier 3 tidak berjalan pada pemanggilan
pytestbiasa. Keduanya melewati diri sendiri lewatpytest.skipdan baru ikut berjalan ketika marker yang sesuai dipilih, misalnyapytest evals/ -m integration. evals/test_workflow_integration.py:69-8311 evals/test_analyst_grounding.py:78-9212
Tier 3: grounding analyst
test_analyst_grounding.py mengukur kualitas pertimbangan LLM, bukan kebenaran parsing tool. Setiap case memuat baris JSONL (tool_output, expected_findings) dari evals/datasets/ dan meminta analyst menafsirkannya. evals/test_analyst_grounding.py:1-2413
_load_jsonl membaca berkas baris per baris, melewati baris kosong dan baris yang dimulai dengan //, serta melempar AssertionError(f"{path}:{line_no} bad JSON: {e}") ketika JSON tidak valid. _all_cases menggabungkan seluruh berkas *.jsonl di DATASETS_DIR secara terurut. evals/test_analyst_grounding.py:47-7514
Dataset seed berisi empat record: tiga dari nuclei termasuk CVE-2021-44228 dan CVE-2017-5638, serta satu dari trivy dengan CVE-2022-3517. evals/datasets/analyst_grounding_seed.jsonl:1-415
test_analyst_grounding memakai marker agent_eval dan saat ini langsung pytest.skip("Agent-level eval pending OllamaProvider (item A4) for free CI runs."), sehingga belum ada panggilan API yang dibelanjakan. evals/test_analyst_grounding.py:78-9212
Presisi, recall, dan F1
BinaryClassificationScore menyimpan true_positives, false_positives, dan false_negatives. Properti precision membagi true_positives dengan true_positives + false_positives, recall membagi dengan true_positives + false_negatives, dan f1 menghitung 2 * p * r / (p + r). Ketiga properti mengembalikan 0.0 ketika penyebutnya nol. evals/scoring.py:19-4016
score_binary mengubah kedua sisi menjadi set lebih dahulu, sehingga duplikat tidak menggelembungkan skor; irisan menjadi true positive, selisih prediksi menjadi false positive, dan selisih ground truth menjadi false negative. evals/scoring.py:93-10917
| Scorer | Dataclass | Metrik | Perilaku sisi kosong |
|---|---|---|---|
score_binary | BinaryClassificationScore | precision, recall, f1 | 0.0 |
score_hallucinations | HallucinationScore | hallucination_rate, grounding_rate | 0.0 |
score_cost | CostEfficiencyScore | cost_per_finding | float("inf") |
| — | RankingScore | top1_accuracy, top3_accuracy | 0.0 |
HallucinationScore melacak referensi CVE/CWE yang dikarang: grounded_refs adalah referensi yang ditemukan di KB atau keluaran tool, hallucinated_refs adalah sisanya, dan hallucination_rate membagi hallucinated_refs dengan total_refs. score_hallucinations menghitung jumlah referensi yang ada di grounded_set. evals/scoring.py:43-5719 evals/scoring.py:112-12320
CostEfficiencyScore menyimpan total_cost_usd dan valid_findings; cost_per_finding menjadi metrik utama untuk routing judge A7 dan mengembalikan float("inf") ketika tidak ada finding valid. evals/scoring.py:60-6921 evals/scoring.py:126-13322
RankingScore dipakai eval tool ranker A5 dengan membandingkan top-k ranker terhadap tool terbaik sebenarnya, melalui top1_accuracy dan top3_accuracy. evals/scoring.py:72-8723
Scorer ini sederhana dengan sengaja; tujuannya adalah konsistensi antar eval, bukan implementasi metrik mutakhir. evals/scoring.py:8-1124
Menjalankan pytest dan job CI
| Perintah | Cakupan |
|---|---|
pytest evals/ | tier unit saja |
pytest evals/ -m integration | menambah tier workflow |
pytest evals/ -m agent_eval | menambah tier agent |
pytest evals/ -m "integration or agent_eval" | keduanya |
pytest tests/ evals/ | semua yang gratis |
Test unit di tests/ memakai fixture base_config dari tests/conftest.py, yaitu dict config minimal berisi scope, pentest, logging, dan output yang diterima validator maupun tool. evals/scoring.py:93-10917 tests/conftest.py:10-2226
Target make lint menjalankan ruff check . lalu black --check ., sedangkan make test menjalankan pytest. Makefile:67-79
Job CI lint-and-test berjalan pada matriks ubuntu-latest dan windows-latest dengan Python 3.11 dan 3.12, memasang pip install -e ".[dev]", lalu menjalankan ruff check ., black --check ., dan pytest -q. Karena tidak ada marker yang dipilih, CI hanya mencakup tier yang tidak di-skip. .github/workflows/ci.yml:12-4227
Job kedua, secret-scan, menjalankan gitleaks/gitleaks-action@v2 dengan fetch-depth: 0 agar seluruh riwayat dapat diperiksa. .github/workflows/ci.yml:44-5428
Acceptance gate per item
| Item | Gate |
|---|---|
| A1 (RAG) | CVE-match rate ≥85% pada evals/datasets/kb_grounding.jsonl |
| A2 (Debate) | F1 ≥ baseline single-agent + 5pp |
| A3 (Vision) | ≥75% deskripsi visual benar pada 20 case |
| A4 (Ollama) | workflow recon selesai dan structured output valid |
| A5 (Ranker) | Top-1 accuracy > baseline LLM |
| A7 (Judge) | biaya ≤30% putaran ke-N pada kualitas setara |
| B-tier | integration test menghasilkan finding tidak kosong dan session JSON valid |
Untuk menambah fixture cukup menaruh dua berkas di evals/fixtures/<tool>/<case>.{input.txt,expected.json}; untuk menambah case workflow, ubah EVAL_CASES; untuk menambah case agent, tambahkan baris ke evals/datasets/<name>.jsonl dan pastikan record tidak memuat IP, hostname, atau kredensial internal. docs/EVAL_GUIDE.md:25-6130
Detail penggunaan workflow yang dijalankan oleh tier integrasi ada di Engine Workflow DSL v2, dan kontrak parser yang diuji tier unit dijelaskan di Integrasi Tool dan Pemilihan Tool.