runpod: let an OCR engine be placed per model, like everything else

OCR engines are picked by name (paddle|doctr|surya) rather than registered like
a model, so nothing could place one individually and the test for them failed
with "not in models.json" before it ever reached a pod. The only way to send
OCR to a pod was a capability remote — a production routing switch, and the
mechanism that once sent live embedding traffic to a rented GPU.

An ocr_models section maps to the ocr capability, so an engine gets the same
per-model placement every other model type already has, and the harness creates
a temporary entry for one when the catalogue has none.
Co-Authored-By: 's avatarClaude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0182DhR1eFNPQyGmDrpHbedY
parent 38929139
...@@ -45,7 +45,7 @@ router = APIRouter() ...@@ -45,7 +45,7 @@ router = APIRouter()
_SECTIONS = { _SECTIONS = {
"text_models", "image_models", "audio_models", "gguf_models", "tts_models", "text_models", "image_models", "audio_models", "gguf_models", "tts_models",
"vision_models", "video_models", "audio_gen_models", "embedding_models", "vision_models", "video_models", "audio_gen_models", "embedding_models",
"spatial_models", "spatial_models", "ocr_models",
} }
......
...@@ -209,6 +209,12 @@ MODEL_TYPE_CAPABILITY = { ...@@ -209,6 +209,12 @@ MODEL_TYPE_CAPABILITY = {
"tts_models": "tts", "tts_models": "tts",
"embedding_models": "embeddings", "embedding_models": "embeddings",
"spatial_models": "spatial", "spatial_models": "spatial",
# OCR engines are picked by name (paddle|doctr|surya) rather than registered
# like a model, so nothing could place one individually: a test had to move
# the whole capability, which is a production routing switch. An entry in
# this section gives an engine the same per-model placement everything else
# already has.
"ocr_models": "ocr",
"audio_gen_models": "audio_gen", "audio_gen_models": "audio_gen",
} }
......
...@@ -102,11 +102,26 @@ def _find(data: dict, model: str): ...@@ -102,11 +102,26 @@ def _find(data: dict, model: str):
return None, None, None return None, None, None
#: Picked by name rather than registered like a model, so there is nothing in the
#: catalogue to pin. A temporary entry gives them the same per-model placement —
#: the alternative was moving the whole OCR capability, which is a production
#: routing switch.
_OCR_ENGINES = ("paddle", "doctr", "surya")
def _pin_to_runpod(model: str, engine: str, vram: float, usd: float, def _pin_to_runpod(model: str, engine: str, vram: float, usd: float,
image: str = "") -> bool: image: str = "") -> bool:
"""Give this ONE model a runpod block. True when the catalogue changed.""" """Give this ONE model a runpod block. True when the catalogue changed."""
data = _load_models() data = _load_models()
section, index, entry = _find(data, model) section, index, entry = _find(data, model)
if entry is None and model.strip().lower() in _OCR_ENGINES:
section = "ocr_models"
data.setdefault(section, [])
data[section].append({"path": model, "model_type": "ocr_models"})
index = len(data[section]) - 1
entry = data[section][index]
print(f" {model}: OCR engine — added a temporary catalogue entry",
flush=True)
if entry is None: if entry is None:
print(f" {model}: not in models.json — nothing to place", flush=True) print(f" {model}: not in models.json — nothing to place", flush=True)
return False return False
......
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