KikoCis commited on
Commit
4946dda
·
verified ·
1 Parent(s): 51ec7b5

Suelo de cola garantizado + referencias (BM25 Robertson 1994, IDF Sparck Jones 1972, RRF Cormack 2009)

Browse files
Files changed (1) hide show
  1. js/acer-core.js +58 -3
js/acer-core.js CHANGED
@@ -88,6 +88,39 @@
88
  * · Degradación limpia: sin función de embeddings, esto es BM25 solo — que ya
89
  * es F1 23,59 frente al 6,07 de la v1. Lo semántico suma, no es requisito.
90
  *
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
91
  * No usa APIs de Node → cargable en el navegador como módulo ES.
92
  */
93
 
@@ -114,7 +147,9 @@ function terms(s) {
114
  }
115
 
116
  /**
117
- * Índice BM25 sobre un conjunto de documentos (aquí: las líneas del historial).
 
 
118
  * La IDF sale del PROPIO corpus → los tokens ubicuos (`the`, `para`, `const`,
119
  * la puntuación de formato) caen a peso ~0 sin que nadie los liste.
120
  */
@@ -207,6 +242,7 @@ const DEFAULTS = {
207
  PIN_QUERY_TERMS: true, // lo que la pregunta nombra no se desaloja jamás
208
  PIN_MAX: 40,
209
  // — presupuesto —
 
210
  HEAD_FRAC: 0.05, // reserva para los PRIMEROS mensajes, sin puntuar
211
  // (solo bajo presión — ver autotune)
212
  RECENT: 6,
@@ -352,13 +388,32 @@ function prepare(history, budgetTok, O) {
352
  // Los turnos recientes se conservan literales, pero SOLO mientras quepan en su
353
  // reserva. Un RECENT fijo puede comerse el presupuesto entero él solo (una
354
  // página de 100 líneas son ~1,5k tokens) y devolver varias veces lo pedido.
 
 
 
 
 
 
 
 
 
 
 
 
355
  const reserve = Math.floor(budgetTok * tuned.recentFrac);
 
356
  const recent = []; let used = 0;
357
  for (let i = history.length - 1, k = 0; i >= 0 && k < O.RECENT; i--, k++) {
358
  const m = clampMsg(history[i], O.MAX_MSG_CHARS);
359
  const t = msgTok(m);
360
- if (used + t > reserve && recent.length >= 1) break;
361
- recent.unshift(m); used += t;
 
 
 
 
 
 
362
  }
363
  let old = history.slice(0, history.length - recent.length);
364
  if (!old.length || used >= budgetTok) return { done: true, recent, old, used, head: [] };
 
88
  * · Degradación limpia: sin función de embeddings, esto es BM25 solo — que ya
89
  * es F1 23,59 frente al 6,07 de la v1. Lo semántico suma, no es requisito.
90
  *
91
+ *
92
+ * ─────────────────────────────────────────────────────────────────────────────
93
+ * REFERENCIAS
94
+ *
95
+ * BM25 (Okapi BM25) — Robertson, Walker, Jones, Hancock-Beaulieu & Gatford,
96
+ * «Okapi at TREC-3», TREC-3, 1994. Formulacion moderna y justificacion de
97
+ * k1 / b: Robertson & Zaragoza, «The Probabilistic Relevance Framework:
98
+ * BM25 and Beyond», Foundations and Trends in IR 3(4), 2009.
99
+ * https://doi.org/10.1561/1500000019
100
+ *
101
+ * IDF — Sparck Jones, «A statistical interpretation of term specificity and
102
+ * its application in retrieval», Journal of Documentation 28(1), 1972.
103
+ * El origen de la idea que sostiene todo esto: lo raro informa.
104
+ *
105
+ * RRF (fusion por rangos reciprocos) — Cormack, Clarke & Buettcher,
106
+ * «Reciprocal Rank Fusion outperforms Condorcet and individual Rank
107
+ * Learning Methods», SIGIR 2009. https://doi.org/10.1145/1571941.1572114
108
+ *
109
+ * MMR (relevancia marginal maxima) — Carbonell & Goldstein, «The use of MMR,
110
+ * diversity-based reranking for reordering documents and producing
111
+ * summaries», SIGIR 1998. https://doi.org/10.1145/290941.291025
112
+ *
113
+ * Sumideros de atencion (por que la CABECERA se protege sin puntuar) —
114
+ * Xiao, Tian, Chen, Han & Lewis, «Efficient Streaming Language Models with
115
+ * Attention Sinks», ICLR 2024. https://arxiv.org/abs/2309.17453
116
+ *
117
+ * Contraste con lo que se publica hoy en compresion de KV, donde el
118
+ * solapamiento lexico aparece solo como bandera BINARIA sobre un ranking que
119
+ * sigue siendo de atencion: CodeComp, «Structural KV Cache Compression for
120
+ * Agentic Coding», 2026, §4.4. https://arxiv.org/abs/2604.10235
121
+ * -> nuestro margen medido frente a esa regla binaria: F1 23,59 vs 8,03;
122
+ * y quitar la IDF cuesta -3,25 F1, o sea que la gradacion hace trabajo.
123
+ *
124
  * No usa APIs de Node → cargable en el navegador como módulo ES.
125
  */
126
 
 
147
  }
148
 
149
  /**
150
+ * Índice BM25 (Okapi BM25 — Robertson et al., TREC-3 1994; formulación moderna
151
+ * en Robertson & Zaragoza 2009) sobre un conjunto de documentos: aquí, las
152
+ * líneas del historial.
153
  * La IDF sale del PROPIO corpus → los tokens ubicuos (`the`, `para`, `const`,
154
  * la puntuación de formato) caen a peso ~0 sin que nadie los liste.
155
  */
 
242
  PIN_QUERY_TERMS: true, // lo que la pregunta nombra no se desaloja jamás
243
  PIN_MAX: 40,
244
  // — presupuesto —
245
+ TAIL_MIN_FRAC: 0.10, // SUELO garantizado para los ultimos turnos
246
  HEAD_FRAC: 0.05, // reserva para los PRIMEROS mensajes, sin puntuar
247
  // (solo bajo presión — ver autotune)
248
  RECENT: 6,
 
388
  // Los turnos recientes se conservan literales, pero SOLO mientras quepan en su
389
  // reserva. Un RECENT fijo puede comerse el presupuesto entero él solo (una
390
  // página de 100 líneas son ~1,5k tokens) y devolver varias veces lo pedido.
391
+ // ── RESERVA DE COLA — es un SUELO, no solo un techo ────────────────────────
392
+ // La version anterior paraba en el primer mensaje que no cabia. Eso convierte
393
+ // la reserva en un tope y no en una garantia: medido, con presupuesto 3.000 la
394
+ // cola se quedaba con el 3-5 % en vez del ~38 % reservado, porque UN resultado
395
+ // de herramienta grande en la penultima posicion bloqueaba todo lo anterior.
396
+ // Los ultimos turnos son lo que el modelo necesita si o si para saber donde
397
+ // esta, y eso no puede depender de que el turno de antes fuera voluminoso.
398
+ //
399
+ // Ahora, mientras no se alcance el suelo, el mensaje que no cabe se TRUNCA por
400
+ // el medio (cabeza y cola, que es lo que importa de un resultado) en vez de
401
+ // descartarse entero. Por encima del suelo se vuelve al comportamiento de
402
+ // siempre: se para y el resto compite por relevancia.
403
  const reserve = Math.floor(budgetTok * tuned.recentFrac);
404
+ const floorTok = Math.floor(budgetTok * O.TAIL_MIN_FRAC);
405
  const recent = []; let used = 0;
406
  for (let i = history.length - 1, k = 0; i >= 0 && k < O.RECENT; i--, k++) {
407
  const m = clampMsg(history[i], O.MAX_MSG_CHARS);
408
  const t = msgTok(m);
409
+ if (used + t <= reserve) { recent.unshift(m); used += t; continue; }
410
+ if (used >= floorTok || recent.length >= 1 && used + t > reserve && used >= floorTok) break;
411
+ // aun por debajo del suelo: cabe recortado, no se tira
412
+ const room = Math.max(40, Math.min(reserve, floorTok) - used - 4);
413
+ if (room < 40) break;
414
+ recent.unshift({ ...m, content: truncateToTokens(m.content, room) });
415
+ used += room + 4;
416
+ if (used >= floorTok) break;
417
  }
418
  let old = history.slice(0, history.length - recent.length);
419
  if (!old.length || used >= budgetTok) return { done: true, recent, old, used, head: [] };