DEV Community

Cover image for Memoria de Grafo: Cuando la Búsqueda Vectorial Falla
Elizabeth Fuentes L for AWS Español

Posted on

Memoria de Grafo: Cuando la Búsqueda Vectorial Falla

📦 Clona y dale ⭐ a stop-ai-agents-losing-memory-sample-for-aws

Arquitectura de memoria de grafo: Strands agent toma dos caminos — recall_semantic devuelve piezas (1/4), recall_graph atraviesa Maya Torres → Iberia → Madrid → Spain (4/4)

Los agentes de IA acumulan hechos a través de conversaciones. La memoria key-value los almacena como blobs etiquetados. La memoria vectorial los recupera por significado. Ninguna puede responder una pregunta que atraviesa múltiples hechos conectados por relaciones. La memoria de grafo cierra esta brecha almacenando memorias como nodos y edges tipados.

Este post usa un asistente de viajes como demo, pero la falla es estructural, no específica de viajes. Aparece en cualquier agente que acumula hechos sobre personas, lugares, productos o eventos con el tiempo. Eventualmente un usuario pregunta algo que solo puede responderse siguiendo los edges entre hechos. Y no hay edges que seguir.

La misma brecha estructural causa que los agentes alucinen respuestas a preguntas de conteo y agregación. En RAG vs GraphRAG, medí un asistente hotelero que no podía responder "¿cuántos hoteles aceptan mascotas?" sin inventar estadísticas, porque no tenía un grafo sobre el cual computar. Aquí la falla es recuperación multi-hop, pero la causa raíz es la misma: no hay edges que seguir.

Así se ve esa falla en una ejecución real, con el asistente de viajes después de acumular cuatro hechos sobre su usuario:

Hechos en memoria:
  Maya Torres trabaja en Iberia.
  Iberia vuela a Madrid.
  Madrid está en España.
  Iberia pertenece a Oneworld.

Pregunta: "¿A quién conozco conectado con vuelos a España?"

Top-3 resultados de similitud vectorial:
  - Iberia. Una aerolínea.
  - Spain. Un país.
  - Madrid. Una ciudad.

(La similitud vectorial buscó conceptos similares a "flights" y "Spain" en la pregunta.
El nombre de la persona "Maya Torres" no tiene similitud semántica con esas palabras clave.)

¿Recupera a la persona (Maya Torres)? Falso
Enter fullscreen mode Exit fullscreen mode

La similitud encontró cada pieza. Nunca encontró la persona, porque un índice vectorial no tiene noción de relación entre sus entradas. La memoria de grafo soluciona esto almacenando memorias como nodos y edges tipados, así la respuesta se alcanza por traversal en lugar de similitud. Este post lo construye con Neo4j, mide las mismas cuatro preguntas contra ambos retrievers (1/4 vs 4/4), y muestra la técnica de prompting que hace que un asistente de IA lo construya correctamente. Todo corre desde el repo companion.

(Post 3 de una serie; el intro mapea todos los tipos de memoria. Esta es la demo más avanzada hasta ahora: asume los posts anteriores y una instancia Neo4j. El código usa Strands Agents; el patrón aplica a cualquier framework de agentes.)


¿Por qué Strands Agents para este demo?

Strands hace simple agregar memoria de grafo a un agente. Crear un agente es solo unas pocas líneas de código, y las tools son funciones con un decorador:

from strands import Agent, tool

@tool
def recall_graph(query: str) -> str:
    """Busca en memoria de grafo atravesando relaciones."""
    return graph_retriever.search(query)

agent = Agent(
    model=model,
    tools=[recall_graph, recall_semantic, remember_fact],
)
Enter fullscreen mode Exit fullscreen mode

Eso es todo. Sin integraciones custom, sin lock-in de framework. El decorador @tool es todo lo que necesitás para conectar retrievers de Neo4j al agente. Cuando book_flight se ejecuta, escribe edges directamente al grafo, y el knowledge graph crece con el uso.

El patrón mostrado aquí (grafo externo + acceso basado en tools) funciona en cualquier framework de agentes. Strands solo lo hace directo.


¿Qué es una pregunta multi-hop?

Una pregunta cuya respuesta no vive en una sola memoria, solo en la cadena entre varias. Almacenados como grafo, los cuatro hechos del asistente forman uno:

(Maya Torres) ──WORKS_AT──▶ (Iberia) ──MEMBER_OF──▶ (Oneworld)
                                 
                            FLIES_TO
                                 
                             (Madrid) ──IN_COUNTRY──▶ (Spain)
Enter fullscreen mode Exit fullscreen mode

"¿A quién conozco conectado con vuelos a España?" requiere tres saltos: persona → aerolínea → ciudad → país. La memoria key-value no puede expresarlo (ninguna key es "la cadena"). La memoria vectorial recupera los tres fragmentos más similares y se detiene. Solo un store que mantiene los edges puede caminarlos.

