Skip to content

Commit 1519e33

Browse files
CaralHsiCarltonXiangfridayL
authored
refactor: Consolidate backward compatibility into API models; simplify handler logic (#520)
* hotfix:hotfix * feat: enhance APIADDRequest with custom_tags, info, and is_feedback fields - Add custom_tags field for user-defined tags (e.g., ['Travel', 'family']) that can be used as search filters - Add info field for additional metadata (agent_id, app_id, source_type, etc.) with all keys usable as search filters - Add is_feedback field to indicate if the request represents user feedback - Reorganize fields with category comments for better readability - Mark async_mode as required with default value 'async' - Mark mem_cube_id, memory_content, doc_path, and source as deprecated - Enhance field descriptions for better API documentation * refactor(api): enhance APISearchRequest model with improved structure and documentation - Reorganize fields with clear section comments (Basic inputs, Cube scoping, Search mode, etc.) - Add comprehensive field descriptions for better API documentation - Add new 'filter' field for structured filter conditions with support for logical operators, comparisons, and string operations - Add 'pref_top_k' and 'include_preference' fields for preference memory handling - Mark 'mem_cube_id' as deprecated, recommend 'readable_cube_ids' for multi-cube search - Make 'user_id' required field (was optional) - Add validation constraints (e.g., top_k >= 1) - Improve backward compatibility notes and internal field descriptions - Add 'threshold' field for internal similarity threshold control * test: add routers api * fix: Fixed the compatibility issue in the product router. * fix: tests unpass * fix: test_api bug * feat: change MessageDict to MessagesType in routers * feat: adjust deprecated input parameters in Add/Search Request --------- Co-authored-by: HarveyXiang <[email protected]> Co-authored-by: fridayL <[email protected]> Co-authored-by: chunyu li <[email protected]>
1 parent 74e0247 commit 1519e33

File tree

4 files changed

+136
-60
lines changed

4 files changed

+136
-60
lines changed

src/memos/api/handlers/add_handler.py

Lines changed: 4 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@
55
using dependency injection for better modularity and testability.
66
"""
77

8-
from datetime import datetime
9-
108
from memos.api.handlers.base_handler import BaseHandler, HandlerDependencies
119
from memos.api.product_models import APIADDRequest, MemoryResponse
1210
from memos.multi_mem_cube.composite_cube import CompositeCubeView
@@ -39,17 +37,13 @@ def handle_add_memories(self, add_req: APIADDRequest) -> MemoryResponse:
3937
supporting concurrent processing.
4038
4139
Args:
42-
add_req: Add memory request
40+
add_req: Add memory request (deprecated fields are converted in model validator)
4341
4442
Returns:
4543
MemoryResponse with added memory information
4644
"""
4745
self.logger.info(f"[AddHandler] Add Req is: {add_req}")
4846

49-
if (not add_req.messages) and getattr(add_req, "memory_content", None):
50-
add_req.messages = self._convert_content_messsage(add_req.memory_content)
51-
self.logger.info(f"[AddHandler] Converted content to messages: {add_req.messages}")
52-
5347
cube_view = self._build_cube_view(add_req)
5448

5549
results = cube_view.add_memories(add_req)
@@ -65,16 +59,12 @@ def _resolve_cube_ids(self, add_req: APIADDRequest) -> list[str]:
6559
"""
6660
Normalize target cube ids from add_req.
6761
Priority:
68-
1) writable_cube_ids
69-
2) mem_cube_id
70-
3) fallback to user_id
62+
1) writable_cube_ids (deprecated mem_cube_id is converted to this in model validator)
63+
2) fallback to user_id
7164
"""
72-
if getattr(add_req, "writable_cube_ids", None):
65+
if add_req.writable_cube_ids:
7366
return list(dict.fromkeys(add_req.writable_cube_ids))
7467

75-
if add_req.mem_cube_id:
76-
return [add_req.mem_cube_id]
77-
7868
return [add_req.user_id]
7969

8070
def _build_cube_view(self, add_req: APIADDRequest) -> MemCubeView:
@@ -106,23 +96,3 @@ def _build_cube_view(self, add_req: APIADDRequest) -> MemCubeView:
10696
cube_views=single_views,
10797
logger=self.logger,
10898
)
109-
110-
def _convert_content_messsage(self, memory_content: str) -> list[dict[str, str]]:
111-
"""
112-
Convert content string to list of message dictionaries.
113-
114-
Args:
115-
content: add content string
116-
117-
Returns:
118-
List of message dictionaries
119-
"""
120-
messages_list = [
121-
{
122-
"role": "user",
123-
"content": memory_content,
124-
"chat_time": str(datetime.now().strftime("%Y-%m-%d %H:%M:%S")),
125-
}
126-
]
127-
# for only user-str input and convert message
128-
return messages_list

