alami-ci
Deploy from alami-eco/alami-trash-ai@aee69796b70947e95efdb9c7483fa52f8d3b4520
76838d6
|
Raw
History Blame Contribute Delete
7.87 kB

Alami Vision API — Referenz

Version 2.0.0 · FastAPI · ONNX Runtime (YOLOv8-seg)

Die API liefert Signale, niemals Geldbeträge. TrashCoin-Berechnung ist server-authoritativ in der Alami-App (siehe AILLMHANDOFF.md §5 im Mobile-Repo).

Authentifizierung

Optional per API-Key. Wird über die Env-Variable ALAMI_API_KEYS (kommasepariert) aktiviert; ohne diese Variable ist die API offen (Dev-Modus / aktueller Mobile-Client). Header: x-api-key: <key>. /healthz ist immer offen.

Achtung Rollout-Reihenfolge: Die Mobile-App sendet heute noch keinen Key. Keys erst erzwingen, wenn der App-Client den Header mitschickt — sonst werden ehrliche Nutzer nicht bezahlt (ai_status='unavailable' ⇒ 0 TC).

Endpoints

GET /healthz

Liveness + Modell-Metadaten.

{ "ok": true, "version": "2.0.0", "imgsz": 640,
  "names": ["plastic","paper","glass","metal","organic","ewaste","other"],
  "conf_thr": 0.35, "supabase": false, "auth": "open", "timestamp": "…" }

POST /predict — Legacy-Contract (Mobile-App, stabil)

Request:

{ "image_url": "https://…", "user_id": "uuid|null", "model_hint": "alami-mobile:v1" }

Response:

{
  "model_version": "v20251018-220439",
  "inference_ms": 77,
  "prediction_id": "uuid",
  "predictions": [
    { "xyxy": [x1,y1,x2,y2], "cls": 0, "conf": 0.86,
      "label": "plastic", "raw_label": "plastic" }
  ]
}
  • label = Material-Bucket (plastic|paper|glass|metal|organic|ewaste|other) — darauf keyed der Economy-Gate der App.
  • raw_label = originale Modellklasse (Nachvollziehbarkeit; bei zukünftigen feingranularen Modellen, z. B. TACO-60 oder Marken-SKUs, weicht sie ab).
  • Koordinaten in Originalbild-Pixeln.

POST /v1/analyze — Standalone Vision API (neu)

Superset von /predict: Materialzusammenfassung, Stückzahl, Gewichtsschätzung pro Objekt.

Request:

{ "image_url": "https://…", "user_id": "uuid|null", "domain": "trash" }

Response:

{
  "prediction_id": "uuid",
  "model_version": "v20251018-220439",
  "domain": "trash",
  "inference_ms": 77,
  "image": { "width": 800, "height": 600 },
  "objects": [
    { "label": "metal", "raw_label": "metal", "confidence": 0.91,
      "bbox_xyxy": [12.0, 40.5, 180.2, 300.0], "area_fraction": 0.105,
      "weight_estimate_g": 15.0, "weight_source": "material_prior_v0" }
  ],
  "summary": {
    "item_count": 1, "trash_detected": true,
    "materials": { "metal": 1 },
    "total_weight_estimate_g": 15.0,
    "weight_source": "material_prior_v0"
  }
}

log (default true): Auf false setzen für Live-Kamera-Preview-Frames — das Frame wird analysiert, aber nicht persistiert (kein trash_predictions/JSONL-Eintrag). So flutet der ~1/s-Preview-Strom die Trainingsdaten nicht; nur das final gesnappte Foto wird mit log=true geloggt.

source (optional): Kontext-Tag, z. B. "product-scan". Geloggte Zeilen mit Nicht-Litter-Source hält der Flywheel-Sync in einem separaten Pool (predictions_other_sources.jsonl) — Regal-/Produktfotos mischen nie ins Litter-Training, bleiben aber für die spätere Marken-Erkennung erhalten. Litter-Pool = source fehlt oder "alami-mobile".

domain ist heute nur "trash"; das Feld reserviert die API-Fläche für weitere visuelle Domänen (siehe Roadmap), ohne dass Clients später migrieren müssen.

Gewicht: material_prior_v0 ist eine ehrliche Heuristik (Median-Stückgewicht pro Material, überschreibbar via deploy/<bundle>/weight_priors.json). Sobald der gelernte Weight-Head live ist, steht in weight_source die Modellversion. Konsumenten MÜSSEN weight_source auswerten, bevor sie Gewichte anzeigen oder verrechnen.

