-
Notifications
You must be signed in to change notification settings - Fork 34
feat(memory): integrate mem0 long-term memory for Chat Shell #295
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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") | ||||||||||||||
|
|
||||||||||||||
|
|
||||||||||||||
| @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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 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: (BLE001) 144-144: Use Replace with (TRY400) 145-145: Within an (B904) 🤖 Prompt for AI Agents |
||||||||||||||
|
|
||||||||||||||
|
|
||||||||||||||
| @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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 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
Suggested change
🧰 Tools🪛 Ruff (0.14.7)196-196: Do not catch blind exception: (BLE001) 197-197: Use Replace with (TRY400) 198-198: Within an (B904) 🤖 Prompt for AI Agents |
||||||||||||||
|
|
||||||||||||||
|
|
||||||||||||||
| @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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 (B008) 🤖 Prompt for AI Agents |
||||||||||||||
| """ | ||||||||||||||
| 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 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
Suggested change
🧰 Tools🪛 Ruff (0.14.7)238-238: Do not catch blind exception: (BLE001) 239-239: Use Replace with (TRY400) 240-240: Within an (B904) 🤖 Prompt for AI Agents |
||||||||||||||
|
|
||||||||||||||
|
|
||||||||||||||
| @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, | ||||||||||||||
| } | ||||||||||||||
There was a problem hiding this comment.
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:
Using
logging.exceptionautomatically includes the traceback, andraise ... from epreserves the exception chain for better debugging.🧰 Tools
🪛 Ruff (0.14.7)
97-97: Do not catch blind exception:
Exception(BLE001)
98-98: Use
logging.exceptioninstead oflogging.errorReplace with
exception(TRY400)
99-99: Within an
exceptclause, raise exceptions withraise ... from errorraise ... from Noneto distinguish them from errors in exception handling(B904)
🤖 Prompt for AI Agents