src/memos/api/handlers/search_handler.py

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -63,16 +63,12 @@ def _resolve_cube_ids(self, search_req: APISearchRequest) -> list[str]:
6363
"""
6464
Normalize target cube ids from search_req.
6565
Priority:
66-
1) readable_cube_ids
67-
2) mem_cube_id
68-
3) fallback to user_id
66+
1) readable_cube_ids (deprecated mem_cube_id is converted to this in model validator)
67+
2) fallback to user_id
6968
"""
70-
if getattr(search_req, "readable_cube_ids", None):
69+
if search_req.readable_cube_ids:
7170
return list(dict.fromkeys(search_req.readable_cube_ids))
7271

73-
if search_req.mem_cube_id:
74-
return [search_req.mem_cube_id]
75-
7672
return [search_req.user_id]
7773

7874
def _build_cube_view(self, search_req: APISearchRequest) -> MemCubeView:

src/memos/api/product_models.py

Lines changed: 128 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@
22

33
from typing import Any, Generic, Literal, TypeVar
44

5-
from pydantic import BaseModel, Field
5+
from pydantic import BaseModel, Field, model_validator
66

77
# Import message types from core types module
8+
from memos.log import get_logger
89
from memos.mem_scheduler.schemas.general_schemas import SearchMode
9-
from memos.types import MessageDict, PermissionDict
10+
from memos.types import MessageDict, MessagesType, PermissionDict
1011

1112

13+
logger = get_logger(__name__)
1214
T = TypeVar("T")
1315

1416

@@ -215,18 +217,11 @@ class APISearchRequest(BaseRequest):
215217
# ==== Basic inputs ====
216218
query: str = Field(
217219
...,
218-
description=("User search query"),
220+
description="User search query",
219221
)
220222
user_id: str = Field(..., description="User ID")
221223

