from typing import Dict, Any, Type, TypeVar, List from abc import ABC, abstractmethod from flask import current_app from common.extensions import cache_manager, db from common.models.interaction import EveAIAgent, EveAITask, EveAITool, Specialist from common.utils.cache.crewai_configuration import ( ProcessedAgentConfig, ProcessedTaskConfig, ProcessedToolConfig, SpecialistProcessedConfig ) T = TypeVar('T') # For generic model types class BaseCrewAIConfigProcessor: """Base processor for specialist configurations""" # Standard mapping between model fields and template placeholders AGENT_FIELD_MAPPING = { 'role': 'custom_role', 'goal': 'custom_goal', 'backstory': 'custom_backstory' } TASK_FIELD_MAPPING = { 'task_description': 'custom_description', 'expected_output': 'custom_expected_output' } def __init__(self, tenant_id: int, specialist_id: int): self.tenant_id = tenant_id self.specialist_id = specialist_id self.specialist = self._get_specialist() self.verbose = self._get_verbose_setting() def _get_specialist(self) -> Specialist: """Get specialist and verify existence""" specialist = Specialist.query.get(self.specialist_id) if not specialist: raise ValueError(f"Specialist {self.specialist_id} not found") return specialist def _get_verbose_setting(self) -> bool: """Get verbose setting from specialist""" return bool(self.specialist.tuning) def _get_db_items(self, model_class: Type[T], type_list: List[str]) -> Dict[str, T]: """Get database items of specified type""" items = (model_class.query .filter_by(specialist_id=self.specialist_id) .filter(model_class.type.in_(type_list)) .all()) return {item.type: item for item in items} def _apply_replacements(self, text: str, replacements: Dict[str, str]) -> str: """Apply text replacements to a string""" result = text for key, value in replacements.items(): if value is not None: # Only replace if value exists placeholder = "{" + key + "}" result = result.replace(placeholder, str(value)) return result def _process_agent_configs(self, specialist_config: Dict[str, Any]) -> Dict[str, ProcessedAgentConfig]: """Process all agent configurations""" agent_configs = {} if 'agents' not in specialist_config: return agent_configs # Get all DB agents at once agent_types = [agent_def['type'] for agent_def in specialist_config['agents']] db_agents = self._get_db_items(EveAIAgent, agent_types) for agent_def in specialist_config['agents']: agent_type = agent_def['type'] agent_type_lower = agent_type.lower() db_agent = db_agents.get(agent_type) # Get full configuration config = cache_manager.agents_config_cache.get_config( agent_type, agent_def.get('version', '1.0') ) # Start with YAML values role = config['role'] goal = config['goal'] backstory = config['backstory'] # Apply DB values if they exist if db_agent: for model_field, placeholder in self.AGENT_FIELD_MAPPING.items(): value = getattr(db_agent, model_field) if value: placeholder_text = "{" + placeholder + "}" role = role.replace(placeholder_text, value) goal = goal.replace(placeholder_text, value) backstory = backstory.replace(placeholder_text, value) agent_configs[agent_type_lower] = ProcessedAgentConfig( role=role, goal=goal, backstory=backstory, name=agent_def.get('name') or config.get('name', agent_type_lower), type=agent_type, description=agent_def.get('description') or config.get('description'), verbose=self.verbose ) return agent_configs def _process_task_configs(self, specialist_config: Dict[str, Any]) -> Dict[str, ProcessedTaskConfig]: """Process all task configurations""" task_configs = {} if 'tasks' not in specialist_config: return task_configs # Get all DB tasks at once task_types = [task_def['type'] for task_def in specialist_config['tasks']] db_tasks = self._get_db_items(EveAITask, task_types) for task_def in specialist_config['tasks']: task_type = task_def['type'] task_type_lower = task_type.lower() db_task = db_tasks.get(task_type) # Get full configuration config = cache_manager.tasks_config_cache.get_config( task_type, task_def.get('version', '1.0') ) # Start with YAML values task_description = config['task_description'] expected_output = config['expected_output'] # Apply DB values if they exist if db_task: for model_field, placeholder in self.TASK_FIELD_MAPPING.items(): value = getattr(db_task, model_field) if value: placeholder_text = "{" + placeholder + "}" task_description = task_description.replace(placeholder_text, value) expected_output = expected_output.replace(placeholder_text, value) task_configs[task_type_lower] = ProcessedTaskConfig( task_description=task_description, expected_output=expected_output, name=task_def.get('name') or config.get('name', task_type_lower), type=task_type, description=task_def.get('description') or config.get('description'), verbose=self.verbose ) return task_configs def _process_tool_configs(self, specialist_config: Dict[str, Any]) -> Dict[str, ProcessedToolConfig]: """Process all tool configurations""" tool_configs = {} if 'tools' not in specialist_config: return tool_configs # Get all DB tools at once tool_types = [tool_def['type'] for tool_def in specialist_config['tools']] db_tools = self._get_db_items(EveAITool, tool_types) for tool_def in specialist_config['tools']: tool_type = tool_def['type'] tool_type_lower = tool_type.lower() db_tool = db_tools.get(tool_type) # Get full configuration config = cache_manager.tools_config_cache.get_config( tool_type, tool_def.get('version', '1.0') ) # Combine configuration tool_config = config.get('configuration', {}) if db_tool and db_tool.configuration: tool_config.update(db_tool.configuration) tool_configs[tool_type_lower] = ProcessedToolConfig( name=tool_def.get('name') or config.get('name', tool_type_lower), type=tool_type, description=tool_def.get('description') or config.get('description'), configuration=tool_config, verbose=self.verbose ) return tool_configs def process_config(self) -> SpecialistProcessedConfig: """Process complete specialist configuration""" try: # Get full specialist configuration specialist_config = cache_manager.specialists_config_cache.get_config( self.specialist.type, self.specialist.type_version ) if not specialist_config: raise ValueError(f"No configuration found for {self.specialist.type}") # Process all configurations processed_config = SpecialistProcessedConfig( agents=self._process_agent_configs(specialist_config), tasks=self._process_task_configs(specialist_config), tools=self._process_tool_configs(specialist_config) ) return processed_config except Exception as e: current_app.logger.error(f"Error processing specialist configuration: {e}") raise