Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
50b419b
feat: update config
fridayL Jul 16, 2025
bceb141
fix:dim
fridayL Jul 16, 2025
1ad36c2
change dim
fridayL Jul 16, 2025
9a71fc0
fix:change default db
fridayL Jul 16, 2025
58addaf
fix:delay
fridayL Jul 16, 2025
1dc8d6f
fix:len
fridayL Jul 16, 2025
3eccccb
fix:change recently mem size
fridayL Jul 17, 2025
bb5ee43
fix:dup node error
fridayL Jul 17, 2025
13639a3
fix: remove mock_data
fridayL Jul 18, 2025
d8616e2
Merge branch 'dev' into feat/mos-product-api-0716
fridayL Jul 22, 2025
ea63a1c
fix: change config
fridayL Jul 22, 2025
81c87ca
feat: reorganize code
fridayL Jul 22, 2025
5b51bec
add: add json parse for en
fridayL Jul 22, 2025
5dece52
fix:change user_id
fridayL Jul 22, 2025
2b7dae8
fix: logger info
fridayL Jul 22, 2025
df8199f
fix: remove unsed change
fridayL Jul 23, 2025
fe9dad5
feat: add topk for api
fridayL Jul 23, 2025
fbb2ffa
Merge branch 'dev' into feat/mos-product-api-0716
fridayL Jul 23, 2025
b219f9b
feat: add logger
fridayL Jul 23, 2025
ae89138
fix:fix scheduler logs and
fridayL Jul 23, 2025
abf4802
add: fix max_user instances
fridayL Jul 23, 2025
b1d1491
Merge branch 'dev' into feat/mos-product-api-0716
fridayL Jul 23, 2025
81ae2ba
fix: logger for config and qa
fridayL Jul 24, 2025
668a8d7
feat: update add context
fridayL Jul 24, 2025
08120e5
fix:mv env to docker
fridayL Jul 24, 2025
9548550
fix: rm ref id for response for scheduler
fridayL Jul 24, 2025
f5fb6ce
add: status for product
fridayL Jul 24, 2025
3b03a83
Merge branch 'dev' into feat/mos-product-api-0716
fridayL Jul 24, 2025
2b2f085
Merge branch 'dev' into feat/mos-product-api-0716
fridayL Jul 24, 2025
8f75a69
Merge branch 'feat/mos-product-api-0716' of github-lcy:fridayL/MemOS …
fridayL Jul 24, 2025
29d8e97
feat: add internet config
fridayL Jul 24, 2025
4c8519a
fix: id is null
fridayL Jul 24, 2025
2a0cafa
add:internet search
fridayL Jul 24, 2025
64b637b
fix:add internet_search
fridayL Jul 24, 2025
b698b3d
fix:chunk
fridayL Jul 24, 2025
c46041e
fix: remove internet
fridayL Jul 24, 2025
100747c
fix: inter net bug
fridayL Jul 24, 2025
8f9cf76
fix: chunk len
fridayL Jul 24, 2025
4bf286a
fix: filed bug
fridayL Jul 24, 2025
ce34688
fix:fix ci and test
fridayL Jul 25, 2025
ba2e3ec
Merge branch 'feat/mos-product-api-0716' of github-lcy:fridayL/MemOS …
fridayL Jul 25, 2025
e42b0e1
fix: url and passwd
fridayL Jul 25, 2025
d3601ec
fix: input len
fridayL Jul 25, 2025
354f7f8
feat: change reorganizer time
fridayL Jul 25, 2025
0cf6dc4
feat:change settings and add env for change default .memos info
fridayL Jul 25, 2025
8d73979
Merge branch 'dev' into feat/mos-product-api-0716
fridayL Jul 25, 2025
30373e4
Merge branch 'dev' into feat/mos-product-api-0716
CaralHsi Jul 25, 2025
1197684
fix:add mem
fridayL Jul 25, 2025
de5e41f
Merge branch 'feat/mos-product-api-0716' of github-lcy:fridayL/MemOS …
fridayL Jul 25, 2025
e16b1d6
fix:datatime
fridayL Jul 25, 2025
b9c8917
fix: update
fridayL Jul 25, 2025
6eeecac
fix:ic and code test
fridayL Jul 25, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 59 additions & 3 deletions src/memos/api/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,48 @@ def get_embedder_config() -> dict[str, Any]:
},
}

