From d859a35464941d8a02b1eb01fbb0c970fc3e80ff Mon Sep 17 00:00:00 2001 From: Chilton Liang <2298185568@qq.com> Date: Thu, 8 Jan 2026 22:54:34 +0800 Subject: [PATCH 1/4] fix: Qdrant empty when using neo4j-community --- src/memos/graph_dbs/neo4j_community.py | 102 +++++++++++++++++++++++++ 1 file changed, 102 insertions(+) diff --git a/src/memos/graph_dbs/neo4j_community.py b/src/memos/graph_dbs/neo4j_community.py index e943616da..8cf92e4c9 100644 --- a/src/memos/graph_dbs/neo4j_community.py +++ b/src/memos/graph_dbs/neo4j_community.py @@ -104,6 +104,108 @@ def add_node( metadata=metadata, ) + def add_nodes_batch(self, nodes: list[dict[str, Any]], user_name: str | None = None) -> None: + + if not nodes: + logger.warning("[add_nodes_batch] Empty nodes list, skipping") + return + + effective_user_name = user_name if user_name else self.config.user_name + + vec_items: list[VecDBItem] = [] + prepared_nodes: list[dict[str, Any]] = [] + + for node_data in nodes: + try: + node_id = node_data.get("id") + memory = node_data.get("memory") + metadata = node_data.get("metadata", {}) + + if node_id is None or memory is None: + logger.warning("[add_nodes_batch] Skip invalid node: missing id/memory") + continue + + if not self.config.use_multi_db and (self.config.user_name or effective_user_name): + metadata["user_name"] = effective_user_name + + metadata = _prepare_node_metadata(metadata) + + embedding = metadata.pop("embedding", None) + if embedding is None: + raise ValueError(f"Missing 'embedding' in metadata for node {node_id}") + + vector_sync_status = "success" + vec_items.append( + VecDBItem( + id=node_id, + vector=embedding, + payload={ + "memory": memory, + "vector_sync": vector_sync_status, + **metadata, + }, + ) + ) + + created_at = metadata.pop("created_at") + updated_at = metadata.pop("updated_at") + metadata["vector_sync"] = vector_sync_status + + prepared_nodes.append( + { + "id": node_id, + "memory": memory, + "created_at": created_at, + "updated_at": updated_at, + "metadata": metadata, + } + ) + except Exception as e: + logger.error( + f"[add_nodes_batch] Failed to prepare node {node_data.get('id', 'unknown')}: {e}", + exc_info=True, + ) + continue + + if not prepared_nodes: + logger.warning("[add_nodes_batch] No valid nodes to insert after preparation") + return + + try: + self.vec_db.add(vec_items) + except Exception as e: + logger.warning(f"[VecDB] batch insert failed: {e}") + for node in prepared_nodes: + node["metadata"]["vector_sync"] = "failed" + + query = """ + UNWIND $nodes AS node + MERGE (n:Memory {id: node.id}) + SET n.memory = node.memory, + n.created_at = datetime(node.created_at), + n.updated_at = datetime(node.updated_at), + n += node.metadata + """ + + nodes_data = [ + { + "id": node["id"], + "memory": node["memory"], + "created_at": node["created_at"], + "updated_at": node["updated_at"], + "metadata": node["metadata"], + } + for node in prepared_nodes + ] + + try: + with self.driver.session(database=self.db_name) as session: + session.run(query, nodes=nodes_data) + logger.info(f"[add_nodes_batch] Successfully inserted {len(prepared_nodes)} nodes") + except Exception as e: + logger.error(f"[add_nodes_batch] Failed to add nodes: {e}", exc_info=True) + raise + def get_children_with_embeddings( self, id: str, user_name: str | None = None ) -> list[dict[str, Any]]: From c46c9e59638fe6e50b8ecac5cfb149100acc12aa Mon Sep 17 00:00:00 2001 From: Chilton Liang <2298185568@qq.com> Date: Fri, 9 Jan 2026 01:24:55 +0800 Subject: [PATCH 2/4] fix: Qdrant empty when using neo4j-community --- src/memos/graph_dbs/neo4j_community.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/memos/graph_dbs/neo4j_community.py b/src/memos/graph_dbs/neo4j_community.py index 8cf92e4c9..2085b8a7a 100644 --- a/src/memos/graph_dbs/neo4j_community.py +++ b/src/memos/graph_dbs/neo4j_community.py @@ -13,7 +13,31 @@ logger = get_logger(__name__) +def _flatten_info_fields(metadata: dict[str, Any]) -> dict[str, Any]: + """ + Flatten the 'info' field in metadata to the top level. + + If metadata contains an 'info' field that is a dictionary, all its key-value pairs + will be moved to the top level of metadata, and the 'info' field will be removed. + + Args: + metadata: Dictionary that may contain an 'info' field + Returns: + Dictionary with 'info' fields flattened to top level + + Example: + Input: {"user_id": "xxx", "info": {"A": "value1", "B": "value2"}} + Output: {"user_id": "xxx", "A": "value1", "B": "value2"} + """ + if "info" in metadata and isinstance(metadata["info"], dict): + # Copy info fields to top level + info_dict = metadata.pop("info") + for key, value in info_dict.items(): + # Only add if key doesn't already exist at top level (to avoid overwriting) + if key not in metadata: + metadata[key] = value + return metadata class Neo4jCommunityGraphDB(Neo4jGraphDB): """ Neo4j Community Edition graph memory store. @@ -129,6 +153,7 @@ def add_nodes_batch(self, nodes: list[dict[str, Any]], user_name: str | None = N metadata["user_name"] = effective_user_name metadata = _prepare_node_metadata(metadata) + metadata = _flatten_info_fields(metadata) embedding = metadata.pop("embedding", None) if embedding is None: From a4df382a1726d85c34d0994c96eef75052574a64 Mon Sep 17 00:00:00 2001 From: Chilton Liang <2298185568@qq.com> Date: Fri, 9 Jan 2026 01:35:04 +0800 Subject: [PATCH 3/4] fix: Qdrant empty when using neo4j-community --- src/memos/graph_dbs/neo4j_community.py | 27 +------------------------- 1 file changed, 1 insertion(+), 26 deletions(-) diff --git a/src/memos/graph_dbs/neo4j_community.py b/src/memos/graph_dbs/neo4j_community.py index 2085b8a7a..12ea681da 100644 --- a/src/memos/graph_dbs/neo4j_community.py +++ b/src/memos/graph_dbs/neo4j_community.py @@ -5,7 +5,7 @@ from typing import Any from memos.configs.graph_db import Neo4jGraphDBConfig -from memos.graph_dbs.neo4j import Neo4jGraphDB, _prepare_node_metadata +from memos.graph_dbs.neo4j import Neo4jGraphDB, _prepare_node_metadata, _flatten_info_fields from memos.log import get_logger from memos.vec_dbs.factory import VecDBFactory from memos.vec_dbs.item import VecDBItem @@ -13,31 +13,6 @@ logger = get_logger(__name__) -def _flatten_info_fields(metadata: dict[str, Any]) -> dict[str, Any]: - """ - Flatten the 'info' field in metadata to the top level. - - If metadata contains an 'info' field that is a dictionary, all its key-value pairs - will be moved to the top level of metadata, and the 'info' field will be removed. - - Args: - metadata: Dictionary that may contain an 'info' field - - Returns: - Dictionary with 'info' fields flattened to top level - - Example: - Input: {"user_id": "xxx", "info": {"A": "value1", "B": "value2"}} - Output: {"user_id": "xxx", "A": "value1", "B": "value2"} - """ - if "info" in metadata and isinstance(metadata["info"], dict): - # Copy info fields to top level - info_dict = metadata.pop("info") - for key, value in info_dict.items(): - # Only add if key doesn't already exist at top level (to avoid overwriting) - if key not in metadata: - metadata[key] = value - return metadata class Neo4jCommunityGraphDB(Neo4jGraphDB): """ Neo4j Community Edition graph memory store. From 545b835c7af0e557f4273d669df19795b3f484ae Mon Sep 17 00:00:00 2001 From: OhhhhPi <2298185568@qq.com> Date: Wed, 14 Jan 2026 12:35:21 +0800 Subject: [PATCH 4/4] format --- src/memos/graph_dbs/neo4j_community.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/memos/graph_dbs/neo4j_community.py b/src/memos/graph_dbs/neo4j_community.py index 12ea681da..f0be3d858 100644 --- a/src/memos/graph_dbs/neo4j_community.py +++ b/src/memos/graph_dbs/neo4j_community.py @@ -5,7 +5,7 @@ from typing import Any from memos.configs.graph_db import Neo4jGraphDBConfig -from memos.graph_dbs.neo4j import Neo4jGraphDB, _prepare_node_metadata, _flatten_info_fields +from memos.graph_dbs.neo4j import Neo4jGraphDB, _flatten_info_fields, _prepare_node_metadata from memos.log import get_logger from memos.vec_dbs.factory import VecDBFactory from memos.vec_dbs.item import VecDBItem @@ -13,6 +13,7 @@ logger = get_logger(__name__) + class Neo4jCommunityGraphDB(Neo4jGraphDB): """ Neo4j Community Edition graph memory store. @@ -104,7 +105,6 @@ def add_node( ) def add_nodes_batch(self, nodes: list[dict[str, Any]], user_name: str | None = None) -> None: - if not nodes: logger.warning("[add_nodes_batch] Empty nodes list, skipping") return