Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion backend/app/api/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
#
# SPDX-License-Identifier: Apache-2.0

from app.api.endpoints import admin, auth, oidc, quota, repository, users
from app.api.endpoints import admin, auth, memory, oidc, quota, repository, users
from app.api.endpoints.adapter import (
agents,
attachments,
Expand Down Expand Up @@ -34,4 +34,5 @@
api_router.include_router(executors.router, prefix="/executors", tags=["executors"])
api_router.include_router(quota.router, prefix="/quota", tags=["quota"])
api_router.include_router(dify.router, prefix="/dify", tags=["dify"])
api_router.include_router(memory.router, prefix="/memories", tags=["memories"])
api_router.include_router(k_router)
1 change: 1 addition & 0 deletions backend/app/api/endpoints/adapter/chat.py
Original file line number Diff line number Diff line change
Expand Up @@ -558,6 +558,7 @@ async def generate_with_ids():
message=final_message,
model_config=model_config,
system_prompt=system_prompt,
user_id=current_user.id,
)

# Forward the stream
Expand Down
256 changes: 256 additions & 0 deletions backend/app/api/endpoints/memory.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,256 @@
# SPDX-FileCopyrightText: 2025 WeCode-AI, Inc.
#
# SPDX-License-Identifier: Apache-2.0

"""
Memory management API endpoints.

Provides endpoints for managing user long-term memories stored in mem0.
"""

import logging
from typing import Optional

from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel

from app.core import security
from app.models.user import User
from app.services.chat.memory_service import memory_service

logger = logging.getLogger(__name__)

router = APIRouter()


class MemoryResponse(BaseModel):
"""Response model for a single memory."""

id: str
content: str
created_at: Optional[str] = None
updated_at: Optional[str] = None


class MemoryListResponse(BaseModel):
"""Response model for memory list."""

memories: list[MemoryResponse]
total: int


class UpdateMemoryRequest(BaseModel):
"""Request model for updating a memory."""

content: str


@router.get("")
async def get_memories(
keyword: Optional[str] = None,
current_user: User = Depends(security.get_current_user),
) -> MemoryListResponse:
"""
Get all memories for the current user.

Args:
keyword: Optional keyword to search/filter memories

Returns:
List of user's memories
"""
if not memory_service.is_configured:
return MemoryListResponse(memories=[], total=0)

try:
if keyword:
# Search memories by keyword
raw_memories = await memory_service.search_memories(
user_id=current_user.id,
query=keyword,
limit=100,
)
else:
# Get all memories
raw_memories = await memory_service.get_all_memories(
user_id=current_user.id,
)

# Transform to response format
memories = []
for mem in raw_memories:
memory_id = mem.get("id", mem.get("memory_id", ""))
content = mem.get("memory", mem.get("text", mem.get("content", "")))
created_at = mem.get("created_at", mem.get("createdAt"))
updated_at = mem.get("updated_at", mem.get("updatedAt"))

if memory_id and content:
memories.append(MemoryResponse(
id=str(memory_id),
content=content,
created_at=str(created_at) if created_at else None,
updated_at=str(updated_at) if updated_at else None,
))

return MemoryListResponse(memories=memories, total=len(memories))

except Exception as e:
logger.error(f"Error fetching memories for user {current_user.id}: {e}")
raise HTTPException(status_code=500, detail="Failed to fetch memories")
Comment on lines +97 to +99
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Improve exception handling with traceback logging and exception chaining.

The current exception handling loses the traceback and doesn't chain the exception properly.

Apply this diff:

     except Exception as e:
-        logger.error(f"Error fetching memories for user {current_user.id}: {e}")
-        raise HTTPException(status_code=500, detail="Failed to fetch memories")
+        logger.exception(f"Error fetching memories for user {current_user.id}: {e}")
+        raise HTTPException(status_code=500, detail="Failed to fetch memories") from e

Using logging.exception automatically includes the traceback, and raise ... from e preserves the exception chain for better debugging.

🧰 Tools
🪛 Ruff (0.14.7)

97-97: Do not catch blind exception: Exception

(BLE001)


98-98: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


99-99: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)

🤖 Prompt for AI Agents
In backend/app/api/endpoints/memory.py around lines 97 to 99, the except block
currently logs the error with logger.error and raises an HTTPException without
preserving the original traceback; replace the logger.error call with
logger.exception(...) to include the traceback automatically and change the
raise to "raise HTTPException(status_code=500, detail='Failed to fetch
memories') from e" so the original exception is chained and debuggable.



@router.get("/{memory_id}")
async def get_memory(
memory_id: str,
current_user: User = Depends(security.get_current_user),
) -> MemoryResponse:
"""
Get a single memory by ID.

Args:
memory_id: Memory ID

Returns:
Memory details
"""
if not memory_service.is_configured:
raise HTTPException(status_code=404, detail="Memory service not configured")

