🔬 NatureVision API

API d'inférence vision IA pour l'identification de végétaux et d'animaux.
Serveur MLX local sur Mac Mini M4 — accès réseau local uniquement.

Pas de clé API JSON multipart/form-data Modèle : chargement…

Vue d'ensemble

L'API NatureVision expose le serveur d'inférence MLX sur le Mac Mini. Elle permet d'envoyer une image et d'obtenir une analyse naturaliste structurée en JSON : identification de l'espèce, état de santé, toxicité, conseils d'entretien.

ℹ️
Architecture : deux serveurs distincts.
Mac Mini :8080 — serveur MLX d'inférence (accès réseau local)
Ubuntu :80 — frontend web + proxy API (accès depuis internet)

URL de base

ServeurURLUsage
Mac Mini (MLX) http://IP_MAC_MINI:8080 Inférence directe — réseau local uniquement
Ubuntu (proxy) http://IP_UBUNTU/api API publique via nginx — upload + analyse complète

Authentification

Aucune authentification requise. L'API est accessible librement sur le réseau local. Veillez à ne pas l'exposer directement sur internet sans protection.

Limites

ParamètreValeur
Taille max image20 MB
Formats acceptésJPEG, PNG, WebP, HEIC
Requêtes simultanées1 (file d'attente automatique)
Timeout analyse180 secondes
Tokens max générés2048
⚠️ Le serveur MLX traite une seule analyse à la fois. Les requêtes simultanées reçoivent une erreur 429 et doivent être retentées.

Endpoints

GET /health État du serveur

Vérifie que le serveur MLX est démarré et que le modèle est chargé en mémoire.

Exemple de requête

curl http://IP_MAC_MINI:8080/health

Réponse — modèle prêt

{
  "status":   "ok",        // "ok" | "loading"
  "model":    "mlx-community/Qwen3-VL-8B-Instruct-4bit",
  "analyses": 42           // nombre d'analyses effectuées depuis démarrage
}
POST /generate Analyser une image

Envoie une image encodée en base64 et retourne l'analyse naturaliste complète en JSON.

💡 Il est recommandé de redimensionner l'image à max 1280px avant envoi pour réduire le temps d'analyse et la consommation mémoire.

Corps de la requête — application/json

ChampTypeRequisDescription
prompt string requis Instruction d'analyse pour le modèle IA
image_b64 string requis Image encodée en base64 (JPEG recommandé)
max_tokens integer optionnel Tokens max à générer (défaut: 2048, max: 4096)
temperature float optionnel Créativité du modèle 0.0–1.0 (défaut: 0.05)

Réponse

ChampTypeDescription
response string Réponse brute du modèle (JSON stringifié ou texte)
duration_ms integer Durée de l'inférence en millisecondes

Exemples de code

# Encoder l'image en base64
IMAGE_B64=$(base64 -i photo.jpg | tr -d '\n')

# Appeler l'API
curl -X POST http://IP_MAC_MINI:8080/generate \
  -H "Content-Type: application/json" \
  -d "{
    \"prompt\": \"Identifie le végétal ou l'animal visible. Réponds en JSON.\",
    \"image_b64\": \"$IMAGE_B64\",
    \"max_tokens\": 1024,
    \"temperature\": 0.05
  }"

Structure JSON de réponse

Le champ response contient un objet JSON stringifié à parser. Voici la structure complète.

{
  "sujet_principal": "vegetal",  // "vegetal" | "animal"

  "vegetaux": [{
    "nom_commun":       "Amanite phalloïde",
    "nom_scientifique": "Amanita phalloides",
    "type":             "champignon",  // arbre|arbuste|plante_ornementale|mauvaise_herbe|fleur|...
    "variete_ou_cultivar": "",
    "etat_sante":       "excellent",  // excellent|bon|moyen|mauvais|critique
    "score_sante":      90,           // 0–100
    "description":     "Chapeau vert olive, lames blanches...",
    "taille_adulte":   "8-15 cm de hauteur",
    "floraison":        "Juillet à octobre",
    "origine":          "Europe, Amérique du Nord",
    "ensoleillement":  "mi_ombre",   // plein_soleil|mi_ombre|ombre|indifferent
    "besoin_eau":      "modere",     // faible|modere|eleve|tres_eleve
    "frequence_arrosage": "",
    "sol_ideal":       "Sol calcaire, sous feuillus",
    "problemes_detectes": [],
    "diagnostic":      [],
    "solutions_traitement": [],
    "methodes_elimination": [],
    "conseils_entretien": [],
    "toxicite_humain":   "mortelle",  // aucune|faible|moderee|elevee|mortelle
    "toxicite_animaux":  "mortelle",
    "animaux_concernes": "tous les animaux",
    "parties_toxiques":  "toute la plante",
    "symptomes_intoxication": ["Douleurs abdominales intenses", "Insuffisance hépatique"],
    "conduite_urgence":  "Appeler le 15 (SAMU) immédiatement",
    "photo_url":         "https://upload.wikimedia.org/..."  // Wikipedia (peut être vide)
  }],

  "animaux": [{
    "nom_commun":          "Chat domestique",
    "nom_scientifique":    "Felis catus",
    "type":                "mammifere",  // mammifere|oiseau|reptile|amphibien|poisson|insecte|arachnide|...
    "race_ou_variete":     "British Shorthair (probable)",
    "statut_conservation": "LC",  // LC|NT|VU|EN|CR|DD|non_evalue
    "description":        "Pelage gris bleuté dense...",
    "taille_moyenne":      "25-30 cm au garrot",
    "poids_moyen":         "4-8 kg",
    "esperance_vie":       "12-17 ans",
    "caractere":           "Calme, affectueux, indépendant",
    "comportement_observe": "Au repos, regard vers l'objectif",
    "habitat_naturel":     "Domestique, territoire ~200m²",
    "alimentation":        "Carnivore strict",
    "reproduction":        "2-3 portées/an, 3-5 chatons",
    "niveau_danger":       "aucun",  // aucun|faible|modere|eleve
    "toxicite_humain":     "aucune",
    "toxicite_animaux":    "aucune",
    "venin_ou_poison":     "",
    "symptomes_contact":   [],
    "conduite_urgence":    "",
    "que_faire_si_rencontre": "Animal domestique, approche normale",
    "protection_legale":   "",
    "faits_interessants":  ["Peut tourner ses oreilles à 180°"],
    "photo_url":           "https://upload.wikimedia.org/..."
  }],

  "resume_general":    "Chat British Shorthair adulte au repos...",
  "conseil_immediat":  "Aucune intervention urgente",
  "saison_recommandee": "Toute l'année pour cet animal"
}

