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.
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 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
) ;
Realizar la búsqueda semántica
Cuando el usuario escribe una consulta:- Convertir el texto de búsqueda a un embedding con "taskType = 'RETRIEVAL_QUERY'".
- Calcular la distancia coseno entre ese vector y todos los vectores de la tabla.
- 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" |