try:
mem = await memory_service.get_memory(memory_id)

if not mem:
raise HTTPException(status_code=404, detail="Memory not found")

# Verify ownership by checking user_id
mem_user_id = mem.get("user_id", "")
if str(mem_user_id) != str(current_user.id):
raise HTTPException(status_code=404, detail="Memory not found")

content = mem.get("memory", mem.get("text", mem.get("content", "")))
created_at = mem.get("created_at", mem.get("createdAt"))
updated_at = mem.get("updated_at", mem.get("updatedAt"))

return MemoryResponse(
id=memory_id,
content=content,
created_at=str(created_at) if created_at else None,
updated_at=str(updated_at) if updated_at else None,
)

except HTTPException:
raise
except Exception as e:
logger.error(f"Error fetching memory {memory_id}: {e}")
raise HTTPException(status_code=500, detail="Failed to fetch memory")
Comment on lines +143 to +145
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Improve exception handling with traceback logging and exception chaining.

Same issue as in get_memories: use logging.exception and chain exceptions.

Apply this diff:

     except Exception as e:
-        logger.error(f"Error fetching memory {memory_id}: {e}")
-        raise HTTPException(status_code=500, detail="Failed to fetch memory")
+        logger.exception(f"Error fetching memory {memory_id}: {e}")
+        raise HTTPException(status_code=500, detail="Failed to fetch memory") from e
🧰 Tools
🪛 Ruff (0.14.7)

143-143: Do not catch blind exception: Exception

(BLE001)


144-144: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


145-145: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)

🤖 Prompt for AI Agents
In backend/app/api/endpoints/memory.py around lines 143 to 145, the except block
currently logs the error with logger.error and raises a new HTTPException
without chaining; change it to use logger.exception(...) to include the full
traceback in the logs and raise HTTPException(...) from e to preserve exception
chaining, so the original exception is attached to the HTTPException for better
debugging.



@router.put("/{memory_id}")
async def update_memory(
memory_id: str,
request: UpdateMemoryRequest,
current_user: User = Depends(security.get_current_user),
) -> MemoryResponse:
"""
Update a memory's content.

Args:
memory_id: Memory ID
request: Update request with new content

Returns:
Updated memory
"""
if not memory_service.is_configured:
raise HTTPException(status_code=404, detail="Memory service not configured")

try:
# First verify the memory exists and belongs to this user
existing = await memory_service.get_memory(memory_id)
if not existing:
raise HTTPException(status_code=404, detail="Memory not found")

mem_user_id = existing.get("user_id", "")
if str(mem_user_id) != str(current_user.id):
raise HTTPException(status_code=404, detail="Memory not found")

# Update the memory
result = await memory_service.update_memory(memory_id, request.content)

if not result:
raise HTTPException(status_code=500, detail="Failed to update memory")

# Return updated memory
created_at = result.get("created_at", existing.get("created_at"))
updated_at = result.get("updated_at", result.get("updatedAt"))

return MemoryResponse(
id=memory_id,
content=request.content,
created_at=str(created_at) if created_at else None,
updated_at=str(updated_at) if updated_at else None,
)

except HTTPException:
raise
except Exception as e:
logger.error(f"Error updating memory {memory_id}: {e}")
raise HTTPException(status_code=500, detail="Failed to update memory")
Comment on lines +196 to +198
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Improve exception handling with traceback logging and exception chaining.

Same issue: use logging.exception and chain exceptions.

Apply this diff:

     except Exception as e:
-        logger.error(f"Error updating memory {memory_id}: {e}")
-        raise HTTPException(status_code=500, detail="Failed to update memory")
+        logger.exception(f"Error updating memory {memory_id}: {e}")
+        raise HTTPException(status_code=500, detail="Failed to update memory") from e
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
except Exception as e:
logger.error(f"Error updating memory {memory_id}: {e}")
raise HTTPException(status_code=500, detail="Failed to update memory")
except Exception as e:
logger.exception(f"Error updating memory {memory_id}: {e}")
raise HTTPException(status_code=500, detail="Failed to update memory") from e
🧰 Tools
🪛 Ruff (0.14.7)

196-196: Do not catch blind exception: Exception

(BLE001)


197-197: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


198-198: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)

