"""Agent 配置管理 - 支持热更新""" import logging import uuid from datetime import datetime, timezone from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from app.agent_framework.exceptions import AgentNotFoundError, ConfigValidationError from app.database import AsyncSessionLocal from app.models.agent import AgentConfig as AgentConfigModel from app.models.agent import AgentRegistry as AgentRegistryModel logger = logging.getLogger(__name__) class AgentConfigManager: """Agent 配置管理,支持热更新""" async def get_config(self, agent_name: str) -> dict: """获取 Agent 的完整配置""" async with AsyncSessionLocal() as db: agent = await self._get_agent_by_name(db, agent_name) if not agent: raise AgentNotFoundError(agent_name) stmt = select(AgentConfigModel).where( AgentConfigModel.agent_id == agent.id ) result = await db.execute(stmt) configs = result.scalars().all() return { "agent_name": agent_name, "agent_id": str(agent.id), "configs": { cfg.config_key: cfg.config_value for cfg in configs }, } async def set_config( self, agent_name: str, key: str, value: dict | list | str | int | float | bool | None, updated_by: str | None = None, ): """设置单个配置项""" async with AsyncSessionLocal() as db: try: agent = await self._get_agent_by_name(db, agent_name) if not agent: raise AgentNotFoundError(agent_name) # 查找已有配置 stmt = select(AgentConfigModel).where( AgentConfigModel.agent_id == agent.id, AgentConfigModel.config_key == key, ) result = await db.execute(stmt) existing = result.scalar_one_or_none() # 将 value 包装为 JSONB 兼容的 dict config_value = self._wrap_value(value) if existing: existing.config_value = config_value if updated_by: existing.updated_by = uuid.UUID(updated_by) else: new_config = AgentConfigModel( agent_id=agent.id, config_key=key, config_value=config_value, updated_by=uuid.UUID(updated_by) if updated_by else None, ) db.add(new_config) await db.commit() logger.info(f"Config '{key}' updated for agent '{agent_name}'") except AgentNotFoundError: raise except Exception as e: await db.rollback() logger.error(f"Failed to set config '{key}' for agent '{agent_name}': {e}") raise async def bulk_update_config( self, agent_name: str, configs: dict, updated_by: str | None = None, ): """批量更新配置""" async with AsyncSessionLocal() as db: try: agent = await self._get_agent_by_name(db, agent_name) if not agent: raise AgentNotFoundError(agent_name) for key, value in configs.items(): config_value = self._wrap_value(value) stmt = select(AgentConfigModel).where( AgentConfigModel.agent_id == agent.id, AgentConfigModel.config_key == key, ) result = await db.execute(stmt) existing = result.scalar_one_or_none() if existing: existing.config_value = config_value if updated_by: existing.updated_by = uuid.UUID(updated_by) else: new_config = AgentConfigModel( agent_id=agent.id, config_key=key, config_value=config_value, updated_by=uuid.UUID(updated_by) if updated_by else None, ) db.add(new_config) await db.commit() logger.info( f"Bulk config update ({len(configs)} keys) for agent '{agent_name}'" ) except AgentNotFoundError: raise except Exception as e: await db.rollback() logger.error(f"Failed to bulk update config for agent '{agent_name}': {e}") raise async def get_config_history( self, agent_name: str, key: str | None = None, ) -> list[dict]: """ 获取配置变更历史。 注意:当前模型不保留历史版本,此方法返回当前配置快照。 未来可扩展 config_versions 表来存储完整历史。 """ async with AsyncSessionLocal() as db: agent = await self._get_agent_by_name(db, agent_name) if not agent: raise AgentNotFoundError(agent_name) stmt = select(AgentConfigModel).where( AgentConfigModel.agent_id == agent.id, ) if key: stmt = stmt.where(AgentConfigModel.config_key == key) result = await db.execute(stmt) configs = result.scalars().all() return [ { "id": str(cfg.id), "agent_name": agent_name, "config_key": cfg.config_key, "config_value": cfg.config_value, "description": cfg.description, "updated_at": cfg.updated_at.isoformat() if cfg.updated_at else None, "updated_by": str(cfg.updated_by) if cfg.updated_by else None, } for cfg in configs ] async def _get_agent_by_name( self, db: AsyncSession, agent_name: str ) -> AgentRegistryModel | None: """根据名称查找 Agent""" stmt = select(AgentRegistryModel).where( AgentRegistryModel.name == agent_name ) result = await db.execute(stmt) return result.scalar_one_or_none() def _wrap_value(self, value: dict | list | str | int | float | bool | None) -> dict: """将任意值包装为 JSONB 兼容的 dict""" if isinstance(value, dict): return value return {"value": value}