diff --git a/docker-compose.yml b/docker-compose.yml index 402c899..35adb3f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -14,8 +14,10 @@ services: - DCC_OPENROUTER_MODEL=${DCC_OPENROUTER_MODEL:-openrouter/free} - DCC_OLLAMA_URL=${DCC_OLLAMA_URL:-http://192.168.0.50:11434} - DCC_OLLAMA_MODEL=${DCC_OLLAMA_MODEL:-gemma2:9b} - - DCC_LOCAL_URL=${DCC_LOCAL_URL:-https://opencode.ai/zen/v1} + - DCC_LOCAL_URL=${DCC_LOCAL_URL:-https://opencode.ai/zen/go/v1} - DCC_LOCAL_KEY=${DCC_LOCAL_KEY:-} - - DCC_LOCAL_MODEL=${DCC_LOCAL_MODEL:-big-pickle} + - DCC_LOCAL_MODEL=${DCC_LOCAL_MODEL:-deepseek-v4-flash} + - DCC_LOCAL_FALLBACK_URL=${DCC_LOCAL_FALLBACK_URL:-https://opencode.ai/zen/v1} + - DCC_LOCAL_FALLBACK_MODEL=${DCC_LOCAL_FALLBACK_MODEL:-big-pickle} - DCC_LLM_TIMEOUT=${DCC_LLM_TIMEOUT:-120} restart: unless-stopped diff --git a/src/engine/config.py b/src/engine/config.py index 37e1b49..9deedcb 100644 --- a/src/engine/config.py +++ b/src/engine/config.py @@ -18,19 +18,23 @@ class ProviderConfig: local_base_url: str = "https://opencode.ai/zen/go/v1" local_api_key: str = "" local_model: str = "deepseek-v4-flash" + local_fallback_url: str = "https://opencode.ai/zen/v1" + local_fallback_model: str = "big-pickle" timeout: int = 120 def load_from_env() -> ProviderConfig: return ProviderConfig( - kind=os.getenv("DCC_LLM_PROVIDER", "openrouter"), # type: ignore + kind=os.getenv("DCC_LLM_PROVIDER", "local"), # type: ignore ollama_base_url=os.getenv("DCC_OLLAMA_URL", "http://192.168.0.50:11434"), ollama_model=os.getenv("DCC_OLLAMA_MODEL", "gemma2:9b"), openrouter_api_key=os.getenv("DCC_OPENROUTER_KEY", os.getenv("OPENROUTER_API_KEY", "")), openrouter_model=os.getenv("DCC_OPENROUTER_MODEL", "openrouter/free"), openrouter_free_fallback=os.getenv("DCC_OR_FREE_FALLBACK", "1") == "1", - local_base_url=os.getenv("DCC_LOCAL_URL", "http://192.168.0.240/v1"), + local_base_url=os.getenv("DCC_LOCAL_URL", "https://opencode.ai/zen/v1"), local_api_key=os.getenv("DCC_LOCAL_KEY", ""), local_model=os.getenv("DCC_LOCAL_MODEL", "big-pickle"), - timeout=int(os.getenv("DCC_LLM_TIMEOUT", "60")), + local_fallback_url=os.getenv("DCC_LOCAL_FALLBACK_URL", "https://opencode.ai/zen/go/v1"), + local_fallback_model=os.getenv("DCC_LOCAL_FALLBACK_MODEL", "deepseek-v4-flash"), + timeout=int(os.getenv("DCC_LLM_TIMEOUT", "120")), ) diff --git a/src/engine/providers.py b/src/engine/providers.py index f6bdef8..a429685 100644 --- a/src/engine/providers.py +++ b/src/engine/providers.py @@ -84,13 +84,15 @@ class OpenRouterProvider(LLMProvider): class LocalProvider(LLMProvider): - def __init__(self, base_url: str, model: str, api_key: str = "", timeout: int = 60): + def __init__(self, base_url: str, model: str, api_key: str = "", + timeout: int = 60, fallbacks: list[tuple[str, str]] | None = None): self.base_url = base_url.rstrip("/") self.model = model self.api_key = api_key self.timeout = timeout + self.fallbacks = fallbacks or [] - def ask(self, prompt: str, system: str = "") -> str: + def _call(self, base_url: str, model: str, prompt: str, system: str) -> str: messages = [] if system: messages.append({"role": "system", "content": system}) @@ -100,10 +102,10 @@ class LocalProvider(LLMProvider): if self.api_key: headers["Authorization"] = f"Bearer {self.api_key}" - body = {"model": self.model, "messages": messages, "stream": False} + body = {"model": model, "messages": messages, "stream": False} resp = httpx.post( - f"{self.base_url}/chat/completions", + f"{base_url}/chat/completions", json=body, headers=headers, timeout=self.timeout, @@ -112,6 +114,21 @@ class LocalProvider(LLMProvider): data = resp.json() return data["choices"][0]["message"]["content"] + def ask(self, prompt: str, system: str = "") -> str: + candidates = [(self.base_url, self.model)] + self.fallbacks + last_err = None + for base_url, model in candidates: + try: + text = self._call(base_url, model, prompt, system) + # Rileva risposte di censura del modello gratuito + if "User Safety" in text and "safe" in text.lower(): + raise ValueError(f"Modello '{model}' ha censurato la risposta: {text[:60]}") + return text + except Exception as e: + last_err = e + continue + raise last_err # type: ignore + _registry: dict[str, Any] = { "ollama": OllamaProvider, @@ -136,6 +153,9 @@ def create_provider(cfg: ProviderConfig | None = None) -> LLMProvider: raise ValueError("DCC_OPENROUTER_KEY non configurata") return cls(key, cfg.openrouter_model, cfg.timeout, cfg.openrouter_free_fallback) elif cfg.kind == "local": - return cls(cfg.local_base_url, cfg.local_model, cfg.local_api_key, cfg.timeout) + fallbacks = [] + if cfg.local_fallback_url and cfg.local_fallback_model: + fallbacks.append((cfg.local_fallback_url, cfg.local_fallback_model)) + return cls(cfg.local_base_url, cfg.local_model, cfg.local_api_key, cfg.timeout, fallbacks=fallbacks) else: raise ValueError(f"Provider non implementato: {cfg.kind}") diff --git a/start.sh b/start.sh index 04a5a95..b4b54e4 100755 --- a/start.sh +++ b/start.sh @@ -19,6 +19,8 @@ export DCC_OLLAMA_MODEL="${DCC_OLLAMA_MODEL:-gemma2:9b}" export DCC_LOCAL_URL="${DCC_LOCAL_URL:-https://opencode.ai/zen/go/v1}" export DCC_LOCAL_KEY="${DCC_LOCAL_KEY:-}" export DCC_LOCAL_MODEL="${DCC_LOCAL_MODEL:-deepseek-v4-flash}" +export DCC_LOCAL_FALLBACK_URL="${DCC_LOCAL_FALLBACK_URL:-https://opencode.ai/zen/v1}" +export DCC_LOCAL_FALLBACK_MODEL="${DCC_LOCAL_FALLBACK_MODEL:-big-pickle}" # ── create data dir ──────────────────────────────────────────────── mkdir -p data