MemoryOS 0.2.0__py3-none-any.whl → 0.2.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of MemoryOS might be problematic. Click here for more details.

Files changed (114) hide show
  1. {memoryos-0.2.0.dist-info → memoryos-0.2.2.dist-info}/METADATA +67 -26
  2. memoryos-0.2.2.dist-info/RECORD +169 -0
  3. memoryos-0.2.2.dist-info/entry_points.txt +3 -0
  4. memos/__init__.py +1 -1
  5. memos/api/config.py +562 -0
  6. memos/api/context/context.py +147 -0
  7. memos/api/context/dependencies.py +90 -0
  8. memos/api/exceptions.py +28 -0
  9. memos/api/mcp_serve.py +502 -0
  10. memos/api/product_api.py +35 -0
  11. memos/api/product_models.py +163 -0
  12. memos/api/routers/__init__.py +1 -0
  13. memos/api/routers/product_router.py +386 -0
  14. memos/chunkers/sentence_chunker.py +8 -2
  15. memos/cli.py +113 -0
  16. memos/configs/embedder.py +27 -0
  17. memos/configs/graph_db.py +132 -3
  18. memos/configs/internet_retriever.py +6 -0
  19. memos/configs/llm.py +47 -0
  20. memos/configs/mem_cube.py +1 -1
  21. memos/configs/mem_os.py +5 -0
  22. memos/configs/mem_reader.py +9 -0
  23. memos/configs/mem_scheduler.py +107 -7
  24. memos/configs/mem_user.py +58 -0
  25. memos/configs/memory.py +5 -4
  26. memos/dependency.py +52 -0
  27. memos/embedders/ark.py +92 -0
  28. memos/embedders/factory.py +4 -0
  29. memos/embedders/sentence_transformer.py +8 -2
  30. memos/embedders/universal_api.py +32 -0
  31. memos/graph_dbs/base.py +11 -3
  32. memos/graph_dbs/factory.py +4 -0
  33. memos/graph_dbs/nebular.py +1364 -0
  34. memos/graph_dbs/neo4j.py +333 -124
  35. memos/graph_dbs/neo4j_community.py +300 -0
  36. memos/llms/base.py +9 -0
  37. memos/llms/deepseek.py +54 -0
  38. memos/llms/factory.py +10 -1
  39. memos/llms/hf.py +170 -13
  40. memos/llms/hf_singleton.py +114 -0
  41. memos/llms/ollama.py +4 -0
  42. memos/llms/openai.py +67 -1
  43. memos/llms/qwen.py +63 -0
  44. memos/llms/vllm.py +153 -0
  45. memos/log.py +1 -1
  46. memos/mem_cube/general.py +77 -16
  47. memos/mem_cube/utils.py +109 -0
  48. memos/mem_os/core.py +251 -51
  49. memos/mem_os/main.py +94 -12
  50. memos/mem_os/product.py +1220 -43
  51. memos/mem_os/utils/default_config.py +352 -0
  52. memos/mem_os/utils/format_utils.py +1401 -0
  53. memos/mem_reader/simple_struct.py +18 -10
  54. memos/mem_scheduler/base_scheduler.py +441 -40
  55. memos/mem_scheduler/general_scheduler.py +249 -248
  56. memos/mem_scheduler/modules/base.py +14 -5
  57. memos/mem_scheduler/modules/dispatcher.py +67 -4
  58. memos/mem_scheduler/modules/misc.py +104 -0
  59. memos/mem_scheduler/modules/monitor.py +240 -50
  60. memos/mem_scheduler/modules/rabbitmq_service.py +319 -0
  61. memos/mem_scheduler/modules/redis_service.py +32 -22
  62. memos/mem_scheduler/modules/retriever.py +167 -23
  63. memos/mem_scheduler/modules/scheduler_logger.py +255 -0
  64. memos/mem_scheduler/mos_for_test_scheduler.py +140 -0
  65. memos/mem_scheduler/schemas/__init__.py +0 -0
  66. memos/mem_scheduler/schemas/general_schemas.py +43 -0
  67. memos/mem_scheduler/{modules/schemas.py → schemas/message_schemas.py} +63 -61
  68. memos/mem_scheduler/schemas/monitor_schemas.py +329 -0
  69. memos/mem_scheduler/utils/__init__.py +0 -0
  70. memos/mem_scheduler/utils/filter_utils.py +176 -0
  71. memos/mem_scheduler/utils/misc_utils.py +61 -0
  72. memos/mem_user/factory.py +94 -0
  73. memos/mem_user/mysql_persistent_user_manager.py +271 -0
  74. memos/mem_user/mysql_user_manager.py +500 -0
  75. memos/mem_user/persistent_factory.py +96 -0
  76. memos/mem_user/persistent_user_manager.py +260 -0
  77. memos/mem_user/user_manager.py +4 -4
  78. memos/memories/activation/item.py +29 -0
  79. memos/memories/activation/kv.py +10 -3
  80. memos/memories/activation/vllmkv.py +219 -0
  81. memos/memories/factory.py +2 -0
  82. memos/memories/textual/base.py +1 -1
  83. memos/memories/textual/general.py +43 -97
  84. memos/memories/textual/item.py +5 -33
  85. memos/memories/textual/tree.py +22 -12
  86. memos/memories/textual/tree_text_memory/organize/conflict.py +9 -5
  87. memos/memories/textual/tree_text_memory/organize/manager.py +26 -18
  88. memos/memories/textual/tree_text_memory/organize/redundancy.py +25 -44
  89. memos/memories/textual/tree_text_memory/organize/relation_reason_detector.py +50 -48
  90. memos/memories/textual/tree_text_memory/organize/reorganizer.py +81 -56
  91. memos/memories/textual/tree_text_memory/retrieve/internet_retriever.py +6 -3
  92. memos/memories/textual/tree_text_memory/retrieve/internet_retriever_factory.py +2 -0
  93. memos/memories/textual/tree_text_memory/retrieve/recall.py +0 -1
  94. memos/memories/textual/tree_text_memory/retrieve/reranker.py +2 -2
  95. memos/memories/textual/tree_text_memory/retrieve/retrieval_mid_structs.py +2 -0
  96. memos/memories/textual/tree_text_memory/retrieve/searcher.py +52 -28
  97. memos/memories/textual/tree_text_memory/retrieve/task_goal_parser.py +42 -15
  98. memos/memories/textual/tree_text_memory/retrieve/utils.py +11 -7
  99. memos/memories/textual/tree_text_memory/retrieve/xinyusearch.py +62 -58
  100. memos/memos_tools/dinding_report_bot.py +422 -0
  101. memos/memos_tools/notification_service.py +44 -0
  102. memos/memos_tools/notification_utils.py +96 -0
  103. memos/parsers/markitdown.py +8 -2
  104. memos/settings.py +3 -1
  105. memos/templates/mem_reader_prompts.py +66 -23
  106. memos/templates/mem_scheduler_prompts.py +126 -43
  107. memos/templates/mos_prompts.py +87 -0
  108. memos/templates/tree_reorganize_prompts.py +85 -30
  109. memos/vec_dbs/base.py +12 -0
  110. memos/vec_dbs/qdrant.py +46 -20
  111. memoryos-0.2.0.dist-info/RECORD +0 -128
  112. memos/mem_scheduler/utils.py +0 -26
  113. {memoryos-0.2.0.dist-info → memoryos-0.2.2.dist-info}/LICENSE +0 -0
  114. {memoryos-0.2.0.dist-info → memoryos-0.2.2.dist-info}/WHEEL +0 -0
