Por qué tu app para tomar notas no encuentra lo que escribiste

Product

Hay un momento concreto en el que una app para tomar notas deja de merecer la pena. No es cuando te quedas sin almacenamiento. Es la primera vez que sabes con certeza que apuntaste algo, lo buscas y la app te dice que no hay resultados.

Sí lo escribiste. La nota está ahí. Solo que buscaste "lo de los niveles de precios" y la nota dice "paquetes en tres franjas, con el SKU intermedio como ancla". Ni una palabra en común.

Este fallo empeora a medida que tu archivo gana valor. Con cien notas, recuerdas las palabras. Con diez mil, recuerdas la idea y nada más. La búsqueda por palabras clave es más débil justo cuando más la necesitas.

Notez ejecuta dos búsquedas por cada consulta y une los resultados. Este artículo cuenta cómo funciona eso, incluidas las partes más rudimentarias de lo que quizá supones.

Rama uno: palabras clave

La rama de palabras clave es una simple coincidencia de subcadena en SQL. Tu consulta se convierte en %query% y se compara, sin distinguir mayúsculas de minúsculas, con:

  • el título de la nota

  • el texto plano extraído de la nota (el documento de texto enriquecido convertido en texto al guardar)

  • los valores de las propiedades personalizadas: los nombres de las opciones de selección y selección múltiple, y el texto de las propiedades de tipo texto

Esto último importa más de lo que parece. Si tienes una propiedad Client con la opción Acme, buscar acme encuentra todas las notas que tienen esa opción, aunque la palabra no aparezca nunca en el cuerpo.

Esta rama devuelve hasta 50 resultados, ordenados por fecha de actualización, de la más reciente a la más antigua.

En qué es buena esta rama: en encontrar lo exacto. Identificadores, códigos de error, nombres, una frase que recuerdas al pie de la letra. Si escribes NOTE_MODIFIED, quieres la nota que contiene esa cadena literal, la primera y al instante.

En qué es mala: en todo lo demás. Una coincidencia de subcadena no es aproximada: una errata en tu consulta no encuentra nada. Los sinónimos no encuentran nada. Los conceptos no encuentran nada.

Rama dos: semántica

La rama semántica convierte tu consulta en un vector y busca contenido de notas que quede cerca de él en ese espacio.

En concreto: la consulta se convierte en un embedding de 1536 dimensiones con el modelo text-embedding-3-small de OpenAI, y Postgres (con pgvector) ordena los fragmentos de notas guardados por su distancia coseno a ella. Devuelve hasta 20 notas.

En qué es buena esta rama: en el caso que rompió la búsqueda por palabras clave. "Niveles de precios" y "paquetes en tres franjas" quedan cerca en el espacio de embeddings porque el modelo se entrenó con suficiente texto como para saber que son el mismo tema.

En qué es mala: en la precisión. Los vectores no conocen el concepto de "exactamente esta cadena". Busca un UUID y la rama semántica te dará, sin dudarlo, algo temáticamente cercano e inútil.

Ninguna rama basta por sí sola. Ese es todo el argumento para ejecutar las dos.

Qué se convierte en vectores

No puedes convertir una nota entera en un solo vector y esperar buenos resultados. Una nota de reunión de 4000 palabras promediada en un único punto del espacio no queda cerca de nada en particular: es una mancha que se extiende por todos los temas que toca.

Por eso las notas se dividen en fragmentos antes de vectorizarlas, y la división sigue la estructura del propio documento:

  1. El título se antepone al contenido antes que nada, así que sus palabras forman parte de lo que se indexa en lugar de ser un campo aparte en el que nadie busca.

  2. División por encabezados. Cada encabezado abre una sección nueva. Un encabezado sin texto debajo se indexa igualmente por sí solo, porque un encabezado tiene significado aunque la sección esté vacía.

  3. División de las secciones largas por párrafos. Todo lo que pasa de 2000 caracteres se corta en los límites entre párrafos, no a mitad de frase.

  4. Los fragmentos diminutos se unen a sus vecinos. Todo lo que tiene menos de 100 caracteres se fusiona con el fragmento contiguo en lugar de descartarse.

Esa última regla existe por un fallo. Una versión anterior descartaba los fragmentos demasiado pequeños, así que de una nota hecha de varias secciones cortas (justo el aspecto de una nota de reunión con viñetas) no quedaba casi nada. Técnicamente, la nota estaba indexada. También era imposible de encontrar.

Cada fragmento guardado conserva su encabezado y su posición en el documento, así que un resultado semántico puede decirte qué parte de una nota larga coincidió, no solo que la nota coincidió en algún sitio.

Como una nota larga produce muchos fragmentos, la consulta pide el triple de resultados de los que necesita y luego se queda solo con el fragmento mejor puntuado de cada nota. Sin eso, una sola nota extensa ocuparía todos los puestos de tus resultados.

Los dos umbrales y por qué son distintos

La distancia coseno va de 0 (misma dirección) hacia arriba. Un resultado solo cuenta como coincidencia por debajo de un límite de distancia, y Notez usa dos límites distintos para dos tareas distintas.

La búsqueda usa 0.62. Estricto.

Notas similares usa 0.8. Permisivo.

