Spaces:
Runtime error
Runtime error
sara.mesquita commited on
Commit ·
80dfdac
1
Parent(s): 0446dcd
Changes nemotron model
Browse files- README.md +1 -1
- core/ai.py +80 -70
README.md
CHANGED
|
@@ -64,7 +64,7 @@ flowchart TD
|
|
| 64 |
A["📷 Photo from phone"] --> B["🤖 Vision AI\nLlama-3.2-11B-Vision\n(NVIDIA NIM / HF)"]
|
| 65 |
B -->|"is it an animal?"| C{Animal detected?}
|
| 66 |
C -->|"No"| ERR["❌ Error message\n'No dog or cat identified'"]
|
| 67 |
-
C -->|"Yes"| D["📝 Structured JSON\nNemotron Mini 4B Instruct
|
| 68 |
D --> E["🔢 Semantic embedding\nall-MiniLM-L6-v2 · 384 dim\n(local, 22M params)"]
|
| 69 |
E --> F{Cosine similarity\n≥ 0.80?}
|
| 70 |
F -->|"Yes — known animal"| G["➕ New sighting\nadded to existing animal"]
|
|
|
|
| 64 |
A["📷 Photo from phone"] --> B["🤖 Vision AI\nLlama-3.2-11B-Vision\n(NVIDIA NIM / HF)"]
|
| 65 |
B -->|"is it an animal?"| C{Animal detected?}
|
| 66 |
C -->|"No"| ERR["❌ Error message\n'No dog or cat identified'"]
|
| 67 |
+
C -->|"Yes"| D["📝 Structured JSON\nNemotron Mini 4B Instruct structures\nspecies · breed · color · condition · marks"]
|
| 68 |
D --> E["🔢 Semantic embedding\nall-MiniLM-L6-v2 · 384 dim\n(local, 22M params)"]
|
| 69 |
E --> F{Cosine similarity\n≥ 0.80?}
|
| 70 |
F -->|"Yes — known animal"| G["➕ New sighting\nadded to existing animal"]
|
core/ai.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
"""
|
| 2 |
-
ai.py — Visao +
|
| 3 |
|
| 4 |
Arquitetura (todos os modelos < 32B):
|
| 5 |
- Visao : meta/llama-3.2-11b-vision-instruct (11B, NVIDIA NIM) -> NVIDIA_API_KEY
|
|
@@ -7,13 +7,15 @@ Arquitetura (todos os modelos < 32B):
|
|
| 7 |
- Texto : nvidia/nemotron-mini-4b-instruct (4B, NVIDIA NIM) -> NVIDIA_API_KEY
|
| 8 |
- Embed : sentence-transformers/all-MiniLM-L6-v2 (22M, local/CPU)
|
| 9 |
|
| 10 |
-
Fluxo: o Llama-3.2-11B-Vision enxerga a foto e
|
| 11 |
-
|
| 12 |
-
|
|
|
|
|
|
|
| 13 |
|
| 14 |
Basta a NVIDIA_API_KEY: ela serve tanto para a visao quanto para o Nemotron.
|
| 15 |
-
|
| 16 |
-
|
| 17 |
|
| 18 |
Variaveis de ambiente:
|
| 19 |
NVIDIA_API_KEY — chave NVIDIA NIM (free tier em build.nvidia.com)
|
|
@@ -37,54 +39,58 @@ _HF_MODEL = "meta-llama/Llama-3.2-11B-Vision-Instruct" # id HF Serverle
|
|
| 37 |
_NIM_VISION_MODEL = "meta/llama-3.2-11b-vision-instruct" # id NVIDIA NIM (11B, free)
|
| 38 |
_NIM_TEXT_MODEL = "nvidia/nemotron-mini-4b-instruct" # id NVIDIA NIM (4B, free)
|
| 39 |
|
| 40 |
-
|
| 41 |
-
"Examine this image carefully.\n"
|
| 42 |
-
"FIRST: Is there a dog or cat clearly visible?\n"
|
| 43 |
-
"If NO dog or cat is present, respond with exactly: {\"is_animal\": false}\n\n"
|
| 44 |
-
"If YES, respond with ONLY valid JSON (no markdown, no explanation).\n\n"
|
| 45 |
-
"For breed_estimate, use visual cues (coat type, ear shape, body build, snout, tail) to pick the SINGLE best match.\n\n"
|
| 46 |
-
"DOG breeds to choose from (use exact spelling):\n"
|
| 47 |
"SRD, Labrador Retriever, Golden Retriever, Pitbull, Poodle, Shih Tzu, Rottweiler, "
|
| 48 |
"German Shepherd, Bulldog, Dachshund, Chihuahua, Siberian Husky, Border Collie, "
|
| 49 |
-
"Beagle, Boxer, Maltese, Chow Chow, Akita, Dalmatian, Doberman
|
| 50 |
-
|
|
|
|
| 51 |
"Domestic Shorthair, Domestic Longhair, Siamese, Persian, Maine Coon, Bengal, "
|
| 52 |
-
"British Shorthair, Ragdoll, Scottish Fold, Turkish Angora, Sphynx, Abyssinian
|
| 53 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 54 |
"{\"is_animal\": true,"
|
| 55 |
" \"species\": \"dog or cat\","
|
| 56 |
-
" \"breed_estimate\": \"
|
| 57 |
" \"size\": \"small, medium or large\","
|
| 58 |
-
" \"primary_color\": \"main color
|
| 59 |
-
" \"secondary_colors\": [\"other
|
| 60 |
-
" \"distinctive_marks\": [\"notable features
|
| 61 |
" \"condition\": \"healthy, thin or injured\","
|
| 62 |
-
" \"description_text\": \"one concise English sentence
|
| 63 |
-
)
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
"
|
| 68 |
-
"
|
| 69 |
-
"Rules:\n"
|
| 70 |
-
"- breed_estimate must be the single best match from the allowed dog/cat breed lists; "
|
| 71 |
-
"use SRD (dog) or Domestic Shorthair (cat) only if truly unidentifiable.\n"
|
| 72 |
-
"- species is 'dog' or 'cat'; size is 'small', 'medium' or 'large'.\n"
|
| 73 |
-
"- primary_color and secondary_colors are simple lowercase words.\n"
|
| 74 |
-
"- condition is exactly one of: healthy, thin, injured.\n"
|
| 75 |
-
"- description_text is ONE concise English sentence for identity matching.\n"
|
| 76 |
-
"- Do NOT invent details not present in the input. No markdown, no explanation.\n\n"
|
| 77 |
-
"Input JSON:\n"
|
| 78 |
)
|
| 79 |
|
| 80 |
|
| 81 |
class AnimalAI:
|
| 82 |
def __init__(self):
|
| 83 |
-
self.vision_mode
|
| 84 |
-
self.vision_model
|
| 85 |
-
self.vision_client
|
| 86 |
self.nim_text_model = _NIM_TEXT_MODEL
|
| 87 |
-
self.nim_client = None
|
| 88 |
|
| 89 |
nvidia_key = os.environ.get("NVIDIA_API_KEY", "")
|
| 90 |
hf_token = os.environ.get("HF_TOKEN", "")
|
|
@@ -98,7 +104,6 @@ class AnimalAI:
|
|
| 98 |
api_key=nvidia_key,
|
| 99 |
)
|
| 100 |
self.nim_text_model = os.environ.get("NVIDIA_MODEL", _NIM_TEXT_MODEL)
|
| 101 |
-
# Visao via NIM (Llama-3.2-11B-Vision)
|
| 102 |
self.vision_mode = "nim"
|
| 103 |
self.vision_model = os.environ.get("NVIDIA_VISION_MODEL", _NIM_VISION_MODEL)
|
| 104 |
self.vision_client = self.nim_client
|
|
@@ -106,7 +111,7 @@ class AnimalAI:
|
|
| 106 |
except ImportError:
|
| 107 |
log.warning("openai nao instalado")
|
| 108 |
|
| 109 |
-
# Alternativa: visao via HF Serverless
|
| 110 |
if self.vision_client is None and hf_token:
|
| 111 |
try:
|
| 112 |
from huggingface_hub import InferenceClient
|
|
@@ -141,68 +146,73 @@ class AnimalAI:
|
|
| 141 |
]
|
| 142 |
|
| 143 |
if self.vision_mode == "nim":
|
| 144 |
-
# NVIDIA NIM — Llama-3.2-11B-Vision (OpenAI-compatible)
|
| 145 |
resp = self.vision_client.chat.completions.create(
|
| 146 |
model=self.vision_model,
|
| 147 |
messages=[{"role": "user", "content": content}],
|
| 148 |
max_tokens=400,
|
| 149 |
-
temperature=0.
|
| 150 |
)
|
| 151 |
raw = resp.choices[0].message.content or ""
|
| 152 |
else:
|
| 153 |
-
# HuggingFace InferenceClient — suporte nativo a multimodal
|
| 154 |
resp = self.vision_client.chat_completion(
|
| 155 |
messages=[{"role": "user", "content": content}],
|
| 156 |
max_tokens=400,
|
| 157 |
-
temperature=0.
|
| 158 |
)
|
| 159 |
raw = resp.choices[0].message.content or ""
|
| 160 |
|
| 161 |
log.info("Visao resposta: %s", raw[:200])
|
| 162 |
-
result = self._parse(raw)
|
| 163 |
|
| 164 |
-
#
|
| 165 |
-
if
|
| 166 |
log.info("IA: nenhum animal detectado na imagem.")
|
| 167 |
return {"is_animal": False, "_ai_success": True}
|
| 168 |
|
| 169 |
-
#
|
| 170 |
if self.nim_client is not None:
|
| 171 |
-
result = self.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 172 |
|
| 173 |
result["is_animal"] = True
|
| 174 |
-
result
|
| 175 |
return result
|
| 176 |
|
| 177 |
except Exception as e:
|
| 178 |
log.error("Erro na API de visao: %s", e)
|
| 179 |
return self._fallback()
|
| 180 |
|
| 181 |
-
def
|
| 182 |
-
"""
|
| 183 |
try:
|
| 184 |
-
payload = json.dumps(result, ensure_ascii=False)
|
| 185 |
resp = self.nim_client.chat.completions.create(
|
| 186 |
model=self.nim_text_model,
|
| 187 |
-
messages=[{"role": "user", "content":
|
| 188 |
-
max_tokens=
|
| 189 |
temperature=0.2,
|
| 190 |
top_p=0.9,
|
| 191 |
)
|
| 192 |
msg = resp.choices[0].message
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
if
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
result[k] = cleaned[k]
|
| 201 |
-
log.info("Nemotron Mini 4B normalizou os campos.")
|
| 202 |
-
return result
|
| 203 |
except Exception as e:
|
| 204 |
-
log.warning("Nemotron
|
| 205 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 206 |
|
| 207 |
def get_embedding(self, description: dict) -> list:
|
| 208 |
"""Embedding da descricao. Aleatorio se IA falhou (evita falsos matches)."""
|
|
|
|
| 1 |
"""
|
| 2 |
+
ai.py — Visao + estruturacao de texto, ambas via NVIDIA NIM (ou HF como alternativa).
|
| 3 |
|
| 4 |
Arquitetura (todos os modelos < 32B):
|
| 5 |
- Visao : meta/llama-3.2-11b-vision-instruct (11B, NVIDIA NIM) -> NVIDIA_API_KEY
|
|
|
|
| 7 |
- Texto : nvidia/nemotron-mini-4b-instruct (4B, NVIDIA NIM) -> NVIDIA_API_KEY
|
| 8 |
- Embed : sentence-transformers/all-MiniLM-L6-v2 (22M, local/CPU)
|
| 9 |
|
| 10 |
+
Fluxo: o Llama-3.2-11B-Vision enxerga a foto e descreve o animal (texto livre —
|
| 11 |
+
modelos de visao raramente cospem JSON limpo). Em seguida o Nemotron Mini 4B
|
| 12 |
+
Instruct (texto puro) LE essa descricao e EXTRAI o JSON estrito (especie, raca da
|
| 13 |
+
lista permitida, cor, condicao, frase de matching). Cada modelo no que e bom:
|
| 14 |
+
Llama ve, Nemotron estrutura.
|
| 15 |
|
| 16 |
Basta a NVIDIA_API_KEY: ela serve tanto para a visao quanto para o Nemotron.
|
| 17 |
+
Sem NVIDIA_API_KEY mas com HF_TOKEN, a visao usa o HF Serverless e a estruturacao
|
| 18 |
+
cai para o parser local (regex de JSON).
|
| 19 |
|
| 20 |
Variaveis de ambiente:
|
| 21 |
NVIDIA_API_KEY — chave NVIDIA NIM (free tier em build.nvidia.com)
|
|
|
|
| 39 |
_NIM_VISION_MODEL = "meta/llama-3.2-11b-vision-instruct" # id NVIDIA NIM (11B, free)
|
| 40 |
_NIM_TEXT_MODEL = "nvidia/nemotron-mini-4b-instruct" # id NVIDIA NIM (4B, free)
|
| 41 |
|
| 42 |
+
_DOG_BREEDS = (
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 43 |
"SRD, Labrador Retriever, Golden Retriever, Pitbull, Poodle, Shih Tzu, Rottweiler, "
|
| 44 |
"German Shepherd, Bulldog, Dachshund, Chihuahua, Siberian Husky, Border Collie, "
|
| 45 |
+
"Beagle, Boxer, Maltese, Chow Chow, Akita, Dalmatian, Doberman"
|
| 46 |
+
)
|
| 47 |
+
_CAT_BREEDS = (
|
| 48 |
"Domestic Shorthair, Domestic Longhair, Siamese, Persian, Maine Coon, Bengal, "
|
| 49 |
+
"British Shorthair, Ragdoll, Scottish Fold, Turkish Angora, Sphynx, Abyssinian"
|
| 50 |
+
)
|
| 51 |
+
|
| 52 |
+
# Prompt da VISAO — texto livre permitido (o Nemotron estrutura depois).
|
| 53 |
+
PROMPT = (
|
| 54 |
+
"Look at this image. Is there a dog or a cat in it?\n"
|
| 55 |
+
"If there is NO dog and NO cat, reply with exactly: NO ANIMAL\n"
|
| 56 |
+
"If there is a dog or a cat, describe the most prominent one in 2-3 sentences: "
|
| 57 |
+
"species (dog or cat), likely breed, size, main color and secondary colors, "
|
| 58 |
+
"any distinctive marks (collar, scar, patch, missing ear), and apparent condition "
|
| 59 |
+
"(healthy, thin or injured)."
|
| 60 |
+
)
|
| 61 |
+
|
| 62 |
+
# Prompt do NEMOTRON — extrai JSON estrito a partir da descricao livre do Llama.
|
| 63 |
+
EXTRACT_PROMPT = (
|
| 64 |
+
"You convert a free-text animal description into STRICT JSON for a stray-animal database.\n"
|
| 65 |
+
"Output ONLY a JSON object — no markdown, no explanation.\n\n"
|
| 66 |
+
"If the description says there is no dog or cat (e.g. 'NO ANIMAL'), output exactly:\n"
|
| 67 |
+
"{\"is_animal\": false}\n\n"
|
| 68 |
+
"Otherwise output exactly these keys:\n"
|
| 69 |
"{\"is_animal\": true,"
|
| 70 |
" \"species\": \"dog or cat\","
|
| 71 |
+
" \"breed_estimate\": \"single best match from the lists below\","
|
| 72 |
" \"size\": \"small, medium or large\","
|
| 73 |
+
" \"primary_color\": \"main color\","
|
| 74 |
+
" \"secondary_colors\": [\"other colors or empty list\"],"
|
| 75 |
+
" \"distinctive_marks\": [\"notable features or empty list\"],"
|
| 76 |
" \"condition\": \"healthy, thin or injured\","
|
| 77 |
+
" \"description_text\": \"one concise English sentence identifying this specific animal\"}\n\n"
|
| 78 |
+
"DOG breeds (exact spelling): " + _DOG_BREEDS + "\n"
|
| 79 |
+
"CAT breeds (exact spelling): " + _CAT_BREEDS + "\n"
|
| 80 |
+
"Use SRD (dog) or Domestic Shorthair (cat) only if the breed is truly unclear.\n"
|
| 81 |
+
"If several animals appear, structure only the most prominent one.\n"
|
| 82 |
+
"Do NOT invent details that are not in the description.\n\n"
|
| 83 |
+
"DESCRIPTION:\n"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 84 |
)
|
| 85 |
|
| 86 |
|
| 87 |
class AnimalAI:
|
| 88 |
def __init__(self):
|
| 89 |
+
self.vision_mode = None # "nim" | "hf"
|
| 90 |
+
self.vision_model = None
|
| 91 |
+
self.vision_client = None # OpenAI (NIM) ou InferenceClient (HF)
|
| 92 |
self.nim_text_model = _NIM_TEXT_MODEL
|
| 93 |
+
self.nim_client = None # OpenAI -> NVIDIA NIM, p/ extracao Nemotron
|
| 94 |
|
| 95 |
nvidia_key = os.environ.get("NVIDIA_API_KEY", "")
|
| 96 |
hf_token = os.environ.get("HF_TOKEN", "")
|
|
|
|
| 104 |
api_key=nvidia_key,
|
| 105 |
)
|
| 106 |
self.nim_text_model = os.environ.get("NVIDIA_MODEL", _NIM_TEXT_MODEL)
|
|
|
|
| 107 |
self.vision_mode = "nim"
|
| 108 |
self.vision_model = os.environ.get("NVIDIA_VISION_MODEL", _NIM_VISION_MODEL)
|
| 109 |
self.vision_client = self.nim_client
|
|
|
|
| 111 |
except ImportError:
|
| 112 |
log.warning("openai nao instalado")
|
| 113 |
|
| 114 |
+
# Alternativa: visao via HF Serverless se nao houver NVIDIA
|
| 115 |
if self.vision_client is None and hf_token:
|
| 116 |
try:
|
| 117 |
from huggingface_hub import InferenceClient
|
|
|
|
| 146 |
]
|
| 147 |
|
| 148 |
if self.vision_mode == "nim":
|
|
|
|
| 149 |
resp = self.vision_client.chat.completions.create(
|
| 150 |
model=self.vision_model,
|
| 151 |
messages=[{"role": "user", "content": content}],
|
| 152 |
max_tokens=400,
|
| 153 |
+
temperature=0.2,
|
| 154 |
)
|
| 155 |
raw = resp.choices[0].message.content or ""
|
| 156 |
else:
|
|
|
|
| 157 |
resp = self.vision_client.chat_completion(
|
| 158 |
messages=[{"role": "user", "content": content}],
|
| 159 |
max_tokens=400,
|
| 160 |
+
temperature=0.2,
|
| 161 |
)
|
| 162 |
raw = resp.choices[0].message.content or ""
|
| 163 |
|
| 164 |
log.info("Visao resposta: %s", raw[:200])
|
|
|
|
| 165 |
|
| 166 |
+
# Atalho: visao declarou que nao ha animal
|
| 167 |
+
if "NO ANIMAL" in raw.upper() and self._extract_json(raw) is None:
|
| 168 |
log.info("IA: nenhum animal detectado na imagem.")
|
| 169 |
return {"is_animal": False, "_ai_success": True}
|
| 170 |
|
| 171 |
+
# Estrutura a descricao em JSON (Nemotron preferido; robusto a prosa)
|
| 172 |
if self.nim_client is not None:
|
| 173 |
+
result = self._structure_with_nemotron(raw)
|
| 174 |
+
else:
|
| 175 |
+
result = self._parse(raw)
|
| 176 |
+
|
| 177 |
+
if result.get("is_animal") is False:
|
| 178 |
+
log.info("IA: nenhum animal detectado (estruturacao).")
|
| 179 |
+
return {"is_animal": False, "_ai_success": True}
|
| 180 |
|
| 181 |
result["is_animal"] = True
|
| 182 |
+
result.setdefault("_ai_success", True)
|
| 183 |
return result
|
| 184 |
|
| 185 |
except Exception as e:
|
| 186 |
log.error("Erro na API de visao: %s", e)
|
| 187 |
return self._fallback()
|
| 188 |
|
| 189 |
+
def _structure_with_nemotron(self, raw_text: str) -> dict:
|
| 190 |
+
"""Extrai JSON estrito da descricao livre da visao usando Nemotron Mini 4B Instruct."""
|
| 191 |
try:
|
|
|
|
| 192 |
resp = self.nim_client.chat.completions.create(
|
| 193 |
model=self.nim_text_model,
|
| 194 |
+
messages=[{"role": "user", "content": EXTRACT_PROMPT + raw_text}],
|
| 195 |
+
max_tokens=500,
|
| 196 |
temperature=0.2,
|
| 197 |
top_p=0.9,
|
| 198 |
)
|
| 199 |
msg = resp.choices[0].message
|
| 200 |
+
out = (msg.content or "") or (getattr(msg, "reasoning_content", "") or "")
|
| 201 |
+
parsed = self._extract_json(out)
|
| 202 |
+
if parsed is not None:
|
| 203 |
+
log.info("Nemotron Mini 4B estruturou o JSON.")
|
| 204 |
+
parsed["_ai_success"] = True
|
| 205 |
+
return parsed
|
| 206 |
+
log.warning("Nemotron nao retornou JSON — tentando parse direto da visao")
|
|
|
|
|
|
|
|
|
|
| 207 |
except Exception as e:
|
| 208 |
+
log.warning("Nemotron structure falhou: %s", e)
|
| 209 |
+
|
| 210 |
+
# Reservas: JSON direto da visao, senao fallback generico
|
| 211 |
+
direct = self._extract_json(raw_text)
|
| 212 |
+
if direct is not None:
|
| 213 |
+
direct["_ai_success"] = True
|
| 214 |
+
return direct
|
| 215 |
+
return self._fallback()
|
| 216 |
|
| 217 |
def get_embedding(self, description: dict) -> list:
|
| 218 |
"""Embedding da descricao. Aleatorio se IA falhou (evita falsos matches)."""
|