Asistentes personales y agentes: un modelo práctico para un chatbot seguro, multiusuario y autohospedado

cómo he creado una plataforma autohospedada de extremo a extremo que brinda a cada usuario un chatbot personal y agente que puede buscar de forma autónoma solo en los archivos a los que el usuario le permite acceder explícitamente.

En otras palabras: control total, 100% privado, todos los beneficios de LLM sin filtraciones de privacidad, costos de tokens o dependencias externas.

Introducción

Durante la semana pasada, me reté a mí mismo a construir algo que había estado en mi mente por un tiempo:

¿Cómo puedo potenciar un LLM con mis datos personales sin sacrificar la privacidad ante las grandes empresas de tecnología?

Eso llevó al desafío de esta semana:

Cree un chatbot agente equipado con herramientas para acceder a las notas personales de un usuario de forma segura, sin comprometer la privacidad.

Como desafío adicional, quería que el sistema admitiera múltiples usuarios. No es un asistente compartido, sino un agente privado para cada usuario donde el usuario tiene control total sobre qué archivos puede leer y razonar su agente.

Construiremos el sistema en los siguientes pasos:

Arquitectura ¿Cómo creamos un agente y le proporcionamos herramientas? Flujo 1: Gestión de archivos de usuario: ¿Qué sucede cuando enviamos un archivo? Flujo 2: ¿Cómo incrustamos documentos y almacenamos archivos? Flujo 3: ¿Qué pasa cuando charlamos con nuestro asistente de agente? Demostración

1) Arquitectura

He definido tres “flujos” principales que el sistema debe permitir:

A) Gestión de archivos de usuario
Los usuarios se autentican a través del frontend, cargan o eliminan archivos y asignan cada archivo a grupos específicos que determinan qué agentes de usuarios pueden acceder a él.

B) Incrustar y almacenar archivos
Los archivos cargados se fragmentan, incrustan y almacenan en la base de datos de una manera que garantiza que solo los usuarios autorizados puedan recuperar o buscar esas incrustaciones.

C) Charla
Un usuario chatea con su propio agente. El agente está equipado con herramientas, incluida una herramienta de búsqueda de vectores semánticos, y solo puede buscar documentos a los que el usuario tiene permiso para acceder.

Para respaldar estos flujos, el sistema se compone de seis componentes clave:

Arquitectura (imagen del autor)

Aplicación
Una aplicación Python que es el corazón del sistema. Expone puntos finales API para el front-end y escucha mensajes provenientes de MessageQueue

Interfaz
Normalmente usaría Angular pero para este prototipo elegí Streamlit. Fue muy rápido y fácil de construir. Esta facilidad de uso, por supuesto, tenía la desventaja de no poder hacer todo lo que quería. Estoy planeando reemplazar este componente con mi Anluar preferido, pero en mi opinión, Streamlit fue muy bueno para la creación de prototipos.

Almacenamiento de blobs
Este contenedor ejecuta Minio; un sistema de almacenamiento de objetos distribuido, de código abierto y de alto rendimiento. Definitivamente excesivo para mi prototipo, pero fue muy fácil de usar y se integra bien con Python, así que no me arrepiento.

(Vector) Base de datos
Postgres maneja todos los datos relacionales, como metadatos de documentos, usuarios, grupos de usuarios y fragmentos de texto. Además, Postgres ofrece una extensión que uso para guardar datos vectoriales como las incrustaciones que pretendemos crear. Esto es muy conveniente para mi caso de uso ya que puedo permitir la búsqueda vectorial en una tabla, uniendo esa tabla a la tabla de usuarios, asegurando que cada usuario solo pueda ver sus propios datos.

Ollama
Ollama alberga dos modelos locales: uno para incrustaciones y otro para chat. Los modelos son bastante livianos pero se pueden actualizar fácilmente, según el hardware disponible.

Cola de mensajes
RabbitMQ hace que el sistema responda. Los usuarios no tienen que esperar mientras se fragmentan e incrustan archivos grandes. En cambio, regreso inmediatamente y proceso la incrustación en segundo plano. También me brinda escalabilidad horizontal: varios trabajadores pueden procesar archivos simultáneamente.

2) Crear un agente con una caja de herramientas

