71da162e1f
Container FastAPI separato (Dockerfile.vision, python:3.13-slim) che espone run_graph/engine_version del Task 2 via POST /run e GET /health, cosi' l'immagine del server principale non importa mai VisionSuite. engine_version() ora legge VISION_ENGINE_VERSION se impostata, altrimenti ricade su git rev-parse nel checkout di sviluppo, e non inventa mai un valore: senza nessuna delle due solleva un errore esplicito. Nel container il fallback a git non puo' funzionare (.git del submodule punta fuori dal build context), quindi Dockerfile.vision prende il commit come build arg e lo fissa in ambiente; i compose file lo passano da VISION_ENGINE_VERSION. Nessuna porta pubblicata e nessuna label Traefik sul servizio vision: e' raggiungibile solo dal server, su tmflow-net. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014BBnuACZSCJqXrMYC3LUMU
59 lines
1.7 KiB
Python
59 lines
1.7 KiB
Python
"""The heavy container: the runner behind an internal API.
|
|
|
|
Separate from the FastAPI server on purpose. The API image stays light, a
|
|
VisionSuite upgrade does not restart production traffic, and an execution that
|
|
crashes does not take the other tablets' requests down with it.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
|
|
import numpy as np
|
|
from fastapi import FastAPI, File, Form, HTTPException, UploadFile, status
|
|
from PIL import Image
|
|
|
|
from src.vision.runner import engine_version, run_graph
|
|
|
|
app = FastAPI(title="TieMeasureFlow Vision Worker", version="0.1.0")
|
|
|
|
|
|
@app.get("/health")
|
|
async def health() -> dict:
|
|
return {"status": "ok", "engine_version": engine_version()}
|
|
|
|
|
|
@app.post("/run")
|
|
async def run(
|
|
image: UploadFile = File(...),
|
|
graph: str = Form(...),
|
|
) -> dict:
|
|
try:
|
|
parsed_graph = json.loads(graph)
|
|
except json.JSONDecodeError as exc:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
|
|
detail=f"graph is not valid JSON: {exc}",
|
|
) from exc
|
|
|
|
frame = np.array(Image.open(image.file).convert("L"))
|
|
|
|
try:
|
|
outcome = run_graph(frame, parsed_graph)
|
|
except ValueError as exc:
|
|
# from_dict refuses a schema version it does not handle; that is a bad
|
|
# request, not a server fault.
|
|
raise HTTPException(
|
|
status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
|
|
detail=str(exc),
|
|
) from exc
|
|
|
|
return {
|
|
"outputs": outcome.outputs,
|
|
"failures": [
|
|
{"tool_id": f.tool_id, "tool_name": f.tool_name, "error": f.error}
|
|
for f in outcome.failures
|
|
],
|
|
"engine_version": outcome.engine_version,
|
|
"duration_ms": outcome.duration_ms,
|
|
}
|