Lompat ke konten
dokumentasi.id
Commit e4397d1
v1
Star2,4 rb

Kamu membaca versi lama. Halaman ini hanya bisa dibaca.

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-192).

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-124). ClaudeProvider.generate mengirim messages.create dan mengulang sekali tanpa temperature jika API menolak parameter tersebut (ai_providers/claude_provider.py:71-110). 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-51). LiteLLMProvider.generate memakai litellm.completion dengan drop_params: True sehingga parameter yang tidak dikenal vendor dibuang (ai_providers/litellm_provider.py:52-75).

Key configKelasModel defaultSumber
openaiOpenAIProvidergpt-4oai_providers/openai_provider.py:37-62
claudeClaudeProviderclaude-sonnet-4-20250514ai_providers/claude_provider.py:30-54
ollamaOllamaProviderllama2ai_providers/ollama_provider.py:15-22
litellmLiteLLMProvideropenai/gpt-4oai_providers/litellm_provider.py:25-39
mistral, grok, gemini, openrouter, orcarouter, groq, lmstudio, nvidia_nimkelas <Vendor>Provider di ai_providers/ditentukan blok config masing-masingconfig/config.example.yaml:5-110

OpenAIProvider memakai is_reasoning_model untuk mengenali keluarga o1, o3, dan o4 (ai_providers/openai_provider.py:32-34). 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-39).

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-26). 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-38). Pola yang sama diulang untuk groq, lmstudio, litellm, dan nvidia_nim di ekor method (ai_providers/provider_manager.py:121-137).

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.
Fig. 1: Init provider yang di-gate enabledSetiap 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-62), dan ClaudeProvider melakukan hal setara untuk prefiks sk-ant-your (ai_providers/claude_provider.py:30-54). 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-146).

⚠️ set_provider yang mengembalikan False tidak terlihat oleh pemanggil yang mengabaikan return value. deep_eye.py memanggil ai_manager.set_provider(ai_provider) dengan scanner.ai_provider yang default-nya openai (deep_eye.py:297-300), 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-192). 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-192).

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.
Fig. 2: Jalur retry dan failover antar providerProvider aktif dicoba tiga kali dengan backoff, lalu manager berpindah ke provider lain dan memindahkan status aktif bila salah satunya menjawab.
MekanismeNilai atau string di kodeSumber
Batas retry provider aktifmax_retries = 3ai_providers/provider_manager.py:148-192
Backoffwait = 2 ** attempt, tidur hanya bila attempt < max_retries - 1ai_providers/provider_manager.py:148-192
Respons kosongraise RuntimeError("empty AI response")ai_providers/provider_manager.py:148-192
Skip saat failoverif provider == self.active_provider: continueai_providers/provider_manager.py:148-192
Semua gagallogger.error("All AI providers failed (returning empty string)") lalu return ""ai_providers/provider_manager.py:148-192
Tanpa provider terdaftarlogger.error("No AI providers available") lalu return ""ai_providers/provider_manager.py:148-192

⚠️ generate tidak 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-237). 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-177).

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-248). 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-179).

Consumer di dalam repository

ConsumerPanggilanPerilaku saat AI gagal
AIPayloadGenerator._get_ai_responseself.ai_manager.generate(prompt)menangkap exception, logger.warning, mengembalikan []; hasil sukses dipotong lines[:10] (core/ai_payload_generator.py:419-437)
AIPayloadGenerator._ai_enrich_payloadsmengirim tiga prompt untuk sql_injection, xss, ssrfexception per attack hanya logger.debug, attack itu dilewati (core/ai_payload_generator.py:439-469)
generate_payloadsmenggabungkan hasil AI dengan payload dasarlist(dict.fromkeys(extra + base))[:25] (core/ai_payload_generator.py:105-111)
AITriage._triage_oneself.ai_manager.generate(prompt)confidence 0.5, triage_reason "AI unavailable" (modules/ai_triage/triage.py:68-75)

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.