Jedes Objekt trägt zusätzlich Felder für Kamera-Overlays (siehe docs/MOBILE_AGENT_HANDOFF.md §4b):

  • bbox_norm: [x, y, w, h] normiert in 0..1 — Box ohne Kenntnis der Bildgröße zeichnen
  • label_de: deutsches Anzeige-Label (z. B. „Glas")
  • color: stabile Overlay-Farbe (Hex) für das Material

POST /v1/analyze/upload

Wie /v1/analyze, aber Multipart-Upload statt URL (für API-Kunden ohne öffentliche Bild-URLs). Felder: file (Bild, max. 10 MB), user_id?, domain?.

GET /v1/product/{barcode}

Produkt-Nachhaltigkeits-Lookup (Open Food/Products/Beauty Facts): Eco-Score, Verpackung → Material-Bucket + deutscher Entsorgungs-Guide, Litter-Gewicht. Immer HTTP 200 mit status: found | not_found | unavailable (400 nur bei ungültigem Barcode). 24-h-Cache serverseitig. Getrennt vom Modell-Pfad — kein Einfluss auf /predict. Client MUSS attribution anzeigen (ODbL). Details + UI-Anleitung: MOBILE_AGENT_HANDOFF.md §4d.

GET /v1/materials

Statische Anzeige-Metadaten aller 7 Materialien (bucket, label_de, color). Für konsistente Kamera-Overlays — App holt einmal und cacht.

{ "materials": [
  {"bucket":"plastic","label_de":"Plastik","color":"#2563eb"},
  {"bucket":"glass","label_de":"Glas","color":"#059669"}, ... ] }

POST /feedback — Human Reinforcement

Fire-and-forget-Korrekturen; Trainingsrohstoff für das Flywheel (ml/scripts/ingest_feedback.py).

{
  "prediction_id": "uuid",
  "corrected_type": "plastic",
  "corrected_weight_kg": 0.5,
  "notes": "war eine PET-Flasche",
  "source": "alami-mobile",
  "corrected_items": [
    { "index": 0, "action": "reject" },
    { "index": 1, "action": "relabel", "corrected_label": "metal", "corrected_weight_g": 15.0 }
  ],
  "added_items": [
    { "label": "glass", "point": { "x": 0.42, "y": 0.61 } },
    { "label": "plastic", "box": { "x": 0.1, "y": 0.1, "w": 0.2, "h": 0.3 }, "count": 2 }
  ],
  "reasons": ["too_dark", "occluded"]
}

Alle Felder außer prediction_id optional; corrected_weight_kg ∈ [0.001, 20]. corrected_items referenziert per index die Objekte der zugehörigen Prediction (objektgenaue Labels für zukünftiges Training).

Feedback v2 (Mobile-Kontrakt #141) — additiv, spiegelt den Live-Client:

  • corrected_items[].action = confirm | relabel | reject — macht Bestätigungen und False Positives zu expliziten Signalen (fehlt → Alt-Verhalten „relabel").
  • added_items — Objekte, die die KI übersah (Recall-Signal). point/box normalisiert 0..1 zum hochgeladenen Bild; ein verorteter Treffer ist ein trainierbares Detektor-Label, ein bloßes count nur eine Zählung.
  • reasons — maschinenlesbare Fehler-Chips: too_dark · blurry · occluded · unknown_object · not_trash · unsure_skip (offene Liste, vorwärtskompatibel).

Persistenz nach Supabase trash_predictions (added_items, feedback_reasons; action reist im corrected_items-JSONB). Fehlt eine Spalte, wird nur diese weggelassen — Migration siehe docs/FLYWHEEL.md §3.

Environment-Variablen

Variable Default Bedeutung
ALAMI_AI_BUNDLE deploy/latest Modell-Bundle (model.onnx, names.json, postprocess_config.json)
ALAMI_API_KEYS (leer = offen) Kommaseparierte API-Keys
ALAMI_FEEDBACK_DIR feedback_logs JSONL-Logs (predictions/feedback)
ALAMI_CORS_ORIGINS * Kommaseparierte CORS-Origins
ALAMI_DEBUG (aus) 1 ⇒ Debug-Logs und Debug-Endpoints
ALAMI_INSECURE_FETCH (aus) 1 ⇒ TLS-Verify beim Bild-Fetch aus (nur lokal!)
SUPABASE_URL / SUPABASE_SERVICE_ROLE_KEY (leer) Optionales Prediction-Logging nach Supabase (trash_predictions)

Fehlerverhalten

  • Nicht erreichbare/zu große Bilder ⇒ 400/408, kein Absturz.
  • Der Mobile-Client behandelt jeden Fehler als unavailable und lässt die Submission mit needs_review durch — die API darf den Submit-Pfad niemals blockieren (Hard Contract §5.2).

Tests

pip install -r requirements.txt pytest httpx
pytest tests/ -v