Dec 18th, 2025: [ES] Búsqueda Vectorial con ES|QL

This post is also available in English.

Búsqueda Vectorial con ES|QL

Hoy descubrimos una de las incorporaciones más interesantes en ES|QL: Soporte nativo para campos dense_vector, y las funciones para buscar en ellos: KNN y las funciones de similitud vectorial. Si tenías curiosidad por la búsqueda vectorial pero la sintaxis del Query DSL te parecía un poco intimidantes, ES|QL está a punto de convertirse en tu mejor aliado.

¿Por qué ES|QL para Búsqueda Vectorial?

ES|QL es el futuro de Elasticsearch - te permite realizar tus consultas como si fueran una serie de pasos en un proceso. Añadir búsqueda vectorial a ES|QL te proporciona un control experto sobre cómo realizas consultas semánticas usando vectores, ajustando el método de búsqueda (ya sea vecinos más cercanos aproximados mediante KNN, o búsqueda exacta mediante funciones de similitud vectorial).

Preparando Nuestro Entorno de Pruebas

Vamos a crear un índice simple con un campo dense_vector para almacenar algunos vectores sobre productos. Lo mantendremos muy pequeño, sólo 3 dimensiones, para poder razonar sobre los vectores con más facilidad.

PUT products-vectors
{
  "mappings": {
    "properties": {
      "name": {
        "type": "text"
      },
      "category": {
        "type": "keyword"
      },
      "embedding": {
        "type": "dense_vector",
        "dims": 3,
        "similarity": "cosine"
      }
    }
  }
}

El parámetro clave es similarity: cosine (cómo medimos la cercanía de los vectores - otras opciones incluyen l2_norm y dot_product).

Ahora añadiremos algunos productos de ejemplo con sus correspondientes vectores (embeddings):

POST products-vectors/_bulk
{"index": {"_id": "1"}}
{"name": "Warm Winter Jacket", "category": "clothing", "embedding": [0.9, 0.1, 0.2]}
{"index": {"_id": "2"}}
{"name": "Summer Beach Shorts", "category": "clothing", "embedding": [0.1, 0.9, 0.3]}
{"index": {"_id": "3"}}
{"name": "Cozy Wool Sweater", "category": "clothing", "embedding": [0.85, 0.15, 0.25]}
{"index": {"_id": "4"}}
{"name": "Running Sneakers", "category": "footwear", "embedding": [0.4, 0.5, 0.8]}
{"index": {"_id": "5"}}
{"name": "Hiking Boots", "category": "footwear", "embedding": [0.6, 0.3, 0.7]}

Puedes usar ES|QL para recuperar tus datos, incluyendo los embeddings vectoriales:

FROM products-vectors

En este ejemplo sencillo, digamos que nuestra primera dimensión representa "abrigado", la segunda "veraniego", y la tercera "actividades al aire libre". Los embeddings reales de modelos como E5 u OpenAI tendrían cientos de dimensiones, pero el principio sigue siendo el mismo.

Búsqueda usando la función KNN

La función KNN de ES|QL realiza búsqueda aproximada de los k vecinos más cercanos en tus vectores:

FROM products-vectors METADATA _score
| WHERE KNN(embedding, [0.88, 0.12, 0.22])
| KEEP name, category, _score
| SORT _score DESC
| LIMIT 3

Entendamos bien esta query:

  • METADATA _score: Necesitamos este metadato para obtener la puntuación de la función KNN

  • KNN(embedding, [0.88, 0.12, 0.22]) : Encuentra los vecinos más cercanos a nuestro vector de consulta (query)

  • El vector de consulta (query) [0.88, 0.12, 0.22] representa algo "abrigado" (primera dimensión alta)

¿El resultado? Nuestros artículos de ropa de abrigo aparecen en la parte superior:

name category _score
Warm Winter Jacket clothing 0.9994338750839233
Cozy Wool Sweater clothing 0.9992702007293701
Hiking Boots footwear 0.9067620635032654

Combinando KNN con Filtros

Uno de los superpoderes de ES|QL es que puedes combinar fácilmente búsqueda vectorial con filtros tradicionales:

FROM products-vectors 
METADATA _score
| WHERE category == "clothing" AND KNN(embedding, [0.88, 0.12, 0.22])
| KEEP name, _score
| SORT _score DESC
| LIMIT 3

Esto aplica el filtrado como un pre-filtro antes de ejecutar la búsqueda KNN. ¡Eficiente y legible!

name category _score
Warm Winter Jacket clothing 0.9994338750839233
Cozy Wool Sweater clothing 0.9992702007293701
Summer Beach Shorts clothing 0.658316433429718

Ajustando con Parámetros Opcionales

La función KNN acepta parámetros con nombre para tener un mayor control:

FROM products-vectors METADATA _score
| WHERE KNN(embedding, [0.1, 0.85, 0.3], {"k": 2, "boost": 1.5, "min_candidates": 50, "rescore_oversample": 3, "similarity": 0.0001})
| KEEP name, _score
| SORT _score DESC

Los parámetros permitidos son:

  • k: Número de vecinos a devolver (implícitamente desde LIMIT)

  • boost: Multiplicador de puntuación (por defecto: 1.0)

  • min_candidates: Mínimo de candidatos a considerar por shard (mayor = más preciso pero más lento)

  • similarity: La similitud mínima para considerar un resultado

  • visit_percentage: El porcentaje de vectores a explorar por shard al hacer búsqueda knn con bbq_disk

  • rescore_oversample: Aplica el factor de sobremuestreo especificado a k en la búsqueda

Búsqueda usando Funciones de Similitud Vectorial

KNN es genial para buscar vectores a gran escala porque es aproximada - lo que significa que intentará encontrar resultados suficientemente buenos, pero sin considerar todos los posible resultados. Esto es lo que permite que KNN sea eficiente, ya que no tiene que considerar todos los documentos posibles y compararlos con la consulta uno a uno.

En caso de que realmente queramos examinar todos los resultados (porque ya hemos filtrado nuestros resultados, o porque no tenemos muchos documentos de partida), podemos usar funciones de similitud vectorial para calcular la similitud entre nuestra consulta y cada elemento:

FROM products-vectors
| EVAL my_score = V_COSINE(embedding, [0.1, 0.85, 0.3]) + 1.0
| KEEP name, my_score
| SORT my_score DESC

Las funciones de similitud vectorial te permiten realizar un score personalizado para vectores, y tener un cálculo exacto de los vecinos más cercanos.

Bonus: Función TEXT_EMBEDDING

Si tienes un endpoint de inferencia configurado, puedes generar embeddings directamente utilizando:

FROM products-vectors 
METADATA _score
| WHERE KNN(embedding, TEXT_EMBEDDING("cozy winter wear", "my-embedding-model"))
| KEEP name, _score
| SORT _score DESC
| LIMIT 3

¡No necesitas precalcular los vectores de tu consulta—ES|QL lo hace por tí!

Conclusión

Las capacidades de búsqueda vectorial de ES|QL proporcionan un control total para la búsqueda semántica. Ajustar cómo obtienes los vecinos más cercanos para una consulta, o calcular un score personalizado, es posible gracias al soporte de ES|QL para el tipo de campo dense_vector, la función de búsqueda KNN, y las funciones de similitud vectorial.

Ya estés construyendo un sistema de recomendaciones, un motor de búsqueda semántica, o simplemente explorando tus datos vectoriales, la combinación de KNN, filtros y agregaciones convierte a ES|QL en un compañero ideal.