@staticmethod
def get_internet_config() -> dict[str, Any]:
"""Get embedder configuration."""
return {
"backend": "xinyu",
"config": {
"api_key": os.getenv("XINYU_API_KEY"),
"search_engine_id": os.getenv("XINYU_SEARCH_ENGINE_ID"),
"max_results": 15,
"num_per_request": 10,
"reader": {
"backend": "simple_struct",
"config": {
"llm": {
"backend": "openai",
"config": {
"model_name_or_path": os.getenv("MEMRADER_MODEL"),
"temperature": 0.6,
"max_tokens": 5000,
"top_p": 0.95,
"top_k": 20,
"api_key": "EMPTY",
"api_base": os.getenv("MEMRADER_API_BASE"),
"remove_think_prefix": True,
"extra_body": {"chat_template_kwargs": {"enable_thinking": False}},
},
},
"embedder": APIConfig.get_embedder_config(),
"chunker": {
"backend": "sentence",
"config": {
"tokenizer_or_token_counter": "gpt2",
"chunk_size": 512,
"chunk_overlap": 128,
"min_sentences_per_chunk": 1,
},
},
},
},
},
}

@staticmethod
def get_neo4j_community_config(user_id: str | None = None) -> dict[str, Any]:
"""Get Neo4j community configuration."""
Expand Down Expand Up @@ -340,7 +382,6 @@ def create_user_config(user_name: str, user_id: str) -> tuple[MOSConfig, General
"top_k": 30,
"max_turns_window": 20,
}

# Add scheduler configuration if enabled
if APIConfig.is_scheduler_enabled():
config_dict["mem_scheduler"] = APIConfig.get_scheduler_config()
Expand All @@ -352,14 +393,19 @@ def create_user_config(user_name: str, user_id: str) -> tuple[MOSConfig, General

neo4j_community_config = APIConfig.get_neo4j_community_config(user_id)
neo4j_config = APIConfig.get_neo4j_config(user_id)

internet_config = (
APIConfig.get_internet_config()
if os.getenv("ENABLE_INTERNET", "false").lower() == "true"
else None
)
graph_db_backend_map = {
"neo4j-community": neo4j_community_config,
"neo4j": neo4j_config,
}
graph_db_backend = os.getenv("NEO4J_BACKEND", "neo4j-community").lower()
if graph_db_backend in graph_db_backend_map:
# Create MemCube config

default_cube_config = GeneralMemCubeConfig.model_validate(
{
"user_id": user_id,
Expand All @@ -374,6 +420,7 @@ def create_user_config(user_name: str, user_id: str) -> tuple[MOSConfig, General
"config": graph_db_backend_map[graph_db_backend],
},
"embedder": APIConfig.get_embedder_config(),
"internet_retriever": internet_config,
},
},
"act_mem": {}
Expand All @@ -384,7 +431,10 @@ def create_user_config(user_name: str, user_id: str) -> tuple[MOSConfig, General
)
else:
raise ValueError(f"Invalid Neo4j backend: {graph_db_backend}")

if os.getenv("ENABLE_INTERNET", "false").lower() == "true":
default_cube_config.text_mem.config["internet_retriever"] = (
APIConfig.get_internet_config()
)
default_mem_cube = GeneralMemCube(default_cube_config)
return default_config, default_mem_cube

Expand All @@ -405,6 +455,11 @@ def get_default_cube_config() -> GeneralMemCubeConfig | None:
"neo4j-community": neo4j_community_config,
"neo4j": neo4j_config,
}
internet_config = (
APIConfig.get_internet_config()
if os.getenv("ENABLE_INTERNET", "false").lower() == "true"
else None
)
graph_db_backend = os.getenv("NEO4J_BACKEND", "neo4j-community").lower()
if graph_db_backend in graph_db_backend_map:
return GeneralMemCubeConfig.model_validate(
Expand All @@ -423,6 +478,7 @@ def get_default_cube_config() -> GeneralMemCubeConfig | None:
"embedder": APIConfig.get_embedder_config(),
"reorganize": os.getenv("MOS_ENABLE_REORGANIZE", "false").lower()
== "true",
"internet_retriever": internet_config,
},
},
"act_mem": {}
Expand Down
1 change: 1 addition & 0 deletions src/memos/api/product_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ class ChatRequest(BaseRequest):
query: str = Field(..., description="Chat query message")
mem_cube_id: str | None = Field(None, description="Cube ID to use for chat")
history: list[MessageDict] | None = Field(None, description="Chat history")
internet_search: bool = Field(True, description="Whether to use internet search")


