2.4
Lapisan AI Provider
Lapisan AI provider adalah pembungkus multi-vendor. Setiap backend mengekspos satu kontrak generate(prompt, **kwargs) -> str, sedangkan AIProviderManager menjadi satu-satunya pintu masuk yang dipakai consumer. Manager itu juga yang memilih provider aktif, mengulang panggilan yang gagal, dan berpindah ke vendor lain ketika provider aktif berhenti menjawab (ai_providers/provider_manager.py:148-1921).
Kontrak generate(prompt, **kwargs)
Semua kelas provider menandatangani method yang sama, tetapi cara memanggil API berbeda. OpenAIProvider.generate mem-pop tools dan handlers lebih dulu; bila keduanya ada, panggilan dialihkan ke run_tool_loop, bila tidak, ia memilih jalur chat.completions biasa (ai_providers/openai_provider.py:84-1242). ClaudeProvider.generate mengirim messages.create dan mengulang sekali tanpa temperature jika API menolak parameter tersebut (ai_providers/claude_provider.py:71-1103). OllamaProvider.generate melakukan requests.post ke {base_url}/api/generate dengan stream: False dan melempar ulang error sebagai exception (ai_providers/ollama_provider.py:34-514). LiteLLMProvider.generate memakai litellm.completion dengan drop_params: True sehingga parameter yang tidak dikenal vendor dibuang (ai_providers/litellm_provider.py:52-755).
| Key config | Kelas | Model default | Sumber |
|---|---|---|---|
openai | OpenAIProvider | gpt-4o | ai_providers/openai_provider.py:37-626 |
claude | ClaudeProvider | claude-sonnet-4-20250514 | ai_providers/claude_provider.py:30-547 |
ollama | OllamaProvider | llama2 | ai_providers/ollama_provider.py:15-228 |
litellm | LiteLLMProvider | openai/gpt-4o | ai_providers/litellm_provider.py:25-399 |
mistral, grok, gemini, openrouter, orcarouter, groq, lmstudio, nvidia_nim | kelas <Vendor>Provider di ai_providers/ | ditentukan blok config masing-masing | config/config.example.yaml:5-11010 |
OpenAIProvider memakai is_reasoning_model untuk mengenali keluarga o1, o3, dan o4 (ai_providers/openai_provider.py:32-3411). Komentar di LiteLLMProvider.__init__ mengingatkan agar nama model diberi prefiks openai/ ketika base_url menunjuk ke proxy LiteLLM, supaya base_url tidak diabaikan (ai_providers/litellm_provider.py:25-399).
Init yang di-gate oleh enabled
AIProviderManager.__init__ membaca config["ai_providers"], memanggil _initialize_providers(), lalu memilih provider dari scanner.ai_provider; bila key itu tidak ada di self.providers, ia jatuh ke provider pertama yang berhasil didaftarkan (ai_providers/provider_manager.py:16-2612). Setiap blok vendor dibungkus gate ai_config.get("<name>", {}).get("enabled", False) dan satu try/except sendiri, sehingga vendor yang gagal di-init hanya menghasilkan logger.warning dan tidak menghentikan proses (ai_providers/provider_manager.py:28-3813). Pola yang sama diulang untuk groq, lmstudio, litellm, dan nvidia_nim di ekor method (ai_providers/provider_manager.py:121-13714).
graph TD
CFG["config config.yaml: blok ai_providers"] --> GATE{"enabled bernilai true?"}
GATE -->|"tidak"| SKIP["vendor dilewati, tidak masuk self.providers"]
GATE -->|"ya"| IMP["import kelas VendorProvider"]
IMP -->|"ValueError placeholder key"| WARN["logger.warning Failed to initialize provider"]
IMP -->|"sukses"| REG["self.providers[name] = instance"]
REG --> PREF["scanner.ai_provider"]
PREF --> SET["set_provider(name)"]
SET --> ACT["self.active_provider"]
style CFG fill:#f59e0b,color:#000
style GATE fill:#f59e0b,color:#000
style IMP fill:#f59e0b,color:#000
style PREF fill:#f59e0b,color:#000
style SKIP fill:#dc2626,color:#fff
style WARN fill:#dc2626,color:#fff
style REG fill:#8b5cf6,color:#fff
style SET fill:#336791,color:#fff
style ACT fill:#2d7d46,color:#fff
%% fig: Init provider yang di-gate enabled
%% desc: Setiap vendor hanya di-instansiasi bila blok config-nya ber-enabled true, dan kegagalan satu vendor berhenti sebagai warning tanpa membatalkan manager.Kunci API placeholder adalah penyebab kegagalan init yang paling sering. OpenAIProvider melempar ValueError("OpenAI API key not provided or still a placeholder") bila key kosong atau berawalan your- / sk-your (ai_providers/openai_provider.py:37-626), dan ClaudeProvider melakukan hal setara untuk prefiks sk-ant-your (ai_providers/claude_provider.py:30-547). set_provider mengembalikan False dan mencatat Provider <name> not available bila nama tidak ada di self.providers, tanpa mengubah provider aktif (ai_providers/provider_manager.py:139-14615).
⚠️
set_provideryang mengembalikanFalsetidak terlihat oleh pemanggil yang mengabaikan return value.deep_eye.pymemanggilai_manager.set_provider(ai_provider)denganscanner.ai_provideryang default-nyaopenai(deep_eye.py:297-30016), jadi nilai config yang salah ketik akan lolos tanpa peringatan di layar dan manager tetap memakai provider pertama yang berhasil di-init.
Retry dan failover
AIProviderManager.generate mencoba provider aktif maksimal tiga kali dengan backoff eksponensial 2 ** attempt, dan tidur hanya selama attempt < max_retries - 1. Respons None atau string kosong diubah menjadi RuntimeError("empty AI response") supaya ikut terhitung sebagai kegagalan (ai_providers/provider_manager.py:148-1921). Setelah tiga percobaan habis, manager mengiterasi self.providers, melewati provider yang sedang aktif, dan mencoba provider.generate(prompt, **kwargs) satu per satu (ai_providers/provider_manager.py:148-1921).
graph TD
REQ["AIProviderManager.generate(prompt)"] --> NONE{"active_provider ada?"}
NONE -->|"tidak"| EMPTY["return string kosong"]
NONE -->|"ya"| ATT["attempt 0..2, max_retries = 3"]
ATT --> CALL["active_provider.generate(prompt, **kwargs)"]
CALL -->|"teks tidak kosong"| OK["return result"]
CALL -->|"RuntimeError empty AI response"| BACK["sleep 2 ** attempt"]
BACK --> ATT
ATT -->|"3 percobaan habis"| LOOP["iterasi self.providers selain yang aktif"]
LOOP --> FB["provider.generate(prompt, **kwargs)"]
FB -->|"sukses"| SWAP["active_provider dan _active_name diganti"]
SWAP --> OK
FB -->|"gagal"| LOOP
LOOP -->|"semua gagal"| DEAD["All AI providers failed, return string kosong"]
style REQ fill:#f59e0b,color:#000
style ATT fill:#f59e0b,color:#000
style CALL fill:#336791,color:#fff
style BACK fill:#8b5cf6,color:#fff
style OK fill:#2d7d46,color:#fff
style SWAP fill:#2d7d46,color:#fff
style EMPTY fill:#dc2626,color:#fff
style DEAD fill:#dc2626,color:#fff
%% fig: Jalur retry dan failover antar provider
%% desc: Provider aktif dicoba tiga kali dengan backoff, lalu manager berpindah ke provider lain dan memindahkan status aktif bila salah satunya menjawab.| Mekanisme | Nilai atau string di kode | Sumber |
|---|---|---|
| Batas retry provider aktif | max_retries = 3 | ai_providers/provider_manager.py:148-1921 |
| Backoff | wait = 2 ** attempt, tidur hanya bila attempt < max_retries - 1 | ai_providers/provider_manager.py:148-1921 |
| Respons kosong | raise RuntimeError("empty AI response") | ai_providers/provider_manager.py:148-1921 |
| Skip saat failover | if provider == self.active_provider: continue | ai_providers/provider_manager.py:148-1921 |
| Semua gagal | logger.error("All AI providers failed (returning empty string)") lalu return "" | ai_providers/provider_manager.py:148-1921 |
| Tanpa provider terdaftar | logger.error("No AI providers available") lalu return "" | ai_providers/provider_manager.py:148-1921 |
⚠️
generatetidak melempar exception ketika seluruh provider gagal. Nilainya adalah string kosong, sehingga consumer yang tidak memeriksa hasil akan memperlakukan jawaban kosong sebagai jawaban sah.
Tool calling dan fallback-nya
generate_with_tools menyusun urutan percobaan yang dimulai dari provider aktif, melewati provider tanpa method generate_with_tools, lalu memanggil run_tool_loop untuk setiap kandidat. Bila semua kandidat gagal, ia mencatat Tool calling unavailable dan jatuh ke self.generate(prompt, **kwargs) biasa (ai_providers/provider_manager.py:194-23717). run_tool_loop sendiri berhenti ketika model mengembalikan teks tanpa tool_calls atau ketika max_rounds habis, dengan default 5 putaran (ai_providers/tool_runtime.py:130-17718).
Kemampuan ini bisa diperiksa sebelum dipakai: supports_tools mengembalikan hasattr(..., "generate_with_tools") untuk nama provider tertentu atau provider aktif, dan get_available_providers mengembalikan daftar key self.providers (ai_providers/provider_manager.py:239-24819). Test di repository memasang provider palsu, menambahkan ToolSpec("ping", ...) dengan handler pong, lalu memastikan mgr.supports_tools() bernilai benar setelah generate_with_tools berhasil (tests/test_ai_tool_calling.py:170-17920).
Consumer di dalam repository
| Consumer | Panggilan | Perilaku saat AI gagal |
|---|---|---|
AIPayloadGenerator._get_ai_response | self.ai_manager.generate(prompt) | menangkap exception, logger.warning, mengembalikan []; hasil sukses dipotong lines[:10] (core/ai_payload_generator.py:419-43721) |
AIPayloadGenerator._ai_enrich_payloads | mengirim tiga prompt untuk sql_injection, xss, ssrf | exception per attack hanya logger.debug, attack itu dilewati (core/ai_payload_generator.py:439-46922) |
generate_payloads | menggabungkan hasil AI dengan payload dasar | list(dict.fromkeys(extra + base))[:25] (core/ai_payload_generator.py:105-11123) |
AITriage._triage_one | self.ai_manager.generate(prompt) | confidence 0.5, triage_reason "AI unavailable" (modules/ai_triage/triage.py:68-7524) |
Pola yang berulang di consumer: panggil generate, periksa hasil kosong, lalu jatuhkan diri ke jalur deterministik. Karena failover sudah menangani perpindahan vendor, consumer tidak perlu tahu provider mana yang akhirnya menjawab; alur scan dan pemakaian manager-nya dibahas di Alur Scan dan CVE Intelligence dan RAG untuk enrichment berbasis CVE.