-
Stefy Lanza (nextime / spora ) authored
Five faults, found by one client asking where the OCR boxes had gone. **The boxes.** `_page_from_pod` rebuilt a pod's page key by key -- index, text, width, height, and a named list of diagnostics -- and never copied `lines`, `regions` or `tables`. Every page read on a pod therefore arrived with no geometry at all, for every engine, and no request parameter could change it: the pod puts all of it on the wire (it runs the same `OcrPage.to_dict`) and this boundary dropped it. `confidence` survived only by being one of the names. From outside it looked exactly like a missing engine feature, which is the expensive part: the client saw `lines=0` with a populated `conf`, concluded the engines were not emitting boxes, and went hunting for the parameter that turns line detection on. There is none -- `detect` is stamp/signature detection (off|layout|detector|both) and has nothing to do with text lines. The reassembly now lives once, in `codai.ocr.base.page_from_dict`, shared with the worker-subprocess boundary that had the same job and the better implementation of it. Diagnostics ride by exclusion rather than by name, which is what that boundary already learned after losing `accents_repaired` and then `model_attempts` the same way. A test asserted that "confidence" and "confidence_measured" appeared in the SOURCE of the pod reassembly. They did -- in the key-by-key list that was discarding the geometry -- so it passed throughout. It now runs the boundary. **geoclip answered 500 for text.** Reproduced: "una frase" loses its space, and "unafrase" is eight characters of the base64 alphabet, so it decodes to six bytes and only PIL can tell it is not a picture. PIL raises UnidentifiedImageError, which is NOT a ValueError, so it escaped the caller's `except ValueError -> 400` and became "the server broke" for bad input. Fixed at both ends: `_decode_image` raises ValueError quoting the input, and geoclip's IMAGE half joins vpr/salad in the pre-load refusal, because its contract puts the image in `input` -- so a text `input` is answerable from the name, before 1.9 GB is loaded. Its LOCATION half is deliberately excluded: it takes coordinates there and already answers a clean 400 naming the format. **The 5xx logging I added this morning caught nothing.** Starlette lifts a handler registered for status `500` out of the status table and gives it to ServerErrorMiddleware, which fires only for an UNHANDLED exception -- so `raise HTTPException(status_code=500, ...)`, which is how nearly every 500 here is raised, went straight past it. Confirmed live: two geoclip 500s, no `[http]` line, and the cause had to be found by replaying the request. Now registered on the exception class, logging at >= 500, with the status-keyed 400 handler still winning for its own richer line. Exercised with a TestClient rather than asserted from the source, because the first version passed every source-level check and caught nothing. **A model whose package is absent is no longer advertised as fine.** vosk and geoclip were both listed for as long as they existed while answering every request with an import error, because nothing asked. `codai/models/deps.py` answers it with a memoised `find_spec` -- never an import, never a model load -- and is used in two places: the listing annotates `unavailable_reason` (the model stays listed; hiding one that works is the worse mistake) and the embeddings path refuses with a 503 naming the package and the two files it has to be in. Only MAIN-venv dependencies are declared, because a package that lives in an isolated worker venv is absent from this one by design. **A fully-GPU-resident GGUF reported itself as offloaded.** "21.47 GB total ... of which 19.52 GB was offloaded to host RAM; n_gpu_layers=46", for a model with 46 of 60 layers on the card and ~15 GB resident there. That number is the process's RSS growth, and llama.cpp mmaps the whole file, so a GGUF grows RSS by about its file size whatever the split. The arithmetic is untouched -- the TOTAL is load-bearing for sizing, it was right, and under-reporting a footprint is how eviction stops freeing room and the forward OOMs -- but the line now says which of the two numbers it is holding, and the Tasks page no longer prints a delta as an offload size. Co-Authored-By:
Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014T3rZozi1Ehk96NG1WesgC
e0a90d44