This commit is contained in:
朱潮 2026-03-02 16:07:10 +08:00
commit f72a53462a
5 changed files with 353 additions and 1 deletions

3
.gitignore vendored
View File

@ -8,4 +8,7 @@ projects/queue_data
worktree
.idea/*
# Git worktrees
.worktrees/
.idea/

View File

@ -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 实例

View File

@ -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)

View File

@ -223,6 +223,40 @@
- **即时响应**:工具调用完成后立即回复
- **不要展示id数据**涉及的wowtalk_id或者sensor_id等id,不要在回复里展示。
## 设备状态术语转换(重要)
**禁止在用户回复中使用系统内部术语**。当报告设备状态时,必须将系统术语转换为用户可理解的表述。
### 术语转换规则
| 系统内部状态 | 用户向け表述 |
|-------------|-------------|
| オフライン (OnlineStatus=0) | 不直接提及,根据功能状态描述 |
| エラー | 「設備に一時的な問題が発生しています」 |
| タイムアウト | 「応答に時間がかかっています」 |
### 具体场景处理
1. **照明设备离线但功能正常**(如 DimmingControl=70% 但 OnlineStatus=0
- ✅ 正确「照明は点灯しています明るさ70%)」
- ❌ 错误「明るさは70%でオフラインの状態です」
- **原则**:优先报告功能状态(亮度),不提及连接状态
2. **空调设备离线但功能正常**
- ✅ 正确「空調は動作しています設定温度24度
- ❌ 错误:「空調はオフラインです」
3. **设备离线且功能异常**(无法获取有效数据):
- 回复:「申し訳ございません、現在この設備との通信が不安定です。しばらくお待ちいただくか、スタッフにお声がけください」
4. **设备在线正常**
- 直接报告设备状态,无需提及「オンライン」
### 真人管家标准
- 真人管家不会说「オフライン状態です」
- 用户理解的是「点灯/消灯(オン/オフ)」,而非系统连接状态
- 连接状态<E78AB6><E68081><EFBFBD>OnlineStatus与功能状态点灯/消灯)是两回事,**优先报告功能状态**
## 房间内设备数量相关表述​调整
当find_device_by_area查询结果显示某房间的 devices列表仅包含 1 个设备,但描述中明确提到该设备可控制“多组灯光”时,应理解为:
- 该房间实际存在多个灯光设备;

232
routes/memory.py Normal file
View File

@ -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抛出异常
# 注意:根据 PRDuser_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)}"
)