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.8"
version = "2.0.9"
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.8"
__version__ = "2.0.9"

from memos.configs.mem_cube import GeneralMemCubeConfig
from memos.configs.mem_os import MOSConfig
Expand Down
2 changes: 1 addition & 1 deletion src/memos/llms/ollama.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ def generate(self, messages: MessageList, **kwargs) -> Any:
)
str_response = response.message.content
if self.config.remove_think_prefix:
return remove_thinking_tags(str_response)
return remove_thinking_tags(str_response or "")
else:
return str_thinking + str_response

Expand Down
4 changes: 2 additions & 2 deletions src/memos/llms/openai.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ def _parse_response(self, response) -> str:
if isinstance(reasoning_content, str) and reasoning_content:
reasoning_content = f"<think>{reasoning_content}</think>"
if self.config.remove_think_prefix:
return remove_thinking_tags(response_content)
return remove_thinking_tags(response_content or "")
if reasoning_content:
return reasoning_content + (response_content or "")
return response_content or ""
Expand Down Expand Up @@ -202,7 +202,7 @@ def generate(self, messages: MessageList, **kwargs) -> str:
return self.tool_call_parser(response.choices[0].message.tool_calls)
response_content = response.choices[0].message.content
if self.config.remove_think_prefix:
return remove_thinking_tags(response_content)
return remove_thinking_tags(response_content or "")
else:
return response_content or ""

Expand Down
5 changes: 5 additions & 0 deletions src/memos/mem_reader/multi_modal_struct.py
Original file line number Diff line number Diff line change
Expand Up @@ -927,6 +927,10 @@ def _process_tool_trajectory_fine(
project_id = user_context.project_id if user_context else None

for fast_item in fast_memory_items:
sources = fast_item.metadata.sources or []
if not isinstance(sources, list):
sources = [sources]

# Extract memory text (string content)
mem_str = fast_item.memory or ""
if not mem_str.strip() or (
Expand Down Expand Up @@ -954,6 +958,7 @@ def _process_tool_trajectory_fine(
tool_used_status=m.get("tool_used_status", []),
manager_user_id=manager_user_id,
project_id=project_id,
sources=sources,
)
fine_memory_items.append(node)
except Exception as e:
Expand Down
34 changes: 31 additions & 3 deletions src/memos/mem_reader/read_skill_memory/process_skill_memory.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,11 @@
from memos.llms.base import BaseLLM
from memos.log import get_logger
from memos.mem_reader.read_multi_modal import detect_lang
from memos.memories.textual.item import TextualMemoryItem, TreeNodeTextualMemoryMetadata
from memos.memories.textual.item import (
SourceMessage,
TextualMemoryItem,
TreeNodeTextualMemoryMetadata,
)
from memos.memories.textual.tree_text_memory.retrieve.searcher import Searcher
from memos.templates.skill_mem_prompt import (
OTHERS_GENERATION_PROMPT,
Expand Down Expand Up @@ -91,6 +95,7 @@ def _batch_extract_skills(
try:
skill_memory = future.result()
if skill_memory:
skill_memory["_task_type"] = task_type
results.append((skill_memory, task_type, task_chunks.get(task_type, [])))
except Exception as e:
logger.warning(
Expand Down Expand Up @@ -901,6 +906,7 @@ def create_skill_memory_item(
skill_memory: dict[str, Any],
info: dict[str, Any],
embedder: BaseEmbedder | None = None,
sources: list[SourceMessage] | None = None,
**kwargs: Any,
) -> TextualMemoryItem:
info_ = info.copy()
Expand All @@ -923,7 +929,7 @@ def create_skill_memory_item(
status="activated",
tags=skill_memory.get("tags") or skill_memory.get("trigger", []),
key=skill_memory.get("name", ""),
sources=[],
sources=sources or [],
usage=[],
background="",
confidence=0.99,
Expand Down Expand Up @@ -1097,6 +1103,7 @@ def _simple_extract():
try:
skill_memory = future.result()
if skill_memory:
skill_memory["_task_type"] = task_type
memories.append(skill_memory)
except Exception as e:
logger.warning(
Expand Down Expand Up @@ -1223,11 +1230,32 @@ def _full_extract():
except Exception as cleanup_error:
logger.warning(f"[PROCESS_SKILLS] Error cleaning up local files: {cleanup_error}")

# Build source lookup: (role, content) → SourceMessage from fast_memory_items
source_lookup: dict[tuple[str, str], SourceMessage] = {}
for fast_item in fast_memory_items:
for source in getattr(fast_item.metadata, "sources", []) or []:
source_lookup.setdefault((source.role, source.content), source)

# Create TextualMemoryItem objects
skill_memory_items = []
for skill_memory in skill_memories:
try:
memory_item = create_skill_memory_item(skill_memory, info, embedder, **kwargs)
# Match sources precisely via the task chunk messages that produced this skill
task_type = skill_memory.pop("_task_type", None)
chunk_messages = task_chunks.get(task_type, []) if task_type else []
skill_sources = []
seen = set()
for msg in chunk_messages:
key = (msg.get("role"), msg.get("content"))
if key not in seen:
seen.add(key)
source = source_lookup.get(key)
if source:
skill_sources.append(source)

memory_item = create_skill_memory_item(
skill_memory, info, embedder, sources=skill_sources, **kwargs
)
skill_memory_items.append(memory_item)
except Exception as e:
logger.warning(f"[PROCESS_SKILLS] Error creating skill memory item: {e}")
Expand Down
Loading