ocr: match the surya the local install actually runs, and stop hiding its errors

The pod reported 'No module named surya.ocr' — an API that has not existed for
a year. The real error was two tiers up: the loader wrapped each version branch
in a bare except, so a failure INSIDE the right branch was swallowed and it fell
through to the oldest one. The reported error was from code that never should
have run.

The real error was surya 0.17.1's config class choking on transformers 5.17.
The pin came from a stale requirements-surya.txt. The local install — read from
its venv's dist-info — runs surya 0.22.1 on transformers 5.15, and works. Both
now pin 0.22.1.

0.22 dropped surya.foundation and takes RecognitionPredictor() alone, which the
loader already handled — but only as a fallback reached by swallowed exception.
It now selects the API by what IMPORTS: foundation present is 0.17, absent is
0.22 (or pre-0.17). Verified loading 0.22.1 through the engine class on the pod
stack beside docTR.

tts-xtts: coqui's TTS(name) splits on '/' and wants its own four-part catalogue
name; a HuggingFace id has two parts and fails 'expected 4, got 2'. The service
translates (coqui/XTTS-v2 -> tts_models/multilingual/multi-dataset/xtts_v2),
verified against coqui's catalogue. This means XTTS through _CoquiBackend had
never worked with an HF id anywhere, not only on a pod.
Co-Authored-By: 's avatarClaude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0182DhR1eFNPQyGmDrpHbedY
parent 8310821e
......@@ -245,27 +245,37 @@ class SuryaWorker:
self._langs = [l.strip() for l in str(opts.get("langs", "it")).split(",") if l.strip()] or ["it"]
def load(self):
"""Load surya, matching the API of whichever release is installed.
Three layouts exist. They are told apart by what IMPORTS — not by
catching whatever went wrong and moving on. The old version of this
caught Exception around each tier, so a real failure inside the right
tier (a config attribute missing under a newer transformers) was
swallowed, the loader fell through two tiers, and the error reported
was from an API that has not existed for a year: "No module named
surya.ocr". The cause was invisible.
"""
from surya.detection import DetectionPredictor
# 0.17.x: RecognitionPredictor(FoundationPredictor()) — a shared VLM foundation.
from surya.recognition import RecognitionPredictor
try:
from surya.foundation import FoundationPredictor
from surya.recognition import RecognitionPredictor
from surya.foundation import FoundationPredictor # 0.17.x only
except ImportError:
FoundationPredictor = None
if FoundationPredictor is not None:
# 0.17.x: recognition rides a shared VLM foundation.
self._rec = RecognitionPredictor(FoundationPredictor())
self._det = DetectionPredictor()
self._mode = "predictor"
return
except Exception:
pass
# 0.6–0.16: RecognitionPredictor() with no args.
try:
from surya.recognition import RecognitionPredictor
else:
# 0.6–0.16 and 0.22+: no foundation; RecognitionPredictor() alone.
# (0.22 dropped surya.foundation and takes an optional inference
# manager instead — the no-arg form is right for both.)
self._rec = RecognitionPredictor()
self._det = DetectionPredictor()
self._mode = "predictor"
return
except Exception:
pass
# very old: functional run_ocr API.
self._det = DetectionPredictor()
self._mode = "predictor"
return
def _load_legacy(self):
# very old (<0.6): the functional run_ocr API. Kept reachable but no
# longer a silent fallback — a modern install never gets here.
from surya.ocr import run_ocr
from surya.model.detection.model import (
load_model as ldm, load_processor as ldp)
......
......@@ -26,4 +26,4 @@ jsonschema>=4.0.0
#
# SuryaEngine still runs as a subprocess (it is a SubprocessOcrEngine), so the
# pod points ocr.surya_venv at this interpreter rather than building one.
surya-ocr==0.17.1
surya-ocr==0.22.1
......@@ -19,7 +19,7 @@
# right long-term path (serve Surya2 via coderai's planned vLLM/llama.cpp backend), but it
# does NOT run standalone in this isolated venv. 0.17.1 is the last version that runs
# det+recognition locally on torch (GPU), so it's the classic pin. See docs/ocr.md.
surya-ocr==0.17.1
surya-ocr==0.22.1
pypdfium2>=4.20.0
# NOTE: 0.17.1 is for ocr.surya_serve="local" (classic det+recognition on torch, runs in
# THIS venv). For ocr.surya_serve="vllm"/"llamacpp" (the latest "Surya2" VLM, Qwen3.5-VL
......
......@@ -31,10 +31,33 @@ import sys
_state = {"tts": None, "sr": 24000}
#: HuggingFace repo id -> the name coqui's own catalogue uses. coqui's TTS(name)
#: splits on "/" and wants exactly four parts (type/lang/dataset/model); a HF
#: id has two, and fails with "not enough values to unpack (expected 4, got 2)".
#: The rest of coderai names models by HF id, so the translation lives here.
_HF_TO_COQUI = {
"coqui/xtts-v2": "tts_models/multilingual/multi-dataset/xtts_v2",
"coqui/xtts-v1.1": "tts_models/multilingual/multi-dataset/xtts_v1.1",
}
def _coqui_name(name: str) -> str:
n = (name or "").strip()
if n.count("/") == 3:
return n # already coqui's own naming
mapped = _HF_TO_COQUI.get(n.lower())
if mapped:
return mapped
if "/" in n and "xtts" in n.lower():
# An unknown XTTS repo: the v2 architecture is the safe assumption.
return "tts_models/multilingual/multi-dataset/xtts_v2"
return n
def _load(name: str) -> dict:
import torch
from TTS.api import TTS
tts = TTS(name).to("cuda" if torch.cuda.is_available() else "cpu")
tts = TTS(_coqui_name(name)).to("cuda" if torch.cuda.is_available() else "cpu")
_state["tts"] = tts
# XTTS reports its output rate on the synthesizer; fall back to its default.
try:
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment