Spaces:
Sleeping
Sleeping
| """Ollama HTTP client (optional upgrade). | |
| If ollama is enabled and running, this engine provides better quality | |
| text + vision inference by routing to local ollama models. The HF | |
| engines remain the default for fully-offline operation. | |
| """ | |
| from __future__ import annotations | |
| import base64 | |
| import json | |
| import urllib.error | |
| import urllib.request | |
| from typing import Any, Optional | |
| from captcha_solver.engines.base import BaseEngine | |
| from captcha_solver.config import get_settings | |
| class OllamaEngine(BaseEngine): | |
| name = "ollama" | |
| def __init__(self) -> None: | |
| super().__init__() | |
| self._enabled = False | |
| self._host = "" | |
| def _do_load(self) -> None: | |
| s = get_settings() | |
| self._enabled = s.ollama_enabled | |
| self._host = s.ollama_host.rstrip("/") | |
| if not self._enabled: | |
| raise RuntimeError("ollama disabled (ollama_enabled=false in config)") | |
| try: | |
| with urllib.request.urlopen(f"{self._host}/api/tags", timeout=3) as r: | |
| if r.status != 200: | |
| raise RuntimeError(f"ollama returned {r.status}") | |
| except Exception as exc: | |
| self._enabled = False | |
| raise RuntimeError(f"ollama not reachable at {self._host}: {exc}") from exc | |
| def _do_unload(self) -> None: | |
| self._enabled = False | |
| def enabled(self) -> bool: | |
| return self._enabled and self._loaded | |
| def _post(self, path: str, payload: dict, timeout: int = 30) -> dict: | |
| data = json.dumps(payload).encode("utf-8") | |
| req = urllib.request.Request( | |
| f"{self._host}{path}", | |
| data=data, | |
| headers={"Content-Type": "application/json"}, | |
| method="POST", | |
| ) | |
| with urllib.request.urlopen(req, timeout=timeout) as r: | |
| return json.loads(r.read().decode("utf-8")) | |
| def generate_text( | |
| self, | |
| prompt: str, | |
| system: Optional[str] = None, | |
| model: Optional[str] = None, | |
| max_tokens: int = 64, | |
| ) -> str: | |
| if not self.enabled: | |
| raise RuntimeError("ollama not enabled") | |
| s = get_settings() | |
| payload: dict[str, Any] = { | |
| "model": model or s.ollama_text_model, | |
| "prompt": prompt, | |
| "stream": False, | |
| "options": {"num_predict": max_tokens, "temperature": 0.0}, | |
| } | |
| if system: | |
| payload["system"] = system | |
| return self._post("/api/generate", payload, s.ollama_timeout).get("response", "").strip() | |
| def describe_image(self, pil_image, prompt: str, model: Optional[str] = None) -> str: | |
| if not self.enabled: | |
| raise RuntimeError("ollama not enabled") | |
| s = get_settings() | |
| import io | |
| buf = io.BytesIO() | |
| pil_image.save(buf, format="PNG") | |
| b64 = base64.b64encode(buf.getvalue()).decode("ascii") | |
| payload = { | |
| "model": model or s.ollama_vision_model, | |
| "prompt": prompt, | |
| "images": [b64], | |
| "stream": False, | |
| "options": {"num_predict": 200, "temperature": 0.0}, | |
| } | |
| return self._post("/api/generate", payload, s.ollama_timeout).get("response", "").strip() | |