qwen_agent/routes/memory.py
朱潮 425f3c5bb4 chore: replace Chinese comments and log messages with English
Convert all Chinese comments, docstrings, logger/print output,
HTTPException detail messages, and API response messages to English
across the entire codebase. Functional zh/ja localized strings
(e.g. prompt templates, timezone display names, date formats) are
preserved as-is.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-30 19:45:35 +08:00

339 lines
10 KiB
Python

"""
Memory management API routes.
Provides memory viewing, adding, and deletion features.
"""
import logging
from typing import Literal, Optional, List, Dict, Any
from fastapi import APIRouter, HTTPException, Header, Query
from fastapi.responses import JSONResponse
from pydantic import BaseModel, Field
logger = logging.getLogger('app')
router = APIRouter(prefix="/api/v1", tags=["memory"])
class MemoryItem(BaseModel):
"""Memory item data model."""
id: str
content: str
created_at: Optional[str] = None
updated_at: Optional[str] = None
class MemoryListResponse(BaseModel):
"""Memory list response."""
memories: List[MemoryItem]
total: int
class DeleteAllResponse(BaseModel):
"""Delete-all memories response."""
deleted_count: int
class ConversationMessage(BaseModel):
"""Conversation message."""
role: Literal["user", "assistant"]
content: str = Field(..., min_length=1)
class AddMemoryRequest(BaseModel):
"""Request body for adding memories."""
bot_id: str = Field(..., min_length=1)
user_id: str = Field(..., min_length=1)
messages: List[ConversationMessage] = Field(..., max_length=200)
class AddMemoryResponse(BaseModel):
"""Response for adding memories."""
success: bool
pairs_processed: int
pairs_failed: int = 0
async def get_user_identifier_from_request(
authorization: Optional[str],
user_id: Optional[str] = None
) -> str:
"""
Get the user identifier
Prefer the user_id from request parameters; otherwise try to parse it from the Authorization header
Args:
authorization: Authorization header
user_id: user_id from request parameters
Returns:
User identifier
Raises:
HTTPException: If the user identifier cannot be determined
"""
if user_id:
return user_id
# Raise an exception if user_id is not provided
# Note: According to the PRD, user_id is passed from the frontend
raise HTTPException(
status_code=400,
detail="user_id is required"
)
@router.post("/memory", response_model=AddMemoryResponse)
async def add_memory_from_conversation(data: AddMemoryRequest):
"""
Extract and save memories from conversation messages
Pairs user and assistant messages, then uses Mem0 to extract and store key facts.
Used for scenarios such as realtime voice conversations that do not go through the Agent middleware.
This endpoint is intended for internal services such as felo-mygpt and is not exposed to external users.
"""
try:
from agent.mem0_manager import get_mem0_manager
from utils.settings import MEM0_ENABLED
if not MEM0_ENABLED:
raise HTTPException(
status_code=503,
detail="Memory feature is not enabled"
)
if not data.messages:
return AddMemoryResponse(success=True, pairs_processed=0)
manager = get_mem0_manager()
# Pair messages into user-assistant pairs and then call add_memory
pairs_processed = 0
pairs_failed = 0
i = 0
while i < len(data.messages):
msg = data.messages[i]
if msg.role == 'user':
# Collect consecutive user messages
user_contents = [msg.content]
j = i + 1
while j < len(data.messages) and data.messages[j].role == 'user':
user_contents.append(data.messages[j].content)
j += 1
user_text = '\n'.join(user_contents)
# Check whether there is a corresponding assistant reply
assistant_text = ""
if j < len(data.messages) and data.messages[j].role == 'assistant':
assistant_text = data.messages[j].content or ""
j += 1
if user_text and assistant_text:
conversation_text = f"User: {user_text}\nAssistant: {assistant_text}"
try:
await manager.add_memory(
text=conversation_text,
user_id=data.user_id,
agent_id=data.bot_id,
metadata={"type": "realtime_conversation"},
)
pairs_processed += 1
except Exception as pair_error:
pairs_failed += 1
logger.error(
f"Failed to add memory for pair: {pair_error}"
)
i = j
else:
i += 1
logger.info(
f"Added {pairs_processed} memory pairs (failed={pairs_failed}) "
f"for user={data.user_id}, bot={data.bot_id}"
)
return AddMemoryResponse(
success=pairs_failed == 0,
pairs_processed=pairs_processed,
pairs_failed=pairs_failed,
)
except HTTPException:
raise
except Exception as e:
logger.error(f"Failed to add memory from conversation: {e}")
raise HTTPException(
status_code=500,
detail="Failed to add memory from conversation"
)
@router.get("/memory", response_model=MemoryListResponse)
async def get_memories(
bot_id: str = Query(..., description="Bot ID (corresponds to agent_id)"),
user_id: str = Query(..., description="User ID"),
authorization: Optional[str] = Header(None, description="Authorization header"),
):
"""
Get all memories for the current user under the specified bot
Args:
bot_id: Bot ID (corresponds to agent_id)
user_id: User identifier
authorization: Authorization header (for authentication)
Returns:
MemoryListResponse: Memory list
"""
try:
from agent.mem0_manager import get_mem0_manager
from utils.settings import MEM0_ENABLED
# Check whether the memory feature is enabled
if not MEM0_ENABLED:
raise HTTPException(
status_code=503,
detail="Memory feature is not enabled"
)
# Get the Mem0Manager instance
manager = get_mem0_manager()
# Get all memories
memories = await manager.get_all_memories(
user_id=user_id,
agent_id=bot_id
)
# Convert to the response format
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 (used for permission validation)"),
user_id: str = Query(..., description="User ID"),
authorization: Optional[str] = Header(None, description="Authorization header"),
):
"""
Delete a single memory
Args:
memory_id: Memory ID
bot_id: Bot ID (used for permission validation)
user_id: User identifier
authorization: Authorization header
Returns:
204 No Content
"""
try:
from agent.mem0_manager import get_mem0_manager
from utils.settings import MEM0_ENABLED
# Check whether the memory feature is enabled
if not MEM0_ENABLED:
raise HTTPException(
status_code=503,
detail="Memory feature is not enabled"
)
# Get the Mem0Manager instance
manager = get_mem0_manager()
# Delete the memory
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="User ID"),
authorization: Optional[str] = Header(None, description="Authorization header"),
):
"""
Clear all memories for the current user under the specified bot
Args:
bot_id: Bot ID
user_id: User identifier
authorization: Authorization header
Returns:
DeleteAllResponse: Number of deleted memories
"""
try:
from agent.mem0_manager import get_mem0_manager
from utils.settings import MEM0_ENABLED
# Check whether the memory feature is enabled
if not MEM0_ENABLED:
raise HTTPException(
status_code=503,
detail="Memory feature is not enabled"
)
# Get the Mem0Manager instance
manager = get_mem0_manager()
# Delete all memories
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)}"
)