class UserCreate(BaseRequest):
Expand Down
1 change: 1 addition & 0 deletions src/memos/api/routers/product_router.py
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,7 @@ async def generate_chat_response():
user_id=chat_req.user_id,
cube_id=chat_req.mem_cube_id,
history=chat_req.history,
internet_search=chat_req.internet_search,
):
yield chunk
await asyncio.sleep(0.00001) # 50ms delay between chunks
Expand Down
9 changes: 9 additions & 0 deletions src/memos/configs/mem_reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,15 @@ class BaseMemReaderConfig(BaseConfig):
created_at: datetime = Field(
default_factory=datetime.now, description="Creation timestamp for the MemReader"
)

@field_validator("created_at", mode="before")
@classmethod
def parse_datetime(cls, value):
"""Parse datetime from string if needed."""
if isinstance(value, str):
return datetime.fromisoformat(value.replace("Z", "+00:00"))
return value

llm: LLMConfigFactory = Field(..., description="LLM configuration for the MemReader")
embedder: EmbedderConfigFactory = Field(
..., description="Embedder configuration for the MemReader"
Expand Down
2 changes: 1 addition & 1 deletion src/memos/log.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ def _setup_logfile() -> Path:
"class": "logging.handlers.RotatingFileHandler",
"filename": _setup_logfile(),
"maxBytes": 1024**2 * 10,
"backupCount": 3,
"backupCount": 10,
"formatter": "standard",
},
},
Expand Down
4 changes: 0 additions & 4 deletions src/memos/mem_cube/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,6 @@ def merge_config_with_default(

# Define graph_db fields to preserve (user-specific)
preserve_graph_fields = {
"uri",
"user",
"password",
"db_name",
"auto_create",
"user_name",
"use_multi_db",
Expand Down
8 changes: 7 additions & 1 deletion src/memos/mem_os/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -530,6 +530,8 @@ def search(
user_id: str | None = None,
install_cube_ids: list[str] | None = None,
top_k: int | None = None,
mode: Literal["fast", "fine"] = "fast",
internet_search: bool = False,
) -> MOSSearchResult:
"""
Search for textual memories across all registered MemCubes.
Expand Down Expand Up @@ -567,7 +569,11 @@ def search(
and self.config.enable_textual_memory
):
memories = mem_cube.text_mem.search(
query, top_k=top_k if top_k else self.config.top_k
query,
top_k=top_k if top_k else self.config.top_k,
mode=mode,
manual_close_internet=not internet_search,
info={"user_id": target_user_id, "session_id": str(uuid.uuid4())},
)
result["text_mem"].append({"cube_id": mem_cube_id, "memories": memories})
logger.info(
Expand Down
22 changes: 17 additions & 5 deletions src/memos/mem_os/product.py
Original file line number Diff line number Diff line change
Expand Up @@ -363,7 +363,7 @@ def _build_system_prompt(self, user_id: str, memories_all: list[TextualMemoryIte
for i, memory in enumerate(memories_all, 1):
# Format: [memory_id]: memory_content
memory_id = f"{memory.id.split('-')[0]}" if hasattr(memory, "id") else f"mem_{i}"
memory_content = memory.memory if hasattr(memory, "memory") else str(memory)
memory_content = memory.memory[:500] if hasattr(memory, "memory") else str(memory)
memory_context += f"{memory_id}: {memory_content}\n"
return base_prompt + memory_context

Expand Down Expand Up @@ -694,7 +694,7 @@ def get_suggestion_query(self, user_id: str, language: str = "zh") -> list[str]:
"text_mem"
]
if text_mem_result:
memories = "\n".join([m.memory for m in text_mem_result[0]["memories"]])
memories = "\n".join([m.memory[:200] for m in text_mem_result[0]["memories"]])
else:
memories = ""
message_list = [{"role": "system", "content": suggestion_prompt.format(memories=memories)}]
Expand All @@ -710,6 +710,7 @@ def chat_with_references(
cube_id: str | None = None,
history: MessageList | None = None,
top_k: int = 10,
internet_search: bool = False,
) -> Generator[str, None, None]:
"""
Chat with LLM with memory references and streaming output.
Expand All @@ -729,7 +730,12 @@ def chat_with_references(
memories_list = []
yield f"data: {json.dumps({'type': 'status', 'data': '0'})}\n\n"
memories_result = super().search(
query, user_id, install_cube_ids=[cube_id] if cube_id else None, top_k=top_k
query,
user_id,
install_cube_ids=[cube_id] if cube_id else None,
top_k=top_k,
mode="fine",
internet_search=internet_search,
)["text_mem"]
yield f"data: {json.dumps({'type': 'status', 'data': '1'})}\n\n"
self._send_message_to_scheduler(
Expand Down Expand Up @@ -829,6 +835,7 @@ def chat_with_references(
memories_json["metadata"]["embedding"] = []
memories_json["metadata"]["sources"] = []
memories_json["metadata"]["memory"] = memories.memory
memories_json["metadata"]["id"] = memories.id
reference.append({"metadata": memories_json["metadata"]})

yield f"data: {json.dumps({'type': 'reference', 'data': reference})}\n\n"
Expand Down Expand Up @@ -1008,13 +1015,18 @@ def get_subgraph(
return reformat_memory_list

def search(
self, query: str, user_id: str, install_cube_ids: list[str] | None = None, top_k: int = 10
self,
query: str,
user_id: str,
install_cube_ids: list[str] | None = None,
top_k: int = 10,
mode: Literal["fast", "fine"] = "fast",
):
"""Search memories for a specific user."""

# Load user cubes if not already loaded
self._load_user_cubes(user_id, self.default_cube_config)
search_result = super().search(query, user_id, install_cube_ids, top_k)
search_result = super().search(query, user_id, install_cube_ids, top_k, mode=mode)
text_memory_list = search_result["text_mem"]
reformat_memory_list = []
for memory in text_memory_list:
Expand Down
20 changes: 13 additions & 7 deletions src/memos/memories/textual/tree.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,13 +117,19 @@ def search(
logger.warning(
"Internet retriever is init by config , but this search set manual_close_internet is True and will close it"
)
self.internet_retriever = None
searcher = Searcher(
self.dispatcher_llm,
self.graph_store,
self.embedder,
internet_retriever=self.internet_retriever,
)
searcher = Searcher(
self.dispatcher_llm,
self.graph_store,
self.embedder,
internet_retriever=None,
)
else:
searcher = Searcher(
self.dispatcher_llm,
self.graph_store,
self.embedder,
internet_retriever=self.internet_retriever,
)
return searcher.search(query, top_k, info, mode, memory_type)

def get_relevant_subgraph(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,8 @@ def _run_structure_organizer_loop(self):
"""
import schedule

schedule.every(20).seconds.do(self.optimize_structure, scope="LongTermMemory")
schedule.every(20).seconds.do(self.optimize_structure, scope="UserMemory")
schedule.every(600).seconds.do(self.optimize_structure, scope="LongTermMemory")
schedule.every(600).seconds.do(self.optimize_structure, scope="UserMemory")

logger.info("Structure optimizer schedule started.")
while not getattr(self, "_stop_scheduler", False):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ def retrieve_from_internet():
query=query,
query_embedding=query_embedding[0],
graph_results=internet_items,
top_k=max(top_k, 10),
top_k=min(top_k, 5),
parsed_goal=parsed_goal,
)
return ranked_memories
Expand Down
4 changes: 3 additions & 1 deletion src/memos/settings.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import os

from pathlib import Path


MEMOS_DIR = Path.cwd() / ".memos"
MEMOS_DIR = Path(os.getenv("MEMOS_BASE_PATH", Path.cwd())) / ".memos"
DEBUG = False

# "memos" or "memos.submodules" ... to filter logs from specific packages
Expand Down
11 changes: 10 additions & 1 deletion tests/mem_os/test_memos_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -326,7 +326,16 @@ def test_search_memories(
assert "para_mem" in result
assert len(result["text_mem"]) == 1
assert result["text_mem"][0]["cube_id"] == "test_cube_1"
mock_mem_cube.text_mem.search.assert_called_once_with("football", top_k=5)
# Verify the search was called with the correct parameters
mock_mem_cube.text_mem.search.assert_called_once()
call_args = mock_mem_cube.text_mem.search.call_args
assert call_args[0] == ("football",) # positional args
assert call_args[1]["top_k"] == 5
assert call_args[1]["mode"] == "fast"
assert call_args[1]["manual_close_internet"]
assert "info" in call_args[1]
assert call_args[1]["info"]["user_id"] == "test_user"
assert "session_id" in call_args[1]["info"]

@patch("memos.mem_os.core.UserManager")
@patch("memos.mem_os.core.MemReaderFactory")
Expand Down