La página correcta es la cuarta
Reordenar (reranking) es tomar la lista corta que devolvió una búsqueda y ponerla en mejor orden con una mirada más atenta. Importa porque la primera pasada es rápida y tosca, y la página que responde no siempre va arriba.
Alguien pregunta cómo recuperar su dinero. La página de reembolso y dinero es la cuarta, bajo otras tres, la primera sobre pagar con tarjeta. Una segunda mirada la sube al primer puesto.
Al final de esta lección
Nota
Puedo reordenar una lista corta de resultados con una segunda pasada y decir si valió su coste.
Palabras clave
| Palabra | Qué significa |
|---|---|
| Primera pasada | la búsqueda rápida sobre todo el índice |
| Segunda pasada | una mirada más lenta a los primeros resultados |
| Reordenador | lo que vuelve a puntuar la lista corta |
| Candidato | un resultado que la segunda pasada puede reordenar |
| Techo de recuperación | lo mejor posible, fijado por la primera pasada |
| Acierto en k | la parte de preguntas con su fuente entre las k primeras |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
Una pasada ancha y barata, otra estrecha
Mirar cada página de cerca sería demasiado lento. Por eso la búsqueda trabaja en dos pasadas. La primera pasada es barata y ancha: reduce millones de páginas a unas pocas docenas. La segunda pasada es lenta y estrecha: solo lee esas.
Las cifras de abajo son inventadas, pero la forma es real: una mirada atenta solo se puede pagar sobre una lista corta.
Ejemplo resuelto: lo que cuesta cada pasada
| Pasada | Páginas miradas | Tiempo por página | Tiempo total |
|---|---|---|---|
| Primera pasada | 1 000 000 | 0,00002 ms | 20 ms |
| Segunda pasada | 20 | 5 ms | 100 ms |
| Segunda pasada sobre todo | 1 000 000 | 5 ms | 5 000 segundos |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
Un reordenador puede ser una simple regla
Un reordenador es lo que vuelve a puntuar la lista corta. Puede ser un modelo entrenado, un modelo de lenguaje o una regla que escribes tú. La regla del Run da 3 puntos por una palabra de la consulta en el título y 1 por una en el cuerpo.
Un reordenador con modelo de lenguaje lee más de cerca, pero cuesta una llamada, tokens y tiempo en cada consulta. Empieza con una regla y mide.
Un error frecuente
Advertencia
Mucha gente cree que un reordenador puede encontrar páginas que la primera pasada perdió. No puede. Solo reordena lo que recibe, así que la primera pasada fija el techo. Si la respuesta no está entre las 20 primeras, ninguna segunda pasada la mostrará.
Escribe el ayudante del modelo, parte 1
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const base = process.env.AI_URL || \"http://ai:4000\";' '// Los tokens de una respuesta, o null si no trae ninguno.' 'function count(data, name) {' ' return data.usage && typeof data.usage[name] === \"number\" ? data.usage[name] : null;' '}' > model.js"Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Escribe el ayudante del modelo, parte 2
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function ask(messages, sample, max) {' ' try {' ' const res = await fetch(base + \"/v1/chat/completions\", {' ' method: \"POST\",' ' headers: { \"content-type\": \"application/json\", authorization: \"Bearer lab\" },' ' body: JSON.stringify({ model: \"lab\", messages: messages, max_tokens: max || 100 }),' ' signal: AbortSignal.timeout(30000),' ' });' ' if (res.ok === false) throw new Error(\"estado \" + res.status);' ' const d = await res.json();' ' return { text: d.choices[0].message.content, inTokens: count(d, \"prompt_tokens\"), outTokens: count(d, \"completion_tokens\"), recorded: false };' ' } catch (error) {' ' console.log(\"Muestra grabada: ningún modelo respondió (\" + error.message + \").\");' ' return { text: sample, inTokens: null, outTokens: null, recorded: true };' ' }' '}' 'module.exports = { ask };' >> model.js"Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Escribe el reordenador, parte 1
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const { ask } = require(\"./model\");' 'const query = [\"reembolso\", \"dinero\", \"devolución\"];' 'const found = [' ' { id: \"c1\", title: \"Pagar con tarjeta o transferencia\", body: \"tu dinero sale de tu cuenta cuando pagas\" },' ' { id: \"c2\", title: \"Plazos de reembolso\", body: \"tu dinero vuelve en cinco días tras aceptar la devolución\" },' ' { id: \"c3\", title: \"Seguir un paquete con el número de pedido\", body: \"escribe el número para ver dónde está\" },' ' { id: \"c4\", title: \"Reembolso y dinero tras la devolución\", body: \"devuelve un paquete en treinta días y pide el reembolso\" },' ' { id: \"c5\", title: \"Cambiar el correo de la cuenta\", body: \"abre tu cuenta y edita el correo\" },' '];' > rerank1.js"Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Escribe el reordenador, parte 2
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function count(text) {' ' const w = text.toLowerCase().split(\" \");' ' return query.filter(function (q) { return w.includes(q); }).length;' '}' 'function points(c) { return 3 * count(c.title) + count(c.body); }' 'function ids(list) { return list.map(function (c) { return c.id; }).join(\" \"); }' >> rerank1.js"Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Escribe el reordenador, parte 3
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function main() {' ' console.log(\"primera pasada:\", ids(found));' ' const second = found.slice().sort(function (a, b) { return points(b) - points(a); });' ' console.log(\"segunda pasada:\", second.map(function (c) { return c.id + \"=\" + points(c); }).join(\" \"));' ' console.log(\"pasajes leídos por la segunda pasada:\", found.length);' ' const top = second.slice(0, 3);' ' const lines = top.map(function (c, i) { return (i + 1) + \". \" + c.title; });' ' const prompt = \"¿Qué pasaje responde a: cómo recupero mi dinero? Responde solo con un número. \" + lines.join(\" \");' ' const reply = await ask([{ role: \"user\", content: prompt }], \"1\", 5);' ' const pick = Number(reply.text.trim().charAt(0));' ' console.log(\"el modelo elige:\", pick >= 1 && pick <= 3 ? top[pick - 1].id : \"sin elección clara\");' '}' 'main();' >> rerank1.js"Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Ejecuta y compara los órdenes
docker exec -w /home/node/project agent node rerank1.jsSe muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Cuándo no vale la pena una segunda pasada
Mide el acierto en los primeros puestos antes y después. Si apenas se mueve, prescinde de la pasada. Hazlo también cuando la primera pasada ya acierta, cuando la lista es corta o cuando el retraso rompería tu presupuesto de latencia.
El Run es un sustituto: cinco candidatos grabados y una regla. Un sistema real usa un índice y un modelo reales.
Una salida posible
| Línea | Qué muestra |
|---|---|
| primera pasada: c1 c2 c3 c4 c5 | El orden grabado de la primera pasada |
| segunda pasada: c4=10 c2=5 c1=1 ... | La regla sube c4 al primer puesto |
| pasajes leídos por la segunda pasada: 5 | El coste, contado en pasajes |
| el modelo elige: c4 | Una elección real, o una muestra grabada |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.