@@ -0,0 +1,163 @@
1
+ import uuid
2
+
3
+ from typing import Generic, Literal, TypeAlias, TypeVar
4
+
5
+ from pydantic import BaseModel, Field
6
+ from typing_extensions import TypedDict
7
+
8
+
9
+ T = TypeVar("T")
10
+
11
+
12
+ # ─── Message Types ──────────────────────────────────────────────────────────────
13
+
14
+ # Chat message roles
15
+ MessageRole: TypeAlias = Literal["user", "assistant", "system"]
16
+
17
+
18
+ # Message structure
19
+ class MessageDict(TypedDict):
20
+ """Typed dictionary for chat message dictionaries."""
21
+
22
+ role: MessageRole
23
+ content: str
24
+
25
+
26
+ class BaseRequest(BaseModel):
27
+ """Base model for all requests."""
28
+
29
+
30
+ class BaseResponse(BaseModel, Generic[T]):
31
+ """Base model for all responses."""
32
+
33
+ code: int = Field(200, description="Response status code")
34
+ message: str = Field(..., description="Response message")
35
+ data: T | None = Field(None, description="Response data")
36
+
37
+
38
+ # Product API Models
39
+ class UserRegisterRequest(BaseRequest):
40
+ """Request model for user registration."""
41
+
42
+ user_id: str = Field(
43
+ default_factory=lambda: str(uuid.uuid4()), description="User ID for registration"
44
+ )
45
+ user_name: str | None = Field(None, description="User name for registration")
46
+ interests: str | None = Field(None, description="User interests")
47
+
48
+
49
+ class GetMemoryRequest(BaseRequest):
50
+ """Request model for getting memories."""
51
+
52
+ user_id: str = Field(..., description="User ID")
53
+ memory_type: Literal["text_mem", "act_mem", "param_mem", "para_mem"] = Field(
54
+ ..., description="Memory type"
55
+ )
56
+ mem_cube_ids: list[str] | None = Field(None, description="Cube IDs")
57
+ search_query: str | None = Field(None, description="Search query")
58
+
59
+
60
+ # Start API Models
61
+ class Message(BaseModel):
62
+ role: str = Field(..., description="Role of the message (user or assistant).")
63
+ content: str = Field(..., description="Message content.")
64
+
65
+
66
+ class MemoryCreate(BaseRequest):
67
+ user_id: str = Field(..., description="User ID")
68
+ messages: list[Message] | None = Field(None, description="List of messages to store.")
69
+ memory_content: str | None = Field(None, description="Content to store as memory")
70
+ doc_path: str | None = Field(None, description="Path to document to store")
71
+ mem_cube_id: str | None = Field(None, description="ID of the memory cube")
72
+
73
+
74
+ class MemCubeRegister(BaseRequest):
75
+ mem_cube_name_or_path: str = Field(..., description="Name or path of the MemCube to register.")
76
+ mem_cube_id: str | None = Field(None, description="ID for the MemCube")
77
+
78
+
79
+ class ChatRequest(BaseRequest):
80
+ """Request model for chat operations."""
81
+
82
+ user_id: str = Field(..., description="User ID")
83
+ query: str = Field(..., description="Chat query message")
84
+ mem_cube_id: str | None = Field(None, description="Cube ID to use for chat")
85
+ history: list[MessageDict] | None = Field(None, description="Chat history")
86
+ internet_search: bool = Field(True, description="Whether to use internet search")
87
+
88
+
89
+ class UserCreate(BaseRequest):
90
+ user_name: str | None = Field(None, description="Name of the user")
91
+ role: str = Field("USER", description="Role of the user")
92
+ user_id: str = Field(..., description="User ID")
93
+
94
+
95
+ class CubeShare(BaseRequest):
96
+ target_user_id: str = Field(..., description="Target user ID to share with")
97
+
98
+
99
+ # Response Models
100
+ class SimpleResponse(BaseResponse[None]):
101
+ """Simple response model for operations without data return."""
102
+
103
+
104
+ class UserRegisterResponse(BaseResponse[dict]):
105
+ """Response model for user registration."""
106
+
107
+
108
+ class MemoryResponse(BaseResponse[list]):
109
+ """Response model for memory operations."""
110
+
111
+
112
+ class SuggestionResponse(BaseResponse[list]):
113
+ """Response model for suggestion operations."""
114
+
115
+ data: dict[str, list[str]] | None = Field(None, description="Response data")
116
+
117
+
118
+ class ConfigResponse(BaseResponse[None]):
119
+ """Response model for configuration endpoint."""
120
+
121
+
122
+ class SearchResponse(BaseResponse[dict]):
123
+ """Response model for search operations."""
124
+
125
+
126
+ class ChatResponse(BaseResponse[str]):
127
+ """Response model for chat operations."""
128
+
129
+
130
+ class UserResponse(BaseResponse[dict]):
131
+ """Response model for user operations."""
132
+
133
+
134
+ class UserListResponse(BaseResponse[list]):
135
+ """Response model for user list operations."""
136
+
137
+
138
+ class MemoryCreateRequest(BaseRequest):
139
+ """Request model for creating memories."""
140
+
141
+ user_id: str = Field(..., description="User ID")
142
+ messages: list[MessageDict] | None = Field(None, description="List of messages to store.")
143
+ memory_content: str | None = Field(None, description="Memory content to store")
144
+ doc_path: str | None = Field(None, description="Path to document to store")
145
+ mem_cube_id: str | None = Field(None, description="Cube ID")
146
+ source: str | None = Field(None, description="Source of the memory")
147
+ user_profile: bool = Field(False, description="User profile memory")
148
+
149
+
150
+ class SearchRequest(BaseRequest):
151
+ """Request model for searching memories."""
152
+
153
+ user_id: str = Field(..., description="User ID")
154
+ query: str = Field(..., description="Search query")
155
+ mem_cube_id: str | None = Field(None, description="Cube ID to search in")
156
+ top_k: int = Field(10, description="Number of results to return")
157
+
158
+
159
+ class SuggestionRequest(BaseRequest):
160
+ """Request model for getting suggestion queries."""
161
+
162
+ user_id: str = Field(..., description="User ID")
163
+ language: Literal["zh", "en"] = Field("zh", description="Language for suggestions")
@@ -0,0 +1 @@
1
+ # API routers module
@@ -0,0 +1,386 @@
1
+ import json
2
+ import logging
3
+ import traceback
4
+
5
+ from datetime import datetime
6
+ from typing import Annotated
7
+
8
+ from fastapi import APIRouter, Depends, HTTPException
9
+ from fastapi.responses import StreamingResponse
10
+
11
+ from memos.api.config import APIConfig
12
+ from memos.api.context.dependencies import G, get_g_object
13
+ from memos.api.product_models import (
14
+ BaseResponse,
15
+ ChatRequest,
16
+ GetMemoryRequest,
17
+ MemoryCreateRequest,
18
+ MemoryResponse,
19
+ SearchRequest,
20
+ SearchResponse,
21
+ SimpleResponse,
22
+ SuggestionRequest,
23
+ SuggestionResponse,
24
+ UserRegisterRequest,
25
+ UserRegisterResponse,
26
+ )
27
+ from memos.configs.mem_os import MOSConfig
28
+ from memos.mem_os.product import MOSProduct
29
+ from memos.memos_tools.notification_service import get_error_bot_function, get_online_bot_function
30
+
31
+
32
+ logger = logging.getLogger(__name__)
33
+
34
+ router = APIRouter(prefix="/product", tags=["Product API"])
35
+
36
+ # Initialize MOSProduct instance with lazy initialization
37
+ MOS_PRODUCT_INSTANCE = None
38
+
39
+
40
+ def get_mos_product_instance():
41
+ """Get or create MOSProduct instance."""
42
+ global MOS_PRODUCT_INSTANCE
43
+ if MOS_PRODUCT_INSTANCE is None:
44
+ default_config = APIConfig.get_product_default_config()
45
+ logger.info(f"*********init_default_mos_config********* {default_config}")
46
+ from memos.configs.mem_os import MOSConfig
47
+
48
+ mos_config = MOSConfig(**default_config)
49
+
50
+ # Get default cube config from APIConfig (may be None if disabled)
51
+ default_cube_config = APIConfig.get_default_cube_config()
52
+ logger.info(f"*********initdefault_cube_config******** {default_cube_config}")
53
+
54
+ # Get DingDing bot functions
55
+ dingding_enabled = APIConfig.is_dingding_bot_enabled()
56
+ online_bot = get_online_bot_function() if dingding_enabled else None
57
+ error_bot = get_error_bot_function() if dingding_enabled else None
58
+
59
+ MOS_PRODUCT_INSTANCE = MOSProduct(
60
+ default_config=mos_config,
61
+ default_cube_config=default_cube_config,
62
+ online_bot=online_bot,
63
+ error_bot=error_bot,
64
+ )
65
+ logger.info("MOSProduct instance created successfully with inheritance architecture")
66
+ return MOS_PRODUCT_INSTANCE
67
+
68
+
69
+ get_mos_product_instance()
70
+
71
+
72
+ @router.post("/configure", summary="Configure MOSProduct", response_model=SimpleResponse)
73
+ def set_config(config):
74
+ """Set MOSProduct configuration."""
75
+ global MOS_PRODUCT_INSTANCE
76
+ MOS_PRODUCT_INSTANCE = MOSProduct(default_config=config)
77
+ return SimpleResponse(message="Configuration set successfully")
78
+
79
+
80
+ @router.post("/users/register", summary="Register a new user", response_model=UserRegisterResponse)
81
+ def register_user(user_req: UserRegisterRequest, g: Annotated[G, Depends(get_g_object)]):
82
+ """Register a new user with configuration and default cube."""
83
+ try:
84
+ # Set request-related information in g object
85
+ g.user_id = user_req.user_id
86
+ g.action = "user_register"
87
+ g.timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
88
+
89
+ logger.info(f"Starting user registration for user_id: {user_req.user_id}")
90
+ logger.info(f"Request trace_id: {g.trace_id}")
91
+ logger.info(f"Request timestamp: {g.timestamp}")
92
+
93
+ # Get configuration for the user
94
+ user_config, default_mem_cube = APIConfig.create_user_config(
95
+ user_name=user_req.user_id, user_id=user_req.user_id
96
+ )
97
+ logger.info(f"user_config: {user_config.model_dump(mode='json')}")
98
+ logger.info(f"default_mem_cube: {default_mem_cube.config.model_dump(mode='json')}")
99
+ mos_product = get_mos_product_instance()
100
+
101
+ # Register user with default config and mem cube
102
+ result = mos_product.user_register(
103
+ user_id=user_req.user_id,
104
+ user_name=user_req.user_name,
105
+ interests=user_req.interests,
106
+ config=user_config,
107
+ default_mem_cube=default_mem_cube,
108
+ )
109
+
110
+ if result["status"] == "success":
111
+ return UserRegisterResponse(
112
+ message="User registered successfully",
113
+ data={"user_id": result["user_id"], "mem_cube_id": result["default_cube_id"]},
114
+ )
115
+ else:
116
+ raise HTTPException(status_code=400, detail=result["message"])
117
+
118
+ except Exception as err:
119
+ logger.error(f"Failed to register user: {traceback.format_exc()}")
120
+ raise HTTPException(status_code=500, detail=str(traceback.format_exc())) from err
121
+
122
+
123
+ @router.get(
124
+ "/suggestions/{user_id}", summary="Get suggestion queries", response_model=SuggestionResponse
125
+ )
126
+ def get_suggestion_queries(user_id: str):
127
+ """Get suggestion queries for a specific user."""
128
+ try:
129
+ mos_product = get_mos_product_instance()
130
+ suggestions = mos_product.get_suggestion_query(user_id)
131
+ return SuggestionResponse(
132
+ message="Suggestions retrieved successfully", data={"query": suggestions}
133
+ )
134
+ except ValueError as err:
135
+ raise HTTPException(status_code=404, detail=str(traceback.format_exc())) from err
136
+ except Exception as err:
137
+ logger.error(f"Failed to get suggestions: {traceback.format_exc()}")
138
+ raise HTTPException(status_code=500, detail=str(traceback.format_exc())) from err
139
+
140
+
141
+ @router.post(
142
+ "/suggestions",
143
+ summary="Get suggestion queries with language",
144
+ response_model=SuggestionResponse,
145
+ )
146
+ def get_suggestion_queries_post(suggestion_req: SuggestionRequest):
147
+ """Get suggestion queries for a specific user with language preference."""
148
+ try:
149
+ mos_product = get_mos_product_instance()
150
+ suggestions = mos_product.get_suggestion_query(
151
+ user_id=suggestion_req.user_id, language=suggestion_req.language
152
+ )
153
+ return SuggestionResponse(
154
+ message="Suggestions retrieved successfully", data={"query": suggestions}
155
+ )
156
+ except ValueError as err:
157
+ raise HTTPException(status_code=404, detail=str(traceback.format_exc())) from err
158
+ except Exception as err:
159
+ logger.error(f"Failed to get suggestions: {traceback.format_exc()}")
160
+ raise HTTPException(status_code=500, detail=str(traceback.format_exc())) from err
161
+
162
+
163
+ @router.post("/get_all", summary="Get all memories for user", response_model=MemoryResponse)
164
+ def get_all_memories(memory_req: GetMemoryRequest):
165
+ """Get all memories for a specific user."""
166
+ try:
167
+ mos_product = get_mos_product_instance()
168
+ if memory_req.search_query:
169
+ result = mos_product.get_subgraph(
170
+ user_id=memory_req.user_id,
171
+ query=memory_req.search_query,
172
+ mem_cube_ids=memory_req.mem_cube_ids,
173
+ )
174
+ return MemoryResponse(message="Memories retrieved successfully", data=result)
175
+ else:
176
+ result = mos_product.get_all(
177
+ user_id=memory_req.user_id,
178
+ memory_type=memory_req.memory_type,
179
+ mem_cube_ids=memory_req.mem_cube_ids,
180
+ )
181
+ return MemoryResponse(message="Memories retrieved successfully", data=result)
182
+
183
+ except ValueError as err:
184
+ raise HTTPException(status_code=404, detail=str(traceback.format_exc())) from err
185
+ except Exception as err:
186
+ logger.error(f"Failed to get memories: {traceback.format_exc()}")
187
+ raise HTTPException(status_code=500, detail=str(traceback.format_exc())) from err
188
+
189
+
190
+ @router.post("/add", summary="add a new memory", response_model=SimpleResponse)
191
+ def create_memory(memory_req: MemoryCreateRequest):
192
+ """Create a new memory for a specific user."""
193
+ try:
194
+ mos_product = get_mos_product_instance()
195
+ mos_product.add(
196
+ user_id=memory_req.user_id,
197
+ memory_content=memory_req.memory_content,
198
+ messages=memory_req.messages,
199
+ doc_path=memory_req.doc_path,
200
+ mem_cube_id=memory_req.mem_cube_id,
201
+ source=memory_req.source,
202
+ user_profile=memory_req.user_profile,
203
+ )
204
+ return SimpleResponse(message="Memory created successfully")
205
+
206
+ except ValueError as err:
207
+ raise HTTPException(status_code=404, detail=str(traceback.format_exc())) from err
208
+ except Exception as err:
209
+ logger.error(f"Failed to create memory: {traceback.format_exc()}")
210
+ raise HTTPException(status_code=500, detail=str(traceback.format_exc())) from err
211
+
212
+
213
+ @router.post("/search", summary="Search memories", response_model=SearchResponse)
214
+ def search_memories(search_req: SearchRequest):
215
+ """Search memories for a specific user."""
216
+ try:
217
+ mos_product = get_mos_product_instance()
218
+ result = mos_product.search(
219
+ query=search_req.query,
220
+ user_id=search_req.user_id,
221
+ install_cube_ids=[search_req.mem_cube_id] if search_req.mem_cube_id else None,
222
+ top_k=search_req.top_k,
223
+ )
224
+ return SearchResponse(message="Search completed successfully", data=result)
225
+
226
+ except ValueError as err:
227
+ raise HTTPException(status_code=404, detail=str(traceback.format_exc())) from err
228
+ except Exception as err:
229
+ logger.error(f"Failed to search memories: {traceback.format_exc()}")
230
+ raise HTTPException(status_code=500, detail=str(traceback.format_exc())) from err
231
+
232
+
233
+ @router.post("/chat", summary="Chat with MemOS")
234
+ def chat(chat_req: ChatRequest):
235
+ """Chat with MemOS for a specific user. Returns SSE stream."""
236
+ try:
237
+ mos_product = get_mos_product_instance()
238
+
239
+ def generate_chat_response():
240
+ """Generate chat response as SSE stream."""
241
+ try:
242
+ # Directly yield from the generator without async wrapper
243
+ yield from mos_product.chat_with_references(
244
+ query=chat_req.query,
245
+ user_id=chat_req.user_id,
246
+ cube_id=chat_req.mem_cube_id,
247
+ history=chat_req.history,
248
+ internet_search=chat_req.internet_search,
249
+ )
250
+
251
+ except Exception as e:
252
+ logger.error(f"Error in chat stream: {e}")
253
+ error_data = f"data: {json.dumps({'type': 'error', 'content': str(traceback.format_exc())})}\n\n"
254
+ yield error_data
255
+
256
+ return StreamingResponse(
257
+ generate_chat_response(),
258
+ media_type="text/event-stream",
259
+ headers={
260
+ "Cache-Control": "no-cache",
261
+ "Connection": "keep-alive",
262
+ "Content-Type": "text/event-stream",
263
+ "Access-Control-Allow-Origin": "*",
264
+ "Access-Control-Allow-Headers": "*",
265
+ "Access-Control-Allow-Methods": "*",
266
+ },
267
+ )
268
+
269
+ except ValueError as err:
270
+ raise HTTPException(status_code=404, detail=str(traceback.format_exc())) from err
271
+ except Exception as err:
272
+ logger.error(f"Failed to start chat: {traceback.format_exc()}")
273
+ raise HTTPException(status_code=500, detail=str(traceback.format_exc())) from err
274
+
275
+
276
+ @router.get("/users", summary="List all users", response_model=BaseResponse[list])
277
+ def list_users():
278
+ """List all registered users."""
279
+ try:
280
+ mos_product = get_mos_product_instance()
281
+ users = mos_product.list_users()
282
+ return BaseResponse(message="Users retrieved successfully", data=users)
283
+ except Exception as err:
284
+ logger.error(f"Failed to list users: {traceback.format_exc()}")
285
+ raise HTTPException(status_code=500, detail=str(traceback.format_exc())) from err
286
+
287
+
288
+ @router.get("/users/{user_id}", summary="Get user info", response_model=BaseResponse[dict])
289
+ async def get_user_info(user_id: str):
290
+ """Get user information including accessible cubes."""
291
+ try:
292
+ mos_product = get_mos_product_instance()
293
+ user_info = mos_product.get_user_info(user_id)
294
+ return BaseResponse(message="User info retrieved successfully", data=user_info)
295
+ except ValueError as err:
296
+ raise HTTPException(status_code=404, detail=str(traceback.format_exc())) from err
297
+ except Exception as err:
298
+ logger.error(f"Failed to get user info: {traceback.format_exc()}")
299
+ raise HTTPException(status_code=500, detail=str(traceback.format_exc())) from err
300
+
301
+
302
+ @router.get(
303
+ "/configure/{user_id}", summary="Get MOSProduct configuration", response_model=SimpleResponse
304
+ )
305
+ def get_config(user_id: str):
306
+ """Get MOSProduct configuration."""
307
+ global MOS_PRODUCT_INSTANCE
308
+ config = MOS_PRODUCT_INSTANCE.default_config
309
+ return SimpleResponse(message="Configuration retrieved successfully", data=config)
310
+
311
+
312
+ @router.get(
313
+ "/users/{user_id}/config", summary="Get user configuration", response_model=BaseResponse[dict]
314
+ )
315
+ def get_user_config(user_id: str):
316
+ """Get user-specific configuration."""
317
+ try:
318
+ mos_product = get_mos_product_instance()
319
+ config = mos_product.get_user_config(user_id)
320
+ if config:
321
+ return BaseResponse(
322
+ message="User configuration retrieved successfully",
323
+ data=config.model_dump(mode="json"),
324
+ )
325
+ else:
326
+ raise HTTPException(
327
+ status_code=404, detail=f"Configuration not found for user {user_id}"
328
+ )
329
+ except ValueError as err:
330
+ raise HTTPException(status_code=404, detail=str(traceback.format_exc())) from err
331
+ except Exception as err:
332
+ logger.error(f"Failed to get user config: {traceback.format_exc()}")
333
+ raise HTTPException(status_code=500, detail=str(traceback.format_exc())) from err
334
+
335
+
336
+ @router.put(
337
+ "/users/{user_id}/config", summary="Update user configuration", response_model=SimpleResponse
338
+ )
339
+ def update_user_config(user_id: str, config_data: dict):
340
+ """Update user-specific configuration."""
341
+ try:
342
+ mos_product = get_mos_product_instance()
343
+
344
+ # Create MOSConfig from the provided data
345
+ config = MOSConfig(**config_data)
346
+
347
+ # Update the configuration
348
+ success = mos_product.update_user_config(user_id, config)
349
+ if success:
350
+ return SimpleResponse(message="User configuration updated successfully")
351
+ else:
352
+ raise HTTPException(status_code=500, detail="Failed to update user configuration")
353
+
354
+ except ValueError as err:
355
+ raise HTTPException(status_code=400, detail=str(traceback.format_exc())) from err
356
+ except Exception as err:
357
+ logger.error(f"Failed to update user config: {traceback.format_exc()}")
358
+ raise HTTPException(status_code=500, detail=str(traceback.format_exc())) from err
359
+
360
+
361
+ @router.get(
362
+ "/instances/status", summary="Get user configuration status", response_model=BaseResponse[dict]
363
+ )
364
+ def get_instance_status():
365
+ """Get information about active user configurations in memory."""
366
+ try:
367
+ mos_product = get_mos_product_instance()
368
+ status_info = mos_product.get_user_instance_info()
369
+ return BaseResponse(
370
+ message="User configuration status retrieved successfully", data=status_info
371
+ )
372
+ except Exception as err:
373
+ logger.error(f"Failed to get user configuration status: {traceback.format_exc()}")
374
+ raise HTTPException(status_code=500, detail=str(traceback.format_exc())) from err
375
+
376
+
377
+ @router.get("/instances/count", summary="Get active user count", response_model=BaseResponse[int])
378
+ def get_active_user_count():
379
+ """Get the number of active user configurations in memory."""
380
+ try:
381
+ mos_product = get_mos_product_instance()
382
+ count = mos_product.get_active_user_count()
383
+ return BaseResponse(message="Active user count retrieved successfully", data=count)
384
+ except Exception as err:
385
+ logger.error(f"Failed to get active user count: {traceback.format_exc()}")
386
+ raise HTTPException(status_code=500, detail=str(traceback.format_exc())) from err
@@ -1,6 +1,5 @@
1
- from chonkie import SentenceChunker as ChonkieSentenceChunker
2
-
3
1
  from memos.configs.chunker import SentenceChunkerConfig
