outshine84 commited on
Commit
69bb39e
·
1 Parent(s): 5d06145

fix threashold

Browse files
Files changed (2) hide show
  1. api.py +40 -4
  2. pwa-app/src/App.jsx +2 -11
api.py CHANGED
@@ -223,6 +223,39 @@ def _normalize_image_path(raw_path: str) -> str:
223
  # ---------------------------------------------------------------------------
224
 
225
  FAISS_CONFIDENCE_THRESHOLD = float(os.getenv("FAISS_CONFIDENCE_THRESHOLD", "0.82"))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
226
 
227
 
228
  def _gpt_vision_identify_plant(
@@ -1291,11 +1324,13 @@ async def search_similar(
1291
  gpt_job_status: dict[str, Any] | None = None
1292
  gpt_fallback_attempted = False
1293
  gpt_fallback_reason = "not_attempted"
1294
- if top_planclef_score < FAISS_CONFIDENCE_THRESHOLD and api_key:
 
1295
  gpt_fallback_attempted = True
1296
  logger.info(
1297
- f"FAISS top score {top_planclef_score:.4f} < threshold {FAISS_CONFIDENCE_THRESHOLD} "
1298
- f" activating GPT-4o vision fallback"
 
1299
  )
1300
  fallback_candidates = [species for species, _, _ in results[:12]]
1301
  gpt_species, gpt_fallback_reason = _gpt_vision_identify_plant(
@@ -1314,7 +1349,7 @@ async def search_similar(
1314
  results = results[:k]
1315
  else:
1316
  logger.info(f"GPT fallback attempted but no species accepted: {gpt_fallback_reason}")
1317
- elif top_planclef_score < FAISS_CONFIDENCE_THRESHOLD:
1318
  gpt_fallback_reason = "OPENAI_API_KEY missing"
1319
 
1320
  except RuntimeError as e:
@@ -1351,6 +1386,7 @@ async def search_similar(
1351
  "gpt_fallback_attempted": gpt_fallback_attempted if 'gpt_fallback_attempted' in dir() else False,
1352
  "gpt_fallback_used": gpt_species is not None if 'gpt_species' in dir() else False,
1353
  "gpt_fallback_reason": gpt_fallback_reason if 'gpt_fallback_reason' in dir() else "not_attempted",
 
1354
  "gpt_job_status": gpt_job_status if 'gpt_job_status' in dir() else None,
1355
  "species_found": [species for species, _, _ in results],
1356
  "scores": [float(score) for _, score, _ in results],
 
223
  # ---------------------------------------------------------------------------
224
 
225
  FAISS_CONFIDENCE_THRESHOLD = float(os.getenv("FAISS_CONFIDENCE_THRESHOLD", "0.82"))
226
+ FAISS_AMBIGUITY_MARGIN = float(os.getenv("FAISS_AMBIGUITY_MARGIN", "0.015"))
227
+ RRF_AMBIGUITY_MARGIN = float(os.getenv("RRF_AMBIGUITY_MARGIN", "0.0015"))
228
+ FORCE_OPENAI_FALLBACK = os.getenv("FORCE_OPENAI_FALLBACK", "0").strip().lower() in {
229
+ "1", "true", "yes", "on"
230
+ }
231
+
232
+
233
+ def _should_trigger_gpt_fallback(top_score: float, results: list[tuple[str, float, list]]) -> tuple[bool, str]:
234
+ """Decide whether GPT vision fallback should run.
235
+
236
+ Triggers on low FAISS confidence, explicit force flag, or very ambiguous top-vs-second gap.
237
+ """
238
+ if FORCE_OPENAI_FALLBACK:
239
+ return True, "forced_by_env"
240
+
241
+ if top_score < FAISS_CONFIDENCE_THRESHOLD:
242
+ return True, "low_top_score"
243
+
244
+ if len(results) < 2:
245
+ return False, "single_result"
246
+
247
+ top_result_score = float(results[0][1])
248
+ second_result_score = float(results[1][1])
249
+ gap = max(0.0, top_result_score - second_result_score)
250
+ rrf_like = top_result_score <= 0.1 and second_result_score <= 0.1
251
+
252
+ if rrf_like and gap < RRF_AMBIGUITY_MARGIN:
253
+ return True, "ambiguous_rrf_gap"
254
+
255
+ if (not rrf_like) and gap < FAISS_AMBIGUITY_MARGIN:
256
+ return True, "ambiguous_similarity_gap"
257
+
258
+ return False, "high_confidence"
259
 
260
 
261
  def _gpt_vision_identify_plant(
 
1324
  gpt_job_status: dict[str, Any] | None = None
1325
  gpt_fallback_attempted = False
1326
  gpt_fallback_reason = "not_attempted"
1327
+ should_trigger_gpt, gpt_trigger_basis = _should_trigger_gpt_fallback(top_planclef_score, results)
1328
+ if should_trigger_gpt and api_key:
1329
  gpt_fallback_attempted = True
1330
  logger.info(
1331
+ "Activating GPT-4o vision fallback: "
1332
+ f"basis={gpt_trigger_basis}, top_planclef_score={top_planclef_score:.4f}, "
1333
+ f"threshold={FAISS_CONFIDENCE_THRESHOLD}"
1334
  )
1335
  fallback_candidates = [species for species, _, _ in results[:12]]
1336
  gpt_species, gpt_fallback_reason = _gpt_vision_identify_plant(
 
1349
  results = results[:k]
1350
  else:
1351
  logger.info(f"GPT fallback attempted but no species accepted: {gpt_fallback_reason}")
1352
+ elif should_trigger_gpt:
1353
  gpt_fallback_reason = "OPENAI_API_KEY missing"
1354
 
1355
  except RuntimeError as e:
 
1386
  "gpt_fallback_attempted": gpt_fallback_attempted if 'gpt_fallback_attempted' in dir() else False,
1387
  "gpt_fallback_used": gpt_species is not None if 'gpt_species' in dir() else False,
1388
  "gpt_fallback_reason": gpt_fallback_reason if 'gpt_fallback_reason' in dir() else "not_attempted",
1389
+ "gpt_trigger_basis": gpt_trigger_basis if 'gpt_trigger_basis' in dir() else "not_evaluated",
1390
  "gpt_job_status": gpt_job_status if 'gpt_job_status' in dir() else None,
1391
  "species_found": [species for species, _, _ in results],
1392
  "scores": [float(score) for _, score, _ in results],
pwa-app/src/App.jsx CHANGED
@@ -87,19 +87,10 @@ function normalizeSearchResults(rawResults) {
87
  const scores = sorted.map((item) => Number(item?.score ?? 0));
88
  const maxScore = Math.max(...scores);
89
 
90
- // RRF scores are typically very small (around 0.01-0.03), so scale them for UI display.
91
- const isRrfLike = maxScore <= 0.1;
92
-
93
  return sorted.map((item) => {
94
  const score = Number(item?.score ?? 0);
95
- let displayScore = 0;
96
-
97
- if (isRrfLike) {
98
- // Keep relative proportions between RRF scores (no min-max stretching).
99
- displayScore = maxScore > 0 ? score / maxScore : 0;
100
- } else {
101
- displayScore = Math.max(0, Math.min(1, score));
102
- }
103
 
104
  return {
105
  ...item,
 
87
  const scores = sorted.map((item) => Number(item?.score ?? 0));
88
  const maxScore = Math.max(...scores);
89
 
 
 
 
90
  return sorted.map((item) => {
91
  const score = Number(item?.score ?? 0);
92
+ // Keep bars readable by always scaling against the best result in the current list.
93
+ const displayScore = maxScore > 0 ? Math.max(0, Math.min(1, score / maxScore)) : 0;
 
 
 
 
 
 
94
 
95
  return {
96
  ...item,