1+ """Memory configuration and CRUD API endpoints for the app layer.
2+
3+ This module exposes HTTP endpoints under the `/memory` prefix. It follows the
4+ app-layer responsibilities:
5+ - Parse and validate HTTP inputs
6+ - Delegate business logic to the service layer
7+ - Convert unexpected exceptions to error JSON responses
8+
9+ Routes:
10+ - GET `/memory/config/load`: Load memory-related configuration for current user
11+ - POST `/memory/config/set`: Set a single configuration entry
12+ - POST `/memory/config/disable_agent`: Add a disabled agent id
13+ - DELETE `/memory/config/disable_agent/{agent_id}`: Remove a disabled agent id
14+ - POST `/memory/config/disable_useragent`: Add a disabled user-agent id
15+ - DELETE `/memory/config/disable_useragent/{agent_id}`: Remove a disabled user-agent id
16+ - POST `/memory/add`: Add memory items (optionally with LLM inference)
17+ - POST `/memory/search`: Semantic search memory items
18+ - GET `/memory/list`: List memory items
19+ - DELETE `/memory/delete/{memory_id}`: Delete a single memory item
20+ - DELETE `/memory/clear`: Clear memory items by scope
21+ """
122import asyncio
223import logging
324from typing import Any , Dict , List , Optional
425
5- from fastapi import APIRouter , Body , Header , Path , Query
26+ from http import HTTPStatus
27+ from fastapi import APIRouter , Body , Header , Path , Query , HTTPException
628from fastapi .responses import JSONResponse
29+
730from nexent .memory .memory_service import (
831 add_memory as svc_add_memory ,
932 clear_memory as svc_clear_memory ,
1033 delete_memory as svc_delete_memory ,
1134 list_memory as svc_list_memory ,
1235 search_memory as svc_search_memory ,
1336)
14-
1537from consts .const import (
1638 MEMORY_AGENT_SHARE_KEY ,
1739 MEMORY_SWITCH_KEY ,
1840)
1941from consts .model import MemoryAgentShareMode
42+ from consts .exceptions import UnauthorizedError
2043from services .memory_config_service import (
2144 add_disabled_agent_id ,
2245 add_disabled_useragent_id ,
3457router = APIRouter (prefix = "/memory" )
3558
3659
37- # ---------------------------------------------------------------------------
38- # Generic helpers
39- # ---------------------------------------------------------------------------
40- def _success (message : str = "success" , content : Optional [Any ] = None ):
41- return JSONResponse (status_code = 200 , content = {"message" : message , "status" : "success" , "content" : content })
42-
43-
44- def _error (message : str = "error" ):
45- return JSONResponse (status_code = 400 , content = {"message" : message , "status" : "error" })
46-
47-
48- # ---------------------------------------------------------------------------
49- # Helper function
50- # ---------------------------------------------------------------------------
51-
52-
5360# ---------------------------------------------------------------------------
5461# Configuration Endpoints
5562# ---------------------------------------------------------------------------
5663@router .get ("/config/load" )
5764def load_configs (authorization : Optional [str ] = Header (None )):
58- """Load all memory-related configuration for current user."""
65+ """Load all memory-related configuration for the current user.
66+
67+ Args:
68+ authorization: Optional authorization header used to identify the user.
69+ """
5970 try :
6071 user_id , _ = get_current_user_id (authorization )
6172 configs = get_user_configs (user_id )
62- return _success (content = configs )
73+ return JSONResponse (status_code = HTTPStatus .OK , content = configs )
74+ except UnauthorizedError as e :
75+ raise HTTPException (status_code = HTTPStatus .UNAUTHORIZED , detail = str (e ))
6376 except Exception as e :
6477 logger .error ("load_configs failed: %s" , e )
65- return _error ("Failed to load configuration" )
78+ raise HTTPException (status_code = HTTPStatus .BAD_REQUEST ,
79+ detail = "Failed to load configuration" )
6680
6781
6882@router .post ("/config/set" )
@@ -71,7 +85,17 @@ def set_single_config(
7185 value : Any = Body (..., embed = True , description = "Configuration value" ),
7286 authorization : Optional [str ] = Header (None ),
7387):
74- """Unified endpoint to set single-value configuration items."""
88+ """Set a single-value configuration item for the current user.
89+
90+ Supported keys:
91+ - `MEMORY_SWITCH_KEY`: Toggle memory system on/off (boolean-like values accepted)
92+ - `MEMORY_AGENT_SHARE_KEY`: Set agent share mode (`always`/`ask`/`never`)
93+
94+ Args:
95+ key: Configuration key to update.
96+ value: New value for the configuration key.
97+ authorization: Optional authorization header used to identify the user.
98+ """
7599 user_id , _ = get_current_user_id (authorization )
76100
77101 if key == MEMORY_SWITCH_KEY :
@@ -82,52 +106,93 @@ def set_single_config(
82106 try :
83107 mode = MemoryAgentShareMode (str (value ))
84108 except ValueError :
85- return _error ("Invalid value for MEMORY_AGENT_SHARE (expected always/ask/never)" )
109+ raise HTTPException (status_code = HTTPStatus .NOT_ACCEPTABLE ,
110+ detail = "Invalid value for MEMORY_AGENT_SHARE (expected always/ask/never)" )
86111 ok = set_agent_share (user_id , mode )
87112 else :
88- return _error ("Unsupported configuration key" )
113+ raise HTTPException (status_code = HTTPStatus .NOT_ACCEPTABLE ,
114+ detail = "Unsupported configuration key" )
89115
90- return _success () if ok else _error ("Failed to update configuration" )
116+ if ok :
117+ return JSONResponse (status_code = HTTPStatus .OK , content = {"success" : True })
118+ raise HTTPException (status_code = HTTPStatus .BAD_REQUEST ,
119+ detail = "Failed to update configuration" )
91120
92121
93122@router .post ("/config/disable_agent" )
94123def add_disable_agent (
95124 agent_id : str = Body (..., embed = True ),
96125 authorization : Optional [str ] = Header (None ),
97126):
127+ """Add an agent id to the user's disabled agent list.
128+
129+ Args:
130+ agent_id: Identifier of the agent to disable.
131+ authorization: Optional authorization header used to identify the user.
132+ """
98133 user_id , _ = get_current_user_id (authorization )
99134 ok = add_disabled_agent_id (user_id , agent_id )
100- return _success () if ok else _error ("Failed to add disable agent id" )
135+ if ok :
136+ return JSONResponse (status_code = HTTPStatus .OK , content = {"success" : True })
137+ raise HTTPException (status_code = HTTPStatus .BAD_REQUEST ,
138+ detail = "Failed to add disable agent id" )
101139
102140
103141@router .delete ("/config/disable_agent/{agent_id}" )
104142def remove_disable_agent (
105143 agent_id : str = Path (...),
106144 authorization : Optional [str ] = Header (None ),
107145):
146+ """Remove an agent id from the user's disabled agent list.
147+
148+ Args:
149+ agent_id: Identifier of the agent to remove from the disabled list.
150+ authorization: Optional authorization header used to identify the user.
151+ """
108152 user_id , _ = get_current_user_id (authorization )
109153 ok = remove_disabled_agent_id (user_id , agent_id )
110- return _success () if ok else _error ("Failed to remove disable agent id" )
154+ if ok :
155+ return JSONResponse (status_code = HTTPStatus .OK , content = {"success" : True })
156+ raise HTTPException (status_code = HTTPStatus .BAD_REQUEST ,
157+ detail = "Failed to remove disable agent id" )
111158
112159
113160@router .post ("/config/disable_useragent" )
114161def add_disable_useragent (
115162 agent_id : str = Body (..., embed = True ),
116163 authorization : Optional [str ] = Header (None ),
117164):
165+ """Add a user-agent id to the user's disabled user-agent list.
166+
167+ Args:
168+ agent_id: Identifier of the user-agent to disable.
169+ authorization: Optional authorization header used to identify the user.
170+ """
118171 user_id , _ = get_current_user_id (authorization )
119172 ok = add_disabled_useragent_id (user_id , agent_id )
120- return _success () if ok else _error ("Failed to add disable user-agent id" )
173+ if ok :
174+ return JSONResponse (status_code = HTTPStatus .OK , content = {"success" : True })
175+ raise HTTPException (status_code = HTTPStatus .BAD_REQUEST ,
176+ detail = "Failed to add disable user-agent id" )
121177
122178
123179@router .delete ("/config/disable_useragent/{agent_id}" )
124180def remove_disable_useragent (
125181 agent_id : str = Path (...),
126182 authorization : Optional [str ] = Header (None ),
127183):
184+ """Remove a user-agent id from the user's disabled user-agent list.
185+
186+ Args:
187+ agent_id: Identifier of the user-agent to remove from the disabled list.
188+ authorization: Optional authorization header used to identify the user.
189+ """
128190 user_id , _ = get_current_user_id (authorization )
129191 ok = remove_disabled_useragent_id (user_id , agent_id )
130- return _success () if ok else _error ("Failed to remove disable user-agent id" )
192+ if ok :
193+ return JSONResponse (status_code = HTTPStatus .OK , content = {"success" : True })
194+ raise HTTPException (status_code = HTTPStatus .BAD_REQUEST ,
195+ detail = "Failed to remove disable user-agent id" )
131196
132197
133198# ---------------------------------------------------------------------------
@@ -144,6 +209,15 @@ def add_memory(
144209 True , embed = True , description = "Whether to run LLM inference during add" ),
145210 authorization : Optional [str ] = Header (None ),
146211):
212+ """Add memory records for the given scope.
213+
214+ Args:
215+ messages: List of chat messages as dictionaries.
216+ memory_level: Scope for the memory record (tenant/agent/user/user_agent).
217+ agent_id: Optional agent identifier when scope is agent-related.
218+ infer: Whether to run LLM inference during add.
219+ authorization: Optional authorization header used to identify the user.
220+ """
147221 user_id , tenant_id = get_current_user_id (authorization )
148222 try :
149223 result = asyncio .run (svc_add_memory (
@@ -155,10 +229,10 @@ def add_memory(
155229 agent_id = agent_id ,
156230 infer = infer ,
157231 ))
158- return _success ( content = result )
232+ return JSONResponse ( status_code = HTTPStatus . OK , content = result )
159233 except Exception as e :
160234 logger .error ("add_memory error: %s" , e , exc_info = True )
161- return _error ( str (e ))
235+ raise HTTPException ( status_code = HTTPStatus . BAD_REQUEST , detail = str (e ))
162236
163237
164238@router .post ("/search" )
@@ -169,6 +243,15 @@ def search_memory(
169243 agent_id : Optional [str ] = Body (None , embed = True ),
170244 authorization : Optional [str ] = Header (None ),
171245):
246+ """Search memory semantically for the given scope.
247+
248+ Args:
249+ query_text: Natural language query to search memory.
250+ memory_level: Scope for search (tenant/agent/user/user_agent).
251+ top_k: Maximum number of results to return.
252+ agent_id: Optional agent identifier when scope is agent-related.
253+ authorization: Optional authorization header used to identify the user.
254+ """
172255 user_id , tenant_id = get_current_user_id (authorization )
173256 try :
174257 results = asyncio .run (svc_search_memory (
@@ -180,10 +263,10 @@ def search_memory(
180263 top_k = top_k ,
181264 agent_id = agent_id ,
182265 ))
183- return _success ( content = results )
266+ return JSONResponse ( status_code = HTTPStatus . OK , content = results )
184267 except Exception as e :
185268 logger .error ("search_memory error: %s" , e , exc_info = True )
186- return _error ( str (e ))
269+ raise HTTPException ( status_code = HTTPStatus . BAD_REQUEST , detail = str (e ))
187270
188271
189272@router .get ("/list" )
@@ -194,6 +277,13 @@ def list_memory(
194277 None , description = "Filter by agent id if applicable" ),
195278 authorization : Optional [str ] = Header (None ),
196279):
280+ """List memory for the given scope.
281+
282+ Args:
283+ memory_level: Scope for listing (tenant/agent/user/user_agent).
284+ agent_id: Optional agent filter when scope is agent-related.
285+ authorization: Optional authorization header used to identify the user.
286+ """
197287 user_id , tenant_id = get_current_user_id (authorization )
198288 try :
199289 payload = asyncio .run (svc_list_memory (
@@ -203,25 +293,31 @@ def list_memory(
203293 user_id = user_id ,
204294 agent_id = agent_id ,
205295 ))
206- return _success ( content = payload )
296+ return JSONResponse ( status_code = HTTPStatus . OK , content = payload )
207297 except Exception as e :
208298 logger .error ("list_memory error: %s" , e , exc_info = True )
209- return _error ( str (e ))
299+ raise HTTPException ( status_code = HTTPStatus . BAD_REQUEST , detail = str (e ))
210300
211301
212302@router .delete ("/delete/{memory_id}" )
213303def delete_memory (
214304 memory_id : str = Path (..., description = "ID of memory to delete" ),
215305 authorization : Optional [str ] = Header (None ),
216306):
307+ """Delete a specific memory record by id.
308+
309+ Args:
310+ memory_id: Identifier of the memory record to delete.
311+ authorization: Optional authorization header used to identify the user.
312+ """
217313 _user_id , tenant_id = get_current_user_id (authorization )
218314 try :
219315 result = asyncio .run (svc_delete_memory (
220316 memory_id = memory_id , memory_config = build_memory_config (tenant_id )))
221- return _success ( content = result )
317+ return JSONResponse ( status_code = HTTPStatus . OK , content = result )
222318 except Exception as e :
223319 logger .error ("delete_memory error: %s" , e , exc_info = True )
224- return _error ( str (e ))
320+ raise HTTPException ( status_code = HTTPStatus . BAD_REQUEST , detail = str (e ))
225321
226322
227323@router .delete ("/clear" )
@@ -232,6 +328,13 @@ def clear_memory(
232328 None , description = "Filter by agent id if applicable" ),
233329 authorization : Optional [str ] = Header (None ),
234330):
331+ """Clear memory records for the given scope.
332+
333+ Args:
334+ memory_level: Scope for clearing (tenant/agent/user/user_agent).
335+ agent_id: Optional agent filter when scope is agent-related.
336+ authorization: Optional authorization header used to identify the user.
337+ """
235338 user_id , tenant_id = get_current_user_id (authorization )
236339 try :
237340 result = asyncio .run (svc_clear_memory (
@@ -241,7 +344,7 @@ def clear_memory(
241344 user_id = user_id ,
242345 agent_id = agent_id ,
243346 ))
244- return _success ( content = result )
347+ return JSONResponse ( status_code = HTTPStatus . OK , content = result )
245348 except Exception as e :
246349 logger .error ("clear_memory error: %s" , e , exc_info = True )
247- return _error ( str (e ))
350+ raise HTTPException ( status_code = HTTPStatus . BAD_REQUEST , detail = str (e ))
0 commit comments