Lo que aprendimos construyendo nuestro propio widget de bot de soporte (chatea con tu contenido)
Un desglose tecnico de lo que realmente importa al construir un widget de chat de soporte: fallos de retrieval, navegacion de documentos como archivos, cache, control de acceso y guardrails para respuestas fiables.
Lo que aprendimos construyendo nuestro propio widget de bot de soporte (chatea con tu contenido)
Construimos nuestro propio widget de bot de soporte: una burbuja de chat que incrustas en un sitio web para que las personas puedan hacer preguntas y recibir respuestas desde tu contenido (docs, base de conocimiento, paginas de servicio, politicas).Si alguna vez has entregado uno de estos, sabes que el pitch es facil y la realidad es complicada.Los problemas que aparecen rara vez son "problemas del modelo". Son problemas de ingenieria:
el retrieval falla de formas predecibles
la latencia mata la confianza antes de que llegue el primer token
el control de acceso es facil de implementar mal
la inyeccion de prompt se convierte en "envenenamiento de contenido"
las respuestas deben ser auditables o terminas con riesgo de soporte
Este post es un teardown en detalle: arquitectura, modelo de datos, estrategia de retrieval, diseno de herramientas, caching, RBAC, patrones de UI y los guardrails que hicieron nuestro widget fiable.
Que estabamos construyendo (requisitos y restricciones)
Definimos algunos requisitos no negociables desde el inicio:
Primer token rapido: la gente debe ver una respuesta rapido, incluso si el asistente necesita "buscar" en segundo plano.
Respuestas fundamentadas: las respuestas deben estar soportadas por contenido que realmente publicamos.
Read-only por defecto: el asistente no debe mutar contenido.
Listo para multi-locale: nuestro sitio esta localizado; el asistente no debe mezclar idiomas a la ligera.
Escalado seguro: cuando no pueda responder, debe dirigir al usuario a contacto/soporte sin alucinar.
De ahi sacamos una definicion simple de exito:
El asistente debe comportarse como un ingeniero cuidadoso leyendo nuestro sitio, no como un escritor creativo improvisando.
Un modelo mental util: "bot de soporte = busqueda + navegacion + sintesis"
Un buen asistente de soporte hace tres cosas en secuencia:
Buscar: identificar paginas/secciones candidatas.
Navegar: abrir paginas, escanear encabezados, seguir enlaces, refinar la busqueda.
Sintetizar: producir una respuesta corta y correcta con indicios de de donde salio.
La mayoria de implementaciones solo hacen (1) y (3). Se saltan (2), y ahi es donde muere la precision.
Leccion 1: un RAG solo por chunks falla cuando la respuesta cruza paginas
El retrieval Top-K por chunks falla de formas consistentes:
la respuesta esta dividida entre dos paginas
la "sintaxis exacta" esta en un bloque de codigo que no rankeo
la respuesta correcta esta en una pagina con solapamiento lexico debil
la pregunta del usuario esta poco especificada, asi que el match por embeddings es "lo bastante cercano"
Cuando el modelo ve solo un punado de chunks, puede sonar seguro y aun asi estar incompleto. La experiencia del usuario se ve asi:
tono correcto
detalles incorrectos
sin forma de verificar
Nuestra conclusion: retrieval no alcanza. Necesitas exploracion.
Leccion 2: dale al asistente primitivas deterministas (docs como archivos)
Las personas no responden preguntas de docs leyendo cinco parrafos al azar. Nosotros:
find la pagina
la open
escaneamos encabezados
buscamos tokens exactos
seguimos enlaces internos
repetimos hasta poder probar la respuesta
Asi que moldeamos la interfaz de herramientas del asistente como un mini filesystem sobre nuestro contenido:
cada pagina es un "archivo"
los directorios representan secciones (o segmentos del path de la URL)
el asistente puede ls, find, cat y grep
Esto no es un truco. Le da al modelo primitivas que son:
auditables (puedes registrar tool calls)
deterministas (la misma consulta produce las mismas lecturas)
escalables (puede explorar sin que hardcodees flujos)
Normalizar: colapsar whitespace, quitar query strings de tracking en enlaces.
Segmentar:
registro a nivel pagina para navegacion y citas
registros a nivel chunk para retrieval
Anotar:
locale
visibilidad (publico, solo clientes, interno)
outline de headings
enlaces internos salientes
La leccion principal: indexa el sitio renderizado, no solo el repo, o te pierdes lo que el usuario realmente ve y terminas citando contenido que no existe en produccion.
Retrieval: combina lexico y vector antes de "preguntar al modelo"
No confiamos en una sola estrategia de retrieval.Hacemos:
busqueda lexica para tokens exactos (ideal para identificadores, siglas, codigos de error)
busqueda vectorial para match semantico (ideal para preguntas vagas)
Luego mezclamos candidatos y aplicamos checks de sanidad:
rechazar paginas que no coinciden con el locale del usuario (salvo que lo pida)
preferir paginas canonicas frente a tag pages / listados duplicados
preferir paginas con fuerte solapamiento de headings con los terminos de la query
Solo entonces dejamos que el asistente abra paginas y sintetice.
Diseno de herramientas: que puede hacer el asistente (y que no)
Implementamos una superficie de herramientas pequena y estricta:Permitido:
listar directorios: ls/services
buscar paths: find-name"billing"
leer una pagina completa: cat/services/seo
buscar dentro del contenido: grep-ri"canonical"/
No permitido:
escribir o editar contenido
traer URLs arbitrarias
llamadas de red arbitrarias
El beneficio de seguridad es enorme: hace que la inyeccion de prompt sea sobre todo un problema de calidad de contenido, no un problema de compromiso del sistema.
La leccion de latencia: filesystems reales son demasiado lentos para chat interactivo
Si levantas un sandbox/container por sesion para proveer un filesystem real:
el cold start se vuelve visible
el chat se siente roto
te tienta agregar complejidad como warm pools
En un widget de soporte, el usuario esta mirando la UI. Quieres herramientas instantaneas.Asi que virtualizamos las operaciones del filesystem sobre nuestro indice:
ls y find resuelven desde el arbol de paths cacheado
cat recompone la pagina desde chunks almacenados (ordenados por chunk_index)
los resultados se cachean por sesion (y en parte globalmente, cuando es seguro)
El asistente obtiene la ilusion de un shell sobre archivos, pero no hay archivos reales.
Cachea lo que se repite: arbol de directorios, paginas y "targets de grep"
Cachear "respuestas" es debil porque las preguntas varian.Lo que se repite en conversaciones reales es:
listar las mismas secciones
abrir las mismas 5-10 paginas importantes
hacer grep de los mismos tokens
Asi que cacheamos:
el arbol de paths
paginas completas reconstruidas
candidatos recientes de grep
Esto importo mas que micro-optimizar embeddings, porque mejoro la velocidad de follow-up y redujo loops de "buscando...".
RBAC: el control de acceso debe ser estructural, no basado en prompt
Si algunos docs no son publicos (borradores, notas internas, docs solo para clientes), no puedes depender de prompting.Aplicamos RBAC antes de que el asistente ejecute un solo tool call:
construir un arbol de paths scopeado por usuario
podar todo lo que el usuario no puede acceder
aplicar el mismo filtro a cada query y lectura de pagina
Si el asistente no puede ls un archivo, no puede catlo y no puede citarlo.Ese es el unico modelo mental que aguanta bajo presion.
Guardrails: como paramos respuestas equivocadas pero confiadas
Agregamos algunas reglas que redujeron mucho las malas respuestas:
Regla 1: sin evidencia, no hay respuesta
Si el asistente no puede encontrar contenido de soporte con herramientas, debe:
decir que no pudo verificar
mostrar lo que reviso (paginas o secciones)
ofrecer un siguiente paso (contacto/soporte)
Regla 2: prefiere citar antes que parafrasear en detalles criticos
Cuando la respuesta depende de wording exacto (requisitos, limitaciones, copy legal):
citar la(s) frase(s) relevante(s) de la pagina que leyo
mantener la sintesis al minimo
Regla 3: se estricto con locale y paginas canonicas
Si el usuario esta en una ruta de locale, el asistente debe:
preferir contenido de ese locale
evitar mezclar idiomas en una sola respuesta
volver al locale por defecto solo si la pagina del locale no existe
El "problema del grep": la feature clave necesita un plan en dos fases
Un grep recursivo ingenuo es lento si lee todo a traves de la red.Usamos un enfoque en dos fases:
filtro grueso usando el indice (que paginas podrian contener el token)
filtro fino en memoria sobre texto de pagina cacheado para extraer matches exactos + contexto
Esto hizo que el asistente se sintiera como alguien que busca como developer, no como un chatbot que adivina.
UX del widget: la UI hace o rompe la confianza
Iteramos la UI varias veces antes de que el widget se sintiera fiable.Lo que mas importo:
streaming de tokens con layout estable (evitar reflow saltarin)
estados claros:
"Searching..."
"Reading page..."
"Answering..."
citas cortas en linea:
"From: /services/seo"
"From: /legal/privacy"
fallbacks que no se sienten como fracaso:
"Revise X e Y pero no pude confirmarlo; aqui esta como contactarnos."
Esto combina bien con hacer visible el "trabajo" del asistente. La gente perdona mas a un bot que dice "no pude confirmarlo" que a uno que inventa una respuesta confiada.
Observabilidad: registra las lecturas, no solo los tokens
Si quieres mejorar calidad, necesitas saber que paso.Registramos:
tool calls (paths listados/leidos/buscados)
que paginas se usaron como evidencia
longitud de la respuesta final y buckets de latencia
tasas de "no se pudo verificar"
tasas de escalado (clicks a contacto)
Eso nos permitio responder preguntas practicas:
que paginas carecen de informacion importante?
que preguntas nunca encuentran evidencia?
que paginas generan confusion y necesitan reestructuracion?
Una checklist practica de construccion (lo que hariamos otra vez)
Si estas construyendo un widget de soporte que chatea con tu contenido, empezariamos por:
indexar el sitio renderizado y guardar registros a nivel pagina
combinar retrieval lexico + vectorial
agregar una capa de exploracion por herramientas (files + grep)
podar contenido por RBAC antes de iniciar sesiones
mantener herramientas read-only por defecto
agregar la regla "sin evidencia, no hay respuesta"
hacer visible "searching/reading/answering" en la UI
registrar tool calls para poder depurar fallos
Si quieres que te ayudemos a implementarlo (widget + indexing + arquitectura segura):