🤖 Prompt for AI Agents
In backend/app/api/endpoints/memory.py around lines 196 to 198, the except block
currently logs the error with logger.error and raises an HTTPException without
chaining; replace it to log the full traceback using logger.exception(...) so
the stack trace is captured, and re-raise the HTTPException with "from e" to
preserve exception chaining (raise HTTPException(status_code=500, detail="Failed
to update memory") from e) so the original exception is linked.



@router.delete("/{memory_id}")
async def delete_memory(
memory_id: str,
current_user: User = Depends(security.get_current_user),
):
Comment on lines +201 to +205
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Add missing return type hint.

The function is missing a return type annotation, which violates the coding guidelines requirement that all functions must include type hints.

Apply this diff:

 @router.delete("/{memory_id}")
 async def delete_memory(
     memory_id: str,
     current_user: User = Depends(security.get_current_user),
-):
+) -> dict[str, bool | str]:

As per coding guidelines, Python functions must include type hints for all functions.

🧰 Tools
🪛 Ruff (0.14.7)

204-204: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)

🤖 Prompt for AI Agents
backend/app/api/endpoints/memory.py lines 201-205: the delete_memory endpoint
signature is missing a return type annotation; update the function signature to
include an explicit return type (e.g., -> None if it returns nothing, or ->
dict/Response/JSONResponse as appropriate for the actual return value) so it
complies with the project's type-hinting guidelines.

"""
Delete a memory.

Args:
memory_id: Memory ID

Returns:
Success message
"""
if not memory_service.is_configured:
raise HTTPException(status_code=404, detail="Memory service not configured")

try:
# First verify the memory exists and belongs to this user
existing = await memory_service.get_memory(memory_id)
if not existing:
raise HTTPException(status_code=404, detail="Memory not found")

mem_user_id = existing.get("user_id", "")
if str(mem_user_id) != str(current_user.id):
raise HTTPException(status_code=404, detail="Memory not found")

# Delete the memory
success = await memory_service.delete_memory(memory_id)

if not success:
raise HTTPException(status_code=500, detail="Failed to delete memory")

return {"success": True, "message": "Memory deleted successfully"}

except HTTPException:
raise
except Exception as e:
logger.error(f"Error deleting memory {memory_id}: {e}")
raise HTTPException(status_code=500, detail="Failed to delete memory")
Comment on lines +238 to +240
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Improve exception handling with traceback logging and exception chaining.

Same issue: use logging.exception and chain exceptions.

Apply this diff:

     except Exception as e:
-        logger.error(f"Error deleting memory {memory_id}: {e}")
-        raise HTTPException(status_code=500, detail="Failed to delete memory")
+        logger.exception(f"Error deleting memory {memory_id}: {e}")
+        raise HTTPException(status_code=500, detail="Failed to delete memory") from e
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
except Exception as e:
logger.error(f"Error deleting memory {memory_id}: {e}")
raise HTTPException(status_code=500, detail="Failed to delete memory")
except Exception as e:
logger.exception(f"Error deleting memory {memory_id}: {e}")
raise HTTPException(status_code=500, detail="Failed to delete memory") from e
🧰 Tools
🪛 Ruff (0.14.7)

238-238: Do not catch blind exception: Exception

(BLE001)


239-239: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


240-240: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)

🤖 Prompt for AI Agents
backend/app/api/endpoints/memory.py around lines 238 to 240, the except block
currently logs the error with logger.error and re-raises a generic
HTTPException; replace logger.error with logger.exception to record the full
traceback and then raise the HTTPException using exception chaining (raise
HTTPException(status_code=500, detail="Failed to delete memory") from e) so the
original exception is preserved.



@router.get("/health/check")
async def check_memory_service():
"""
Check if memory service is healthy and configured.

Returns:
Service status
"""
is_healthy = await memory_service.health_check()

return {
"configured": memory_service.is_configured,
"healthy": is_healthy,
}
5 changes: 5 additions & 0 deletions backend/app/core/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,11 @@ class Settings(BaseSettings):
MAX_UPLOAD_FILE_SIZE_MB: int = 50 # Maximum file size in MB
MAX_EXTRACTED_TEXT_LENGTH: int = 1000000 # Maximum extracted text length

# mem0 long-term memory configuration
MEM0_BASE_URL: str = "" # mem0 service URL (e.g., http://localhost:8080)
MEM0_API_KEY: str = "" # mem0 API key (if required)
MEM0_ENABLED: bool = True # Enable/disable mem0 integration


class Config:
env_file = ".env"
Expand Down
3 changes: 3 additions & 0 deletions backend/app/services/chat/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,17 @@

This module provides direct LLM API calling capabilities for Chat Shell type,
bypassing the Docker Executor container for lightweight chat scenarios.
Also includes long-term memory integration via mem0.
"""

from app.services.chat.chat_service import chat_service
from app.services.chat.session_manager import session_manager
from app.services.chat.model_resolver import get_model_config_for_bot
from app.services.chat.memory_service import memory_service

__all__ = [
"chat_service",
"session_manager",
"get_model_config_for_bot",
"memory_service",
]
Loading