Skip to content

Commit 8003c2f

Browse files
CaralHsiCarltonXiangfridayL
authored
feat: enhance APIADDRequest with custom_tags, info, and is_feedback fields (#515)
* 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 --------- Co-authored-by: HarveyXiang <[email protected]> Co-authored-by: fridayL <[email protected]> Co-authored-by: chunyu li <[email protected]>
1 parent c63555f commit 8003c2f

File tree

5 files changed

+1045
-29
lines changed

5 files changed

+1045
-29
lines changed

src/memos/api/config.py

Lines changed: 3 additions & 1 deletion
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
},

src/memos/api/product_models.py

Lines changed: 204 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,9 @@ class ChatRequest(BaseRequest):
9393
temperature: float | None = Field(None, description="Temperature for sampling")
9494
top_p: float | None = Field(None, description="Top-p (nucleus) sampling parameter")
9595
add_message_on_answer: bool = Field(True, description="Add dialogs to memory after chat")
96+
moscube: bool = Field(
97+
False, description="(Deprecated) Whether to use legacy MemOSCube pipeline"
98+
)
9699

97100

98101
class ChatCompleteRequest(BaseRequest):
@@ -116,6 +119,11 @@ class ChatCompleteRequest(BaseRequest):
116119
top_p: float | None = Field(None, description="Top-p (nucleus) sampling parameter")
117120
add_message_on_answer: bool = Field(True, description="Add dialogs to memory after chat")
118121

122+
base_prompt: str | None = Field(None, description="(Deprecated) Base prompt alias")
123+
moscube: bool = Field(
124+
False, description="(Deprecated) Whether to use legacy MemOSCube pipeline"
125+
)
126+
119127

120128
class UserCreate(BaseRequest):
121129
user_name: str | None = Field(None, description="Name of the user")
@@ -204,49 +212,218 @@ class SearchRequest(BaseRequest):
204212
class APISearchRequest(BaseRequest):
205213
"""Request model for searching memories."""
206214

207-
query: str = Field(..., description="Search query")
208-
user_id: str = Field(None, description="User ID")
209-
mem_cube_id: str | None = Field(None, description="Cube ID to search in")
215+
# ==== Basic inputs ====
216+
query: str = Field(
217+
...,
218+
description=("User search query"),
219+
)
220+
user_id: str = Field(..., description="User ID")
221+
222+
# ==== 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+
)
210230
readable_cube_ids: list[str] | None = Field(
211-
None, description="List of cube IDs user can read for multi-cube search"
231+
None,
232+
description=(
233+
"List of cube IDs that are readable for this request. "
234+
"Required for algorithm-facing API; optional for developer-facing API."
235+
),
212236
)
213-
mode: SearchMode = Field(SearchMode.FAST, description="search mode: fast, fine, or mixture")
214-
internet_search: bool = Field(False, description="Whether to use internet search")
215-
top_k: int = Field(10, description="Number of results to return")
216-
chat_history: list[MessageDict] | None = Field(None, description="Chat history")
217-
session_id: str | None = Field(None, description="Session ID for soft-filtering memories")
237+
238+
# ==== Search mode ====
239+
mode: SearchMode = Field(
240+
SearchMode.FAST,
241+
description="Search mode: fast, fine, or mixture.",
242+
)
243+
244+
session_id: str | None = Field(
245+
None,
246+
description=(
247+
"Session ID used as a soft signal to prioritize more relevant memories. "
248+
"Only used for weighting, not as a hard filter."
249+
),
250+
)
251+
252+
# ==== Result control ====
253+
top_k: int = Field(
254+
10,
255+
ge=1,
256+
description="Number of textual memories to retrieve (top-K). Default: 10.",
257+
)
258+
259+
pref_top_k: int = Field(
260+
6,
261+
ge=0,
262+
description="Number of preference memories to retrieve (top-K). Default: 6.",
263+
)
264+
265+
include_preference: bool = Field(
266+
True,
267+
description=(
268+
"Whether to retrieve preference memories along with general memories. "
269+
"If enabled, the system will automatically recall user preferences "
270+
"relevant to the query. Default: True."
271+
),
272+
)
273+
274+
# ==== Filter conditions ====
275+
# TODO: maybe add detailed description later
276+
filter: dict[str, Any] | None = Field(
277+
None,
278+
description=("Filter for the memory"),
279+
)
280+
281+
# ==== Extended capabilities ====
282+
internet_search: bool = Field(
283+
False,
284+
description=(
285+
"Whether to enable internet search in addition to memory search. "
286+
"Primarily used by internal algorithms. Default: False."
287+
),
288+
)
289+
290+
# Inner user, not supported in API yet
291+
threshold: float | None = Field(
292+
None,
293+
description=(
294+
"Internal similarity threshold for searching plaintext memories. "
295+
"If None, default thresholds will be applied."
296+
),
297+
)
298+
299+
# ==== Context ====
300+
chat_history: list[MessageDict] | None = Field(
301+
None,
302+
description=(
303+
"Historical chat messages used internally by algorithms. "
304+
"If None, internal stored history may be used; "
305+
"if provided (even an empty list), this value will be used as-is."
306+
),
307+
)
308+
309+
# ==== Backward compatibility ====
310+
moscube: bool = Field(
311+
False,
312+
description="(Deprecated / internal) Whether to use legacy MemOSCube path.",
313+
)
314+
218315
operation: list[PermissionDict] | None = Field(
219-
None, description="operation ids for multi cubes"
316+
None,
317+
description="(Internal) Operation definitions for multi-cube read permissions.",
220318
)
221-
include_preference: bool = Field(True, description="Whether to handle preference memory")
222-
pref_top_k: int = Field(6, description="Number of preference results to return")
223-
filter: dict[str, Any] | None = Field(None, description="Filter for the memory")
224319