222224
# ==== Cube scoping ====
223-
mem_cube_id: str | None = Field(
224-
None,
225-
description=(
226-
"(Deprecated) Single cube ID to search in. "
227-
"Prefer `readable_cube_ids` for multi-cube search."
228-
),
229-
)
230225
readable_cube_ids: list[str] | None = Field(
231226
None,
232227
description=(
@@ -297,7 +292,7 @@ class APISearchRequest(BaseRequest):
297292
)
298293

299294
# ==== Context ====
300-
chat_history: list[MessageDict] | None = Field(
295+
chat_history: MessagesType | None = Field(
301296
None,
302297
description=(
303298
"Historical chat messages used internally by algorithms. "
@@ -307,6 +302,14 @@ class APISearchRequest(BaseRequest):
307302
)
308303

309304
# ==== Backward compatibility ====
305+
mem_cube_id: str | None = Field(
306+
None,
307+
description=(
308+
"(Deprecated) Single cube ID to search in. "
309+
"Prefer `readable_cube_ids` for multi-cube search."
310+
),
311+
)
312+
310313
moscube: bool = Field(
311314
False,
312315
description="(Deprecated / internal) Whether to use legacy MemOSCube path.",
@@ -317,6 +320,41 @@ class APISearchRequest(BaseRequest):
317320
description="(Internal) Operation definitions for multi-cube read permissions.",
318321
)
319322

323+
@model_validator(mode="after")
324+
def _convert_deprecated_fields(self) -> "APISearchRequest":
325+
"""
326+
Convert deprecated fields to new fields for backward compatibility.
327+
Ensures full backward compatibility:
328+
- mem_cube_id → readable_cube_ids
329+
- moscube is ignored with warning
330+
- operation ignored
331+
"""
332+
# Convert mem_cube_id to readable_cube_ids (new field takes priority)
333+
if self.mem_cube_id is not None:
334+
if not self.readable_cube_ids:
335+
self.readable_cube_ids = [self.mem_cube_id]
336+
logger.warning(
337+
"Deprecated field `mem_cube_id` is used in APISearchRequest. "
338+
"It will be removed in a future version. "
339+
"Please migrate to `readable_cube_ids`."
340+
)
341+
342+
# Reject moscube if set to True (no longer supported)
343+
if self.moscube:
344+
logger.warning(
345+
"Deprecated field `moscube` is used in APISearchRequest. "
346+
"Legacy MemOSCube pipeline will be removed soon."
347+
)
348+
349+
# Warn about operation (internal)
350+
if self.operation:
351+
logger.warning(
352+
"Internal field `operation` is provided in APISearchRequest. "
353+
"This field is deprecated and ignored."
354+
)
355+
356+
return self
357+
320358

321359
class APIADDRequest(BaseRequest):
322360
"""Request model for creating memories."""
@@ -328,12 +366,6 @@ class APIADDRequest(BaseRequest):
328366
description="Session ID. If not provided, a default session will be used.",
329367
)
330368

331-
# ==== Single-cube writing (Deprecated) ====
332-
mem_cube_id: str | None = Field(
333-
None,
334-
description="(Deprecated) Target cube ID for this add request (optional for developer API).",
335-
)
336-
337369
# ==== Multi-cube writing ====
338370
writable_cube_ids: list[str] | None = Field(
339371
None, description="List of cube IDs user can write for multi-cube add"
@@ -374,7 +406,7 @@ class APIADDRequest(BaseRequest):
374406
)
375407

376408
# ==== Input content ====
377-
messages: list[MessageDict] | None = Field(
409+
messages: MessagesType | None = Field(
378410
None,
379411
description=(
380412
"List of messages to store. Supports: "
@@ -390,7 +422,7 @@ class APIADDRequest(BaseRequest):
390422
)
391423

392424
# ==== Chat history ====
393-
chat_history: list[MessageDict] | None = Field(
425+
chat_history: MessagesType | None = Field(
394426
None,
395427
description=(
396428
"Historical chat messages used internally by algorithms. "
@@ -406,6 +438,11 @@ class APIADDRequest(BaseRequest):
406438
)
407439

408440
# ==== Backward compatibility fields (will delete later) ====
441+
mem_cube_id: str | None = Field(
442+
None,
443+
description="(Deprecated) Target cube ID for this add request (optional for developer API).",
444+
)
445+
409446
memory_content: str | None = Field(
410447
None,
411448
description="(Deprecated) Plain memory content to store. Prefer using `messages`.",
@@ -426,6 +463,78 @@ class APIADDRequest(BaseRequest):
426463
description="(Internal) Operation definitions for multi-cube write permissions.",
427464
)
428465

466+
@model_validator(mode="after")
467+
def _convert_deprecated_fields(self) -> "APIADDRequest":
468+
"""
469+
Convert deprecated fields to new fields for backward compatibility.
470+
This keeps the API fully backward-compatible while allowing
471+
internal logic to use only the new fields.
472+
473+
Rules:
474+
- mem_cube_id → writable_cube_ids
475+
- memory_content → messages
476+
- doc_path → messages (input_file)
477+
- source → info["source"]
478+
- operation → merged into writable_cube_ids (ignored otherwise)
479+
"""
480+
# Convert mem_cube_id to writable_cube_ids (new field takes priority)
481+
if self.mem_cube_id:
482+
logger.warning(
483+
"APIADDRequest.mem_cube_id is deprecated and will be removed in a future version. "
484+
"Please use `writable_cube_ids` instead."
485+
)
486+
if not self.writable_cube_ids:
487+
self.writable_cube_ids = [self.mem_cube_id]
488+
489+
# Handle deprecated operation field
490+
if self.operation:
491+
logger.warning(
492+
"APIADDRequest.operation is deprecated and will be removed. "
493+
"Use `writable_cube_ids` for multi-cube writes."
494+
)
495+
496+
# Convert memory_content to messages (new field takes priority)
497+
if self.memory_content:
498+
logger.warning(
499+
"APIADDRequest.memory_content is deprecated. "
500+
"Use `messages` with a structured message instead."
501+
)
502+
if self.messages is None:
503+
self.messages = []
504+
self.messages.append(
505+
{
506+
"type": "text",
507+
"text": self.memory_content,
508+
}
509+
)
510+
511+
# Handle deprecated doc_path
512+
if self.doc_path:
513+
logger.warning(
514+
"APIADDRequest.doc_path is deprecated. "
515+
"Use `messages` with an input_file item instead."
516+
)
517+
if self.messages is None:
518+
self.messages = []
519+
self.messages.append(
520+
{
521+
"type": "file",
522+
"file": {"path": self.doc_path},
523+
}
524+
)
525+
526+
# Convert source to info.source_type (new field takes priority)
527+
if self.source:
528+
logger.warning(
529+
"APIADDRequest.source is deprecated. "
530+
"Use `info['source_type']` / `info['source_url']` instead."
531+
)
532+
if self.info is None:
533+
self.info = {}
534+
self.info.setdefault("source", self.source)
535+
536+
return self
537+
429538

430539
class APIChatCompleteRequest(BaseRequest):
431540
"""Request model for chat operations."""

src/memos/types/types.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
"MessageDict",
2828
"MessageList",
2929
"MessageRole",
30+
"MessagesType",
3031
"Permission",
3132
"PermissionDict",
3233
"UserContext",

0 commit comments

Comments
 (0)