Cuando un grafo no debe recordarlo todo: memoria híbrida para un agente personal en .NET
Este artículo forma parte del pilar IA empresarial y sistemas de agentes.
Durante un tiempo consulté mis exámenes médicos de la forma más directa posible: le entregaba los archivos a ChatGPT o a la IA que estuviera usando y hacía mis preguntas.
Funcionaba, pero cada examen nuevo hacía crecer el problema. Había que volver a extraer información de imágenes y PDF, el contexto consumía más tokens y la respuesta no siempre priorizaba el resultado más reciente. Podía reconocer que dos hematologías hablaban de los mismos indicadores, pero eso no garantizaba que entendiera bien su evolución.
Terminé construyendo una solución con un grafo temporal. Después descubrí que tampoco tenía sentido guardar cada mensaje de la conversación dentro de ese grafo. Mi solución final fue menos pura y bastante más útil: Graphiti y FalkorDB para el conocimiento médico histórico; un RAG más simple para la memoria conversacional.
Aviso: este artículo describe una experiencia personal de arquitectura de software. No es una recomendación médica, no interpreta resultados clínicos y no pretende sustituir la evaluación, el diagnóstico ni el tratamiento de un profesional de la salud. Ninguna decisión médica debería tomarse basándose únicamente en las respuestas de este agente.
Lo comparto porque es un buen ejemplo de cómo una arquitectura cambia cuando deja de ser un tutorial y empieza a encontrarse con datos, costos y decisiones reales.
El primer ahorro fue dejar de procesar los archivos una y otra vez
Mi primer problema no era todavía el RAG ni el grafo. Eran los documentos.
Los resultados venían en imágenes y PDF. Si se los entregaba directamente al modelo en cada conversación, repetía un trabajo que ya había hecho antes: OCR, interpretación del formato y extracción de valores. Además del consumo de tokens, cada nueva extracción podía introducir pequeñas diferencias.
Por eso separé la ingestión de la consulta. Procesé cada archivo una sola vez, extraje el contenido, normalicé los datos y retiré la información identificable antes de usarla en el flujo de IA. Los archivos originales siguen siendo la fuente, pero el agente trabaja con una representación textual y estructurada preparada de antemano.
Esa decisión parece pequeña, pero cambió el sistema. Ya no estaba preguntando sobre una colección de documentos heterogéneos. Estaba consultando datos que podían relacionarse y ordenarse.
Aquí también hay un límite importante: retirar nombres o identificadores directos no convierte automáticamente un conjunto de datos médicos en anónimo. La privacidad depende de qué otros atributos se conserven, dónde se procese la información y quién pueda acceder. En mi caso, la reducción y protección de esos datos forman parte del pipeline, no son una propiedad mágica del OCR.
Un RAG puede recuperar datos, pero el tiempo hay que modelarlo
El siguiente paso natural fue un RAG clásico. Troceas el contenido, generas embeddings y recuperas los fragmentos más parecidos a la pregunta.
Eso funciona muy bien cuando la pregunta es semántica: «¿qué decía el informe sobre determinado indicador?». El problema aparece cuando la intención real es temporal y relacional: «¿cómo evolucionó este valor?», «¿qué cambió desde el examen anterior?» o «¿cuál es el resultado vigente?».
Un RAG no es incapaz de responder esas preguntas. Puedes guardar fechas como metadata, filtrar, reordenar por recencia y diseñar una estrategia de recuperación específica. Pero si la relación entre exámenes, indicadores, episodios y fechas es central para el dominio, terminas reconstruyendo parte de ese modelo en cada consulta.
En mis pruebas, la similitud semántica no siempre coincidía con la prioridad que yo necesitaba. Un resultado anterior podía parecer más relevante por su redacción, aunque el examen reciente fuera el que debía dominar la respuesta. El problema no era que el vector store estuviera fallando. Yo le estaba pidiendo que resolviera una estructura temporal que no había modelado de forma explícita.
Graphiti resolvió la historia, no el costo de escribirla
Ahí entró Graphiti, un framework para construir grafos de contexto temporales. Lo ejecuté sobre FalkorDB, que actúa como base de datos de grafos.
La idea de los episodios encajaba especialmente bien con mis exámenes. En Graphiti, un episodio representa un evento de ingestión, conserva el momento de referencia y mantiene la procedencia de las entidades y relaciones que se derivaron de él. Los hechos pueden tener ventanas de validez, de modo que el sistema no solo sabe qué relación existe, sino cuándo era válida y qué información la reemplazó.
En vez de tener fragmentos aislados, pasé a tener conceptos conectados: examen, fecha, tipo de prueba, indicador, valor y relación con resultados anteriores. Eso hizo más naturales las preguntas históricas y redujo el trabajo necesario durante la recuperación.
Pero apareció la pregunta incómoda: ¿de verdad cada mensaje del chat merecía convertirse en conocimiento del grafo?
Agregar un episodio no es una simple escritura. Graphiti usa modelos para extraer entidades, resolver duplicados, construir relaciones y actualizar la dimensión temporal. Esa ingestión tiene un costo en tokens, latencia y cómputo. Para los exámenes históricos tenía sentido pagarlo una vez: eran datos valiosos, con relaciones duraderas y que no iban a cambiar retroactivamente.
Para una conversación cotidiana, no siempre.
Cuando intenté guardar cada intervención como un episodio, estaba pagando el proceso más costoso del grafo por mensajes cuya utilidad podía durar solo unos minutos. La recuperación era rápida, pero la escritura continua convertía la memoria conversacional en una decisión económicamente difícil de defender.
La arquitectura híbrida fue la respuesta pragmática
La solución no fue abandonar el grafo. Fue dejar de pedirle que almacenara todo.
Construí el grafo con el conjunto histórico de exámenes y lo mantuve como la fuente especializada para preguntas médicas temporales y relacionales. Para los mensajes de la conversación usé una memoria RAG más sencilla, pensada para recuperar contexto reciente o intercambios anteriores sin extraer un nuevo subgrafo en cada turno.

