Por qué un modelo no debe juzgar las cifras de otro, cómo funciona una comprobación determinista de números y qué obliga a cambiar en la base de conocimiento.
El problema: un chatbot que da precios
El asistente de esta web contesta preguntas sobre mis servicios, y entre las preguntas que recibe están las de dinero: cuánto cuesta, qué incluye, cuánto tarda. Un precio inventado no es un error de redacción: es un compromiso que no he hecho. La última barrera antes de enviar una respuesta es una comprobación de cifras, y la he tenido que rehacer entera.
Primera versión: un modelo juzgando a otro modelo
La primera comprobación era la solución obvia: sacar las afirmaciones con cifras de la respuesta y pedirle a un modelo pequeño que juzgara si cada una estaba respaldada por los fragmentos recuperados. Costaba una llamada extra al modelo por cada respuesta con números.
El resultado no justificaba la llamada. El juez tumbaba respuestas correctas porque "no le cuadraban". Y cuando rechazaba, señalaba la afirmación, pero no las cifras que sí había en la fuente, así que costaba ver qué había que corregir.
La decisión: una reja determinista
Lo sustituí por algo sin modelo: un normalizador de números que ya usaba en otros pipelines de contenido. La lógica cabe en una línea: se extraen los valores numéricos de los fragmentos recuperados, se extraen los de cada afirmación de la respuesta, y cada precio, porcentaje o cantidad de la respuesta tiene que estar en la fuente.
La parte con criterio está en qué cuenta como "estar":
- Redondear sí, inventar no. Se aceptan redondeos válidos de una cifra de la fuente (79,18 puede aparecer como 79 o como 80), pero no hay bandas de tolerancia. Un 85 no es "casi 80".
- Ante la duda, dejar pasar. Fue una decisión explícita: prefiero un falso negativo a un falso positivo. Tumbar una respuesta correcta es justo el fallo que tenía el juez anterior. Si una afirmación no tiene números que se puedan interpretar, pasa.
- El caso peligroso de verdad. Si la fuente no trae ningún número y la respuesta sí, se rechaza: no hay nada contra lo que anclarlo.
- El rechazo dice qué cifra sobra. El registro guarda el número que sobraba y los que había en la fuente. Un rechazo que no señala nada no se puede arreglar.
Cuando la reja rechaza, esa respuesta no se envía: el usuario recibe un mensaje que le pide confirmar el dato con una persona, y el rechazo queda registrado con la cifra que sobraba y las que había en la fuente. El resto del pipeline no se tocó ni una línea; la reja respeta la misma interfaz que el juez al que sustituye.
Lo que costó: dos fugas que los tests no veían
Hubo dos agujeros que solo aparecieron al exigir ver la salida real y no el recuento de tests en verde:
- Un test que no probaba nada. Uno de los casos usaba "3 planes", que el extractor ni siquiera considera una afirmación. El test pasaba sin ejercitar la reja. Lo cambié por "El plan cuesta 3 €".
- Un precio disfrazado de año. Para no rechazar fechas, los números entre 1900 y 2199 tenían una exención. Así, un "2.100 €" inventado se colaba como si fuera un año, igual que "desde enero de 2026 cuesta 2.100 €". La solución fue que las cifras de dinero nunca reciben la exención de fechas, ni por su valor ni por el contexto.
El precio real: te obliga a diseñar el corpus
Una reja así no es gratis en diseño. Solo acepta cifras presentes en los fragmentos recuperados para esa consulta concreta, así que la base de conocimiento tiene que estar pensada para ello:
- Trocear por pregunta, no por servicio. Cada fragmento responde una pregunta real de principio a fin.
- Repetir el precio donde haga falta. Si el precio vive en un único fragmento y la consulta recupera otro, la respuesta correcta se tumba. Duplicar el dato es deliberado.
- Fragmentos estancos. Dos fragmentos que compiten por la misma pregunta se desplazan entre sí en la recuperación.
- Toda cifra, anclada a algo publicado. Si un número no está en la web, no entra en la base de conocimiento.
El tamaño importa: con una base muy pequeña la reja se convierte en un cerrojo que bloquea casi todo, y con una enorme y mal repartida la recuperación empeora y también bloquea más. El punto bueno está en una base mediana, bien troceada.
Resultado
Coste por respuesta de la comprobación: de una llamada al modelo a cero. Sin latencia de red, reproducible (la misma respuesta da siempre el mismo veredicto) y con rechazos que dicen qué hay que corregir. Salió con 24 tests nuevos, incluidos los dos casos de las fugas. Lo que queda abierto es llevarla al generador de este blog, donde primero hay que probarla en modo observación para no convertirla en un cerrojo.
Si quieres un chatbot que no se invente tus precios, esta es la comprobación que tiene el asistente de esta web y que se puede activar en el tuyo: chatbot para empresas. Y los fallos que van más allá de las cifras, en chatbots para empresas: por qué fallan y cómo evitarlo.
Daniel · Arquitecto Digital. Los sistemas los diseño y dirijo yo; la implementación la hago con asistentes de IA de programación.