Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 60 additions & 18 deletions src/praisonai-agents/praisonaiagents/agents/autoagents.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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"""
Expand Down Expand Up @@ -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:
Expand Down
82 changes: 82 additions & 0 deletions src/praisonai-agents/test_autoagents_fix.py
Original file line number Diff line number Diff line change
@@ -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()
Loading