225320

226321
class APIADDRequest(BaseRequest):
227322
"""Request model for creating memories."""
228323

324+
# ==== Basic identifiers ====
229325
user_id: str = Field(None, description="User ID")
230-
mem_cube_id: str | None = Field(None, description="Cube ID")
326+
session_id: str | None = Field(
327+
None,
328+
description="Session ID. If not provided, a default session will be used.",
329+
)
330+
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+
337+
# ==== Multi-cube writing ====
231338
writable_cube_ids: list[str] | None = Field(
232339
None, description="List of cube IDs user can write for multi-cube add"
233340
)
234-
messages: list[MessageDict] | None = Field(None, description="List of messages to store.")
235-
memory_content: str | None = Field(None, description="Memory content to store")
236-
doc_path: str | None = Field(None, description="Path to document to store")
237-
source: str | None = Field(None, description="Source of the memory")
238-
chat_history: list[MessageDict] | None = Field(None, description="Chat history")
239-
session_id: str | None = Field(None, description="Session id")
240-
operation: list[PermissionDict] | None = Field(
241-
None, description="operation ids for multi cubes"
242-
)
341+
342+
# ==== Async control ====
243343
async_mode: Literal["async", "sync"] = Field(
244-
"async", description="Whether to add memory in async mode"
344+
"async",
345+
description=(
346+
"Whether to add memory in async mode. "
347+
"Use 'async' to enqueue background add (non-blocking), "
348+
"or 'sync' to add memories in the current call. "
349+
"Default: 'async'."
350+
),
245351
)
246-
custom_tags: list[str] | None = Field(None, description="Custom tags for the memory")
247-
info: dict[str, str] | None = Field(None, description="Additional information for the memory")
352+
353+
# ==== Business tags & info ====
354+
custom_tags: list[str] | None = Field(
355+
None,
356+
description=(
357+
"Custom tags for this add request, e.g. ['Travel', 'family']. "
358+
"These tags can be used as filters in search."
359+
),
360+
)
361+
362+
info: dict[str, str] | None = Field(
363+
None,
364+
description=(
365+
"Additional metadata for the add request. "
366+
"All keys can be used as filters in search. "
367+
"Example: "
368+
"{'agent_id': 'xxxxxx', "
369+
"'app_id': 'xxxx', "
370+
"'source_type': 'web', "
371+
"'source_url': 'https://www.baidu.com', "
372+
"'source_content': '西湖是杭州最著名的景点'}."
373+
),
374+
)
375+
376+
# ==== Input content ====
377+
messages: list[MessageDict] | None = Field(
378+
None,
379+
description=(
380+
"List of messages to store. Supports: "
381+
"- system / user / assistant messages with 'content' and 'chat_time'; "
382+
"- tool messages including: "
383+
" * tool_description (name, description, parameters), "
384+
" * tool_input (call_id, name, argument), "
385+
" * raw tool messages where content is str or list[str], "
386+
" * tool_output with structured output items "
387+
" (input_text / input_image / input_file, etc.). "
388+
"Also supports pure input items when there is no dialog."
389+
),
390+
)
391+
392+
# ==== Chat history ====
393+
chat_history: list[MessageDict] | None = Field(
394+
None,
395+
description=(
396+
"Historical chat messages used internally by algorithms. "
397+
"If None, internal stored history will be used; "
398+
"if provided (even an empty list), this value will be used as-is."
399+
),
400+
)
401+
402+
# ==== Feedback flag ====
248403
is_feedback: bool = Field(
249-
False, description="Whether the user feedback in knowladge base service"
404+
False,
405+
description=("Whether this request represents user feedback. Default: False."),
406+
)
407+
408+
# ==== Backward compatibility fields (will delete later) ====
409+
memory_content: str | None = Field(
410+
None,
411+
description="(Deprecated) Plain memory content to store. Prefer using `messages`.",
412+
)
413+
doc_path: str | None = Field(
414+
None,
415+
description="(Deprecated / internal) Path to document to store.",
416+
)
417+
source: str | None = Field(
418+
None,
419+
description=(
420+
"(Deprecated) Simple source tag of the memory. "
421+
"Prefer using `info.source_type` / `info.source_url`."
422+
),
423+
)
424+
operation: list[PermissionDict] | None = Field(
425+
None,
426+
description="(Internal) Operation definitions for multi-cube write permissions.",
250427
)
251428

252429

src/memos/api/routers/product_router.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,8 @@ def chat_complete(chat_req: ChatCompleteRequest):
297297
history=chat_req.history,
298298
internet_search=chat_req.internet_search,
299299
moscube=chat_req.moscube,
300-
base_prompt=chat_req.base_prompt,
300+
base_prompt=chat_req.base_prompt or chat_req.system_prompt,
301+
# will deprecate base_prompt in the future
301302
top_k=chat_req.top_k,
302303
threshold=chat_req.threshold,
303304
session_id=chat_req.session_id,

0 commit comments

Comments
 (0)