← Volver al blog

·5 min de lectura·Daniel Diaz

Detección de duplicados en datos maestros con un LLM en local: las reglas buscan candidatos, el modelo decide

Cómo monté un pipeline de detección de duplicados de cuentas con Python y un LLM local vía Ollama: bloqueo, clasificación por pares con motivo, cola de revisión humana y por qué corre en local.

Todos los equipos de datos maestros tienen el mismo atasco: cuentas que son la misma empresa escrita dos veces. «Hotel Royal SL», «HOTEL ROYAL S.L.» y «Royal Hotel (Madrid)» con tres ids, tres responsables y tres condiciones de pago distintas. La herramienta de MDM caza las fáciles. Lo que queda es la pila que nadie quiere: miles de pares que quizá son duplicados y en los que alguien tiene que mirar dos registros y decidir.

Así convertí esa pila en una cola que una persona puede sacar adelante, con Python haciendo la búsqueda y un modelo de lenguaje en local haciendo el juicio.

La forma del problema

La detección de duplicados (record linkage, entity resolution, elige el nombre) tiene dos mitades que piden herramientas muy distintas:

  1. Encontrar pares candidatos. Con n registros hay n²/2 pares posibles. No puedes mirarlos todos y no hace falta: la mayoría son obviamente distintos. Esta mitad va de exhaustividad (recall): que ningún duplicado real se escape.
  2. Decidir sobre cada candidato. Dados dos registros parecidos, ¿son la misma entidad? Esta mitad va de precisión y de explicar la decisión, porque deshacer una fusión en MDM es caro.

Las reglas son excelentes en la primera mitad y mediocres en la segunda. Los modelos de lenguaje, al revés. Así que el pipeline se parte justo por ahí.

Primera mitad: las reglas generan los candidatos

Python a secas, sin modelo:

Este paso, por sí solo, elimina más del 95 % de los pares. Y es completamente determinista, que importa cuando alguien pregunta «¿por qué ha salido este par?».

Segunda mitad: un LLM local decide, con motivo

Para cada par de la zona gris, el modelo recibe los dos registros lado a lado y una instrucción estricta: responder con un objeto JSON y nada más.

{
  "same_entity": true,
  "confidence": "high",
  "reason": "Misma raíz de NIF y código postal; el nombre solo difiere en la forma jurídica y el orden de palabras."
}

Lo que hizo que funcionara:

Por qué un modelo en local

Los registros son datos de clientes. Mandarlos a una API alojada implicaba una revisión de tratamiento de datos, un contrato y una línea de presupuesto, para un pipeline que corre unas pocas veces al mes. Un modelo local vía Ollama (un qwen2.5 pequeño) se saltó todo eso: los datos nunca salieron del portátil y el coste fue cero.

La limitación era el hardware: un portátil de trabajo con 32 GB de RAM y sin GPU. Eso descarta modelos grandes, pero un modelo de clase 3B en CPU responde a un par en pocos segundos, y la zona gris tras el bloqueo es lo bastante pequeña como para cubrirla en una ejecución nocturna. Las reglas son lo que hace que un modelo pequeño baste: el modelo nunca ve los pares fáciles, solo aquellos en los que hace falta criterio.

La cola de revisión

Nada se fusiona automáticamente. La salida es una cola ordenada por confianza e impacto (cuántas transacciones cuelgan de las cuentas), con los dos registros, la puntuación, la decisión del modelo y su motivo. Un steward acepta o rechaza; la fusión se hace en la herramienta de MDM, que es donde toca. Los rechazos se guardan: son el mejor conjunto de prueba para la siguiente versión del prompt y de los umbrales.

Lo que sale mal

Lo que le diría a quien empiece