diff --git a/.gitignore b/.gitignore index e300b2e..bbf8dae 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,7 @@ projects/queue_data worktree .idea/* +# Git worktrees +.worktrees/ + .idea/ diff --git a/agent/mem0_manager.py b/agent/mem0_manager.py index 4fcc3b2..0c9cef1 100644 --- a/agent/mem0_manager.py +++ b/agent/mem0_manager.py @@ -485,6 +485,88 @@ class Mem0Manager: logger.error(f"Failed to get all memories: {e}") return [] + async def delete_memory( + self, + memory_id: str, + user_id: str, + agent_id: str, + ) -> bool: + """删除单条记忆 + + Args: + memory_id: 记忆 ID + user_id: 用户 ID + agent_id: Agent/Bot ID + + Returns: + 是否删除成功 + """ + try: + mem = await self.get_mem0(user_id, agent_id, "default") + + # 先获取记忆以验证所有权 + memories = mem.get_all(user_id=user_id) + target_memory = None + for m in memories: + if m.get("id") == memory_id: + # 验证 agent_id 匹配 + if m.get("metadata", {}).get("agent_id") == agent_id: + target_memory = m + break + + if not target_memory: + logger.warning(f"Memory {memory_id} not found or access denied for user={user_id}, agent={agent_id}") + return False + + # 删除记忆 + mem.delete(memory_id=memory_id) + + logger.info(f"Deleted memory {memory_id} for user={user_id}, agent={agent_id}") + return True + + except Exception as e: + logger.error(f"Failed to delete memory {memory_id}: {e}") + return False + + async def delete_all_memories( + self, + user_id: str, + agent_id: str, + ) -> int: + """删除用户在指定 Agent 下的所有记忆 + + Args: + user_id: 用户 ID + agent_id: Agent/Bot ID + + Returns: + 删除的记忆数量 + """ + try: + mem = await self.get_mem0(user_id, agent_id, "default") + + # 获取所有记忆 + memories = mem.get_all(user_id=user_id) + + # 过滤 agent_id 并删除 + deleted_count = 0 + for m in memories: + if m.get("metadata", {}).get("agent_id") == agent_id: + memory_id = m.get("id") + if memory_id: + try: + mem.delete(memory_id=memory_id) + deleted_count += 1 + except Exception as e: + logger.warning(f"Failed to delete memory {memory_id}: {e}") + + logger.info(f"Deleted {deleted_count} memories for user={user_id}, agent={agent_id}") + return deleted_count + + except Exception as e: + logger.error(f"Failed to delete all memories: {e}") + return 0 + def clear_cache(self, user_id: Optional[str] = None, agent_id: Optional[str] = None) -> None: """清除缓存的 Mem0 实例 diff --git a/fastapi_app.py b/fastapi_app.py index 2466f16..1aa17c9 100644 --- a/fastapi_app.py +++ b/fastapi_app.py @@ -81,7 +81,7 @@ from utils.log_util.logger import init_with_fastapi logger = logging.getLogger('app') # Import route modules -from routes import chat, files, projects, system, skill_manager, database +from routes import chat, files, projects, system, skill_manager, database, memory @asynccontextmanager @@ -185,6 +185,7 @@ app.include_router(projects.router) app.include_router(system.router) app.include_router(skill_manager.router) app.include_router(database.router) +app.include_router(memory.router) # 注册文件管理API路由 app.include_router(file_manager_router) diff --git a/prompt/novare.md b/prompt/novare.md index ad241b2..b336a66 100644 --- a/prompt/novare.md +++ b/prompt/novare.md @@ -223,6 +223,40 @@ - **即时响应**:工具调用完成后立即回复 - **不要展示id数据**:涉及的wowtalk_id或者sensor_id等id,不要在回复里展示。 +## 设备状态术语转换(重要) + +**禁止在用户回复中使用系统内部术语**。当报告设备状态时,必须将系统术语转换为用户可理解的表述。 + +### 术语转换规则 + +| 系统内部状态 | 用户向け表述 | +|-------------|-------------| +| オフライン (OnlineStatus=0) | 不直接提及,根据功能状态描述 | +| エラー | 「設備に一時的な問題が発生しています」 | +| タイムアウト | 「応答に時間がかかっています」 | + +### 具体场景处理 + +1. **照明设备离线但功能正常**(如 DimmingControl=70% 但 OnlineStatus=0): + - ✅ 正确:「照明は点灯しています(明るさ70%)」 + - ❌ 错误:「明るさは70%でオフラインの状態です」 + - **原则**:优先报告功能状态(亮度),不提及连接状态 + +2. **空调设备离线但功能正常**: + - ✅ 正确:「空調は動作しています(設定温度24度)」 + - ❌ 错误:「空調はオフラインです」 + +3. **设备离线且功能异常**(无法获取有效数据): + - 回复:「申し訳ございません、現在この設備との通信が不安定です。しばらくお待ちいただくか、スタッフにお声がけください」 + +4. **设备在线正常**: + - 直接报告设备状态,无需提及「オンライン」 + +### 真人管家标准 +- 真人管家不会说「オフライン状態です」 +- 用户理解的是「点灯/消灯(オン/オフ)」,而非系统连接状态 +- 连接状态���OnlineStatus)与功能状态(点灯/消灯)是两回事,**优先报告功能状态** + ## 房间内设备数量相关表述​调整 当find_device_by_area查询结果显示某房间的 devices列表仅包含 1 个设备,但描述中明确提到该设备可控制“多组灯光”时,应理解为: - 该房间实际存在多个灯光设备; diff --git a/routes/memory.py b/routes/memory.py new file mode 100644 index 0000000..a0b6a95 --- /dev/null +++ b/routes/memory.py @@ -0,0 +1,232 @@ +""" +Memory 管理 API 路由 +提供记忆查看和删除功能 +""" + +import logging +from typing import Optional, List, Dict, Any +from fastapi import APIRouter, HTTPException, Header, Query +from fastapi.responses import JSONResponse +from pydantic import BaseModel + +logger = logging.getLogger('app') + +router = APIRouter(prefix="/api/v1", tags=["memory"]) + + +class MemoryItem(BaseModel): + """单条记忆的数据模型""" + id: str + content: str + created_at: Optional[str] = None + updated_at: Optional[str] = None + + +class MemoryListResponse(BaseModel): + """记忆列表响应""" + memories: List[MemoryItem] + total: int + + +class DeleteAllResponse(BaseModel): + """删除所有记忆响应""" + deleted_count: int + + +async def get_user_identifier_from_request( + authorization: Optional[str], + user_id: Optional[str] = None +) -> str: + """ + 获取用户标识符 + + 优先使用请求参数中的 user_id,否则尝试从 Authorization header 解析 + + Args: + authorization: Authorization header + user_id: 请求参数中的 user_id + + Returns: + 用户标识符 + + Raises: + HTTPException: 如果无法获取用户标识符 + """ + if user_id: + return user_id + + # 如果没有提供 user_id,抛出异常 + # 注意:根据 PRD,user_id 从前端传入 + raise HTTPException( + status_code=400, + detail="user_id is required" + ) + + +@router.get("/memory", response_model=MemoryListResponse) +async def get_memories( + bot_id: str = Query(..., description="Bot ID (对应 agent_id)"), + user_id: str = Query(..., description="用户 ID"), + authorization: Optional[str] = Header(None, description="Authorization header"), +): + """ + 获取当前用户在指定 Bot 下的所有记忆 + + Args: + bot_id: Bot ID(对应 agent_id) + user_id: 用户标识符 + authorization: Authorization header(用于鉴权) + + Returns: + MemoryListResponse: 记忆列表 + """ + try: + from agent.mem0_manager import get_mem0_manager + from utils.settings import MEM0_ENABLED + + # 检查 Memory 功能是否启用 + if not MEM0_ENABLED: + raise HTTPException( + status_code=503, + detail="Memory feature is not enabled" + ) + + # 获取 Mem0Manager 实例 + manager = get_mem0_manager() + + # 获取所有记忆 + memories = await manager.get_all_memories( + user_id=user_id, + agent_id=bot_id + ) + + # 转换为响应格式 + memory_items = [] + for m in memories: + memory_items.append(MemoryItem( + id=m.get("id", ""), + content=m.get("memory", ""), + created_at=m.get("created_at"), + updated_at=m.get("updated_at") + )) + + return MemoryListResponse( + memories=memory_items, + total=len(memory_items) + ) + + except HTTPException: + raise + except Exception as e: + logger.error(f"Failed to get memories: {e}") + raise HTTPException( + status_code=500, + detail=f"Failed to get memories: {str(e)}" + ) + + +@router.delete("/memory/{memory_id}", status_code=204) +async def delete_memory( + memory_id: str, + bot_id: str = Query(..., description="Bot ID (用于权限校验)"), + user_id: str = Query(..., description="用户 ID"), + authorization: Optional[str] = Header(None, description="Authorization header"), +): + """ + 删除单条记忆 + + Args: + memory_id: 记忆 ID + bot_id: Bot ID(用于权限校验) + user_id: 用户标识符 + authorization: Authorization header + + Returns: + 204 No Content + """ + try: + from agent.mem0_manager import get_mem0_manager + from utils.settings import MEM0_ENABLED + + # 检查 Memory 功能是否启用 + if not MEM0_ENABLED: + raise HTTPException( + status_code=503, + detail="Memory feature is not enabled" + ) + + # 获取 Mem0Manager 实例 + manager = get_mem0_manager() + + # 删除记忆 + success = await manager.delete_memory( + memory_id=memory_id, + user_id=user_id, + agent_id=bot_id + ) + + if not success: + raise HTTPException( + status_code=404, + detail="Memory not found or delete failed" + ) + + return JSONResponse(status_code=204, content=None) + + except HTTPException: + raise + except Exception as e: + logger.error(f"Failed to delete memory {memory_id}: {e}") + raise HTTPException( + status_code=500, + detail=f"Failed to delete memory: {str(e)}" + ) + + +@router.delete("/memory", response_model=DeleteAllResponse) +async def delete_all_memories( + bot_id: str = Query(..., description="Bot ID"), + user_id: str = Query(..., description="用户 ID"), + authorization: Optional[str] = Header(None, description="Authorization header"), +): + """ + 清除指定 Bot 下当前用户的所有记忆 + + Args: + bot_id: Bot ID + user_id: 用户标识符 + authorization: Authorization header + + Returns: + DeleteAllResponse: 删除的记忆数量 + """ + try: + from agent.mem0_manager import get_mem0_manager + from utils.settings import MEM0_ENABLED + + # 检查 Memory 功能是否启用 + if not MEM0_ENABLED: + raise HTTPException( + status_code=503, + detail="Memory feature is not enabled" + ) + + # 获取 Mem0Manager 实例 + manager = get_mem0_manager() + + # 删除所有记忆 + deleted_count = await manager.delete_all_memories( + user_id=user_id, + agent_id=bot_id + ) + + return DeleteAllResponse(deleted_count=deleted_count) + + except HTTPException: + raise + except Exception as e: + logger.error(f"Failed to delete all memories: {e}") + raise HTTPException( + status_code=500, + detail=f"Failed to delete all memories: {str(e)}" + )