13-08-2026

Cómo crear una búsqueda semántica con php, gemini embeddings y mariadb vector

Cuando un usuario busca en una tienda online “ropa para la nieve” o “chaqueta para no pasar frío” se pueden obtener malos resultados o incompletos usando una búsqueda tradicional por palabras clave. Con la búsqueda semántica el sistema entiende el significado de la consulta y encuentra productos relacionados aunque no compartan exactamente las mismas palabras.

¿Qué es un embedding?

Un embedding es un vector que representa el significado de un texto. Dos textos con significados parecidos tendrán vectores cercanos en el espacio vectorial. En el ejemplo de este post se usa el modelo gemini-embedding-001 de Gemini, aconsejable para tareas de recuperación de información, "RETRIEVAL_DOCUMENT" y "RETRIEVAL_QUERY" que son dos valores del parámetro taskType, o task_type de la API de embeddings de Gemini y sirven para optimizar el embedding según el uso que le vas a dar. No es lo mismo representar un documento largo, como la descripción de un producto, que representar una consulta corta de un usuario.

Obtiener el embedding

Función de PHP que obtiene el embedding de Gemini. Se puede obtener un embedding con ChatGPT.

function obtenerEmbedding(string $texto, string $apiKey, string $taskType = 'RETRIEVAL_DOCUMENT'): array
{
$url = "https://generativelanguage.googleapis.com/v1beta/models/gemini-embedding-001:embedContent?key=" . urlencode($apiKey);

$cuerpo = [
'model'=> 'models/gemini-embedding-001',
'content' => [
  'parts' => [
  ['text' => $texto]
  ]
],
'taskType' => $taskType
];

// ... llamada cURL ...
}

Puntos importantes

  • Se usa el endpoint "embedContent".
  • El parámetro "taskType" cambia según el uso:
    • "RETRIEVAL_DOCUMENT": cuando se indexan productos.
    • "RETRIEVAL_QUERY":cuando se convierte la búsqueda del usuario.
  • En desarrollo se desactiva la verificación "SSL", "CURLOPT_SSL_VERIFYPEER => false". No hacerlo en producción.
La función devuelve un array de números flotantes, el vector..

Guardar los productos con su embedding en MariaDB

MariaDB permite almacenar vectores nativamente y realizar búsquedas por similitud con funciones como "VEC_FromTex"t y "VEC_DISTANCE_COSINE".Ejemplo de inserción de productos de ejemplo:

$productos = [
[
  'nombre' => 'Chaqueta Térmica Impermeable para Nieve',
  'descripcion' => 'Chaqueta técnica impermeable y transpirable con aislamiento térmico. Ideal para esquí, snowboard y actividades en nieve. Capucha ajustable y costuras selladas.',
  'precio' => 89.99
],
[
  'nombre' => 'Parka de Invierno Polar',
  'descripcion' => 'Parka larga con relleno polar y membrana impermeable. Perfecta para frío extremo y lluvia.',
  'precio' => 119.50
],
[
  'nombre' => 'Chaqueta Softshell Trekking',
  'descripcion' => 'Chaqueta ligera softshell resistente al viento y agua. Ideal para senderismo y montaña en primavera.',
  'precio' => 69.90
],
[
  'nombre' => 'Anorak de Esquí Profesional',
  'descripcion' => 'Anorak técnico con membrana Gore-Tex y aislamiento PrimaLoft. Diseñado para nieve y condiciones extremas.',
  'precio' => 249.00
],
[
  'nombre' => 'Camiseta Técnica Running',
  'descripcion' => 'Camiseta transpirable de manga corta para correr en verano.',
  'precio' => 24.95
],
[
  'nombre' => 'Pantalón de Esquí Impermeable',
  'descripcion' => 'Pantalón técnico impermeable y térmico para esquí y snowboard. Refuerzos en rodillas y trasero.',
  'precio' => 79.90
]
];

echo "Insertando productos";
foreach ($productos as $p) {
try {
  // Nombre + descripción = embedding más rico
  $texto = $p['nombre'] . '. ' . $p['descripcion'];
  $vector = obtenerEmbedding($texto, $geminiApiKey, 'RETRIEVAL_DOCUMENT');
  $vectorTexto = '[' . implode(',', $vector) . ']';

  $sql = "INSERT INTO productos_ia (nombre, descripcion, precio, embedding) VALUES (?, ?, ?, VEC_FromText(?))";
  $stmt = $pdo->prepare($sql);
  $stmt->execute([$p['nombre'], $p['descripcion'], $p['precio'], $vectorTexto]);

  echo "Insertado: {$p['nombre']}";
} catch (Exception $e) {
  echo " Error con {$p['nombre']}: " . $e->getMessage();
}
}
La clave está en concatenar nombre + descripción. Así el embedding captura mucho más contexto semántico que si solo usáramos el nombre.
La tabla de MariaDB tiene la estructura:

CREATE TABLE `productos` (
  `id` int(10) UNSIGNED NOT NULL,
  `nombre` varchar(255) NOT NULL,
  `descripcion` varchar(255) NOT NULL,
  `precio` decimal(10,2) NOT NULL,
  `embedding` vector(3072) NOT NULL,
  `creado_en` timestamp NULL DEFAULT current_timestamp(),
  `VECTOR` key
) ;
En el siguiente post se explica el tipo de dato Vector en MariaDB.

Realizar la búsqueda semántica

Cuando el usuario escribe una consulta:
  1. Convertir el texto de búsqueda a un embedding con "taskType = 'RETRIEVAL_QUERY'".
  2. Calcular la distancia coseno entre ese vector y todos los vectores de la tabla.
  3. Ordenar de menor a mayor distancia, más similar primero.
$sql = 'SELECT id, nombre, descripcion, precio, VEC_DISTANCE_COSINE(embedding, VEC_FromText(:vector)) AS distancia FROM productos_ia ORDER BY distancia ASC LIMIT 6';
VEC_DISTANCE_COSINE devuelve un valor entre 0 y 2. Cuanto el valor sea más próximo a 0, mayor será la similitud del texto.

Flujo completo del sistema

Acción Qué ocurre taskType usado
Insertar productos Genera embedding del producto y lo guarda "RETRIEVAL_DOCUMENT"
Buscar Genera embedding de la consulta y busca vecinos "RETRIEVAL_QUERY"
Este enfoque es especialmente potente con catálogos de productos porque captura relaciones semánticas, como “térmica”, “nieve”, “impermeable”, “esquí”…, sin necesidad de mantener sinónimos manualmente.