Skip to content

Commit 6923c52

Browse files
authored
Merge branch 'dev' into feat/ark_embed_support
2 parents c6bfb7d + 2189062 commit 6923c52

File tree

13 files changed

+98
-75
lines changed

13 files changed

+98
-75
lines changed

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
[tool.poetry]
44
name = "MemoryOS"
5-
version = "0.1.13"
5+
version = "0.2.0"
66
description = "Intelligence Begins with Memory"
77
license = "Apache-2.0"
88
authors = ["MemTensor <[email protected]>"]

src/memos/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
__version__ = "0.1.13"
1+
__version__ = "0.2.0"
22

33
from memos.configs.mem_cube import GeneralMemCubeConfig
44
from memos.configs.mem_os import MOSConfig

src/memos/configs/llm.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ class OpenAILLMConfig(BaseLLMConfig):
2424
api_base: str = Field(
2525
default="https://api.openai.com/v1", description="Base URL for OpenAI API"
2626
)
27+
extra_body: Any = Field(default=None, description="extra body")
2728

2829

2930
class OllamaLLMConfig(BaseLLMConfig):

src/memos/configs/mem_reader.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ class BaseMemReaderConfig(BaseConfig):
2222
chunker: ChunkerConfigFactory = Field(
2323
..., description="Chunker configuration for the MemReader"
2424
)
25+
remove_prompt_example: bool = Field(
26+
default=False,
27+
description="whether remove example in memory extraction prompt to save token",
28+
)
2529

2630

2731
class SimpleStructMemReaderConfig(BaseMemReaderConfig):

src/memos/graph_dbs/neo4j.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -811,7 +811,7 @@ def drop_database(self) -> None:
811811

812812
def _ensure_database_exists(self):
813813
with self.driver.session(database="system") as session:
814-
session.run(f"CREATE DATABASE {self.db_name} IF NOT EXISTS")
814+
session.run(f"CREATE DATABASE $db_name IF NOT EXISTS", db_name=self.db_name)
815815

816816
# Wait until the database is available
817817
for _ in range(10):

src/memos/llms/openai.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ def generate(self, messages: MessageList) -> str:
2222
response = self.client.chat.completions.create(
2323
model=self.config.model_name_or_path,
2424
messages=messages,
25+
extra_body=self.config.extra_body,
2526
temperature=self.config.temperature,
2627
max_tokens=self.config.max_tokens,
2728
top_p=self.config.top_p,

src/memos/mem_reader/simple_struct.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import concurrent.futures
22
import copy
33
import json
4-
4+
import re
55
from abc import ABC
66
from typing import Any
77

@@ -17,6 +17,7 @@
1717
from memos.templates.mem_reader_prompts import (
1818
SIMPLE_STRUCT_DOC_READER_PROMPT,
1919
SIMPLE_STRUCT_MEM_READER_PROMPT,
20+
SIMPLE_STRUCT_MEM_READER_EXAMPLE,
2021
)
2122

2223

@@ -39,11 +40,11 @@ def __init__(self, config: SimpleStructMemReaderConfig):
3940
self.chunker = ChunkerFactory.from_config(config.chunker)
4041

4142
def _process_chat_data(self, scene_data_info, info):
42-
prompt = (
43-
SIMPLE_STRUCT_MEM_READER_PROMPT.replace("${user_a}", "user")
44-
.replace("${user_b}", "assistant")
45-
.replace("${conversation}", "\n".join(scene_data_info))
43+
prompt = SIMPLE_STRUCT_MEM_READER_PROMPT.replace(
44+
"${conversation}", "\n".join(scene_data_info)
4645
)
46+
if self.config.remove_prompt_example:
47+
prompt = prompt.replace(SIMPLE_STRUCT_MEM_READER_EXAMPLE, "")
4748

4849
messages = [{"role": "user", "content": prompt}]
4950

@@ -228,7 +229,11 @@ def _process_doc_data(self, scene_data_info, info):
228229

229230
def parse_json_result(self, response_text):
230231
try:
231-
response_text = response_text.replace("```", "").replace("json", "")
232+
json_start = response_text.find("{")
233+
response_text = response_text[json_start:]
234+
response_text = response_text.replace("```", "").strip()
235+
if response_text[-1] != "}":
236+
response_text += "}"
232237
response_json = json.loads(response_text)
233238
return response_json
234239
except json.JSONDecodeError as e:

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)