LangGraph facilita la definición de un agente: qué pasos puede seguir, cómo debe razonar y qué herramienta puede utilizar. Luego, este agente puede inspeccionar de forma autónoma las herramientas disponibles, leer sus descripciones y decidir si llamar a una de ellas ayudará a responder la pregunta del usuario.

El flujo de trabajo se describe como un gráfico. Piense en esto como el modelo del comportamiento del agente. En este prototipo el gráfico es intencionalmente simple:

Nuestro gráfico de agentes (imagen del autor)

El LLM verifica qué herramientas están disponibles y decide si es necesaria una llamada a la herramienta (como la búsqueda vectorial). y El gráfico recorre el nodo de herramientas y regresa al nodo LLM hasta que no se necesitan más herramientas y el agente tiene suficiente información para responder.

3) Flujo 1: Envío de un archivo

Esta parte describe lo que sucede cuando un usuario envía uno o más archivos. Primero, un usuario debe iniciar sesión en el front-end y recibir un token que se utiliza para autenticar las llamadas API.

Después de eso, pueden cargar archivos y asignarlos a uno o más grupos. Cualquier usuario de esos grupos podrá acceder al archivo a través de su agente.

Agregar archivos al sistema (imagen del autor)

En la captura de pantalla anterior, el usuario seleccionó dos archivos; un documento PDF y un documento Word y los asigna a dos grupos. Detrás de escena, así es como el sistema procesa una carga como esta:

Envío de un archivo (imagen del autor)

El archivo y los grupos se envían a la API validando al usuario con el token. El archivo se guarda en el almacenamiento de blobs y se devuelve la ubicación de almacenamiento. Los metadatos y la ubicación de almacenamiento del archivo se guardan en la base de datos y se devuelve file_id. El file_id se publica en una cola de mensajes y se completa la solicitud; los usuarios pueden seguir usando el front-end. Los procesos pesados ​​(fragmentación, incrustación) ocurren más tarde en segundo plano)

Este flujo garantiza que la experiencia de carga sea rápida y receptiva, incluso para archivos grandes.

4) Flujo 2: Incrustar y almacenar archivos

Una vez que se envía un documento, el siguiente paso es hacer que se pueda buscar. Para hacer esto necesitamos incrustar nuestros documentos. Esto significa que convertimos el texto del documento en vectores numéricos que pueden capturar significado semántico.

En el flujo anterior, enviamos un mensaje a la cola. Este mensaje solo contiene un file_id y por lo tanto es muy pequeño. Esto significa que el sistema sigue siendo rápido incluso cuando un usuario carga docenas o cientos de archivos.

La cola de mensajes también nos brinda dos beneficios importantes:

suaviza la carga procesando documentos uno por uno en lugar de todos a la vez; prepara nuestro sistema para el futuro al permitir el escalado horizontal; varios trabajadores pueden escuchar la misma cola y procesar archivos en paralelo.

Esto es lo que sucede cuando el trabajador de inserción recibe un mensaje:

Cómo se incrusta un mensaje (imagen del autor)

Tome un mensaje de la cola, el mensaje contiene un file_id Use file_id para recuperar los metadatos del documento (filtrado por usuario y grupos permitidos) Use la ubicación de almacenamiento de los metadatos para descargar el archivo El archivo se lee, se extrae el texto y se divide en fragmentos más pequeños. Cada fragmento está incrustado: se envía a la instancia local de Ollama para generar una incrustación. Los fragmentos y sus vectores se escriben en la base de datos, junto con la información de control de acceso del archivo.

En este punto, el agente puede buscar completamente el documento a través de la búsqueda vectorial, pero solo para los usuarios a los que se les ha otorgado acceso.

5) Flujo 3: Charlando con nuestro Agente

Con todos los componentes instalados, podemos empezar a chatear con el agente.

Cómo utiliza el agente la búsqueda vectorial (imagen del autor)

Cuando un usuario escribe un mensaje, el sistema organiza varios pasos entre bastidores para ofrecer una respuesta rápida y contextual:

