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.
- {memoryos-0.2.0.dist-info → memoryos-0.2.2.dist-info}/METADATA +67 -26
- memoryos-0.2.2.dist-info/RECORD +169 -0
- memoryos-0.2.2.dist-info/entry_points.txt +3 -0
- memos/__init__.py +1 -1
- memos/api/config.py +562 -0
- memos/api/context/context.py +147 -0
- memos/api/context/dependencies.py +90 -0
- memos/api/exceptions.py +28 -0
- memos/api/mcp_serve.py +502 -0
- memos/api/product_api.py +35 -0
- memos/api/product_models.py +163 -0
- memos/api/routers/__init__.py +1 -0
- memos/api/routers/product_router.py +386 -0
- memos/chunkers/sentence_chunker.py +8 -2
- memos/cli.py +113 -0
- memos/configs/embedder.py +27 -0
- memos/configs/graph_db.py +132 -3
- memos/configs/internet_retriever.py +6 -0
- memos/configs/llm.py +47 -0
- memos/configs/mem_cube.py +1 -1
- memos/configs/mem_os.py +5 -0
- memos/configs/mem_reader.py +9 -0
- memos/configs/mem_scheduler.py +107 -7
- memos/configs/mem_user.py +58 -0
- memos/configs/memory.py +5 -4
- memos/dependency.py +52 -0
- memos/embedders/ark.py +92 -0
- memos/embedders/factory.py +4 -0
- memos/embedders/sentence_transformer.py +8 -2
- memos/embedders/universal_api.py +32 -0
- memos/graph_dbs/base.py +11 -3
- memos/graph_dbs/factory.py +4 -0
- memos/graph_dbs/nebular.py +1364 -0
- memos/graph_dbs/neo4j.py +333 -124
- memos/graph_dbs/neo4j_community.py +300 -0
- memos/llms/base.py +9 -0
- memos/llms/deepseek.py +54 -0
- memos/llms/factory.py +10 -1
- memos/llms/hf.py +170 -13
- memos/llms/hf_singleton.py +114 -0
- memos/llms/ollama.py +4 -0
- memos/llms/openai.py +67 -1
- memos/llms/qwen.py +63 -0
- memos/llms/vllm.py +153 -0
- memos/log.py +1 -1
- memos/mem_cube/general.py +77 -16
- memos/mem_cube/utils.py +109 -0
- memos/mem_os/core.py +251 -51
- memos/mem_os/main.py +94 -12
- memos/mem_os/product.py +1220 -43
- memos/mem_os/utils/default_config.py +352 -0
- memos/mem_os/utils/format_utils.py +1401 -0
- memos/mem_reader/simple_struct.py +18 -10
- memos/mem_scheduler/base_scheduler.py +441 -40
- memos/mem_scheduler/general_scheduler.py +249 -248
- memos/mem_scheduler/modules/base.py +14 -5
- memos/mem_scheduler/modules/dispatcher.py +67 -4
- memos/mem_scheduler/modules/misc.py +104 -0
- memos/mem_scheduler/modules/monitor.py +240 -50
- memos/mem_scheduler/modules/rabbitmq_service.py +319 -0
- memos/mem_scheduler/modules/redis_service.py +32 -22
- memos/mem_scheduler/modules/retriever.py +167 -23
- memos/mem_scheduler/modules/scheduler_logger.py +255 -0
- memos/mem_scheduler/mos_for_test_scheduler.py +140 -0
- memos/mem_scheduler/schemas/__init__.py +0 -0
- memos/mem_scheduler/schemas/general_schemas.py +43 -0
- memos/mem_scheduler/{modules/schemas.py → schemas/message_schemas.py} +63 -61
- memos/mem_scheduler/schemas/monitor_schemas.py +329 -0
- memos/mem_scheduler/utils/__init__.py +0 -0
- memos/mem_scheduler/utils/filter_utils.py +176 -0
- memos/mem_scheduler/utils/misc_utils.py +61 -0
- memos/mem_user/factory.py +94 -0
- memos/mem_user/mysql_persistent_user_manager.py +271 -0
- memos/mem_user/mysql_user_manager.py +500 -0
- memos/mem_user/persistent_factory.py +96 -0
- memos/mem_user/persistent_user_manager.py +260 -0
- memos/mem_user/user_manager.py +4 -4
- memos/memories/activation/item.py +29 -0
- memos/memories/activation/kv.py +10 -3
- memos/memories/activation/vllmkv.py +219 -0
- memos/memories/factory.py +2 -0
- memos/memories/textual/base.py +1 -1
- memos/memories/textual/general.py +43 -97
- memos/memories/textual/item.py +5 -33
- memos/memories/textual/tree.py +22 -12
- memos/memories/textual/tree_text_memory/organize/conflict.py +9 -5
- memos/memories/textual/tree_text_memory/organize/manager.py +26 -18
- memos/memories/textual/tree_text_memory/organize/redundancy.py +25 -44
- memos/memories/textual/tree_text_memory/organize/relation_reason_detector.py +50 -48
- memos/memories/textual/tree_text_memory/organize/reorganizer.py +81 -56
- memos/memories/textual/tree_text_memory/retrieve/internet_retriever.py +6 -3
- memos/memories/textual/tree_text_memory/retrieve/internet_retriever_factory.py +2 -0
- memos/memories/textual/tree_text_memory/retrieve/recall.py +0 -1
- memos/memories/textual/tree_text_memory/retrieve/reranker.py +2 -2
- memos/memories/textual/tree_text_memory/retrieve/retrieval_mid_structs.py +2 -0
- memos/memories/textual/tree_text_memory/retrieve/searcher.py +52 -28
- memos/memories/textual/tree_text_memory/retrieve/task_goal_parser.py +42 -15
- memos/memories/textual/tree_text_memory/retrieve/utils.py +11 -7
- memos/memories/textual/tree_text_memory/retrieve/xinyusearch.py +62 -58
- memos/memos_tools/dinding_report_bot.py +422 -0
- memos/memos_tools/notification_service.py +44 -0
- memos/memos_tools/notification_utils.py +96 -0
- memos/parsers/markitdown.py +8 -2
- memos/settings.py +3 -1
- memos/templates/mem_reader_prompts.py +66 -23
- memos/templates/mem_scheduler_prompts.py +126 -43
- memos/templates/mos_prompts.py +87 -0
- memos/templates/tree_reorganize_prompts.py +85 -30
- memos/vec_dbs/base.py +12 -0
- memos/vec_dbs/qdrant.py +46 -20
- memoryos-0.2.0.dist-info/RECORD +0 -128
- memos/mem_scheduler/utils.py +0 -26
- {memoryos-0.2.0.dist-info → memoryos-0.2.2.dist-info}/LICENSE +0 -0
- {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()
|