sara.mesquita commited on
Commit
80dfdac
·
1 Parent(s): 0446dcd

Changes nemotron model

Browse files
Files changed (2) hide show
  1. README.md +1 -1
  2. 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 normalizes\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"]
 
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 + normalizacao 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,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 devolve a descricao bruta. Em
11
- seguida o Nemotron Mini 4B Instruct (texto puro) normaliza esse JSON para o schema
12
- estrito (raca da lista permitida, cores simples, condicao, frase de matching).
 
 
13
 
14
  Basta a NVIDIA_API_KEY: ela serve tanto para a visao quanto para o Nemotron.
15
- Se nao houver NVIDIA_API_KEY mas houver HF_TOKEN, a visao usa o HF Serverless
16
- (sem a etapa de normalizacao do Nemotron).
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
- PROMPT = (
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\n\n"
50
- "CAT breeds to choose from (use exact spelling):\n"
 
51
  "Domestic Shorthair, Domestic Longhair, Siamese, Persian, Maine Coon, Bengal, "
52
- "British Shorthair, Ragdoll, Scottish Fold, Turkish Angora, Sphynx, Abyssinian\n\n"
53
- "JSON format:\n"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
  "{\"is_animal\": true,"
55
  " \"species\": \"dog or cat\","
56
- " \"breed_estimate\": \"exact name from the list above — SRD/Domestic Shorthair only if truly unidentifiable\","
57
  " \"size\": \"small, medium or large\","
58
- " \"primary_color\": \"main color: caramel, black, white, gray, brown, golden, orange, tabby, mixed\","
59
- " \"secondary_colors\": [\"other visible colors, or empty list\"],"
60
- " \"distinctive_marks\": [\"notable features: e.g. white chest patch, red collar, scar, missing ear — or empty list\"],"
61
  " \"condition\": \"healthy, thin or injured\","
62
- " \"description_text\": \"one concise English sentence describing this specific animal for identity matching\"}"
63
- )
64
-
65
- # Nemotron Mini 4B Instruct normaliza a saida da visao para o schema estrito (texto puro).
66
- NORMALIZE_PROMPT = (
67
- "You normalize raw animal descriptions for a stray-animal database.\n"
68
- "Given the input JSON, return ONLY a cleaned JSON object with the SAME keys.\n"
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 = None # "nim" | "hf"
84
- self.vision_model = None
85
- self.vision_client = None # OpenAI (NIM) ou InferenceClient (HF)
86
  self.nim_text_model = _NIM_TEXT_MODEL
87
- self.nim_client = None # OpenAI -> NVIDIA NIM, p/ normalizacao Nemotron
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 (Llama-3.2-11B-Vision) se nao houver NVIDIA
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.1,
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.1,
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
- # Rejeição explícita: a IA não viu nenhum animal
165
- if result.get("is_animal") is False:
166
  log.info("IA: nenhum animal detectado na imagem.")
167
  return {"is_animal": False, "_ai_success": True}
168
 
169
- # Normalização opcional via Nemotron Mini 4B Instruct (texto puro, NVIDIA NIM)
170
  if self.nim_client is not None:
171
- result = self._normalize_with_nemotron(result)
 
 
 
 
 
 
172
 
173
  result["is_animal"] = True
174
- result["_ai_success"] = True
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 _normalize_with_nemotron(self, result: dict) -> dict:
182
- """Coage o JSON da visao para o schema estrito usando Nemotron Mini 4B Instruct (texto)."""
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": NORMALIZE_PROMPT + payload}],
188
- max_tokens=400,
189
  temperature=0.2,
190
  top_p=0.9,
191
  )
192
  msg = resp.choices[0].message
193
- raw = (msg.content or "") or (getattr(msg, "reasoning_content", "") or "")
194
- cleaned = self._extract_json(raw)
195
- if cleaned:
196
- for k in ("species", "breed_estimate", "size", "primary_color",
197
- "secondary_colors", "distinctive_marks", "condition",
198
- "description_text"):
199
- if k in cleaned and cleaned[k] not in (None, "", []):
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 normalize falhou, usando saida da visao: %s", e)
205
- return result
 
 
 
 
 
 
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)."""