La diferencia no es un descuido. Notas similares es una ayuda para explorar al pie de una nota que ya estás leyendo; ahí una coincidencia floja solo te cuesta un vistazo. Los resultados de búsqueda, en cambio, afirman que esas notas responden a tu consulta.

Con 0.8, la búsqueda se volvió inútil de una forma que tardó en notarse. Una consulta cualquiera coincidía con casi cualquier nota corta (el texto corto produce embeddings que quedan cerca del centro de todo), así que un espacio de trabajo con notas nunca podía mostrar "No se encontraron notas". Toda búsqueda devolvía algo. Parte era basura, y nada distinguía un caso del otro.

Una búsqueda que nunca puede decir "aquí no hay nada" no es una búsqueda. Es un generador de notas aleatorias con pasos extra.

Cómo se unen las dos ramas

Esta es la parte más simple de lo que imaginas: primero van los resultados por palabras clave, en orden. Después se añaden los resultados semánticos que no estén ya en esa lista.

Eso es todo. No hay reordenación con cross-encoder, ni fusión de puntuaciones, ni mezcla por rango recíproco. Las dos ramas se ejecutan en paralelo, se eliminan los duplicados por ID de nota y la rama de palabras clave gana los empates porque va primero.

Lo digo claramente porque tiene una consecuencia que notarás: una coincidencia semántica perfecta puede aparecer por debajo de una coincidencia mediocre por palabras clave. Si tu consulta resulta ser una subcadena común, lo primero de tus resultados serán esas coincidencias de subcadena.

Una clasificación con fusión de verdad sería una mejora real, y no está hecha. Lo que está hecho es honesto sobre lo que es: una unión, no una clasificación.

Los filtros se aplican a las dos ramas

Lo que hayas usado para filtrar (etiquetas, valores de propiedades personalizadas, la papelera) limita las dos búsquedas, no solo la de palabras clave. La rama semántica incluye esas condiciones en la misma consulta que la distancia vectorial, así que filtrar nunca te hace perder en silencio la mitad semántica de tus resultados.

Qué pasa cuando falla la rama semántica

Vectorizar una consulta supone una llamada de red a un modelo externo. Las llamadas de red fallan.

Cuando la rama semántica lanza una excepción, el error se registra y esa rama devuelve una lista vacía. La búsqueda no falla. Recibes los resultados por palabras clave, justo lo que te habría dado cualquier app de notas convencional, y la página se muestra.

Es deliberado: la mitad sofisticada de una función nunca debería poder tumbar la mitad aburrida. Quedarse en la búsqueda simple por palabras clave es un mal día. Mostrar una página de error porque una base de datos vectorial fue lenta es uno peor.

Notas similares, en breve

Cada nota tiene un panel Notas similares, que funciona de forma algo distinta a la búsqueda: en lugar de vectorizar una consulta, toma el embedding del primer fragmento de la propia nota y busca sus vecinos más cercanos en el mismo espacio de trabajo, excluyéndose a sí misma. Hasta cinco resultados, con el límite más permisivo de 0.8.

Esta es la función que saca a la luz cosas que nunca habrías buscado, porque para buscar tienes que sospechar ya que la conexión existe.

Aquí el límite del espacio de trabajo se garantiza con un join a través de la tabla de notas en lugar de fiarse de las filas de embeddings, que no tienen columna de espacio de trabajo propia. Si eso sale mal, tu espacio de trabajo laboral empieza a recomendarte tus notas personales.

Cómo buscar bien de verdad

Teniendo en cuenta cómo se reparten el trabajo las dos ramas:

  • Para algo que recuerdas con exactitud (un nombre, un mensaje de error, un identificador), escríbelo tal cual. La rama de palabras clave lo encontrará.

  • Para algo que solo recuerdas a grandes rasgos, escribe una frase corta que describa la idea, no las palabras. "Por qué quitamos el reintento de la cola" funciona mejor que intentar adivinar el vocabulario original.

  • No escribas consultas largas. Las consultas muy largas se convierten en un promedio de varios temas y no coinciden bien con nada.

  • Combina la búsqueda con filtros. Limitarte a una etiqueta antes de buscar convierte una consulta amplia en una precisa, y restringe las dos ramas.

  • Si encuentras la nota pero no el pasaje, ábrela y mira Notas similares: ese panel suele ser la forma de encontrar las otras tres notas sobre el tema.

Lo que no está hecho

Para que no lo descubras por las malas:

  • No hay coincidencia aproximada ni tolerante a erratas por palabras clave. Escríbelo mal y la rama de palabras clave no devuelve nada.

  • No hay operadores de búsqueda: ni frases entre comillas, ni AND/OR, ni sintaxis para buscar en un campo concreto.

  • No hay clasificación por relevancia entre las dos ramas, como expliqué arriba.

  • No hay búsquedas guardadas. Los filtros van en la URL y puedes guardarla en marcadores, pero no hay vistas con nombre.

Nada de esto es un secreto, y nada de esto es la razón por la que te cuesta encontrar tu nota. Esa razón casi siempre es la primera de este artículo: buscaste las palabras y apuntaste la idea.

Dos búsquedas unidas resuelven ese problema concreto. No es magia. Es reconocer que la memoria humana de lo que uno mismo escribió es semántica, y una consulta LIKE no lo es.

Sigue leyendo