feat(memory): add protected memory config deletion with end-user safeguards

- Add force parameter to delete_config endpoint for controlled deletion of in-use configs
- Implement MemoryConfigService.delete_config with protection against deleting default configs
- Add validation to prevent deletion of configs with connected end-users unless force=True
- Reorganize controller imports to remove duplicates and improve maintainability
- Clean up unused database connection management code from memory_storage_controller
- Add detailed docstring to delete_config endpoint explaining protection mechanisms
- Update error handling with specific BizCode.RESOURCE_IN_USE for configs in active use
- Add comprehensive logging for deletion attempts, warnings, and affected users
- Refactor ConfigParamsDelete schema usage to use MemoryConfigService directly
- Improve API response structure with affected_users count and force_required flag
This commit is contained in:
Ke Sun
2026-01-28 12:02:35 +08:00
parent d9fa9039bb
commit 42b59a644d
13 changed files with 823 additions and 188 deletions

View File

@@ -9,10 +9,15 @@ import os
import re
import time
import uuid
from uuid import UUID
from typing import Any, AsyncGenerator, Dict, List, Optional
from uuid import UUID
import redis
from langchain_core.messages import AIMessage, HumanMessage
from pydantic import BaseModel, Field
from sqlalchemy import func
from sqlalchemy.orm import Session
from app.core.config import settings
from app.core.logging_config import get_config_logger, get_logger
from app.core.memory.agent.langgraph_graph.read_graph import make_read_graph
@@ -37,11 +42,6 @@ from app.services.memory_config_service import MemoryConfigService
from app.services.memory_konwledges_server import (
write_rag,
)
from langchain_core.messages import AIMessage
from langchain_core.messages import HumanMessage
from pydantic import BaseModel, Field
from sqlalchemy import func
from sqlalchemy.orm import Session
try:
from app.core.memory.utils.log.audit_logger import audit_logger
@@ -732,8 +732,12 @@ class MemoryAgentService:
)
# 导入必要的模块
from app.core.memory.agent.langgraph_graph.nodes.summary_nodes import summary_llm
from app.core.memory.agent.models.summary_models import RetrieveSummaryResponse
from app.core.memory.agent.langgraph_graph.nodes.summary_nodes import (
summary_llm,
)
from app.core.memory.agent.models.summary_models import (
RetrieveSummaryResponse,
)
# 构建状态对象
state = {
@@ -1144,9 +1148,8 @@ class MemoryAgentService:
# LogStreamer uses context manager for file handling, so cleanup is automatic
def get_end_user_memory_config_id(end_user_id: str, db: Session) -> Optional[int]:
"""
快速获取终端用户的 memory_config_id直接从 end_user 表读取)
def get_end_user_memory_config_id(end_user_id: str, db: Session) -> Optional[uuid.UUID]:
"""快速获取终端用户的 memory_config_id直接从 end_user 表读取)。
如果 end_user 已有缓存的 memory_config_id直接返回
否则返回 None调用方应使用 get_end_user_connected_config 获取完整配置。
@@ -1156,14 +1159,16 @@ def get_end_user_memory_config_id(end_user_id: str, db: Session) -> Optional[int
db: 数据库会话
Returns:
memory_config_id 或 None
Optional[uuid.UUID]: memory_config_id 或 None
"""
from app.models.end_user_model import EndUser
from app.repositories.end_user_repository import get_memory_config_id
end_user = db.query(EndUser).filter(EndUser.id == end_user_id).first()
if end_user and end_user.memory_config_id:
return end_user.memory_config_id
return None
try:
end_user_uuid = uuid.UUID(end_user_id) if isinstance(end_user_id, str) else end_user_id
return get_memory_config_id(db, end_user_uuid)
except (ValueError, TypeError) as e:
logger.warning(f"Invalid end_user_id format: {end_user_id}, error: {e}")
return None
def get_end_user_connected_config(end_user_id: str, db: Session) -> Dict[str, Any]:
@@ -1185,9 +1190,9 @@ def get_end_user_connected_config(end_user_id: str, db: Session) -> Dict[str, An
Raises:
ValueError: 当终端用户不存在或应用未发布时
"""
from app.models.app_model import App
from app.models.app_release_model import AppRelease
from app.models.end_user_model import EndUser
from sqlalchemy import select
logger.info(f"Getting connected config for end_user: {end_user_id}")
@@ -1200,22 +1205,25 @@ def get_end_user_connected_config(end_user_id: str, db: Session) -> Dict[str, An
app_id = end_user.app_id
logger.debug(f"Found end_user app_id: {app_id}")
# 2. 获取应用的最新发布版本
stmt = (
select(AppRelease)
.where(AppRelease.app_id == app_id, AppRelease.is_active.is_(True))
.order_by(AppRelease.version.desc())
)
latest_release = db.scalars(stmt).first()
# 2. 获取应用的当前发布版本(通过 apps.current_release_id
app = db.query(App).filter(App.id == app_id).first()
if not app:
logger.warning(f"App not found: {app_id}")
raise ValueError(f"应用不存在: {app_id}")
if not latest_release:
logger.warning(f"No active release found for app: {app_id}")
if not app.current_release_id:
logger.warning(f"No current release for app: {app_id}")
raise ValueError(f"应用未发布: {app_id}")
logger.debug(f"Found latest release: version={latest_release.version}, id={latest_release.id}")
current_release = db.query(AppRelease).filter(AppRelease.id == app.current_release_id).first()
if not current_release:
logger.warning(f"Current release not found: {app.current_release_id}")
raise ValueError(f"应用发布版本不存在: {app.current_release_id}")
logger.debug(f"Found current release: version={current_release.version}, id={current_release.id}")
# 3. 从 config 中提取 memory_config_id
config = latest_release.config or {}
config = current_release.config or {}
# 如果 config 是字符串,解析为字典
if isinstance(config, str):
@@ -1223,27 +1231,17 @@ def get_end_user_connected_config(end_user_id: str, db: Session) -> Dict[str, An
try:
config = json.loads(config)
except json.JSONDecodeError:
logger.warning(f"Failed to parse config JSON for release {latest_release.id}")
logger.warning(f"Failed to parse config JSON for release {current_release.id}")
config = {}
memory_obj = config.get('memory', {})
memory_config_id = memory_obj.get('memory_content') if isinstance(memory_obj, dict) else None
# 4. 更新 end_user 的 memory_config_id懒更新
if memory_config_id is not None and end_user.memory_config_id != memory_config_id:
try:
end_user.memory_config_id = memory_config_id
db.commit()
logger.debug(f"Updated end_user memory_config_id: {end_user_id} -> {memory_config_id}")
except Exception as e:
db.rollback()
logger.warning(f"Failed to update end_user memory_config_id: {e}")
result = {
"end_user_id": str(end_user_id),
"app_id": str(app_id),
"release_id": str(latest_release.id),
"release_version": latest_release.version,
"release_id": str(current_release.id),
"release_version": current_release.version,
"memory_config_id": memory_config_id
}
@@ -1272,10 +1270,11 @@ def get_end_users_connected_configs_batch(end_user_ids: List[str], db: Session)
...
}
"""
from sqlalchemy import select
from app.models.app_release_model import AppRelease
from app.models.end_user_model import EndUser
from app.models.memory_config_model import MemoryConfig
from sqlalchemy import select
logger.info(f"Batch getting connected configs for {len(end_user_ids)} end_users")