import os from dataclasses import dataclass, field from time import perf_counter from typing import Any, Callable, Dict, List, Optional, Tuple, Union from agno.agent import Agent from agno.db.base import SessionType from agno.models.openai import OpenAIChat from .agente_kanboard import AgenteBasico from .base import AgentDefinition from mcp_wrapper import MCPConfigError, initialize_mcp_tools, load_mcp_tools from utils.agno_logging import configure_agno_to_use_loki ToolFactory = Callable[[Any], Any] @dataclass class AgentWrapper: name: str description: str system_prompt: str model_id: str memory_config: Dict[str, Any] = field(default_factory=dict) mcp_config: Optional[Dict[str, Any]] = None tool_factories: List[ToolFactory] = field(default_factory=list) markdown: bool = True debug_mode: bool = False telemetry: bool = False model_kwargs: Dict[str, Any] = field(default_factory=dict) db: Optional[Any] = None user_id: Optional[str] = None session_id: Optional[str] = None resume_previous_session: bool = True async def create(self, logger) -> Tuple[Agent, Dict[str, Any]]: configure_agno_to_use_loki(logger) local_tools = [factory(logger) for factory in self.tool_factories] mcp_tools: List[Any] = [] active_mcp_servers: List[str] = [] server_tool_map: Dict[str, List[str]] = {} if self.mcp_config: mcp_config = dict(self.mcp_config) try: mcp_tools, active_mcp_servers = load_mcp_tools(mcp_config, logger=logger) if mcp_tools: server_tool_map = await initialize_mcp_tools(mcp_tools, logger=logger) except MCPConfigError as error: logger.error( "🚨 Configuración MCP inválida", add_fields={ "error": str(error), "agent_call": { "action": "load_mcp_config", "agent_name": self.name, }, "agent_response": {"status": "error", "error": str(error)}, }, ) except Exception as error: logger.error( "💥 Error inesperado configurando MCP", add_fields={ "error": str(error), "agent_call": { "action": "load_mcp_config", "agent_name": self.name, }, "agent_response": {"status": "error", "error": str(error)}, }, ) raise if self.db: logger.info( "🗄️ Base de datos SQLite configurada para el agente", add_fields={ "agent_call": { "action": "configure_memory", "agent_name": self.name, "db_file": getattr(self.db, "db_file", None), }, "agent_response": { "status": "db_ready", "resume_previous_session": self.resume_previous_session, }, }, ) if self.resume_previous_session and not self.session_id and self.user_id: start = perf_counter() try: sessions = self.db.get_sessions(user_id=self.user_id, session_type=SessionType.AGENT) duration_ms = round((perf_counter() - start) * 1000, 3) if sessions: self.session_id = getattr(sessions[0], "session_id", None) logger.info( "📂 Sesión previa recuperada desde memoria persistente", add_fields={ "agent_call": { "action": "restore_session", "agent_name": self.name, "user_id": self.user_id, }, "agent_response": { "status": "session_found", "session_id": self.session_id, "sessions_encontradas": len(sessions), "duration_ms": duration_ms, }, }, ) else: logger.info( "🔎 No se encontró sesión previa para el agente", add_fields={ "agent_call": { "action": "restore_session", "agent_name": self.name, "user_id": self.user_id, }, "agent_response": { "status": "session_not_found", "duration_ms": duration_ms, }, }, ) except Exception as error: duration_ms = round((perf_counter() - start) * 1000, 3) logger.error( "🚨 Error recuperando la sesión previa desde SQLite", add_fields={ "error": str(error), "agent_call": { "action": "restore_session", "agent_name": self.name, "user_id": self.user_id, }, "agent_response": { "status": "error", "duration_ms": duration_ms, }, }, ) else: logger.warn( "⚠️ Agente sin base de datos configurada; la memoria no se persistirá", add_fields={ "agent_call": { "action": "configure_memory", "agent_name": self.name, }, "agent_response": { "status": "db_missing", "add_history_to_context": self.memory_config.get("add_history_to_context", False), }, }, ) api_key = os.getenv("OPENAI_API_KEY") if not api_key: logger.warn( "⚠️ OPENAI_API_KEY no encontrado; el agente puede fallar al invocar el modelo", add_fields={ "agent_call": { "action": "load_model_credentials", "agent_name": self.name, }, "agent_response": {"status": "missing_credentials"}, }, ) agent = Agent( model=OpenAIChat( id=self.model_id, api_key=api_key, **self.model_kwargs, ), db=self.db, user_id=self.user_id, session_id=self.session_id, tools=[*local_tools, *mcp_tools], markdown=self.markdown, name=self.name, description=self.description, system_message=self.system_prompt, debug_mode=self.debug_mode, telemetry=self.telemetry, **self.memory_config, ) logger.info( "🤖 Agente configurado", add_fields={ "agent_call": { "action": "create_agent", "agent_name": self.name, "local_tools": [tool.__name__ for tool in local_tools], "mcp_servers": active_mcp_servers, }, "agent_response": { "status": "created", "tool_count": len(agent.tools) if agent.tools else 0, }, }, ) logger.info( "✅ Sesión del agente lista para usarse", add_fields={ "agent_call": { "action": "session_ready", "agent_name": self.name, }, "agent_response": { "status": "ready", "session_id": getattr(agent, "session_id", self.session_id), "user_id": self.user_id, "db_file": getattr(self.db, "db_file", None) if self.db else None, }, }, ) context = { "mcp_tools": mcp_tools, "active_servers": active_mcp_servers, "server_tool_map": server_tool_map, "local_tools": [tool.__name__ for tool in local_tools], "agent_name": self.name, "db_file": getattr(self.db, "db_file", None) if self.db else None, "user_id": self.user_id, "session_id": getattr(agent, "session_id", self.session_id), "resume_previous_session": self.resume_previous_session, } self.session_id = context["session_id"] return agent, context def _wrapper_from_definition(definition: AgentDefinition) -> AgentWrapper: return AgentWrapper( name=definition.name, description=definition.description, system_prompt=definition.system_prompt, model_id=definition.model_id, memory_config=definition.memory_config, mcp_config=definition.mcp_config, tool_factories=definition.tool_factories, markdown=definition.markdown, debug_mode=definition.debug_mode, telemetry=definition.telemetry, model_kwargs=definition.model_kwargs, db=definition.db, user_id=definition.user_id, session_id=definition.session_id, resume_previous_session=definition.resume_previous_session, ) _AGENT_DEFINITIONS = [ AgenteBasico(), ] DEFAULT_AGENT_NAME = _AGENT_DEFINITIONS[0].get_registry_key() AGENT_REGISTRY: Dict[str, AgentWrapper] = { agent.get_registry_key(): _wrapper_from_definition(agent.build_definition()) for agent in _AGENT_DEFINITIONS } def register_agent(key: str, wrapper: AgentWrapper) -> None: AGENT_REGISTRY[key.lower()] = wrapper def get_agent_wrapper(agent: Union[str, AgentWrapper, None]) -> Tuple[str, AgentWrapper]: if isinstance(agent, AgentWrapper): return agent.name.lower(), agent agent_key = (agent or DEFAULT_AGENT_NAME).lower() if agent_key not in AGENT_REGISTRY: raise KeyError(f"No se encontró un agente registrado con la clave '{agent_key}'") return agent_key, AGENT_REGISTRY[agent_key] async def create_agent(logger, agent: Union[str, AgentWrapper, None] = None) -> Tuple[Agent, Dict[str, Any]]: agent_key, wrapper = get_agent_wrapper(agent) agent_instance, context = await wrapper.create(logger) context.setdefault("agent_name", wrapper.name) context.setdefault("agent_key", agent_key) return agent_instance, context