2
+ from memos.dependency import require_python_package
4
3
  from memos.log import get_logger
5
4
 
6
5
  from .base import BaseChunker, Chunk
@@ -12,7 +11,14 @@ logger = get_logger(__name__)
12
11
  class SentenceChunker(BaseChunker):
13
12
  """Sentence-based text chunker."""
14
13
 
14
+ @require_python_package(
15
+ import_name="chonkie",
16
+ install_command="pip install chonkie",
17
+ install_link="https://docs.chonkie.ai/python-sdk/getting-started/installation",
18
+ )
15
19
  def __init__(self, config: SentenceChunkerConfig):
20
+ from chonkie import SentenceChunker as ChonkieSentenceChunker
21
+
16
22
  self.config = config
17
23
  self.chunker = ChonkieSentenceChunker(
18
24
  tokenizer_or_token_counter=config.tokenizer_or_token_counter,
memos/cli.py ADDED
@@ -0,0 +1,113 @@
1
+ """
2
+ MemOS CLI Tool
3
+ This script provides command-line interface for MemOS operations.
4
+ """
5
+
6
+ import argparse
7
+ import json
8
+ import os
9
+ import zipfile
10
+
11
+ from io import BytesIO
12
+
13
+
14
+ def export_openapi(output: str) -> bool:
15
+ """Export OpenAPI schema to JSON file."""
16
+ from memos.api.start_api import app
17
+
18
+ # Create directory if it doesn't exist
19
+ if os.path.dirname(output):
20
+ os.makedirs(os.path.dirname(output), exist_ok=True)
21
+
22
+ with open(output, "w") as f:
23
+ json.dump(app.openapi(), f, indent=2)
24
+ f.write("\n")
25
+
26
+ print(f"✅ OpenAPI schema exported to: {output}")
27
+ return True
28
+
29
+
30
+ def download_examples(dest: str) -> bool:
31
+ import requests
32
+
33
+ """Download examples from the MemOS repository."""
34
+ zip_url = "https://github.com/MemTensor/MemOS/archive/refs/heads/main.zip"
35
+ print(f"📥 Downloading examples from {zip_url}...")
36
+
37
+ try:
38
+ response = requests.get(zip_url)
39
+ response.raise_for_status()
40
+
41
+ with zipfile.ZipFile(BytesIO(response.content)) as z:
42
+ extracted_files = []
43
+ for file in z.namelist():
44
+ if "MemOS-main/examples/" in file and not file.endswith("/"):
45
+ # Remove the prefix and extract to dest
46
+ relative_path = file.replace("MemOS-main/examples/", "")
47
+ extract_path = os.path.join(dest, relative_path)
48
+
49
+ # Create directory if it doesn't exist
50
+ os.makedirs(os.path.dirname(extract_path), exist_ok=True)
51
+
52
+ # Extract the file
53
+ with z.open(file) as source, open(extract_path, "wb") as target:
54
+ target.write(source.read())
55
+ extracted_files.append(extract_path)
56
+
57
+ print(f"✅ Examples downloaded to: {dest}")
58
+ print(f"📁 {len(extracted_files)} files extracted")
59
+
60
+ except requests.RequestException as e:
61
+ print(f"❌ Error downloading examples: {e}")
62
+ return False
63
+ except Exception as e:
64
+ print(f"❌ Error extracting examples: {e}")
65
+ return False
66
+
67
+ return True
68
+
69
+
70
+ def main():
71
+ """Main CLI entry point."""
72
+ parser = argparse.ArgumentParser(
73
+ prog="memos",
74
+ description="MemOS Command Line Interface",
75
+ )
76
+
77
+ # Create subparsers for different commands
78
+ subparsers = parser.add_subparsers(dest="command", help="Available commands")
79
+
80
+ # Download examples command
81
+ examples_parser = subparsers.add_parser("download_examples", help="Download example files")
82
+ examples_parser.add_argument(
83
+ "--dest",
84
+ type=str,
85
+ default="./examples",
86
+ help="Destination directory for examples (default: ./examples)",
87
+ )
88
+
89
+ # Export API command
90
+ api_parser = subparsers.add_parser("export_openapi", help="Export OpenAPI schema to JSON file")
91
+ api_parser.add_argument(
92
+ "--output",
93
+ type=str,
94
+ default="openapi.json",
95
+ help="Output path for OpenAPI schema (default: openapi.json)",
96
+ )
97
+
98
+ # Parse arguments
99
+ args = parser.parse_args()
100
+
101
+ # Handle commands
102
+ if args.command == "download_examples":
103
+ success = download_examples(args.dest)
104
+ exit(0 if success else 1)
105
+ elif args.command == "export_openapi":
106
+ success = export_openapi(args.output)
107
+ exit(0 if success else 1)
108
+ else:
109
+ parser.print_help()
110
+
111
+
112
+ if __name__ == "__main__":
113
+ main()