Skip to content

Commit 2189062

Browse files
fix: reorganizer bug (#64)
* fix: reorganizer bug * refactor: update conflict redundancy handling to archive nodes instead of deleting * fix: mute redundancy resolution --------- Co-authored-by: dany <[email protected]>
1 parent d780be1 commit 2189062

File tree

3 files changed

+34
-51
lines changed

3 files changed

+34
-51
lines changed

src/memos/memories/textual/tree_text_memory/organize/conflict.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -167,10 +167,12 @@ def _resolve_in_graph(
167167
if not self.graph_store.edge_exists(new_from, new_to, edge["type"], direction="ANY"):
168168
self.graph_store.add_edge(new_from, new_to, edge["type"])
169169

170-
self.graph_store.delete_node(conflict_a.id)
171-
self.graph_store.delete_node(conflict_b.id)
170+
self.graph_store.update_node(conflict_a.id, {"status": "archived"})
171+
self.graph_store.update_node(conflict_b.id, {"status": "archived"})
172+
self.graph_store.add_edge(conflict_a.id, merged.id, type="MERGED_TO")
173+
self.graph_store.add_edge(conflict_b.id, merged.id, type="MERGED_TO")
172174
logger.debug(
173-
f"Remove {conflict_a.id} and {conflict_b.id}, and inherit their edges to {merged.id}."
175+
f"Archive {conflict_a.id} and {conflict_b.id}, and inherit their edges to {merged.id}."
174176
)
175177

176178
def _merge_metadata(

src/memos/memories/textual/tree_text_memory/organize/redundancy.py

Lines changed: 25 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ def detect(
3030
self, memory: TextualMemoryItem, top_k: int = 5, scope: str | None = None
3131
) -> list[tuple[TextualMemoryItem, TextualMemoryItem]]:
3232
"""
33-
Detect redundancy by finding the most similar items in the graph database based on embedding, then use LLM to judge conflict.
33+
Detect redundancy by finding the most similar items in the graph database based on embedding, then use LLM to judge redundancy.
3434
Args:
3535
memory: The memory item (should have an embedding attribute or field).
3636
top_k: Number of top similar nodes to retrieve.
@@ -49,15 +49,15 @@ def detect(
4949
for info in embedding_candidates_info
5050
if info["score"] >= self.EMBEDDING_THRESHOLD and info["id"] != memory.id
5151
]
52-
# 3. Judge conflicts using LLM
52+
# 3. Judge redundancys using LLM
5353
embedding_candidates = self.graph_store.get_nodes(embedding_candidates_ids)
5454
redundant_pairs = []
5555
for embedding_candidate in embedding_candidates:
5656
embedding_candidate = TextualMemoryItem.from_dict(embedding_candidate)
5757
prompt = [
5858
{
5959
"role": "system",
60-
"content": "You are a conflict detector for memory items.",
60+
"content": "You are a redundancy detector for memory items.",
6161
},
6262
{
6363
"role": "user",
@@ -71,25 +71,25 @@ def detect(
7171
if "yes" in result.lower():
7272
redundant_pairs.append([memory, embedding_candidate])
7373
if len(redundant_pairs):
74-
conflict_text = "\n".join(
74+
redundant_text = "\n".join(
7575
f'"{pair[0].memory!s}" <==REDUNDANCY==> "{pair[1].memory!s}"'
7676
for pair in redundant_pairs
7777
)
7878
logger.warning(
79-
f"Detected {len(redundant_pairs)} redundancies for memory {memory.id}\n {conflict_text}"
79+
f"Detected {len(redundant_pairs)} redundancies for memory {memory.id}\n {redundant_text}"
8080
)
8181
return redundant_pairs
8282

8383
def resolve_two_nodes(self, memory_a: TextualMemoryItem, memory_b: TextualMemoryItem) -> None:
8484
"""
8585
Resolve detected redundancies between two memory items using LLM fusion.
8686
Args:
87-
memory_a: The first conflicting memory item.
88-
memory_b: The second conflicting memory item.
87+
memory_a: The first redundant memory item.
88+
memory_b: The second redundant memory item.
8989
Returns:
9090
A fused TextualMemoryItem representing the resolved memory.
9191
"""
92-
92+
return # waiting for implementation
9393
# ———————————— 1. LLM generate fused memory ————————————
9494
metadata_for_resolve = ["key", "background", "confidence", "updated_at"]
9595
metadata_1 = memory_a.metadata.model_dump_json(include=metadata_for_resolve)
@@ -115,18 +115,10 @@ def resolve_two_nodes(self, memory_a: TextualMemoryItem, memory_b: TextualMemory
115115
try:
116116
answer = re.search(r"<answer>(.*?)</answer>", response, re.DOTALL)
117117
answer = answer.group(1).strip()
118-
# —————— 2.1 Can't resolve conflict, hard update by comparing timestamp ————
119-
if len(answer) <= 10 and "no" in answer.lower():
120-
logger.warning(
121-
f"Conflict between {memory_a.id} and {memory_b.id} could not be resolved. "
122-
)
123-
self._hard_update(memory_a, memory_b)
124-
# —————— 2.2 Conflict resolved, update metadata and memory ————
125-
else:
126-
fixed_metadata = self._merge_metadata(answer, memory_a.metadata, memory_b.metadata)
127-
merged_memory = TextualMemoryItem(memory=answer, metadata=fixed_metadata)
128-
logger.info(f"Resolved result: {merged_memory}")
129-
self._resolve_in_graph(memory_a, memory_b, merged_memory)
118+
fixed_metadata = self._merge_metadata(answer, memory_a.metadata, memory_b.metadata)
119+
merged_memory = TextualMemoryItem(memory=answer, metadata=fixed_metadata)
120+
logger.info(f"Resolved result: {merged_memory}")
121+
self._resolve_in_graph(memory_a, memory_b, merged_memory)
130122
except json.decoder.JSONDecodeError:
131123
logger.error(f"Failed to parse LLM response: {response}")
132124

@@ -145,48 +137,37 @@ def resolve_one_node(self, memory: TextualMemoryItem) -> None:
145137
)
146138
logger.debug(f"Merged memory: {memory.memory}")
147139

148-
def _hard_update(self, memory_a: TextualMemoryItem, memory_b: TextualMemoryItem):
149-
"""
150-
Hard update: compare updated_at, keep the newer one, overwrite the older one's metadata.
151-
"""
152-
time_a = datetime.fromisoformat(memory_a.metadata.updated_at)
153-
time_b = datetime.fromisoformat(memory_b.metadata.updated_at)
154-
155-
newer_mem = memory_a if time_a >= time_b else memory_b
156-
older_mem = memory_b if time_a >= time_b else memory_a
157-
158-
self.graph_store.delete_node(older_mem.id)
159-
logger.warning(
160-
f"Delete older memory {older_mem.id}: <{older_mem.memory}> due to conflict with {newer_mem.id}: <{newer_mem.memory}>"
161-
)
162-
163140
def _resolve_in_graph(
164141
self,
165-
conflict_a: TextualMemoryItem,
166-
conflict_b: TextualMemoryItem,
142+
redundant_a: TextualMemoryItem,
143+
redundant_b: TextualMemoryItem,
167144
merged: TextualMemoryItem,
168145
):
169-
edges_a = self.graph_store.get_edges(conflict_a.id, type="ANY", direction="ANY")
170-
edges_b = self.graph_store.get_edges(conflict_b.id, type="ANY", direction="ANY")
146+
edges_a = self.graph_store.get_edges(redundant_a.id, type="ANY", direction="ANY")
147+
edges_b = self.graph_store.get_edges(redundant_b.id, type="ANY", direction="ANY")
171148
all_edges = edges_a + edges_b
172149

173150
self.graph_store.add_node(
174151
merged.id, merged.memory, merged.metadata.model_dump(exclude_none=True)
175152
)
176153

177154
for edge in all_edges:
178-
new_from = merged.id if edge["from"] in (conflict_a.id, conflict_b.id) else edge["from"]
179-
new_to = merged.id if edge["to"] in (conflict_a.id, conflict_b.id) else edge["to"]
155+
new_from = (
156+
merged.id if edge["from"] in (redundant_a.id, redundant_b.id) else edge["from"]
157+
)
158+
new_to = merged.id if edge["to"] in (redundant_a.id, redundant_b.id) else edge["to"]
180159
if new_from == new_to:
181160
continue
182161
# Check if the edge already exists before adding
183162
if not self.graph_store.edge_exists(new_from, new_to, edge["type"], direction="ANY"):
184163
self.graph_store.add_edge(new_from, new_to, edge["type"])
185164

186-
self.graph_store.delete_node(conflict_a.id)
187-
self.graph_store.delete_node(conflict_b.id)
165+
self.graph_store.update_node(redundant_a.id, {"status": "archived"})
166+
self.graph_store.update_node(redundant_b.id, {"status": "archived"})
167+
self.graph_store.add_edge(redundant_a.id, merged.id, type="MERGED_TO")
168+
self.graph_store.add_edge(redundant_b.id, merged.id, type="MERGED_TO")
188169
logger.debug(
189-
f"Remove {conflict_a.id} and {conflict_b.id}, and inherit their edges to {merged.id}."
170+
f"Archive {redundant_a.id} and {redundant_b.id}, and inherit their edges to {merged.id}."
190171
)
191172

192173
def _merge_metadata(

src/memos/memories/textual/tree_text_memory/organize/reorganizer.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -164,9 +164,9 @@ def handle_add(self, message: QueueMessage):
164164
logger.info(f"Resolved conflict between {added_node.id} and {existing_node.id}.")
165165

166166
# ———————— 2. check for redundancy ————————
167-
redundancy = self.redundancy.detect(added_node, scope=added_node.metadata.memory_type)
168-
if redundancy:
169-
for added_node, existing_node in redundancy:
167+
redundancies = self.redundancy.detect(added_node, scope=added_node.metadata.memory_type)
168+
if redundancies:
169+
for added_node, existing_node in redundancies:
170170
self.redundancy.resolve_two_nodes(added_node, existing_node)
171171
logger.info(f"Resolved redundancy between {added_node.id} and {existing_node.id}.")
172172

@@ -176,7 +176,7 @@ def handle_remove(self, message: QueueMessage):
176176
def handle_merge(self, message: QueueMessage):
177177
after_node = message.after_node[0]
178178
logger.debug(f"Handling merge operation: <{after_node.memory}>")
179-
self.redundancy_resolver.resolve_one_node(after_node)
179+
self.redundancy.resolve_one_node(after_node)
180180

181181
def optimize_structure(
182182
self,

0 commit comments

Comments
 (0)