Codes d'erreur

Code HTTPSignificationSolution
400 Image invalide ou trop volumineuse Vérifier le format (JPEG/PNG) et la taille (< 20MB)
422 Le modèle n'a pas retourné du JSON valide Réessayer — le modèle a généré du texte libre
429 Serveur occupé (analyse en cours) Attendre 5–10 secondes et réessayer
500 Erreur interne lors de l'inférence Vérifier les logs du Mac Mini
503 Modèle en cours de chargement Attendre 30–60s après le démarrage du serveur
504 Timeout — analyse trop longue Réduire la taille de l'image ou augmenter le timeout

Exemple complet — Python

Exemple fonctionnel avec gestion d'erreurs, retry automatique et parsing JSON robuste.

import base64, json, time, io, re
import requests
from PIL import Image

NATURE_VISION_URL = "http://IP_MAC_MINI:8080"

def encode_image(path_or_bytes, max_px=1280):
    if isinstance(path_or_bytes, (str, bytes)):
        img = Image.open(path_or_bytes if isinstance(path_or_bytes, str)
                         else io.BytesIO(path_or_bytes))
    else:
        img = path_or_bytes
    img = img.convert("RGB")
    if max(img.size) > max_px:
        r = max_px / max(img.size)
        img = img.resize((int(img.width*r), int(img.height*r)), Image.LANCZOS)
    buf = io.BytesIO()
    img.save(buf, "JPEG", quality=88)
    return base64.b64encode(buf.getvalue()).decode()

def extract_json(text):
    text = re.sub(r'<think>[\s\S]*?</think>', '', text).strip()
    try: return json.loads(text)
    except: pass
    m = re.search(r'```(?:json)?\s*([\s\S]*?)\s*```', text)
    if m:
        try: return json.loads(m.group(1))
        except: pass
    s, e = text.find('{'), text.rfind('}')
    if s != -1 and e > s:
        return json.loads(text[s:e+1])
    raise ValueError("JSON introuvable dans la réponse")

def analyze(image_path, prompt=None, retries=2):
    prompt = prompt or (
        "Tu es un naturaliste expert. Identifie le sujet principal "
        "au premier plan et réponds UNIQUEMENT en JSON."
    )
    image_b64 = encode_image(image_path)

    for attempt in range(retries + 1):
        try:
            r = requests.post(
                f"{NATURE_VISION_URL}/generate",
                json={"prompt": prompt, "image_b64": image_b64,
                      "max_tokens": 1024, "temperature": 0.05},
                timeout=180
            )
            if r.status_code == 429:
                time.sleep(8); continue
            r.raise_for_status()
            data = r.json()
            result = extract_json(data["response"])
            print(f"✓ Analyse en {data['duration_ms']}ms")
            return result
        except Exception as e:
            if attempt == retries: raise
            print(f"Tentative {attempt+1} échouée : {e}")
            time.sleep(3)

# Usage
result = analyze("photo_plante.jpg")
for plant in result.get("vegetaux", []):
    print(f"Plante : {plant['nom_commun']} ({plant['nom_scientifique']})")
    print(f"Santé  : {plant['etat_sante']} — {plant['score_sante']}/100")
    if plant['toxicite_humain'] != 'aucune':
        print(f"⚠️  TOXICITÉ : {plant['toxicite_humain']}")
NatureVision API — Serveur MLX local · Mac Mini M4 · Retour à l'application