Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
##############################################################################

name = "MemoryOS"
version = "2.0.5"
version = "2.0.6"
description = "Intelligence Begins with Memory"
license = {text = "Apache-2.0"}
readme = "README.md"
Expand Down
2 changes: 1 addition & 1 deletion src/memos/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
__version__ = "2.0.5"
__version__ = "2.0.6"

from memos.configs.mem_cube import GeneralMemCubeConfig
from memos.configs.mem_os import MOSConfig
Expand Down
2 changes: 0 additions & 2 deletions src/memos/api/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,6 @@ def get_memreader_config() -> dict[str, Any]:
# validation requirements during tests/import.
"api_base": os.getenv("MEMRADER_API_BASE", "https://api.openai.com/v1"),
"remove_think_prefix": True,
"extra_body": {"chat_template_kwargs": {"enable_thinking": False}},
},
}

Expand Down Expand Up @@ -531,7 +530,6 @@ def get_internet_config() -> dict[str, Any]:
"api_key": os.getenv("MEMRADER_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(),
Expand Down
3 changes: 3 additions & 0 deletions src/memos/api/handlers/chat_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ def handle_chat_complete(self, chat_req: APIChatCompleteRequest) -> dict[str, An
include_preference=chat_req.include_preference,
pref_top_k=chat_req.pref_top_k,
filter=chat_req.filter,
relativity=chat_req.relativity,
)

search_response = self.search_handler.handle_search_memories(search_req)
Expand Down Expand Up @@ -269,6 +270,7 @@ def generate_chat_response() -> Generator[str, None, None]:
include_preference=chat_req.include_preference,
pref_top_k=chat_req.pref_top_k,
filter=chat_req.filter,
relativity=chat_req.relativity,
)

search_response = self.search_handler.handle_search_memories(search_req)
Expand Down Expand Up @@ -811,6 +813,7 @@ def generate_chat_response() -> Generator[str, None, None]:
include_preference=chat_req.include_preference,
pref_top_k=chat_req.pref_top_k,
filter=chat_req.filter,
relativity=chat_req.relativity,
)

search_response = self.search_handler.handle_search_memories(search_req)
Expand Down
1 change: 1 addition & 0 deletions src/memos/api/handlers/config_builders.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ def build_chat_llm_config() -> list[dict[str, Any]]:
}
),
"support_models": cfg.get("support_models", None),
"extra_body": cfg.get("extra_body", None),
}
for cfg in configs
]
Expand Down
74 changes: 70 additions & 4 deletions src/memos/api/handlers/memory_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,20 @@ def handle_get_memory(memory_id: str, naive_mem_cube: NaiveMemCube) -> GetMemory
# Get the data from whichever memory source succeeded
data = (memory or pref).model_dump() if (memory or pref) else None

if data is not None:
# For each returned item, tackle with metadata.info project_id /
# operation / manager_user_id
metadata = data.get("metadata", None)
if metadata is not None and isinstance(metadata, dict):
info = metadata.get("info", None)
if info is not None and isinstance(info, dict):
for key in ("project_id", "operation", "manager_user_id"):
if key not in info:
continue
value = info.pop(key)
if key not in metadata:
metadata[key] = value

