ballagb19 commited on
Commit
0f98ed6
·
verified ·
1 Parent(s): e85ec03

Upload captcha_solver/solvers/base.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. captcha_solver/solvers/base.py +96 -0
captcha_solver/solvers/base.py ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Base solver class."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import abc
6
+ import time
7
+ from dataclasses import dataclass, field
8
+ from typing import Optional
9
+
10
+ from captcha_solver.engines import (
11
+ WhisperEngine,
12
+ FlorenceEngine,
13
+ MoondreamEngine,
14
+ QwenEngine,
15
+ OllamaEngine,
16
+ )
17
+
18
+
19
+ @dataclass
20
+ class SolveAttempt:
21
+ """One solver strategy result."""
22
+
23
+ answer: str
24
+ confidence: float
25
+ solver_name: str
26
+ elapsed_ms: int = 0
27
+ error: Optional[str] = None
28
+ metadata: dict = field(default_factory=dict)
29
+
30
+
31
+ @dataclass
32
+ class SolveContext:
33
+ """Shared resources passed to every solver."""
34
+
35
+ whisper: WhisperEngine
36
+ florence: FlorenceEngine
37
+ moondream: MoondreamEngine
38
+ qwen: QwenEngine
39
+ ollama: OllamaEngine
40
+
41
+
42
+ class BaseSolver(abc.ABC):
43
+ """Abstract captcha solver.
44
+
45
+ Each solver is registered with the router. `name` is unique
46
+ per solver. `attempts` returns an ordered list of strategies to try;
47
+ the first one that yields a confident answer wins.
48
+ """
49
+
50
+ name: str = "base"
51
+ captcha_type: str = "base"
52
+
53
+ def __init__(self, ctx: SolveContext) -> None:
54
+ self.ctx = ctx
55
+
56
+ @abc.abstractmethod
57
+ def attempts(self) -> list[callable]:
58
+ """Return a list of zero-arg callables, each producing a SolveAttempt.
59
+
60
+ Each callable should be self-contained: catch its own errors, set
61
+ `error` on the attempt if it failed, and return a result. The
62
+ router picks the first confident (>= min_confidence) success.
63
+ """
64
+ raise NotImplementedError
65
+
66
+ def run_all(self, min_confidence: float = 0.4) -> SolveAttempt:
67
+ """Run every strategy, return the first confident one.
68
+
69
+ On no confident result, returns the highest-confidence attempt
70
+ (even if it failed). Never raises.
71
+ """
72
+ best: Optional[SolveAttempt] = None
73
+ for fn in self.attempts():
74
+ t0 = time.time()
75
+ try:
76
+ attempt = fn()
77
+ except Exception as exc:
78
+ attempt = SolveAttempt(
79
+ answer="",
80
+ confidence=0.0,
81
+ solver_name=f"{self.name}.{fn.__name__}",
82
+ elapsed_ms=int((time.time() - t0) * 1000),
83
+ error=str(exc),
84
+ )
85
+ attempt.elapsed_ms = int((time.time() - t0) * 1000)
86
+ attempt.solver_name = f"{self.name}.{fn.__name__}"
87
+ if attempt.answer and attempt.confidence >= min_confidence:
88
+ return attempt
89
+ if best is None or attempt.confidence > best.confidence:
90
+ best = attempt
91
+ return best or SolveAttempt(
92
+ answer="",
93
+ confidence=0.0,
94
+ solver_name=f"{self.name}.none",
95
+ error="no attempts",
96
+ )