Integración de bases de datos vectoriales en WordPress para búsqueda semántica
Imagina un sitio WordPress que no solo busca palabras exactas, sino que entiende el significado de lo que pregunta el usuario. Un usuario escribe “¿cómo mejorar la velocidad del sitio?” y el sistema devuelve artículos sobre optimización de caché, compresión de imágenes y CDN, aunque ninguna de esas palabras clave esté en la consulta. Esto ya no es ciencia ficción: es búsqueda semántica WordPress potenciada por bases de datos vectoriales.
En este artículo vamos a desgranar la arquitectura, las herramientas y el código necesario para integrar Pinecone, Weaviate u otros vectores en tu flujo de WordPress. No solo verás la teoría, sino scripts reales para generar embeddings WordPress, almacenarlos y consultarlos.
¿Por qué una base de datos vectorial en WordPress?
WordPress usa MySQL/MariaDB para almacenar posts, metadatos y taxonomías. Su motor de búsqueda (WP_Query, LIKE, FULLTEXT) es puramente léxico: busca coincidencias de caracteres. No entiende sinónimos, contexto ni intención.
Una base de datos vectorial (Vector DB) convierte cada contenido en un vector numérico (embedding) que representa su significado en un espacio multidimensional. Cuando un usuario hace una consulta, esa consulta se convierte también en un vector y se buscan los vectores más cercanos (similitud coseno o distancia euclídea). El resultado es una búsqueda semántica que devuelve contenido relevante aunque no comparta palabras exactas.
[INFO] No estás reemplazando la base de datos de WordPress, sino complementándola. Los vectores se almacenan externamente y solo se consultan para obtener IDs de posts, que luego se recuperan con WP_Query.
Componentes clave de la integración
Para montar este sistema necesitas cuatro piezas:
- Modelo de embeddings: Un modelo de lenguaje que convierta texto en vectores. Ejemplos:
text-embedding-ada-002(OpenAI),all-MiniLM-L6-v2(Sentence Transformers), o modelos locales conwordpress-embeddings. - Base de datos vectorial: Almacén especializado en vectores. Opciones populares: Pinecone (SaaS, muy rápido), Weaviate (open source, autoalojado o cloud), Qdrant, Milvus o incluso
pgvectoren PostgreSQL. - Conector WordPress: Un plugin personalizado o código en
functions.phpque orqueste la generación de embeddings, la sincronización y las consultas. - Frontend: Un formulario de búsqueda que envíe la consulta a un endpoint REST personalizado y renderice los resultados.
Paso 1: Elegir y configurar la base de datos vectorial
Opción A: Pinecone (SaaS, fácil de empezar)
Pinecone es un servicio gestionado. Creas un índice, especificas la dimensión de los vectores (por ejemplo, 1536 para Ada-002) y la métrica de similitud (coseno). Te dan una API Key y un endpoint.
# Ejemplo de creación de índice con la API de Pinecone (CLI o curl)
curl -X POST https://api.pinecone.io/indexes \
-H "Api-Key: tu-api-key" \
-H "Content-Type: application/json" \
-d '{
"name": "wordpress-content",
"dimension": 1536,
"metric": "cosine"
}'
[TIP] Usa dimensiones que coincidan con el modelo de embeddings que elijas. Si usas text-embedding-3-small (OpenAI), son 1536 dimensiones. Si usas all-MiniLM-L6-v2, son 384.
Opción B: Weaviate (open source, control total)
Weaviate puede ejecutarse con Docker. Tiene la ventaja de que integra módulos de vectorización (por ejemplo, OpenAI, Cohere) directamente, por lo que no necesitas generar embeddings por separado.
# docker-compose.yml para Weaviate con módulo OpenAI
version: '3.4'
services:
weaviate:
image: semitechnologies/weaviate:latest
ports:
- "8080:8080"
environment:
AUTHENTICATION_ANONYMOUS_ACCESS_ENABLED: 'true'
DEFAULT_VECTORIZER_MODULE: 'text2vec-openai'
OPENAI_APIKEY: 'tu-api-key'
CLUSTER_HOSTNAME: 'node1'
Luego defines un schema con una clase WordPressPost y un campo content de tipo text. Weaviate vectoriza automáticamente.
Paso 2: Generar embeddings desde WordPress
Cada vez que se guarda o actualiza un post, necesitas generar su embedding y almacenarlo en la base vectorial. El hook ideal es save_post.
Código básico para generar embeddings con OpenAI
Este fragmento va en functions.php de tu tema o en un plugin personalizado.
add_action( 'save_post', 'wpe_generate_and_store_embedding', 10, 3 );
function wpe_generate_and_store_embedding( $post_id, $post, $update ) {
// Evitar autoguardados y revisiones
if ( wp_is_post_revision( $post_id ) || wp_is_post_autosave( $post_id ) ) {
return;
}
// Solo posts públicos (puedes ajustar a 'publish')
if ( $post->post_status !== 'publish' ) {
return;
}
// Obtener el contenido limpio (título + extracto + contenido)
$text = $post->post_title . ' ' . $post->post_excerpt . ' ' . wp_strip_all_tags( $post->post_content );
$text = substr( $text, 0, 8000 ); // Límite de tokens de OpenAI
// Llamar a la API de OpenAI para obtener el embedding
$response = wp_remote_post( 'https://api.openai.com/v1/embeddings', [
'headers' => [
'Authorization' => 'Bearer ' . OPENAI_API_KEY,
'Content-Type' => 'application/json',
],
'body' => json_encode( [
'input' => $text,
'model' => 'text-embedding-ada-002',
] ),
'timeout' => 30,
] );
if ( is_wp_error( $response ) ) {
error_log( 'Error OpenAI: ' . $response->get_error_message() );
return;
}
$body = json_decode( wp_remote_retrieve_body( $response ), true );
$embedding = $body['data'][0]['embedding'];
// Almacenar en Pinecone (o Weaviate)
wpe_store_in_pinecone( $post_id, $embedding );
}
[WARNING] Guardar la API Key de OpenAI en texto plano en functions.php no es seguro. Usa constantes definidas en wp-config.php o un gestor de secretos como Doppler.
Paso 3: Almacenar vectores en Pinecone desde PHP
Necesitas una función que envíe el vector y el ID del post a Pinecone.
function wpe_store_in_pinecone( $post_id, $embedding ) {
$pinecone_url = 'https://wordpress-content-xxxx.svc.pinecone.io/vectors/upsert';
$api_key = PINECONE_API_KEY;
$response = wp_remote_post( $pinecone_url, [
'headers' => [
'Api-Key' => $api_key,
'Content-Type' => 'application/json',
],
'body' => json_encode( [
'vectors' => [
[
'id' => (string) $post_id,
'values' => $embedding,
'metadata' => [
'title' => get_the_title( $post_id ),
'url' => get_permalink( $post_id ),
],
]
],
'namespace' => 'wordpress-posts',
] ),
'timeout' => 15,
] );
if ( is_wp_error( $response ) ) {
error_log( 'Error Pinecone: ' . $response->get_error_message() );
}
}
Para Weaviate, usarías su API GraphQL o REST. El proceso es similar, pero enviando el objeto completo para que Weaviate lo vectorice si configuraste el módulo.
Paso 4: Búsqueda semántica (el frontend)
El usuario escribe una consulta. Necesitas:
- Convertir esa consulta a embedding (mismo modelo).
- Buscar los vectores más cercanos en la base vectorial.
- Obtener los IDs de los posts y devolverlos.
Endpoint REST personalizado
add_action( 'rest_api_init', function () {
register_rest_route( 'wpe/v1', '/semantic-search', [
'methods' => 'GET',
'callback' => 'wpe_semantic_search',
'args' => [
'q' => [
'required' => true,
'sanitize_callback' => 'sanitize_text_field',
],
],
] );
} );
function wpe_semantic_search( $request ) {
$query = $request->get_param( 'q' );
// 1. Obtener embedding de la consulta
$embedding = wpe_get_embedding( $query );
// 2. Consultar Pinecone (o Weaviate)
$pinecone_url = 'https://wordpress-content-xxxx.svc.pinecone.io/query';
$response = wp_remote_post( $pinecone_url, [
'headers' => [
'Api-Key' => PINECONE_API_KEY,
'Content-Type' => 'application/json',
],
'body' => json_encode( [
'vector' => $embedding,
'topK' => 10,
'includeMetadata' => true,
'namespace' => 'wordpress-posts',
] ),
'timeout' => 10,
] );
if ( is_wp_error( $response ) ) {
return new WP_Error( 'search_error', 'Error en la búsqueda', [ 'status' => 500 ] );
}
$body = json_decode( wp_remote_retrieve_body( $response ), true );
$ids = array_map( function( $match ) {
return (int) $match['id'];
}, $body['matches'] );
// 3. Obtener los posts completos
$posts = get_posts( [
'post__in' => $ids,
'post_type' => 'any',
'post_status' => 'publish',
'orderby' => 'post__in', // Mantener el orden de relevancia
] );
return rest_ensure_response( $posts );
}
[INFO] Este endpoint devuelve objetos de post completos. Puedes personalizar la respuesta para incluir solo título, extracto y URL, y añadir el score de similitud.
Paso 5: Consideraciones de rendimiento y costes
Latencia
- Embeddings: Llamar a la API de OpenAI cada vez que se guarda un post puede ralentizar el panel de administración. Solución: usar un sistema de colas (WP Cron, Action Scheduler, o colas externas como RabbitMQ). Genera el embedding en segundo plano.
- Búsqueda: Pinecone y Weaviate responden en milisegundos. El cuello de botella será la red y la conversión a embedding de la consulta. Puedes cachear los embeddings de consultas frecuentes con transients de WordPress.
Costes
- OpenAI:
text-embedding-ada-002cuesta ~$0.0001 por 1K tokens. Un post de 500 palabras (~700 tokens) cuesta ~$0.00007. Para 1000 posts, ~$0.07. Barato. - Pinecone: Plan gratuito (índice de 1 pod) suficiente para desarrollo. Producción: desde ~$70/mes.
- Weaviate: Si lo autoalojas, solo pagas el servidor (VPS desde ~$10/mes).
Actualización de vectores
Cuando un post se actualiza, debes sobrescribir el vector. Si se elimina, debes borrarlo de la base vectorial. Añade hooks para delete_post y post_updated.
Caso de uso real: búsqueda híbrida (léxica + semántica)
La búsqueda semántica pura puede fallar con consultas muy específicas o nombres propios. La solución es combinar ambos métodos: ejecuta una búsqueda tradicional con WP_Query y una búsqueda semántica, y luego fusiona los resultados usando un algoritmo de recíproco (RRF) o simplemente intercalándolos.
function wpe_hybrid_search( $query ) {
// Búsqueda léxica
$lexical_query = new WP_Query( [
's' => $query,
'posts_per_page' => 10,
] );
// Búsqueda semántica (usando el endpoint anterior)
$semantic_ids = wpe_semantic_search_ids( $query );
// Fusionar (eliminar duplicados)
$all_ids = array_unique( array_merge(
wp_list_pluck( $lexical_query->posts, 'ID' ),
$semantic_ids
) );
// Devolver posts ordenados por relevancia (puedes implementar scoring)
return get_posts( [
'post__in' => $all_ids,
'orderby' => 'post__in',
] );
}
Plugin recomendado vs. código a medida
Existen plugins como SearchWP (con addon de vectores) o WPSOLR que integran búsqueda semántica, pero suelen depender de servicios de terceros y tienen costes recurrentes. Si buscas control total, el enfoque descrito aquí es más flexible y escalable.
Para equipos pequeños, Weaviate autoalojado con el módulo text2vec-transformers (modelo local) elimina la dependencia de APIs externas, ideal para sitios con contenido sensible.
Conclusión
Integrar una base de datos vectorial en WordPress para búsqueda semántica no solo es posible, sino que está al alcance de cualquier desarrollador con conocimientos de PHP y APIs. Herramientas como Pinecone y Weaviate simplifican el almacenamiento y la consulta de vectores, mientras que los modelos de embeddings (OpenAI, Sentence Transformers) convierten tu contenido en datos semánticos.
El resultado es una experiencia de búsqueda que entiende a tus usuarios, no solo sus palabras. Los sitios de documentación, knowledge bases, marketplaces y blogs grandes pueden beneficiarse enormemente de esta arquitectura.
[WARNING] No implementes esto en producción sin un sistema de colas para los embeddings. Bloquear el save_post con una llamada HTTP puede colapsar el admin en sitios con alta frecuencia de publicación.
Ahora tienes el mapa. Elige tu base vectorial, escribe el conector y transforma la búsqueda de tu WordPress. La diferencia la notarán tus usuarios en la primera consulta.
