pull/1022/head
harshalmore31 2 weeks ago
parent 0a929e702b
commit aa1be4c065

@ -6,9 +6,10 @@ The `AgentLoader` is a powerful utility for creating Swarms agents from markdown
The AgentLoader enables you to: The AgentLoader enables you to:
- Load single agents from markdown files with YAML frontmatter - Load single agents from markdown files with YAML frontmatter
- Load multiple agents from directories or file lists - Load multiple agents from directories or file lists with concurrent processing
- Parse Claude Code sub-agent YAML frontmatter configurations - Parse Claude Code sub-agent YAML frontmatter configurations
- Extract system prompts from markdown content - Extract system prompts from markdown content
- Utilize 100% CPU cores for high-performance batch loading
- Provide comprehensive error handling and validation - Provide comprehensive error handling and validation
## Installation ## Installation
@ -56,42 +57,37 @@ the subagent should follow.
```python ```python
from swarms.utils import load_agent_from_markdown from swarms.utils import load_agent_from_markdown
# Load Claude Code format agent (YAML frontmatter) # Load agent from markdown file
agent = load_agent_from_markdown( agent = load_agent_from_markdown("finance_advisor.md")
file_path="performance-engineer.md" # Uses YAML frontmatter format
)
# The agent automatically gets configured with: # Use the agent
# - Name, description from frontmatter response = agent.run(
# - Temperature, max_loops, model settings "I have $10,000 to invest. What's a good strategy for a beginner?"
# - System prompt from content after frontmatter )
response = agent.run("Analyze application performance issues")
print(response)
``` ```
### Loading Multiple Agents ### Loading Multiple Agents (Concurrent)
```python ```python
from swarms.utils import load_agents_from_markdown from swarms.utils import load_agents_from_markdown
# Load all agents from directory (YAML frontmatter format) # Load agents from list of files with concurrent processing
agents = load_agents_from_markdown( agents = load_agents_from_markdown([
file_paths="./agents_directory/" # Directory with Claude Code format files "market_researcher.md",
) "financial_analyst.md",
"risk_analyst.md"
], concurrent=True) # Uses all CPU cores for faster loading
# Load agents from specific files # Use agents in a workflow
agents = load_agents_from_markdown( from swarms.structs import SequentialWorkflow
file_paths=[
"performance-engineer.md", # Claude Code YAML format workflow = SequentialWorkflow(
"financial-analyst.md", # Claude Code YAML format agents=agents,
"security-analyst.md" # Claude Code YAML format max_loops=1
]
) )
print(f"Loaded {len(agents)} agents") task = "Analyze the AI healthcare market for a $50M investment."
for agent in agents: result = workflow.run(task)
print(f"- {agent.agent_name}: {getattr(agent, 'temperature', 'default temp')}")
``` ```
## Class-Based Usage ## Class-Based Usage
@ -109,8 +105,12 @@ loader = AgentLoader()
# Load single agent # Load single agent
agent = loader.load_single_agent("path/to/agent.md") agent = loader.load_single_agent("path/to/agent.md")
# Load multiple agents # Load multiple agents with concurrent processing
agents = loader.load_multiple_agents("./agents_directory/") agents = loader.load_multiple_agents(
"./agents_directory/",
concurrent=True, # Enable concurrent processing
max_workers=8 # Optional: limit worker threads
)
# Parse markdown file without creating agent # Parse markdown file without creating agent
config = loader.parse_markdown_file("path/to/agent.md") config = loader.parse_markdown_file("path/to/agent.md")
@ -150,34 +150,39 @@ agent = load_agent_from_markdown(
## Complete Example ## Complete Example
### Example: Claude Code Sub-Agent Format ### Example: Finance Advisor Agent
Create a file `performance-engineer.md`: Create a file `finance_advisor.md`:
```markdown ```markdown
--- ---
name: performance-engineer name: FinanceAdvisor
description: Optimize application performance and identify bottlenecks description: Expert financial advisor for investment and budgeting guidance
model_name: gpt-4 model_name: gpt-4
temperature: 0.3 temperature: 0.7
max_loops: 2 max_loops: 1
mcp_url: http://example.com/mcp
--- ---
You are a Performance Engineer specializing in application optimization and scalability. You are an expert financial advisor with deep knowledge in:
- Investment strategies and portfolio management
Your role involves analyzing system performance, identifying bottlenecks, and implementing - Personal budgeting and financial planning
solutions to improve efficiency and user experience. - Risk assessment and diversification
- Tax optimization strategies
Key responsibilities: - Retirement planning
- Profile applications to identify performance issues
- Optimize database queries and caching strategies Your approach:
- Implement load testing and monitoring solutions - Provide clear, actionable financial advice
- Recommend infrastructure improvements - Consider individual risk tolerance and goals
- Provide actionable optimization recommendations - Explain complex concepts in simple terms
- Always emphasize the importance of diversification
Always provide specific, measurable recommendations with implementation details. - Include relevant disclaimers about financial advice
Focus on both immediate wins and long-term architectural improvements.
When analyzing financial situations:
1. Assess current financial position
2. Identify short-term and long-term goals
3. Evaluate risk tolerance
4. Recommend appropriate strategies
5. Suggest specific action steps
``` ```
### Loading and Using the Agent ### Loading and Using the Agent
@ -185,25 +190,13 @@ Focus on both immediate wins and long-term architectural improvements.
```python ```python
from swarms.utils import load_agent_from_markdown from swarms.utils import load_agent_from_markdown
# Load Claude Code format agent (YAML frontmatter) # Load the Finance Advisor agent
performance_agent = load_agent_from_markdown( agent = load_agent_from_markdown("finance_advisor.md")
file_path="performance-engineer.md"
)
print(f"Agent: {performance_agent.agent_name}")
print(f"Temperature: {getattr(performance_agent, 'temperature', 'default')}")
print(f"Max loops: {performance_agent.max_loops}")
print(f"System prompt preview: {performance_agent.system_prompt[:100]}...")
# Use the performance agent # Use the agent for financial advice
task = """ response = agent.run(
Analyze the performance of a web application that handles 10,000 concurrent users "I have $10,000 to invest. What's a good strategy for a beginner?"
but is experiencing slow response times averaging 3 seconds. The application uses )
a PostgreSQL database and is deployed on AWS with 4 EC2 instances behind a load balancer.
"""
# Note: Actual agent.run() would make API calls
print(f"\nTask for {performance_agent.agent_name}: {task[:100]}...")
``` ```
## Error Handling ## Error Handling
@ -229,6 +222,54 @@ except Exception as e:
print(f"Error loading agents: {e}") print(f"Error loading agents: {e}")
``` ```
## Concurrent Processing Features
### Multi-Core Performance
The AgentLoader utilizes **100% of CPU cores** for concurrent agent loading, providing significant performance improvements when processing multiple markdown files:
```python
from swarms.utils import load_agents_from_markdown
# Automatic concurrent processing for multiple files
agents = load_agents_from_markdown([
"agent1.md", "agent2.md", "agent3.md", "agent4.md"
]) # concurrent=True by default
# Manual control over concurrency
agents = load_agents_from_markdown(
"./agents_directory/",
concurrent=True, # Enable concurrent processing
max_workers=8 # Limit to 8 worker threads
)
# Disable concurrency for debugging or single files
agents = load_agents_from_markdown(
["single_agent.md"],
concurrent=False # Sequential processing
)
```
### Resource Management
```python
# Default: Uses all CPU cores
agents = load_agents_from_markdown(files, concurrent=True)
# Custom worker count for resource control
agents = load_agents_from_markdown(
files,
concurrent=True,
max_workers=4 # Limit to 4 threads
)
# ThreadPoolExecutor automatically manages:
# - Thread lifecycle
# - Resource cleanup
# - Exception handling
# - Result collection
```
## Advanced Features ## Advanced Features
### Custom System Prompt Building ### Custom System Prompt Building
@ -249,33 +290,6 @@ print("Generated System Prompt:")
print(config.system_prompt) print(config.system_prompt)
``` ```
### Batch Processing
Process multiple agent files efficiently:
```python
import os
from pathlib import Path
from swarms.utils import AgentLoader
loader = AgentLoader()
# Find all markdown files in a directory
agent_dir = Path("./agents")
md_files = list(agent_dir.glob("*.md"))
# Load all agents
agents = []
for file_path in md_files:
try:
agent = loader.load_single_agent(str(file_path))
agents.append(agent)
print(f"✓ Loaded: {agent.agent_name}")
except Exception as e:
print(f"✗ Failed to load {file_path}: {e}")
print(f"\nSuccessfully loaded {len(agents)} agents")
```
## Integration with Swarms ## Integration with Swarms
@ -323,8 +337,13 @@ class AgentLoader:
### Convenience Functions ### Convenience Functions
```python ```python
def load_agent_from_markdown(file_path: str, model: Optional[LiteLLM] = None, **kwargs) -> Agent def load_agent_from_markdown(file_path: str, **kwargs) -> Agent
def load_agents_from_markdown(file_paths: Union[str, List[str]], model: Optional[LiteLLM] = None, **kwargs) -> List[Agent] def load_agents_from_markdown(
file_paths: Union[str, List[str]],
concurrent: bool = True, # Enable concurrent processing
max_workers: Optional[int] = None, # Max worker threads (defaults to CPU count)
**kwargs
) -> List[Agent]
``` ```
### Configuration Model ### Configuration Model
@ -347,9 +366,8 @@ class MarkdownAgentConfig(BaseModel):
## Examples Repository ## Examples Repository
Find more examples in the Swarms repository: Find more examples in the Swarms repository:
- `examples/agents_loader_example.py` - Complete usage demonstration - `agents_loader_example.py` - Simple usage example
- `test_agent_loader.py` - Test suite with validation examples - `examples/agent_loader_demo.py` - Multi-agent workflow example
- `examples/single_agent/utils/markdown_agent.py` - Markdown agent utilities
## Support ## Support

@ -22,6 +22,12 @@ from swarms.utils.history_output_formatter import (
from swarms.utils.check_all_model_max_tokens import ( from swarms.utils.check_all_model_max_tokens import (
check_all_model_max_tokens, check_all_model_max_tokens,
) )
from swarms.utils.agent_loader import (
AgentLoader,
MarkdownAgentConfig,
load_agent_from_markdown,
load_agents_from_markdown,
)
__all__ = [ __all__ = [
@ -41,4 +47,8 @@ __all__ = [
"HistoryOutputType", "HistoryOutputType",
"history_output_formatter", "history_output_formatter",
"check_all_model_max_tokens", "check_all_model_max_tokens",
"AgentLoader",
"MarkdownAgentConfig",
"load_agent_from_markdown",
"load_agents_from_markdown",
] ]

@ -1,22 +1,29 @@
import os import os
import re
import random
import yaml import yaml
from pathlib import Path from pathlib import Path
from typing import Any, Dict, List, Optional, Union from typing import Any, Dict, List, Optional, Union
from concurrent.futures import (
ThreadPoolExecutor,
as_completed,
TimeoutError,
)
from pydantic import BaseModel, Field, field_validator from pydantic import BaseModel, Field, field_validator
from loguru import logger from loguru import logger
from swarms.structs.agent import Agent from swarms.structs.agent import Agent
# Default model configuration
DEFAULT_MODEL = "gpt-4o"
class MarkdownAgentConfig(BaseModel): class MarkdownAgentConfig(BaseModel):
"""Configuration model for agents loaded from Claude Code markdown files.""" """Configuration model for agents loaded from Claude Code markdown files."""
name: str name: str
description: str description: str
model_name: Optional[str] = "gpt-4" model_name: Optional[str] = "gpt-4o"
temperature: Optional[float] = Field(default=0.1, ge=0.0, le=2.0) temperature: Optional[float] = Field(default=0.1, ge=0.0, le=2.0)
mcp_url: Optional[str] = None mcp_url: Optional[int] = None
system_prompt: str system_prompt: str
max_loops: int = Field(default=1, ge=1) max_loops: int = Field(default=1, ge=1)
autosave: bool = False autosave: bool = False
@ -33,22 +40,25 @@ class MarkdownAgentConfig(BaseModel):
artifacts_on: bool = False artifacts_on: bool = False
artifacts_file_extension: str = ".md" artifacts_file_extension: str = ".md"
artifacts_output_path: str = "" artifacts_output_path: str = ""
streaming_on: bool = False
@field_validator("system_prompt") @field_validator("system_prompt")
@classmethod @classmethod
def validate_system_prompt(cls, v): def validate_system_prompt(cls, v):
if not v or not isinstance(v, str) or len(v.strip()) == 0: if not v or not isinstance(v, str) or len(v.strip()) == 0:
raise ValueError("System prompt must be a non-empty string") raise ValueError(
"System prompt must be a non-empty string"
)
return v return v
class AgentLoader: class AgentLoader:
""" """
Loader for creating agents from markdown files using Claude Code sub-agent format. Loader for creating agents from markdown files using Claude Code sub-agent format.
Supports both single markdown file and multiple markdown files. Supports both single markdown file and multiple markdown files.
Uses YAML frontmatter format for agent configuration. Uses YAML frontmatter format for agent configuration.
Features: Features:
- Single markdown file loading - Single markdown file loading
- Multiple markdown files loading (batch processing) - Multiple markdown files loading (batch processing)
@ -56,223 +66,335 @@ class AgentLoader:
- Agent configuration extraction from YAML metadata - Agent configuration extraction from YAML metadata
- Error handling and validation - Error handling and validation
""" """
def __init__(self): def __init__(self):
""" """
Initialize the AgentLoader. Initialize the AgentLoader.
""" """
pass pass
def parse_yaml_frontmatter(self, content: str) -> Dict[str, Any]: def parse_yaml_frontmatter(self, content: str) -> Dict[str, Any]:
""" """
Parse YAML frontmatter from markdown content. Parse YAML frontmatter from markdown content.
Args: Args:
content: Markdown content with potential YAML frontmatter content: Markdown content with potential YAML frontmatter
Returns: Returns:
Dictionary with parsed YAML data and remaining content Dictionary with parsed YAML data and remaining content
""" """
lines = content.split('\n') lines = content.split("\n")
# Check if content starts with YAML frontmatter # Check if content starts with YAML frontmatter
if not lines[0].strip() == '---': if not lines[0].strip() == "---":
return {"frontmatter": {}, "content": content} return {"frontmatter": {}, "content": content}
# Find end of frontmatter # Find end of frontmatter
end_marker = -1 end_marker = -1
for i, line in enumerate(lines[1:], 1): for i, line in enumerate(lines[1:], 1):
if line.strip() == '---': if line.strip() == "---":
end_marker = i end_marker = i
break break
if end_marker == -1: if end_marker == -1:
return {"frontmatter": {}, "content": content} return {"frontmatter": {}, "content": content}
# Extract frontmatter and content # Extract frontmatter and content
frontmatter_text = '\n'.join(lines[1:end_marker]) frontmatter_text = "\n".join(lines[1:end_marker])
remaining_content = '\n'.join(lines[end_marker + 1:]).strip() remaining_content = "\n".join(lines[end_marker + 1 :]).strip()
try: try:
frontmatter_data = yaml.safe_load(frontmatter_text) or {} frontmatter_data = yaml.safe_load(frontmatter_text) or {}
except yaml.YAMLError as e: except yaml.YAMLError as e:
logger.warning(f"Failed to parse YAML frontmatter: {e}") logger.warning(f"Failed to parse YAML frontmatter: {e}")
return {"frontmatter": {}, "content": content} return {"frontmatter": {}, "content": content}
return {"frontmatter": frontmatter_data, "content": remaining_content}
return {
"frontmatter": frontmatter_data,
"content": remaining_content,
def parse_markdown_file(self, file_path: str) -> MarkdownAgentConfig: }
def parse_markdown_file(
self, file_path: str
) -> MarkdownAgentConfig:
""" """
Parse a single markdown file to extract agent configuration. Parse a single markdown file to extract agent configuration.
Uses Claude Code sub-agent YAML frontmatter format. Uses Claude Code sub-agent YAML frontmatter format.
Args: Args:
file_path: Path to markdown file file_path: Path to markdown file
Returns: Returns:
MarkdownAgentConfig object with parsed configuration MarkdownAgentConfig object with parsed configuration
Raises: Raises:
FileNotFoundError: If file doesn't exist FileNotFoundError: If file doesn't exist
ValueError: If parsing fails or no YAML frontmatter found ValueError: If parsing fails or no YAML frontmatter found
""" """
if not os.path.exists(file_path): if not os.path.exists(file_path):
raise FileNotFoundError(f"Markdown file {file_path} not found.") raise FileNotFoundError(
f"Markdown file {file_path} not found."
)
try: try:
with open(file_path, 'r', encoding='utf-8') as file: with open(file_path, "r", encoding="utf-8") as file:
content = file.read() content = file.read()
# Parse YAML frontmatter (Claude Code sub-agent format) # Parse YAML frontmatter (Claude Code sub-agent format)
yaml_result = self.parse_yaml_frontmatter(content) yaml_result = self.parse_yaml_frontmatter(content)
frontmatter = yaml_result["frontmatter"] frontmatter = yaml_result["frontmatter"]
remaining_content = yaml_result["content"] remaining_content = yaml_result["content"]
if not frontmatter: if not frontmatter:
raise ValueError(f"No YAML frontmatter found in {file_path}. File must use Claude Code sub-agent format with YAML frontmatter.") raise ValueError(
f"No YAML frontmatter found in {file_path}. File must use Claude Code sub-agent format with YAML frontmatter."
)
# Use YAML frontmatter data # Use YAML frontmatter data
config_data = { config_data = {
'name': frontmatter.get('name', Path(file_path).stem), "name": frontmatter.get("name", Path(file_path).stem),
'description': frontmatter.get('description', 'Agent loaded from markdown'), "description": frontmatter.get(
'model_name': frontmatter.get('model_name') or frontmatter.get('model', 'gpt-4'), "description", "Agent loaded from markdown"
'temperature': frontmatter.get('temperature', 0.1), ),
'max_loops': frontmatter.get('max_loops', 1), "model_name": frontmatter.get("model_name")
'mcp_url': frontmatter.get('mcp_url'), or frontmatter.get("model", DEFAULT_MODEL),
'system_prompt': remaining_content.strip(), "temperature": frontmatter.get("temperature", 0.1),
"max_loops": frontmatter.get("max_loops", 1),
"mcp_url": frontmatter.get("mcp_url"),
"system_prompt": remaining_content.strip(),
"streaming_on": frontmatter.get(
"streaming_on", False
),
} }
# Generate random model if not specified # Use default model if not specified
if not config_data['model_name'] or config_data['model_name'] == 'random': if not config_data["model_name"]:
models = ['gpt-4', 'gpt-4-turbo', 'claude-3-sonnet', 'claude-3-haiku'] config_data["model_name"] = DEFAULT_MODEL
config_data['model_name'] = random.choice(models)
logger.info(
logger.info(f"Successfully parsed markdown file: {file_path}") f"Successfully parsed markdown file: {file_path}"
)
return MarkdownAgentConfig(**config_data) return MarkdownAgentConfig(**config_data)
except Exception as e: except Exception as e:
logger.error(f"Error parsing markdown file {file_path}: {str(e)}") logger.error(
raise ValueError(f"Error parsing markdown file {file_path}: {str(e)}") f"Error parsing markdown file {file_path}: {str(e)}"
)
def load_agent_from_markdown(self, file_path: str, **kwargs) -> Agent: raise ValueError(
f"Error parsing markdown file {file_path}: {str(e)}"
)
def load_agent_from_markdown(
self, file_path: str, **kwargs
) -> Agent:
""" """
Load a single agent from a markdown file. Load a single agent from a markdown file.
Args: Args:
file_path: Path to markdown file file_path: Path to markdown file
**kwargs: Additional arguments to override default configuration **kwargs: Additional arguments to override default configuration
Returns: Returns:
Configured Agent instance Configured Agent instance
""" """
config = self.parse_markdown_file(file_path) config = self.parse_markdown_file(file_path)
# Override with any provided kwargs # Override with any provided kwargs
config_dict = config.model_dump() config_dict = config.model_dump()
config_dict.update(kwargs) config_dict.update(kwargs)
# Remove fields not needed for Agent creation # Map config fields to Agent parameters, handling special cases
agent_fields = { field_mapping = {
'agent_name': config_dict['name'], "name": "agent_name", # name -> agent_name
'system_prompt': config_dict['system_prompt'], "description": None, # not used by Agent
'model_name': config_dict.get('model_name', 'gpt-4'), "mcp_url": None, # not used by Agent
'temperature': config_dict.get('temperature', 0.1),
'max_loops': config_dict['max_loops'],
'autosave': config_dict['autosave'],
'dashboard': config_dict['dashboard'],
'verbose': config_dict['verbose'],
'dynamic_temperature_enabled': config_dict['dynamic_temperature_enabled'],
'saved_state_path': config_dict['saved_state_path'],
'user_name': config_dict['user_name'],
'retry_attempts': config_dict['retry_attempts'],
'context_length': config_dict['context_length'],
'return_step_meta': config_dict['return_step_meta'],
'output_type': config_dict['output_type'],
'auto_generate_prompt': config_dict['auto_generate_prompt'],
'artifacts_on': config_dict['artifacts_on'],
'artifacts_file_extension': config_dict['artifacts_file_extension'],
'artifacts_output_path': config_dict['artifacts_output_path'],
} }
agent_fields = {}
for config_key, config_value in config_dict.items():
# Handle special field mappings
if config_key in field_mapping:
agent_key = field_mapping[config_key]
if agent_key: # Only include if mapped to something
agent_fields[agent_key] = config_value
else:
# Direct mapping for most fields
agent_fields[config_key] = config_value
try: try:
logger.info(f"Creating agent '{config.name}' from {file_path}") logger.info(
f"Creating agent '{config.name}' from {file_path}"
)
agent = Agent(**agent_fields) agent = Agent(**agent_fields)
logger.info(f"Successfully created agent '{config.name}' from {file_path}") logger.info(
f"Successfully created agent '{config.name}' from {file_path}"
)
return agent return agent
except Exception as e: except Exception as e:
import traceback import traceback
logger.error(f"Error creating agent from {file_path}: {str(e)}")
logger.error(
f"Error creating agent from {file_path}: {str(e)}"
)
logger.error(f"Traceback: {traceback.format_exc()}") logger.error(f"Traceback: {traceback.format_exc()}")
raise ValueError(f"Error creating agent from {file_path}: {str(e)}") raise ValueError(
f"Error creating agent from {file_path}: {str(e)}"
def load_agents_from_markdown(self, file_paths: Union[str, List[str]], **kwargs) -> List[Agent]: )
def load_agents_from_markdown(
self,
file_paths: Union[str, List[str]],
concurrent: bool = True,
max_workers: Optional[int] = None,
max_file_size_mb: float = 10.0,
**kwargs,
) -> List[Agent]:
""" """
Load multiple agents from markdown files. Load multiple agents from markdown files with optional concurrent processing.
Args: Args:
file_paths: Single file path, directory path, or list of file paths file_paths: Single file path, directory path, or list of file paths
concurrent: Whether to use concurrent processing for multiple files
max_workers: Maximum number of worker threads (defaults to CPU count)
max_file_size_mb: Maximum file size in MB to prevent memory issues
**kwargs: Additional arguments to override default configuration **kwargs: Additional arguments to override default configuration
Returns: Returns:
List of configured Agent instances List of configured Agent instances
""" """
agents = [] agents = []
paths_to_process = [] paths_to_process = []
# Handle different input types # Handle different input types
if isinstance(file_paths, str): if isinstance(file_paths, str):
if os.path.isdir(file_paths): if os.path.isdir(file_paths):
# Directory - find all .md files # Directory - find all .md files
md_files = list(Path(file_paths).glob('*.md')) md_files = list(Path(file_paths).glob("*.md"))
paths_to_process = [str(f) for f in md_files] paths_to_process = [str(f) for f in md_files]
elif os.path.isfile(file_paths): elif os.path.isfile(file_paths):
# Single file # Single file
paths_to_process = [file_paths] paths_to_process = [file_paths]
else: else:
raise FileNotFoundError(f"Path {file_paths} not found.") raise FileNotFoundError(
f"Path {file_paths} not found."
)
elif isinstance(file_paths, list): elif isinstance(file_paths, list):
paths_to_process = file_paths paths_to_process = file_paths
else: else:
raise ValueError("file_paths must be a string or list of strings") raise ValueError(
"file_paths must be a string or list of strings"
# Process each file )
# Validate file sizes to prevent memory issues
for file_path in paths_to_process: for file_path in paths_to_process:
try: try:
agent = self.load_agent_from_markdown(file_path, **kwargs) file_size_mb = os.path.getsize(file_path) / (
agents.append(agent) 1024 * 1024
except Exception as e: )
logger.warning(f"Skipping {file_path} due to error: {str(e)}") if file_size_mb > max_file_size_mb:
continue logger.warning(
f"Skipping {file_path}: size {file_size_mb:.2f}MB exceeds limit {max_file_size_mb}MB"
logger.info(f"Successfully loaded {len(agents)} agents from markdown files") )
paths_to_process.remove(file_path)
except OSError:
logger.warning(
f"Could not check size of {file_path}, skipping validation"
)
# Adjust max_workers for I/O-bound operations
if max_workers is None and concurrent:
# For I/O-bound: use more threads than CPU count, but cap it
max_workers = min(
20, len(paths_to_process), os.cpu_count() * 2
)
# Use concurrent processing for multiple files if enabled
if concurrent and len(paths_to_process) > 1:
logger.info(
f"Loading {len(paths_to_process)} agents concurrently with {max_workers} workers..."
)
with ThreadPoolExecutor(
max_workers=max_workers
) as executor:
# Submit all tasks
future_to_path = {
executor.submit(
self.load_agent_from_markdown,
file_path,
**kwargs,
): file_path
for file_path in paths_to_process
}
# Collect results as they complete with timeout
for future in as_completed(
future_to_path, timeout=300
): # 5 minute timeout
file_path = future_to_path[future]
try:
agent = future.result(
timeout=60
) # 1 minute per agent
agents.append(agent)
logger.info(
f"Successfully loaded agent from {file_path}"
)
except TimeoutError:
logger.error(f"Timeout loading {file_path}")
continue
except Exception as e:
logger.error(
f"Failed to load {file_path}: {str(e)}"
)
continue
else:
# Sequential processing for single file or when concurrent is disabled
logger.info(
f"Loading {len(paths_to_process)} agents sequentially..."
)
for file_path in paths_to_process:
try:
agent = self.load_agent_from_markdown(
file_path, **kwargs
)
agents.append(agent)
except Exception as e:
logger.warning(
f"Skipping {file_path} due to error: {str(e)}"
)
continue
logger.info(
f"Successfully loaded {len(agents)} agents from markdown files"
)
return agents return agents
def load_single_agent(self, file_path: str, **kwargs) -> Agent: def load_single_agent(self, file_path: str, **kwargs) -> Agent:
""" """
Convenience method for loading a single agent. Convenience method for loading a single agent.
Uses Claude Code sub-agent YAML frontmatter format. Uses Claude Code sub-agent YAML frontmatter format.
Args: Args:
file_path: Path to markdown file with YAML frontmatter file_path: Path to markdown file with YAML frontmatter
**kwargs: Additional configuration overrides **kwargs: Additional configuration overrides
Returns: Returns:
Configured Agent instance Configured Agent instance
""" """
return self.load_agent_from_markdown(file_path, **kwargs) return self.load_agent_from_markdown(file_path, **kwargs)
def load_multiple_agents(self, file_paths: Union[str, List[str]], **kwargs) -> List[Agent]: def load_multiple_agents(
self, file_paths: Union[str, List[str]], **kwargs
) -> List[Agent]:
""" """
Convenience method for loading multiple agents. Convenience method for loading multiple agents.
Uses Claude Code sub-agent YAML frontmatter format. Uses Claude Code sub-agent YAML frontmatter format.
Args: Args:
file_paths: Directory path or list of file paths with YAML frontmatter file_paths: Directory path or list of file paths with YAML frontmatter
**kwargs: Additional configuration overrides **kwargs: Additional configuration overrides
Returns: Returns:
List of configured Agent instances List of configured Agent instances
""" """
@ -283,11 +405,11 @@ class AgentLoader:
def load_agent_from_markdown(file_path: str, **kwargs) -> Agent: def load_agent_from_markdown(file_path: str, **kwargs) -> Agent:
""" """
Load a single agent from a markdown file with Claude Code YAML frontmatter format. Load a single agent from a markdown file with Claude Code YAML frontmatter format.
Args: Args:
file_path: Path to markdown file with YAML frontmatter file_path: Path to markdown file with YAML frontmatter
**kwargs: Additional configuration overrides **kwargs: Additional configuration overrides
Returns: Returns:
Configured Agent instance Configured Agent instance
""" """
@ -295,16 +417,28 @@ def load_agent_from_markdown(file_path: str, **kwargs) -> Agent:
return loader.load_single_agent(file_path, **kwargs) return loader.load_single_agent(file_path, **kwargs)
def load_agents_from_markdown(file_paths: Union[str, List[str]], **kwargs) -> List[Agent]: def load_agents_from_markdown(
file_paths: Union[str, List[str]],
concurrent: bool = True,
max_file_size_mb: float = 10.0,
**kwargs,
) -> List[Agent]:
""" """
Load multiple agents from markdown files with Claude Code YAML frontmatter format. Load multiple agents from markdown files with Claude Code YAML frontmatter format.
Args: Args:
file_paths: Directory path or list of file paths with YAML frontmatter file_paths: Directory path or list of file paths with YAML frontmatter
concurrent: Whether to use concurrent processing for multiple files
max_file_size_mb: Maximum file size in MB to prevent memory issues
**kwargs: Additional configuration overrides **kwargs: Additional configuration overrides
Returns: Returns:
List of configured Agent instances List of configured Agent instances
""" """
loader = AgentLoader() loader = AgentLoader()
return loader.load_multiple_agents(file_paths, **kwargs) return loader.load_agents_from_markdown(
file_paths,
concurrent=concurrent,
max_file_size_mb=max_file_size_mb,
**kwargs,
)

Loading…
Cancel
Save