The situation.
A media company needed to classify a library of millions of images against a strict publication policy. Its full-time development team had already built the hard integration work into the company's CMS: an API could fetch candidate images and record the approved picks. The remaining problem was the decision between those two calls. The team had an ONNX implementation of a YOLOv8 detector that had been state of the art three years earlier, but one detector could not express the full policy reliably on its own.
The constraint.
Nothing could leave the client's network, ruling out cloud vision APIs for both privacy and cost. The library also grows every day, so sending every image through a large vision-language model would have made the most expensive layer the default path. Finally, a rejection needed more than a yes-or-no answer: the team had to know which layer made the decision, which policy rule it applied, and which region of the image triggered it.
What we found.
The existing detector was a useful first layer, not a system to discard. It was fast and produced localized detections, but its broad visual classes were not the same thing as the client's policy. Some detections needed a more specific second opinion, while policy violations that depended on the whole image could fall outside the detector's vocabulary. Replacing it with one larger model would have lost its speed, bounding boxes, and explicit class controls.
What we built.
We integrated the existing CMS API with a local HTTP classifier and turned its detector into Layer 1 of a gated policy engine. The ONNX YOLOv8 model reports classes, confidence, and bounding boxes; only classes on an explicit denied list can reject an image. Layer 2 sends selected detection crops through CLIP ViT-B/32 to separate genuinely disallowed details from visually similar edge cases. Images that clear both detector layers reach Layer 3, a 4B vision-language model that scores the whole image against written policy. That final gate can tighten a verdict, never overturn an earlier rejection. Fixed thresholds make every route deterministic, and each result records the deciding layer, policy outcome, confidence, and available geometry. Around the classifier we built single-image and batch interfaces, a multi-site registry, scheduled automation, persistent reports, and deliberate dry-run and live modes. Live runs require credentials and confirmation; dry runs cannot post picks upstream. The API, picker, GPU-backed models, caches, and run history are deployed with Docker on client-owned CUDA hardware, with persistent data kept outside the containers.
What changed.
The backlog cleared, and new images now receive a policy decision before publication. Straightforward cases finish in the detector layers while the vision-language model is reserved for images that need whole-image judgment. When a result is questioned, the client can trace the exact layer and rule that produced it. The complete workflow, from source image to recorded pick, runs against the CMS its development team already built, and the images never leave infrastructure the client owns.
- millions of images
- ONNX YOLOv8, CLIP ViT-B/32, 4B VLM
- deterministic, client-owned, Docker
Related work
If this sounds like your operation, apply to work with us.
Apply