Spaces:
Sleeping
Sleeping
File size: 6,890 Bytes
d28ac45 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 | """Provider-agnostic hosted-LLM client for the enhancer's optional "AI" engine.
Keys are supplied per-request by the user (never stored server-side). Supports
OpenAI-compatible providers (OpenAI, DeepSeek, OpenRouter, Ollama), Anthropic,
and Google Gemini. Every call is best-effort; failures raise so the caller can
fall back to the deterministic engine.
"""
import re
DEFAULT_MODELS = {
"openai": "gpt-4o-mini",
"anthropic": "claude-haiku-4-5-20251001",
"deepseek": "deepseek-chat",
"openrouter": "openai/gpt-4o-mini",
"gemini": "gemini-1.5-flash",
"ollama": "llama3.2",
}
# Providers that speak the OpenAI /chat/completions dialect.
_OPENAI_BASE = {
"openai": "https://api.openai.com/v1",
"deepseek": "https://api.deepseek.com/v1",
"openrouter": "https://openrouter.ai/api/v1",
}
SUPPORTED = set(DEFAULT_MODELS)
def _system_prompt(language: str) -> str:
return (
f"You are a secure-coding assistant. Rewrite the following {language} code "
"to fix every security vulnerability while preserving its behaviour. Return "
"ONLY the corrected code, with no explanations, no commentary, and no "
"markdown code fences."
)
def strip_code_fences(text: str) -> str:
text = (text or "").strip()
fenced = re.match(r"^```[A-Za-z0-9_+-]*\n(.*)\n?```$", text, re.DOTALL)
if fenced:
return fenced.group(1).strip()
text = re.sub(r"^```[A-Za-z0-9_+-]*\n?", "", text)
text = re.sub(r"\n?```$", "", text)
return text.strip()
def _ollama_base(base_url: str) -> str:
base = (base_url or "http://localhost:11434/v1").strip().rstrip("/")
if not re.match(r"^https?://", base):
raise ValueError("Ollama base URL must start with http:// or https://")
if not base.endswith("/v1"):
base = base + "/v1"
return base
def _openai_chat(base, api_key, model, system, user, max_tokens, timeout, referer=None):
import requests
headers = {"content-type": "application/json"}
if api_key:
headers["Authorization"] = f"Bearer {api_key}"
if referer: # OpenRouter likes these; harmless elsewhere.
headers["HTTP-Referer"] = referer
headers["X-Title"] = "FortiScan"
resp = requests.post(
f"{base}/chat/completions",
headers=headers,
json={
"model": model,
"max_tokens": max_tokens,
"messages": [
{"role": "system", "content": system},
{"role": "user", "content": user},
],
},
timeout=timeout,
)
resp.raise_for_status()
return resp.json()["choices"][0]["message"]["content"]
def _anthropic(api_key, model, system, user, max_tokens, timeout):
import requests
resp = requests.post(
"https://api.anthropic.com/v1/messages",
headers={
"x-api-key": api_key,
"anthropic-version": "2023-06-01",
"content-type": "application/json",
},
json={
"model": model,
"max_tokens": max_tokens,
"system": system,
"messages": [{"role": "user", "content": user}],
},
timeout=timeout,
)
resp.raise_for_status()
return resp.json()["content"][0]["text"]
def _gemini(api_key, model, system, user, max_tokens, timeout):
import requests
url = f"https://generativelanguage.googleapis.com/v1beta/models/{model}:generateContent"
resp = requests.post(
url,
headers={"content-type": "application/json", "x-goog-api-key": api_key},
json={
"system_instruction": {"parts": [{"text": system}]},
"contents": [{"parts": [{"text": user}]}],
"generationConfig": {"maxOutputTokens": max_tokens},
},
timeout=timeout,
)
resp.raise_for_status()
data = resp.json()
return data["candidates"][0]["content"]["parts"][0]["text"]
def call_llm(provider, api_key="", model="", code="", language="python",
base_url="", max_tokens=2048, timeout=45):
"""Return the LLM's rewritten code (fences stripped). Raises on failure."""
provider = (provider or "").strip().lower()
if provider not in SUPPORTED:
raise ValueError(f"Unsupported provider: {provider!r}")
model = (model or "").strip() or DEFAULT_MODELS[provider]
system = _system_prompt(language)
if provider in _OPENAI_BASE:
referer = "https://fortiscan.app" if provider == "openrouter" else None
text = _openai_chat(_OPENAI_BASE[provider], api_key, model, system, code,
max_tokens, timeout, referer=referer)
elif provider == "ollama":
text = _openai_chat(_ollama_base(base_url), "", model, system, code, max_tokens, timeout)
elif provider == "anthropic":
text = _anthropic(api_key, model, system, code, max_tokens, timeout)
elif provider == "gemini":
text = _gemini(api_key, model, system, code, max_tokens, timeout)
else: # pragma: no cover - guarded above
raise ValueError(f"Unsupported provider: {provider!r}")
return strip_code_fences(text)
def test_llm(provider, api_key="", model="", base_url="", timeout=15):
"""Live connectivity/auth check. Returns (ok: bool, message: str)."""
import requests
provider = (provider or "").strip().lower()
if provider not in SUPPORTED:
return False, f"Unsupported provider: {provider}"
if provider != "ollama" and not api_key:
return False, "An API key is required for this provider."
model = (model or "").strip() or DEFAULT_MODELS[provider]
try:
# Minimal generation verifies connectivity, auth, and model availability.
call_llm(provider, api_key=api_key, model=model, code="print(1)",
language="python", base_url=base_url, max_tokens=16, timeout=timeout)
return True, f"Connected — {provider} / {model} is live."
except requests.HTTPError as e:
status = e.response.status_code if e.response is not None else "?"
detail = ""
if e.response is not None:
try:
body = e.response.json()
detail = (body.get("error", {}) or {}).get("message", "") if isinstance(body.get("error"), dict) else str(body.get("error", ""))
except Exception:
detail = e.response.text[:200]
hint = " (check the API key)" if status in (401, 403) else " (check the model name)" if status == 404 else ""
return False, f"HTTP {status}{hint}: {detail or 'request rejected'}"
except requests.ConnectionError:
return False, "Connection failed — is the endpoint reachable? (For Ollama, is it running?)"
except requests.Timeout:
return False, "Timed out contacting the provider."
except Exception as e:
return False, f"{e.__class__.__name__}: {e}"
|