cazyundee commited on
Commit
8141a5d
·
1 Parent(s): 43f97a4

sync: from ael backend/space/

Browse files
Files changed (1) hide show
  1. rerank.py +30 -2
rerank.py CHANGED
@@ -105,6 +105,18 @@ DROP_BELOW = float(os.environ.get("AEL_RERANK_DROP", "0.35"))
105
  # one, not a target: a short page is fine, an empty one is not.
106
  MIN_RESULTS = 1
107
 
 
 
 
 
 
 
 
 
 
 
 
 
108
  # How many results the model is allowed to judge. A 421M cross-encoder on a
109
  # shared CPU does not score eight documents inside an 800ms budget, and the
110
  # first version's answer to that was to throw the whole set away — which is why
@@ -370,8 +382,24 @@ def rerank(query, results, budget_ms=800):
370
  # Highest score first. Unscored results keep their relative position at the
371
  # end rather than being dropped: we have no opinion about them.
372
  scored.sort(key=lambda t: (t[0] is None, -(t[0] if t[0] is not None else 0.0), t[1]))
373
- kept = [r for p, _, r in scored if p is None or p >= DROP_BELOW]
374
- dropped = len(scored) - len(kept)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
375
 
376
  out["scores"] = [
377
  {"title": str(r.get("title") or "")[:80], "score": p}
 
105
  # one, not a target: a short page is fine, an empty one is not.
106
  MIN_RESULTS = 1
107
 
108
+ # …but one result is not a page you can research with. When the threshold would
109
+ # leave fewer than this, the *highest-scoring* results are kept instead of the
110
+ # original set: the floor must not resurrect the junk, so it tops the list back
111
+ # up from the model's own ranking rather than falling back to engine order.
112
+ #
113
+ # This is not the "floor of 3" the module's own docstring warns about. That one
114
+ # fell back to the whole original set, so a set of eight with two worth showing
115
+ # served all eight. Topping up from the ranking cannot do that: the results that
116
+ # come back are still the ones the model rated highest, just including a couple
117
+ # more of them.
118
+ MIN_KEEP = int(os.environ.get("AEL_RERANK_MIN_KEEP", "2"))
119
+
120
  # How many results the model is allowed to judge. A 421M cross-encoder on a
121
  # shared CPU does not score eight documents inside an 800ms budget, and the
122
  # first version's answer to that was to throw the whole set away — which is why
 
382
  # Highest score first. Unscored results keep their relative position at the
383
  # end rather than being dropped: we have no opinion about them.
384
  scored.sort(key=lambda t: (t[0] is None, -(t[0] if t[0] is not None else 0.0), t[1]))
385
+
386
+ judged_rows = [row for row in scored if row[0] is not None]
387
+ below = [row for row in judged_rows if row[0] < DROP_BELOW]
388
+ above = [row for row in judged_rows if row[0] >= DROP_BELOW]
389
+ unscored = [row for row in scored if row[0] is None]
390
+
391
+ if len(above) + len(unscored) < MIN_KEEP and below and above:
392
+ # Top up from the model's own ranking — never from the original order.
393
+ # Guarded on `above` being non-empty: when *nothing* cleared the bar
394
+ # that is not a thin page, it is a model that did not understand the
395
+ # query, and it is reported as starvation below instead of being
396
+ # disguised as two confident results.
397
+ need = MIN_KEEP - (len(above) + len(unscored))
398
+ above = above + below[:need]
399
+ below = below[need:]
400
+
401
+ kept = [r for _, _, r in above] + [r for _, _, r in unscored]
402
+ dropped = len(below)
403
 
404
  out["scores"] = [
405
  {"title": str(r.get("title") or "")[:80], "score": p}