return GetMemoryResponse(
message="Memory retrieved successfully"
if data
Expand Down Expand Up @@ -241,6 +255,25 @@ def handle_get_memory_by_ids(
except Exception:
continue

# For each returned item, tackle with metadata.info project_id /
# operation / manager_user_id
for item in memories:
if not isinstance(item, dict):
continue
metadata = item.get("metadata")
if not isinstance(metadata, dict):
continue
info = metadata.get("info")
if not isinstance(info, dict):
continue

for key in ("project_id", "operation", "manager_user_id"):
if key not in info:
continue
value = info.pop(key)
if key not in metadata:
metadata[key] = value

return GetMemoryResponse(
message="Memories retrieved successfully", code=200, data={"memories": memories}
)
Expand Down Expand Up @@ -345,6 +378,25 @@ def handle_get_memories(
)
format_preferences = [format_memory_item(item, save_sources=False) for item in preferences]

# For each returned item, tackle with metadata.info project_id /
# operation / manager_user_id
for item in format_preferences:
if not isinstance(item, dict):
continue
metadata = item.get("metadata")
if not isinstance(metadata, dict):
continue
info = metadata.get("info")
if not isinstance(info, dict):
continue

for key in ("project_id", "operation", "manager_user_id"):
if key not in info:
continue
value = info.pop(key)
if key not in metadata:
metadata[key] = value

results = post_process_pref_mem(
results, format_preferences, get_mem_req.mem_cube_id, get_mem_req.include_preference
)
Expand Down Expand Up @@ -412,6 +464,12 @@ def handle_get_memories_dashboard(
get_mem_req: GetMemoryDashboardRequest, naive_mem_cube: NaiveMemCube
) -> GetMemoryResponse:
results: dict[str, Any] = {"text_mem": [], "pref_mem": [], "tool_mem": [], "skill_mem": []}
# for statistics
total_text_nodes, total_tool_nodes, total_skill_nodes, total_preference_nodes = 0, 0, 0, 0
total_tool_nodes = 0
total_skill_nodes = 0
total_preference_nodes = 0

text_memory_type = ["WorkingMemory", "LongTermMemory", "UserMemory", "OuterMemory"]
text_memories_info = naive_mem_cube.text_mem.get_all(
user_name=get_mem_req.mem_cube_id,
Expand All @@ -421,7 +479,7 @@ def handle_get_memories_dashboard(
filter=get_mem_req.filter,
memory_type=text_memory_type,
)
text_memories, _ = text_memories_info["nodes"], text_memories_info["total_nodes"]
text_memories, total_text_nodes = text_memories_info["nodes"], text_memories_info["total_nodes"]

# Group text memories by cube_id from metadata.user_name
text_mem_by_cube: dict[str, list] = {}
Expand Down Expand Up @@ -453,7 +511,7 @@ def handle_get_memories_dashboard(
filter=get_mem_req.filter,
memory_type=["ToolSchemaMemory", "ToolTrajectoryMemory"],
)
tool_memories, _ = (
tool_memories, total_tool_nodes = (
tool_memories_info["nodes"],
tool_memories_info["total_nodes"],
)
Expand Down Expand Up @@ -488,7 +546,7 @@ def handle_get_memories_dashboard(
filter=get_mem_req.filter,
memory_type=["SkillMemory"],
)
skill_memories, _ = (
skill_memories, total_skill_nodes = (
skill_memories_info["nodes"],
skill_memories_info["total_nodes"],
)
Expand All @@ -515,7 +573,6 @@ def handle_get_memories_dashboard(
]

preferences: list[TextualMemoryItem] = []
total_preference_nodes = 0

format_preferences = []
if get_mem_req.include_preference and naive_mem_cube.pref_mem is not None:
Expand Down Expand Up @@ -578,4 +635,13 @@ def handle_get_memories_dashboard(
"skill_mem": results.get("skill_mem", []),
}

# statistics
statistics = {
"total_text_nodes": total_text_nodes,
"total_tool_nodes": total_tool_nodes,
"total_skill_nodes": total_skill_nodes,
"total_preference_nodes": total_preference_nodes,
}
filtered_results["statistics"] = statistics

return GetMemoryResponse(message="Memories retrieved successfully", data=filtered_results)
15 changes: 11 additions & 4 deletions src/memos/api/handlers/search_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,11 +120,14 @@ def _apply_relativity_threshold(results: dict[str, Any], relativity: float) -> d
if not isinstance(mem, dict):
continue
meta = mem.get("metadata", {})
score = meta.get("relativity", 0.0) if isinstance(meta, dict) else 0.0
if key == "text_mem":
score = meta.get("relativity", 1.0) if isinstance(meta, dict) else 1.0
else:
score = meta.get("score", 1.0) if isinstance(meta, dict) else 1.0
try:
score_val = float(score) if score is not None else 0.0
score_val = float(score) if score is not None else 1.0
except (TypeError, ValueError):
score_val = 0.0
score_val = 1.0
if score_val >= relativity:
filtered.append(mem)

Expand Down Expand Up @@ -220,7 +223,11 @@ def _mmr_dedup_text_memories(
# Flatten preference memories
for bucket_idx, bucket in enumerate(pref_buckets):
for mem in bucket.get("memories", []):
score = mem.get("metadata", {}).get("relativity", 0.0)
meta = mem.get("metadata", {})
if isinstance(meta, dict):
score = meta.get("score", meta.get("relativity", 0.0))
else:
score = 0.0
flat.append(
("preference", bucket_idx, mem, float(score) if score is not None else 0.0)
)
Expand Down
18 changes: 18 additions & 0 deletions src/memos/api/product_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,15 @@ class ChatRequest(BaseRequest):
add_message_on_answer: bool = Field(True, description="Add dialogs to memory after chat")
manager_user_id: str | None = Field(None, description="Manager User ID")
project_id: str | None = Field(None, description="Project ID")
relativity: float = Field(
0.0,
ge=0,
description=(
"Relevance threshold for recalled memories. "
"Only memories with metadata.relativity >= relativity will be returned. "
"Use 0 to disable threshold filtering. Default: 0.3."
),
)

# ==== Filter conditions ====
filter: dict[str, Any] | None = Field(
Expand Down Expand Up @@ -775,6 +784,15 @@ class APIChatCompleteRequest(BaseRequest):
add_message_on_answer: bool = Field(True, description="Add dialogs to memory after chat")
manager_user_id: str | None = Field(None, description="Manager User ID")
project_id: str | None = Field(None, description="Project ID")
relativity: float = Field(
0.0,
ge=0,
description=(
"Relevance threshold for recalled memories. "
"Only memories with metadata.relativity >= relativity will be returned. "
"Use 0 to disable threshold filtering. Default: 0.3."
),
)

# ==== Filter conditions ====
filter: dict[str, Any] | None = Field(
Expand Down
10 changes: 0 additions & 10 deletions src/memos/api/routers/server_router.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@
SuggestionResponse,
TaskQueueResponse,
)
from memos.graph_dbs.polardb import PolarDBGraphDB
from memos.log import get_logger
from memos.mem_scheduler.base_scheduler import BaseScheduler
from memos.mem_scheduler.utils.status_tracker import TaskStatusTracker
Expand Down Expand Up @@ -371,15 +370,6 @@ def feedback_memories(feedback_req: APIFeedbackRequest):
)
def get_user_names_by_memory_ids(request: GetUserNamesByMemoryIdsRequest):
"""Get user names by memory ids."""
if not isinstance(graph_db, PolarDBGraphDB):
raise HTTPException(
status_code=400,
detail=(
"graph_db must be an instance of PolarDBGraphDB to use "
"get_user_names_by_memory_ids"
f"current graph_db is: {graph_db.__class__.__name__}"
),
)
result = graph_db.get_user_names_by_memory_ids(memory_ids=request.memory_ids)
if vector_db:
prefs = []
Expand Down
1 change: 1 addition & 0 deletions src/memos/configs/llm.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ class VLLMLLMConfig(BaseLLMConfig):
default=False,
description="Enable reasoning outputs from vLLM",
)
extra_body: Any = Field(default=None, description="Extra options for API")


class LLMConfigFactory(BaseConfig):
Expand Down
16 changes: 2 additions & 14 deletions src/memos/llms/vllm.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,13 +111,7 @@ def _generate_with_api_client(self, messages: list[MessageDict], **kwargs) -> st
"temperature": kwargs.get("temperature", self.config.temperature),
"max_tokens": kwargs.get("max_tokens", self.config.max_tokens),
"top_p": kwargs.get("top_p", self.config.top_p),
"extra_body": {
"chat_template_kwargs": {
"enable_thinking": kwargs.get(
"enable_thinking", self.config.enable_thinking
)
}
},
"extra_body": kwargs.get("extra_body", self.config.extra_body),
}
if kwargs.get("tools"):
completion_kwargs["tools"] = kwargs.get("tools")
Expand Down Expand Up @@ -175,13 +169,7 @@ def generate_stream(self, messages: list[MessageDict], **kwargs):
"max_tokens": kwargs.get("max_tokens", self.config.max_tokens),
"top_p": kwargs.get("top_p", self.config.top_p),
"stream": True,
"extra_body": {
"chat_template_kwargs": {
"enable_thinking": kwargs.get(
"enable_thinking", self.config.enable_thinking
)
}
},
"extra_body": kwargs.get("extra_body", self.config.extra_body),
}

stream = self.client.chat.completions.create(**completion_kwargs)
Expand Down
2 changes: 2 additions & 0 deletions src/memos/mem_reader/multi_modal_struct.py
Original file line number Diff line number Diff line change
Expand Up @@ -646,6 +646,7 @@ def _merge_memories_with_llm(

return None

@timed
def _process_string_fine(
self,
fast_memory_items: list[TextualMemoryItem],
Expand Down Expand Up @@ -883,6 +884,7 @@ def _get_llm_tool_trajectory_response(self, mem_str: str) -> dict:
logger.error(f"[MultiModalFine] Error calling LLM for tool trajectory: {e}")
return []

@timed
def _process_tool_trajectory_fine(
self, fast_memory_items: list[TextualMemoryItem], info: dict[str, Any], **kwargs
) -> list[TextualMemoryItem]:
Expand Down
4 changes: 4 additions & 0 deletions src/memos/mem_reader/read_multi_modal/multi_modal_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from memos.log import get_logger
from memos.memories.textual.item import SourceMessage, TextualMemoryItem
from memos.types import MessagesType
from memos.utils import timed

from .assistant_parser import AssistantParser
from .base import BaseMessageParser
Expand Down Expand Up @@ -120,6 +121,7 @@ def _get_parser(self, message: Any) -> BaseMessageParser | None:
logger.warning(f"[MultiModalParser] Could not determine parser for message: {message}")
return None

@timed
def parse(
self,
message: MessagesType,
Expand Down Expand Up @@ -157,6 +159,7 @@ def parse(
logger.error(f"[MultiModalParser] Error parsing message: {e}")
return []

@timed
def parse_batch(
self,
messages: list[MessagesType],
Expand All @@ -182,6 +185,7 @@ def parse_batch(
results.append(items)
return results

@timed
def process_transfer(
self,
source: SourceMessage,
Expand Down
Loading