¿Te resulta más fácil leer con fondo crema, un tono de color, más contraste o un texto más grande? Elige tus colores y tu texto una sola vez, para todas las páginas.

Lección gratuita

Búsqueda híbrida con BM25 y vectores

Después de esta lección: Puedo combinar la búsqueda por palabras y la búsqueda por significado en una sola lista ordenada y decir cuál gana en cada caso.

Lección de 65 min5 comandosLaboratorio de agente de programación con IA: usa opencode para explicar y corregir código, y comprueba su trabajo

Dos búsquedas, dos puntos ciegos

La búsqueda híbrida lanza una búsqueda por palabras y otra por significado, y fusiona las dos listas ordenadas. Importa porque cada una pierde lo que la otra encuentra.

Alguien escribe «olvidé mi usuario». Ninguna página de ayuda contiene «usuario», así que las palabras no encuentran nada. Otra persona escribe «pedido 4417». La búsqueda por significado no sabe qué es 4417.

Al final de esta lección

Nota

Puedo combinar la búsqueda por palabras y la búsqueda por significado en una sola lista ordenada y decir cuál gana en cada caso.

Palabras clave

PalabraQué significa
BM25una puntuación de palabras que premia las raras y las páginas cortas
Frecuencia del términocuántas veces aparece una palabra en un documento
Frecuencia inversaun peso alto para las palabras que pocos documentos contienen
Rangoel puesto de un resultado en una lista, contado desde 1
Fusión por rango recíprocounir listas sumando 1 entre k más el rango
Búsqueda léxicauna búsqueda que compara las palabras mismas

Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.

BM25 puntúa las palabras mismas

Una búsqueda por palabras cuenta las de la consulta en cada documento. BM25 mejora ese simple recuento de tres maneras. Una palabra pesa más cuando pocos documentos la tienen. Repetirla ayuda cada vez menos. Un documento corto gana a uno largo con el mismo recuento.

El resultado es exacto: una palabra ausente no suma nada. Es su fuerza y su punto ciego.

La búsqueda por significado compara vectores

En el módulo de Generación aumentada con recuperación convertiste el texto en vectores y comparaste su dirección. Aquí cada página de ayuda tiene tres números inventados: cuánto trata de cuenta, de envío y de dinero.

La consulta recibe números propios, y ganan las páginas más cercanas. «Olvidé mi usuario» trata sobre todo de cuenta, así que las páginas de cuenta salen primero, sin ninguna palabra en común. Un sistema real saca sus vectores de un modelo de embeddings, con cientos de números.

Fusiona los rangos, nunca las puntuaciones

Las puntuaciones BM25 van de 0 a diez o más. Las del coseno se quedan entre 0 y 1. Sumarlas dejaría que una lista ahogara a la otra.

La fusión por rango recíproco ignora las puntuaciones y usa los rangos. Un elemento recibe 1 / (60 + rango) de cada lista que lo contiene, y los puntos se suman. Un elemento que gusta a las dos listas gana a uno que gusta a una sola.

Ejemplo resuelto: pedido 4417

PáginaRango por palabrasRango por significadoPuntos fusionados
d4 seguir un paquete130,0323
d3 reembolsoninguno10,0164
d5 pagar con tarjetaninguno20,0161

Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.

Un error frecuente

Advertencia

Mucha gente cree que la búsqueda híbrida consiste en sumar las dos puntuaciones. Las escalas difieren, así que una lista gana siempre. Suma mejor los rangos, con 1 / (k + rango), y las dos listas pesan lo mismo.

Escribe la búsqueda híbrida, parte 1

Comando
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const docs = [' '  { id: \"d1\", text: \"restablecer tu contraseña desde la página de acceso\", v: [9, 0, 1] },' '  { id: \"d2\", text: \"cambiar el correo de tu cuenta\", v: [8, 1, 0] },' '  { id: \"d3\", text: \"política de reembolso de paquetes devueltos\", v: [1, 5, 8] },' '  { id: \"d4\", text: \"seguir un paquete con el número de pedido\", v: [0, 9, 1] },' '  { id: \"d5\", text: \"pagar con tarjeta o transferencia\", v: [1, 1, 9] },' '  { id: \"d6\", text: \"borrar tu cuenta y tus datos\", v: [7, 0, 2] },' '];' 'const K1 = 1.5, B = 0.75;' 'function words(t) { return t.toLowerCase().split(\" \"); }' 'const avg = docs.reduce(function (s, d) { return s + words(d.text).length; }, 0) / docs.length;' > hybrid1.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 la búsqueda híbrida, parte 2

Comando
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function bm25(query, doc) {' '  let score = 0;' '  words(query).forEach(function (w) {' '    const n = docs.filter(function (d) { return words(d.text).includes(w); }).length;' '    if (n === 0) return;' '    const idf = Math.log(1 + (docs.length - n + 0.5) / (n + 0.5));' '    const f = words(doc.text).filter(function (x) { return x === w; }).length;' '    const len = words(doc.text).length;' '    score += idf * f * (K1 + 1) / (f + K1 * (1 - B + B * len / avg));' '  });' '  return score;' '}' >> hybrid1.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 la búsqueda híbrida, parte 3

Comando
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function cosine(a, b) {' '  let dot = 0, sa = 0, sb = 0;' '  for (let i = 0; i < a.length; i++) { dot += a[i] * b[i]; sa += a[i] * a[i]; sb += b[i] * b[i]; }' '  return dot / Math.sqrt(sa * sb);' '}' 'function ranked(scores) {' '  return docs.map(function (d, i) { return { id: d.id, s: scores[i] }; })' '    .filter(function (x) { return x.s > 0.01; })' '    .sort(function (x, y) { return y.s - x.s; })' '    .map(function (x) { return x.id; });' '}' 'function fuse(lists) {' '  const total = {};' '  lists.forEach(function (list) {' '    list.forEach(function (id, at) { total[id] = (total[id] || 0) + 1 / (60 + at + 1); });' '  });' '  return Object.keys(total).sort(function (x, y) { return total[y] - total[x]; });' '}' >> hybrid1.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 la búsqueda híbrida, parte 4

Comando
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const queries = [' '  { text: \"olvidé mi usuario\", v: [9, 0, 0] },' '  { text: \"pedido 4417\", v: [2, 3, 3] },' '];' 'queries.forEach(function (q) {' '  const a = ranked(docs.map(function (d) { return bm25(q.text, d); }));' '  const b = ranked(docs.map(function (d) { return cosine(q.v, d.v); })).slice(0, 3);' '  console.log(q.text);' '  console.log(\"  palabras:\", a.join(\" \") || \"nada\");' '  console.log(\"  significado:\", b.join(\" \"));' '  console.log(\"  fusión:\", fuse([a, b]).join(\" \"));' '});' >> hybrid1.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 las listas

Comando
docker exec -w /home/node/project agent node hybrid1.js

Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.

Esto es un sustituto

El Run no llama a ningún modelo. Los vectores son tres números inventados y seis páginas hacen de índice. Un sistema real guarda millones de páginas y saca sus vectores de un modelo de embeddings.

La forma es la misma: dos listas ordenadas y luego una lista fusionada.

Lo que imprime el Run

ConsultaPalabrasSignificadoFusión
olvidé mi usuarionadad1 d2 d6d1 d2 d6
pedido 4417d4d3 d5 d4d4 d3 d5

Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.

Sobre este laboratorio y su código: Laboratorio de agente de programación con IA: usa opencode para explicar y corregir código, y comprueba su trabajo

¿Enseñas a una clase? Asigna este módulo a tus alumnos con guía del profesor, tareas con corrección automática y un laboratorio para cada alumno.