Pregunta multi-hop sobre memoria del agente: la similitud vectorial superficializa Iberia, Madrid y España como piezas desconectadas, el traversal de grafo camina los edges de vuelta a Maya Torres


¿Cómo lo responde la memoria de grafo?

En dos movimientos: la similitud encuentra el punto de entrada, el traversal encuentra la respuesta. Ambos retrievers en la demo son clases oficiales neo4j-graphrag, compartiendo el mismo grafo y el mismo índice vectorial. La única variable es si los edges se caminan:

from neo4j_graphrag.retrievers import VectorRetriever, VectorCypherRetriever

# Antes: similitud pura — devuelve los nodos más cercanos, desconectados
before = VectorRetriever(driver, "memory_embeddings", embedder=embedder)

# Después: similitud encuentra un nodo de entrada, luego Cypher camina de vuelta a la persona
RETRIEVAL_QUERY = """
WITH node AS entry, score
MATCH (person:Person) WHERE person <> entry
MATCH path = shortestPath((person)-[*1..5]-(entry))
RETURN person.name AS who, [n IN nodes(path) | n.name] AS chain, max(score) AS score
ORDER BY score DESC
"""
after = VectorCypherRetriever(driver, "memory_embeddings",
                              RETRIEVAL_QUERY, embedder=embedder)
Enter fullscreen mode Exit fullscreen mode

Misma pregunta, segundo retriever, misma ejecución:

- who='Maya Torres' chain=['Maya Torres', 'Iberia'] score=0.77
- who='Maya Torres' chain=['Maya Torres', 'Iberia', 'Madrid', 'Spain'] score=0.71

¿Recupera a la persona (Maya Torres)? Verdadero
Enter fullscreen mode Exit fullscreen mode

Notá lo que el grafo agrega más allá de la respuesta: la cadena. Cada resultado lleva el path que lo produjo (Maya → Iberia → Madrid → Spain). Ese recibo es lo que hace la memoria de grafo trazable, y se convierte en la estrella de un post posterior sobre auditoría de decisiones del agente.


¿Qué muestran los resultados medidos?

Cuatro preguntas multi-hop, ambos retrievers, verificados determinísticamente contra el grafo conocido (sin judge LLM, así los números se reproducen):

Pregunta Similitud vectorial Traversal de grafo
¿A quién conozco conectado con vuelos a España?
¿A quién conozco conectado con una aerolínea que vuela a Madrid?
¿Quién trabaja en la aerolínea Oneworld que conozco?
¿Qué persona está vinculada con aerolíneas en España?
Total 1/4 4/4

El que la similitud acertó vale la pena pausar: en la pregunta 3 el nodo persona resultó ranquear alto por similitud sola. La similitud no siempre está mal en preguntas multi-hop; es no confiable, mientras que el traversal es consistente. Ese es el hallazgo real, y coincide con lo que la investigación de memoria de grafo mide a escala (MAGMA, GAAMA, Zep).

El agente también escribe de vuelta: cuando se le dice "recordá que Maya trabaja en Iberia", el agente Strands llama una tool remember_fact que hace MERGE del edge en Neo4j y lo loggea en agent.state. La memoria crece como un grafo, un hecho por conversación.


¿Cuándo es un grafo la elección equivocada?

Cuando tus memorias son notas independientes. Un grafo de nodos desconectados es un key-value store lento con pasos extras, además una base de datos para correr y un schema sobre el cual pensar. Evitá un grafo cuando nada en tus preguntas cruza más de un hecho. La línea de decisión honesta, extendiendo la tabla de la serie:

Necesitás Elegí Por qué
Hechos bajo keys conocidas Key-value (post 1) Exacto, instantáneo, cero infraestructura
Búsqueda por significado sobre notas independientes Vector (post 2) La similitud es suficiente cuando nada se conecta
Preguntas que saltan a través de relaciones Grafo (este post) Solo edges responden preguntas en cadena, con recibos

Dos costos honestos más: diseñás el schema (cada tipo de edge debe ganarse una pregunta real: modelá "¿a quién conozco en X?", no todo), y la conectividad corta en ambas direcciones, porque un hecho erróneo contamina cada traversal que lo cruza. Ese problema de blast-radius tiene su propio post (memory hygiene).


¿Cómo probarlo?

Todo corre desde Demo 03 del repo companion. Necesitás Neo4j corriendo (Desktop, Docker, o tier gratuito Aura) y OPENAI_API_KEY. El README también documenta un gotcha real de churn de versiones (neo4j-graphrag 1.18 emite la cláusula SEARCH de Cypher 25, que falla en servidores que aún defaultean a Cypher 5) y cómo la demo lo maneja automáticamente.


Investigación referenciada

Reproducimos el mecanismo que estos papers describen (memoria estructurada como grafo + traversal de relaciones), no sus números específicos de benchmark.


¿Cuál de las cinco reglas de prompting te sorprendió más? Compartí en los comentarios.


¡Gracias!

🇻🇪 Dev.to Linkedin GitHub Twitter Instagram Youtube

Top comments (0)