Skip to content

Commit e27483c

Browse files
committed
Merge remote-tracking branch 'upstream/dev' into dev
2 parents 4226a77 + 1519e33 commit e27483c

File tree

73 files changed

+4329
-1139
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

73 files changed

+4329
-1139
lines changed

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,8 @@ Designed for **AI companions, role-playing NPCs, and multi-agent systems**, MemO
5353
</div>
5454

5555

56-
Get Free API: [Try API](https://memos-dashboard.openmem.net/quickstart/?source=github)
57-
56+
Get Free API: [Try API](https://memos-dashboard.openmem.net/quickstart/?source=github)
57+
5858

5959
---
6060

@@ -64,7 +64,7 @@ Get Free API: [Try API](https://memos-dashboard.openmem.net/quickstart/?source=g
6464

6565
- **Website**: https://memos.openmem.net/
6666
- **Documentation**: https://memos-docs.openmem.net/home/overview/
67-
- **API Reference**: https://memos-docs.openmem.net/docs/api/info/
67+
- **API Reference**: https://memos-docs.openmem.net/api-reference/configure-memos/
6868
- **Source Code**: https://github.com/MemTensor/MemOS
6969

7070
## 📰 News

examples/mem_scheduler/memos_w_scheduler.py

Lines changed: 105 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1+
import re
12
import shutil
23
import sys
34

5+
from datetime import datetime
46
from pathlib import Path
57
from queue import Queue
6-
from typing import TYPE_CHECKING
78

89
from memos.configs.mem_cube import GeneralMemCubeConfig
910
from memos.configs.mem_os import MOSConfig
@@ -12,12 +13,16 @@
1213
from memos.mem_cube.general import GeneralMemCube
1314
from memos.mem_os.main import MOS
1415
from memos.mem_scheduler.general_scheduler import GeneralScheduler
15-
16-
17-
if TYPE_CHECKING:
18-
from memos.mem_scheduler.schemas.message_schemas import (
19-
ScheduleLogForWebItem,
20-
)
16+
from memos.mem_scheduler.schemas.general_schemas import (
17+
ADD_LABEL,
18+
ANSWER_LABEL,
19+
MEM_ARCHIVE_LABEL,
20+
MEM_ORGANIZE_LABEL,
21+
MEM_UPDATE_LABEL,
22+
QUERY_LABEL,
23+
)
24+
from memos.mem_scheduler.schemas.message_schemas import ScheduleLogForWebItem
25+
from memos.mem_scheduler.utils.filter_utils import transform_name_to_key
2126

2227

2328
FILE_PATH = Path(__file__).absolute()
@@ -70,6 +75,91 @@ def init_task():
7075
return conversations, questions
7176

7277

78+
def _truncate_with_rules(text: str) -> str:
79+
has_cjk = bool(re.search(r"[\u4e00-\u9fff]", text))
80+
limit = 32 if has_cjk else 64
81+
normalized = text.strip().replace("\n", " ")
82+
if len(normalized) <= limit:
83+
return normalized
84+
return normalized[:limit] + "..."
85+
86+
87+
def _format_title(ts: datetime, title_text: str) -> str:
88+
return f"{ts.astimezone().strftime('%H:%M:%S')} {title_text}"
89+
90+
91+
def _cube_display_from(mem_cube_id: str) -> str:
92+
if "public" in (mem_cube_id or "").lower():
93+
return "PublicMemCube"
94+
return "UserMemCube"
95+
96+
97+
_TYPE_SHORT = {
98+
"LongTermMemory": "LTM",
99+
"UserMemory": "User",
100+
"WorkingMemory": "Working",
101+
"ActivationMemory": "Activation",
102+
"ParameterMemory": "Parameter",
103+
"TextMemory": "Text",
104+
"UserInput": "Input",
105+
"NotApplicable": "NA",
106+
}
107+
108+
109+
def _format_entry(item: ScheduleLogForWebItem) -> tuple[str, str]:
110+
cube_display = getattr(item, "memcube_name", None) or _cube_display_from(item.mem_cube_id)
111+
label = item.label
112+
content = item.log_content or ""
113+
memcube_content = getattr(item, "memcube_log_content", None) or []
114+
memory_len = getattr(item, "memory_len", None) or len(memcube_content) or 1
115+
116+
def _first_content() -> str:
117+
if memcube_content:
118+
return memcube_content[0].get("content", "") or content
119+
return content
120+
121+
if label in ("addMessage", QUERY_LABEL, ANSWER_LABEL):
122+
target_cube = cube_display.replace("MemCube", "")
123+
title = _format_title(item.timestamp, f"addMessages to {target_cube} MemCube")
124+
return title, _truncate_with_rules(_first_content())
125+
126+
if label in ("addMemory", ADD_LABEL):
127+
title = _format_title(item.timestamp, f"{cube_display} added {memory_len} memories")
128+
return title, _truncate_with_rules(_first_content())
129+
130+
if label in ("updateMemory", MEM_UPDATE_LABEL):
131+
title = _format_title(item.timestamp, f"{cube_display} updated {memory_len} memories")
132+
return title, _truncate_with_rules(_first_content())
133+
134+
if label in ("archiveMemory", MEM_ARCHIVE_LABEL):
135+
title = _format_title(item.timestamp, f"{cube_display} archived {memory_len} memories")
136+
return title, _truncate_with_rules(_first_content())
137+
138+
if label in ("mergeMemory", MEM_ORGANIZE_LABEL):
139+
title = _format_title(item.timestamp, f"{cube_display} merged {memory_len} memories")
140+
merged = [c for c in memcube_content if c.get("type") == "merged"]
141+
post = [c for c in memcube_content if c.get("type") == "postMerge"]
142+
parts = []
143+
if merged:
144+
parts.append("Merged: " + " | ".join(c.get("content", "") for c in merged))
145+
if post:
146+
parts.append("Result: " + " | ".join(c.get("content", "") for c in post))
147+
detail = " ".join(parts) if parts else _first_content()
148+
return title, _truncate_with_rules(detail)
149+
150+
if label == "scheduleMemory":
151+
title = _format_title(item.timestamp, f"{cube_display} scheduled {memory_len} memories")
152+
if memcube_content:
153+
return title, _truncate_with_rules(memcube_content[0].get("content", ""))
154+
key = transform_name_to_key(content)
155+
from_short = _TYPE_SHORT.get(item.from_memory_type, item.from_memory_type)
156+
to_short = _TYPE_SHORT.get(item.to_memory_type, item.to_memory_type)
157+
return title, _truncate_with_rules(f"[{from_short}{to_short}] {key}: {content}")
158+
159+
title = _format_title(item.timestamp, f"{cube_display} event")
160+
return title, _truncate_with_rules(_first_content())
161+
162+
73163
def show_web_logs(mem_scheduler: GeneralScheduler):
74164
"""Display all web log entries from the scheduler's log queue.
75165
@@ -84,24 +174,25 @@ def show_web_logs(mem_scheduler: GeneralScheduler):
84174

85175
# Create a temporary queue to preserve the original queue contents
86176
temp_queue = Queue()
87-
log_count = 0
177+
collected: list[ScheduleLogForWebItem] = []
88178

89179
while not mem_scheduler._web_log_message_queue.empty():
90180
log_item: ScheduleLogForWebItem = mem_scheduler._web_log_message_queue.get()
181+
collected.append(log_item)
91182
temp_queue.put(log_item)
92-
log_count += 1
93-
94-
# Print log entry details
95-
print(f"\nLog Entry #{log_count}:")
96-
print(f'- "{log_item.label}" log: {log_item}')
97183

184+
for idx, log_item in enumerate(sorted(collected, key=lambda x: x.timestamp, reverse=True), 1):
185+
title, content = _format_entry(log_item)
186+
print(f"\nLog Entry #{idx}:")
187+
print(title)
188+
print(content)
98189
print("-" * 50)
99190

100191
# Restore items back to the original queue
101192
while not temp_queue.empty():
102193
mem_scheduler._web_log_message_queue.put(temp_queue.get())
103194

104-
print(f"\nTotal {log_count} web log entries displayed.")
195+
print(f"\nTotal {len(collected)} web log entries displayed.")
105196
print("=" * 110 + "\n")
106197

107198

src/memos/api/config.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,9 @@ def get_memreader_config() -> dict[str, Any]:
328328
"top_p": 0.95,
329329
"top_k": 20,
330330
"api_key": os.getenv("MEMRADER_API_KEY", "EMPTY"),
331-
"api_base": os.getenv("MEMRADER_API_BASE"),
331+
# Default to OpenAI base URL when env var is not provided to satisfy pydantic
332+
# validation requirements during tests/import.
333+
"api_base": os.getenv("MEMRADER_API_BASE", "https://api.openai.com/v1"),
332334
"remove_think_prefix": True,
333335
"extra_body": {"chat_template_kwargs": {"enable_thinking": False}},
334336
},
@@ -379,7 +381,7 @@ def get_reranker_config() -> dict[str, Any]:
379381
"url": os.getenv("MOS_RERANKER_URL"),
380382
"model": os.getenv("MOS_RERANKER_MODEL", "bge-reranker-v2-m3"),
381383
"timeout": 10,
382-
"headers_extra": os.getenv("MOS_RERANKER_HEADERS_EXTRA"),
384+
"headers_extra": json.loads(os.getenv("MOS_RERANKER_HEADERS_EXTRA", "{}")),
383385
"rerank_source": os.getenv("MOS_RERANK_SOURCE"),
384386
"reranker_strategy": os.getenv("MOS_RERANKER_STRATEGY", "single_turn"),
385387
},
@@ -405,6 +407,7 @@ def get_embedder_config() -> dict[str, Any]:
405407
"provider": os.getenv("MOS_EMBEDDER_PROVIDER", "openai"),
406408
"api_key": os.getenv("MOS_EMBEDDER_API_KEY", "sk-xxxx"),
407409
"model_name_or_path": os.getenv("MOS_EMBEDDER_MODEL", "text-embedding-3-large"),
410+
"headers_extra": json.loads(os.getenv("MOS_EMBEDDER_HEADERS_EXTRA", "{}")),
408411
"base_url": os.getenv("MOS_EMBEDDER_API_BASE", "http://openai.com"),
409412
},
410413
}

0 commit comments

Comments
 (0)