El usuario envía un mensaje a la API y se autentica ya que solo los usuarios autorizados pueden interactuar con su agente privado. Opcionalmente, la aplicación recupera mensajes anteriores para que el agente tenga una “memoria” de la conversación actual. Esto garantiza que pueda responder en el contexto de la conversación en curso. Se invoca el agente LangGraph compilado. El LLM, (que se imparte en Ollama) razona y opcionalmente utiliza herramientas. Si es necesario, llama a la herramienta de búsqueda vectorial que hemos definido en el gráfico, para encontrar fragmentos de documentos relevantes a los que el usuario puede acceder.
Luego, el agente incorpora esos hallazgos en su razonamiento y decide si tiene suficiente información para brindar una respuesta adecuada. La respuesta del agente se genera de forma incremental y se transmite al usuario para una experiencia de chat fluida y en tiempo real.

En este punto, el usuario está chateando con su propio agente privado y totalmente local que está equipado con la capacidad de buscar semánticamente en sus notas personales.

6) demostración

Veamos cómo se ve esto en la práctica.
He subido un documento de Word con el siguiente contenido:

Notas El 21 de noviembre hablé con un tipo llamado “Gert Vektorman” que resultó ser un desarrollador en una empresa de Groningen llamada “super soluciones de datos”. Resulta que estaba muy interesado en implementar RAG agente en su empresa. Hemos acordado reunirnos a finales de diciembre. Editar: le pregunté a Gert cuál era su lenguaje de programación favorito; Le gusta usar Python Edit: nos reunimos y acordamos crear una implementación de prueba. Llamaremos a este proyecto “proyecto greenfield”

Iré al front-end y subiré este archivo.

El archivo de notas se sube al sistema (imagen del autor)

Después de cargar, puedo ver en el front-end que:

el documento está almacenado en la base de datos se ha incrustado mi agente tiene acceso a él

Ahora charlemos.

Nuestro agente es capaz de buscar de forma autónoma información relevante a la que tiene acceso (imagen del autor)

Como ve, el agente puede responder con la información de nuestro expediente. También es sorprendentemente rápido; Esta pregunta fue respondida en unos segundos.

Conclusión

Me encantan los desafíos que me permiten experimentar con nuevas tecnologías y trabajar en toda la pila, desde la base de datos hasta los gráficos de agentes y el front-end hasta las imágenes de la ventana acoplable. Diseñar el sistema y elegir una arquitectura funcional es algo que siempre disfruto. Me permite convertir nuestros objetivos en requisitos, flujos, arquitectura, componentes, código y, finalmente, en un producto funcional.

El desafío de esta semana fue exactamente eso: explorar y experimentar con RAG privado, multiusuario y agente. He creado un prototipo funcional, ampliable, reutilizable y escalable que se puede mejorar en el futuro. En la mayoría de los casos, he descubierto que son posibles los LLM locales, 100% privados y agentes.

Aprendizajes técnicos

Postgres + pgvector es poderoso. El almacenamiento de incrustaciones junto con metadatos relacionales mantuvo todo limpio, consistente y fácil de consultar, ya que no había necesidad de una base de datos vectorial adicional. LangGraph hace que sea sorprendentemente fácil definir el flujo de trabajo de un agente, equiparlo con herramientas y dejar que el agente decida cuándo usarlas. Son factibles agentes privados, locales y autohospedados. Con Ollama ejecutando dos modelos livianos (uno para chat y otro para incrustaciones), todo se ejecuta en mi MacBook con una velocidad impresionante. Construir un sistema multi-inquilino con aislamiento de datos estricto fue mucho más fácil una vez que la arquitectura estuvo limpia y las responsabilidades se separaron entre los componentes. El acoplamiento flojo hace que sea más fácil reemplazar y escalar componentes.

Próximos pasos

Este sistema está listo para actualizaciones:

Reinserción incremental para documentos que cambian con el tiempo
(para poder conectar mi bóveda de obsidiana sin problemas). Citas que dirigen al usuario a los archivos/páginas/fragmentos exactos que utilizó el LLM para responder mi pregunta, lo que mejora la confianza y la explicabilidad. Más herramientas para el agente: desde resúmenes estructurados hasta acceso SQL. ¿Quizás incluso ontologías o perfiles de usuario? Una interfaz más rica con mejor gestión de archivos y experiencia de usuario

Espero que este artículo haya sido tan claro como pretendía, pero si este no es el caso, hágame saber qué puedo hacer para aclararlo más. Mientras tanto, consulte mis otros artículos sobre todo tipo de temas relacionados con la programación.

¡Feliz codificación!

—Mike

Pd: ¿te gusta lo que estoy haciendo? ¡Sígueme!