Saltar a contenido

ADR-003 — Patrón extractor: LLM + regex fallback

Estado: Aceptado
Fecha: 2026-04-09
Rama: feature/rag-studio


Contexto

El extractor_node usa un LLM pequeño (llama3.1:8b vía Ollama) con with_structured_output para extraer guest_name, guests_count, check_in y check_out de los mensajes del huésped.

Se detectó que llama3.1:8b falla silenciosamente en la extracción de nombres: devuelve null para guest_name incluso cuando el usuario dice explícitamente "Me llamo Ernesto Rodríguez", sin lanzar excepción. Esto causaba un bucle donde el bot pedía el nombre repetidamente al usuario aunque ya lo hubiese dado.


Decisión

Implementar extracción en dos capas en cascada:

  1. Capa primaria — LLM structured output: intento principal. Cubre fechas en lenguaje natural, cantidades implícitas ("somos tres") y nombres en cualquier posición del mensaje.

  2. Capa secundaria — Regex fallback: se activa cuando la capa primaria devuelve null para guest_name (no solo en excepción). Patrón específico para frases de presentación:

    (?:me llamo|mi nombre es|soy|a nombre de)\s+[Nombre Apellido]
    

  3. Preservación de estado: si ambas capas fallan pero el estado ya tiene un guest_name válido de turnos anteriores, se preserva. Evita que el bot "olvide" el nombre entre turnos.


Consecuencias

Positivas: - Elimina el bucle de nombre repetido, el error de UX más frecuente en las evals. - El regex es determinista y rápido (sin latencia de LLM). - La lógica de preservación evita regresiones cuando el extractor fluctúa.

Negativas: - El regex solo cubre frases explícitas de presentación. Nombres dados de forma implícita ("para Juan García") no se capturan por esta capa. - Dos rutas de código que mantener.


Alternativas descartadas

  • Solo regex: demasiado frágil para fechas en lenguaje natural ("el próximo viernes").
  • Modelo más grande: llama3.1:70b resolvería el problema pero no está disponible en Ollama local con el hardware actual.
  • Groq para extracción: añade latencia de red y coste; el modelo local es suficiente con el fallback.