Spaces:
Sleeping
Sleeping
| """Base solver class.""" | |
| from __future__ import annotations | |
| import abc | |
| import time | |
| from dataclasses import dataclass, field | |
| from typing import Optional | |
| from captcha_solver.engines import ( | |
| WhisperEngine, | |
| FlorenceEngine, | |
| MoondreamEngine, | |
| QwenEngine, | |
| OllamaEngine, | |
| ) | |
| class SolveAttempt: | |
| """One solver strategy result.""" | |
| answer: str | |
| confidence: float | |
| solver_name: str | |
| elapsed_ms: int = 0 | |
| error: Optional[str] = None | |
| metadata: dict = field(default_factory=dict) | |
| class SolveContext: | |
| """Shared resources passed to every solver.""" | |
| whisper: WhisperEngine | |
| florence: FlorenceEngine | |
| moondream: MoondreamEngine | |
| qwen: QwenEngine | |
| ollama: OllamaEngine | |
| class BaseSolver(abc.ABC): | |
| """Abstract captcha solver. | |
| Each solver is registered with the router. `name` is unique | |
| per solver. `attempts` returns an ordered list of strategies to try; | |
| the first one that yields a confident answer wins. | |
| """ | |
| name: str = "base" | |
| captcha_type: str = "base" | |
| def __init__(self, ctx: SolveContext) -> None: | |
| self.ctx = ctx | |
| def attempts(self) -> list[callable]: | |
| """Return a list of zero-arg callables, each producing a SolveAttempt. | |
| Each callable should be self-contained: catch its own errors, set | |
| `error` on the attempt if it failed, and return a result. The | |
| router picks the first confident (>= min_confidence) success. | |
| """ | |
| raise NotImplementedError | |
| def run_all(self, min_confidence: float = 0.4) -> SolveAttempt: | |
| """Run every strategy, return the first confident one. | |
| On no confident result, returns the highest-confidence attempt | |
| (even if it failed). Never raises. | |
| """ | |
| best: Optional[SolveAttempt] = None | |
| for fn in self.attempts(): | |
| t0 = time.time() | |
| try: | |
| attempt = fn() | |
| except Exception as exc: | |
| attempt = SolveAttempt( | |
| answer="", | |
| confidence=0.0, | |
| solver_name=f"{self.name}.{fn.__name__}", | |
| elapsed_ms=int((time.time() - t0) * 1000), | |
| error=str(exc), | |
| ) | |
| attempt.elapsed_ms = int((time.time() - t0) * 1000) | |
| attempt.solver_name = f"{self.name}.{fn.__name__}" | |
| if attempt.answer and attempt.confidence >= min_confidence: | |
| return attempt | |
| if best is None or attempt.confidence > best.confidence: | |
| best = attempt | |
| return best or SolveAttempt( | |
| answer="", | |
| confidence=0.0, | |
| solver_name=f"{self.name}.none", | |
| error="no attempts", | |
| ) | |