El flujo quedó dividido por la naturaleza del dato:
- Documentos médicos históricos: OCR, normalización, reducción de datos identificables e ingestión en Graphiti sobre FalkorDB.
- Conversaciones: fragmentación, embeddings y recuperación desde un índice más barato de actualizar.
- Consulta: el agente decide qué memoria necesita, recupera contexto de una o ambas fuentes y construye la respuesta.
Esta separación también deja una regla para el futuro. Un nuevo examen sí puede justificar un episodio porque agrega hechos que deben relacionarse con la historia anterior. Un «gracias» o una pregunta de seguimiento no necesita pasar por el mismo pipeline.
No es una arquitectura universal. Si tienes pocos documentos, pocas consultas y ninguna pregunta temporal, probablemente basta con cargar los archivos o usar un RAG bien diseñado. Tampoco usaría un grafo si no hay relaciones que valga la pena atravesar. El costo de construir y operar esa capa tiene que devolver algo concreto.
El agente sigue siendo software, y ahí .NET importa
Implementé la capa conversacional con Microsoft Agent Framework. El framework me permitió integrar el modelo, las herramientas y las dos fuentes de memoria desde .NET, sin convertir la solución en un conjunto de scripts desconectados.
Durante las pruebas consumí el agente desde una interfaz web de chat. Me servía para conversar con él y validar el comportamiento, pero esa no es la aplicación final que tengo en mente.
El destino real es una aplicación que construí para consumir varios agentes propios y que comentaré en otro artículo. En ese escenario elegí A2A porque necesito que la aplicación descubra las capacidades de cada agente y se comunique con todos mediante un contrato común. No lo agregué porque estuviera de moda ni porque mejorara la memoria: lo agregué porque existe una frontera real entre la aplicación cliente y varios agentes independientes.
Aquí conviene separar dos piezas que trabajan juntas. A2A define cómo se presentan y se comunican los agentes; OAuth 2.0 controla quién está autorizado para acceder y con qué permisos. En mi caso, la aplicación usa OAuth para obtener acceso a esos agentes, mientras A2A resuelve el intercambio entre ellos. La propia especificación de A2A permite declarar esquemas OAuth 2.0 dentro de la Agent Card.
Por eso A2A tiene sentido en este caso particular. Para probar un único agente desde un chat web habría podido usar un endpoint convencional. Cuando una aplicación debe integrarse con varios agentes, la interoperabilidad y la autorización dejan de ser detalles y pasan a formar parte de la arquitectura.
Lo menciono porque todavía aparece la idea de que trabajar con IA implica abandonar el ecosistema .NET. Para entrenamiento de modelos, Python mantiene una posición dominante. Para integrar agentes con servicios, persistencia, observabilidad, autenticación y protocolos, la conversación es otra.
Hoy hablamos más de modelos, agentes y protocolos que de lenguajes. Pero detrás de cada demo que llega a producción siguen existiendo decisiones de software. Alguien tiene que diseñar la ingestión, controlar el costo, proteger los datos, observar el sistema y decidir qué merece convertirse en memoria.
En mi caso, pude hacerlo con .NET y exponerlo sin encerrar el agente dentro de ese stack. Para mí, esa es una señal mucho más importante que cualquier comparación superficial de lenguajes.
La versión corta de esta historia podría sonar así: «construí un agente con Graphiti, FalkorDB, RAG y .NET». Pero lo valioso no fue conectar esas piezas.
Lo valioso fue descubrir dónde dejaba de compensar cada una.
El OCR repetido desperdiciaba trabajo. El RAG simple no expresaba por sí solo la historia que necesitaba consultar. El grafo temporal resolvió las relaciones y la vigencia, pero resultaba demasiado costoso para convertir cada mensaje en conocimiento estructurado. La arquitectura híbrida apareció cuando dejé de buscar una sola herramienta para todas las memorias.
Esa es la diferencia entre leer un tutorial y operar algo propio. En el tutorial todas las piezas parecen necesarias. En un sistema real, la pregunta es cuáles merecen seguir ahí cuando empiezas a pagar por cada decisión.
Si quieres seguir aprendiendo sobre estos temas te invito a ver mis otras publicaciones.
