diff --git a/src/praisonai-agents/praisonaiagents/agents/autoagents.py b/src/praisonai-agents/praisonaiagents/agents/autoagents.py index e93d2409f..b43f322ca 100644 --- a/src/praisonai-agents/praisonaiagents/agents/autoagents.py +++ b/src/praisonai-agents/praisonaiagents/agents/autoagents.py @@ -171,6 +171,44 @@ def _display_agents_and_tasks(self, agents: List[Agent], tasks: List[Task]): Tools: {', '.join(task_tools)}""" ) + def _normalize_config(self, config_dict: Dict[str, Any]) -> Dict[str, Any]: + """ + Normalize the configuration dictionary to ensure tasks are proper TaskConfig objects. + + This handles cases where LLMs return tasks as strings instead of dictionaries. + """ + if 'agents' in config_dict: + for agent in config_dict['agents']: + if 'tasks' in agent and isinstance(agent['tasks'], list): + normalized_tasks = [] + for task in agent['tasks']: + if isinstance(task, str): + # Convert string task to TaskConfig format + normalized_task = { + 'name': task[:50].rsplit(' ', 1)[0] if len(task) > 50 else task, # Word-aware truncation + 'description': task, + 'expected_output': f"Completed: {task}", + 'tools': [] # Will be populated based on agent tools + } + normalized_tasks.append(normalized_task) + elif isinstance(task, dict): + # Ensure all required fields are present + if 'name' not in task: + desc = str(task.get('description', 'Task')) + task['name'] = desc[:50].rsplit(' ', 1)[0] if len(desc) > 50 else desc + if 'description' not in task: + task['description'] = str(task.get('name', 'Task description')) + if 'expected_output' not in task: + task['expected_output'] = f"Completed: {str(task.get('name', 'task'))}" + if 'tools' not in task: + task['tools'] = [] + normalized_tasks.append(task) + else: + # Skip invalid task types + logging.warning(f"Skipping invalid task type: {type(task)}") + agent['tasks'] = normalized_tasks + return config_dict + def _get_available_tools(self) -> List[str]: """Get list of available tools""" if not self.tools: @@ -214,6 +252,24 @@ def _assign_tools_to_agent(self, agent_config: AgentConfig) -> List[Any]: return assigned_tools + def _parse_json_response(self, response_text: str) -> Dict[str, Any]: + """Parse JSON from LLM response, handling markdown blocks.""" + try: + # First try to parse as is + return json.loads(response_text) + except json.JSONDecodeError: + # If that fails, try to extract JSON from the response + # Handle cases where the model might wrap JSON in markdown blocks + cleaned_response = response_text.strip() + if cleaned_response.startswith("```json"): + cleaned_response = cleaned_response[7:] + if cleaned_response.startswith("```"): + cleaned_response = cleaned_response[3:] + if cleaned_response.endswith("```"): + cleaned_response = cleaned_response[:-3] + cleaned_response = cleaned_response.strip() + return json.loads(cleaned_response) + def _generate_config(self) -> AutoAgentsConfig: """Generate the configuration for agents and tasks""" prompt = f""" @@ -286,24 +342,10 @@ def _generate_config(self) -> AutoAgentsConfig: ) # Parse the JSON response - try: - # First try to parse as is - config_dict = json.loads(response_text) - config = AutoAgentsConfig(**config_dict) - except json.JSONDecodeError: - # If that fails, try to extract JSON from the response - # Handle cases where the model might wrap JSON in markdown blocks - cleaned_response = response_text.strip() - if cleaned_response.startswith("```json"): - cleaned_response = cleaned_response[7:] - if cleaned_response.startswith("```"): - cleaned_response = cleaned_response[3:] - if cleaned_response.endswith("```"): - cleaned_response = cleaned_response[:-3] - cleaned_response = cleaned_response.strip() - - config_dict = json.loads(cleaned_response) - config = AutoAgentsConfig(**config_dict) + config_dict = self._parse_json_response(response_text) + # Normalize tasks if they are strings + config_dict = self._normalize_config(config_dict) + config = AutoAgentsConfig(**config_dict) # Ensure we have exactly max_agents number of agents if len(config.agents) > self.max_agents: diff --git a/src/praisonai-agents/test_autoagents_fix.py b/src/praisonai-agents/test_autoagents_fix.py new file mode 100644 index 000000000..b63a78f90 --- /dev/null +++ b/src/praisonai-agents/test_autoagents_fix.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python3 +"""Test script to verify the AutoAgents fix for string tasks""" + +import json +from praisonaiagents.agents.autoagents import AutoAgents + +# Test the _normalize_config method directly +def test_normalize_config(): + # Create a minimal AutoAgents instance just to test the method + agents = AutoAgents( + instructions="Test", + max_agents=1 + ) + + # Test case 1: Tasks as strings (the problematic case) + config_dict = { + "main_instruction": "Test instruction", + "process_type": "sequential", + "agents": [ + { + "name": "Agent 1", + "role": "Test role", + "goal": "Test goal", + "backstory": "Test backstory", + "tools": [], + "tasks": [ + "Get the current stock price for Google (GOOG).", + "Get the current stock price for Apple (AAPL)." + ] + } + ] + } + + print("Original config with string tasks:") + print(json.dumps(config_dict, indent=2)) + + # Normalize the config + normalized = agents._normalize_config(config_dict.copy()) + + print("\nNormalized config:") + print(json.dumps(normalized, indent=2)) + + # Verify tasks are now dictionaries + for agent in normalized['agents']: + for task in agent['tasks']: + assert isinstance(task, dict), f"Task should be dict, got {type(task)}" + assert 'name' in task, "Task missing 'name' field" + assert 'description' in task, "Task missing 'description' field" + assert 'expected_output' in task, "Task missing 'expected_output' field" + assert 'tools' in task, "Task missing 'tools' field" + + print("\n✅ Test passed: String tasks are properly normalized to TaskConfig format") + + # Test case 2: Tasks already as dictionaries (should work as before) + config_dict2 = { + "main_instruction": "Test instruction", + "process_type": "sequential", + "agents": [ + { + "name": "Agent 1", + "role": "Test role", + "goal": "Test goal", + "backstory": "Test backstory", + "tools": [], + "tasks": [ + { + "name": "Get Google stock", + "description": "Get the current stock price for Google (GOOG).", + "expected_output": "Stock price of Google", + "tools": ["get_stock_price"] + } + ] + } + ] + } + + normalized2 = agents._normalize_config(config_dict2.copy()) + assert normalized2 == config_dict2, "Normalization should not alter valid configurations" + print("\n✅ Test passed: Dict tasks remain properly formatted") + +if __name__ == "__main__": + test_normalize_config() \ No newline at end of file