4dim / Notas

El velo: fichas en vez de nombres, y Postgres resuelve

Mandarle los nombres al modelo acierta y expone; mandarle datos borrosos protege y falla. Nosotros le mandamos referencias («C-7») y resolvemos la búsqueda en la base antes de que la pregunta salga. Incluido el punto donde este diseño se rompe si no se mira.

La disyuntiva falsa

Si te preocupa qué datos de tus clientes salen hacia un proveedor de IA, esta nota cuenta cómo lo resolvimos nosotros. Para que un modelo de lenguaje responda «¿qué pasó con María?» sobre un CRM, alguien tiene que mandarle datos del CRM a un servidor ajeno.

Ahí aparece la disyuntiva de siempre. Si le mandas los nombres, los teléfonos y las conversaciones, el modelo acierta y los datos de tus clientes quedan en casa de otro. Si le mandas datos borrosos o anonimizados, protege y falla, porque «¿qué pasó con la persona 3?» no es una pregunta que nadie haga.

Nosotros no le mandamos ninguna de las dos cosas. Le mandamos una referencia, y quien la resuelve somos nosotros.

Fichas: lo que ve el modelo

Cada cosa con identidad propia viaja al modelo como una ficha opaca: un contacto, una persona del equipo, una bandeja, una campaña, un hilo. Una ficha es una etiqueta corta que no dice nada por sí sola: «C-7», «U-2», «H-14».

El modelo razona sobre fichas: «C-7 lleva dos días esperando; propongo asignárselo a U-2». Cuando la respuesta vuelve, el sistema cambia cada ficha por el nombre real, y la persona lee «María lleva dos días esperando; propongo asignárselo a Andrés».

La tabla de traducción guarda ficha → identificador numérico, nunca ficha → nombre. Si guardara nombres, esa tabla sería una segunda copia de los datos personales de tus clientes. Y una copia más es un sitio más del que se pueden filtrar.

En el texto libre, lo que el cliente escribió, no hay entidad a la que referirse. Ahí actúa otro mecanismo: un barrido sin vuelta atrás que borra teléfonos, correos, documentos y tarjetas antes de que el texto salga. Lo contamos en otra nota. Fichas para lo que tiene identificador, barrido para lo que no.

Sembrar: «¿qué pasó con María?»

La objeción obvia: si el modelo solo ve fichas, ¿cómo responde una pregunta que menciona un nombre? La respuesta es que el nombre nunca llega al modelo. Antes de tapar nada, le preguntamos a nuestra base de datos qué contactos de ese negocio se llaman así.

Si hay una María, se le acuña su ficha y la pregunta sale ya como «¿qué pasó con C-4?». El modelo llama a la herramienta con C-4, la herramienta consulta el contacto 4 en Postgres, que es nuestra base de datos, y la respuesta vuelve con datos exactos.

No se pierde precisión ni comodidad, porque quien busca no es el modelo. Es la base de datos. El modelo orquesta; la base resuelve.

Un detalle de umbral: hacen falta al menos tres letras para intentar emparejar un nombre. Con menos, un contacto llamado «Ana» casaría dentro de media conversación.

La ficha no cambia dentro de una conversación

Una conversación con el asistente tiene varios turnos. Si la ficha de María fuera «C-4» en la primera pregunta y «C-9» en la tercera, el modelo perdería el hilo de a quién se refería antes. Es justo lo que una conversación no puede permitirse. Así que la ficha se acuña una vez por hilo y se guarda. La misma persona es la misma ficha mientras dure la conversación.

Acuñar, además, puede ocurrir dos veces a la vez: dos pestañas abiertas sobre el mismo hilo intentan crear la misma ficha. Por eso la orden de guardar lleva ON CONFLICT DO NOTHING: si ya existe, no hace nada. La segunda pestaña encuentra la ficha que creó la primera.

El agujero más fácil de abrir

El historial de la conversación se guarda destapado, con nombres reales, porque es lo que la persona lee en pantalla. Pero cada vez que hay un turno nuevo, ese historial se vuelve a mandar al modelo para que sepa de qué se venía hablando. Es decir: hay que volver a taparlo.

Y ahí está la trampa. El segundo tapado solo reconoce los nombres que tiene cargados. Si no se recarga antes el mapa de nombre → ficha con todas las entidades ya acuñadas en el hilo, los nombres reales de las preguntas anteriores salen enteros hacia el proveedor. En el turno siguiente, y sin que nada avise. El comentario del código lo llama «el agujero más fácil de abrir de todo el velo», y la consulta que recarga el mapa es lo que lo cierra.

Lo contamos porque es el punto donde se rompe cualquier implementación parecida. Anonimizar la primera pregunta es fácil. Anonimizar la quinta, con cuatro respuestas destapadas en el historial, es donde hay que mirar.

Lo que vuelve del modelo es entrada de fuera

Cuando la respuesta del modelo vuelve con fichas, destaparlas es buscar cada ficha en la tabla. Esa consulta filtra por el identificador del negocio, y no es un adorno. La ficha vino de una respuesta del modelo, y lo que venga del modelo se trata como algo de fuera. Sin ese filtro, una ficha inventada por el modelo podría resolver a una fila de otro negocio.

Una ficha que este hilo nunca acuñó no es un error del que haya que recuperarse. Es el modelo nombrando algo que nunca vio. La herramienta contesta que no existe, y se sigue.

Qué hacer entonces

  • Mandar referencias, no nombres ni datos borrosos. Resolver la búsqueda en la base, antes de que la pregunta salga.
  • Guardar la traducción como ficha → identificador. Nunca ficha → nombre.
  • Mantener la ficha estable dentro de la conversación.
  • Al reconstruir el contexto, recargar el mapa antes de volver a tapar el historial. Probar el quinto turno, no el primero.
  • Tratar las fichas que devuelve el modelo como algo de fuera: filtrar por negocio, y contestar «no existe» a las que no se acuñaron.

Esta nota sale de nuestro trabajo en Agentes conversacionales.

← todas las notas