|
|
"""Factory for creating orchestrators. |
|
|
|
|
|
Implements the Factory Pattern (GoF) for creating the appropriate |
|
|
orchestrator based on configuration and available credentials. |
|
|
|
|
|
Design Principles: |
|
|
- Open/Closed: Easy to add new orchestrator types without modifying existing code |
|
|
- Dependency Inversion: Returns protocol-compatible objects, not concrete types |
|
|
- Single Responsibility: Only handles orchestrator creation logic |
|
|
""" |
|
|
|
|
|
from typing import TYPE_CHECKING, Literal |
|
|
|
|
|
import structlog |
|
|
|
|
|
from src.config.domain import ResearchDomain |
|
|
from src.orchestrators.base import ( |
|
|
JudgeHandlerProtocol, |
|
|
OrchestratorProtocol, |
|
|
SearchHandlerProtocol, |
|
|
) |
|
|
from src.orchestrators.simple import Orchestrator |
|
|
from src.utils.config import settings |
|
|
from src.utils.models import OrchestratorConfig |
|
|
|
|
|
if TYPE_CHECKING: |
|
|
from src.orchestrators.advanced import AdvancedOrchestrator |
|
|
|
|
|
logger = structlog.get_logger() |
|
|
|
|
|
|
|
|
def _get_advanced_orchestrator_class() -> type["AdvancedOrchestrator"]: |
|
|
"""Import AdvancedOrchestrator lazily to avoid hard dependency. |
|
|
|
|
|
This allows the simple mode to work without agent-framework-core installed. |
|
|
|
|
|
Returns: |
|
|
The AdvancedOrchestrator class |
|
|
|
|
|
Raises: |
|
|
ValueError: If agent-framework-core is not installed |
|
|
""" |
|
|
try: |
|
|
from src.orchestrators.advanced import AdvancedOrchestrator |
|
|
|
|
|
return AdvancedOrchestrator |
|
|
except ImportError as e: |
|
|
logger.error("Failed to import AdvancedOrchestrator", error=str(e)) |
|
|
raise ValueError( |
|
|
"Advanced mode requires agent-framework-core. " |
|
|
"Install with: pip install agent-framework-core. " |
|
|
"Or use mode='simple' instead." |
|
|
) from e |
|
|
|
|
|
|
|
|
def create_orchestrator( |
|
|
search_handler: SearchHandlerProtocol | None = None, |
|
|
judge_handler: JudgeHandlerProtocol | None = None, |
|
|
config: OrchestratorConfig | None = None, |
|
|
mode: Literal["simple", "magentic", "advanced", "hierarchical"] | None = None, |
|
|
api_key: str | None = None, |
|
|
domain: ResearchDomain | str | None = None, |
|
|
) -> OrchestratorProtocol: |
|
|
""" |
|
|
Create an orchestrator instance. |
|
|
|
|
|
This factory automatically selects the appropriate orchestrator based on: |
|
|
1. Explicit mode parameter (if provided) |
|
|
2. Available API keys (auto-detection) |
|
|
|
|
|
Args: |
|
|
search_handler: The search handler (required for simple mode) |
|
|
judge_handler: The judge handler (required for simple mode) |
|
|
config: Optional configuration (max_iterations, timeouts, etc.) |
|
|
Note: This parameter is only used by simple and hierarchical modes. |
|
|
Advanced mode uses settings.advanced_max_rounds instead. |
|
|
mode: "simple", "magentic", "advanced", or "hierarchical" |
|
|
Note: "magentic" is an alias for "advanced" (kept for backwards compatibility) |
|
|
api_key: Optional API key for advanced mode (OpenAI) |
|
|
domain: Research domain for customization (default: sexual_health) |
|
|
|
|
|
Returns: |
|
|
Orchestrator instance implementing OrchestratorProtocol |
|
|
|
|
|
Raises: |
|
|
ValueError: If required handlers are missing for simple mode |
|
|
ValueError: If advanced mode is requested but dependencies are missing |
|
|
""" |
|
|
effective_config = config or OrchestratorConfig() |
|
|
effective_mode = _determine_mode(mode, api_key) |
|
|
logger.info("Creating orchestrator", mode=effective_mode, domain=domain) |
|
|
|
|
|
if effective_mode == "advanced": |
|
|
orchestrator_cls = _get_advanced_orchestrator_class() |
|
|
return orchestrator_cls( |
|
|
max_rounds=settings.advanced_max_rounds, |
|
|
api_key=api_key, |
|
|
domain=domain, |
|
|
) |
|
|
|
|
|
if effective_mode == "hierarchical": |
|
|
from src.orchestrators.hierarchical import HierarchicalOrchestrator |
|
|
|
|
|
return HierarchicalOrchestrator(config=effective_config, domain=domain) |
|
|
|
|
|
|
|
|
if search_handler is None or judge_handler is None: |
|
|
raise ValueError("Simple mode requires search_handler and judge_handler") |
|
|
|
|
|
return Orchestrator( |
|
|
search_handler=search_handler, |
|
|
judge_handler=judge_handler, |
|
|
config=effective_config, |
|
|
domain=domain, |
|
|
) |
|
|
|
|
|
|
|
|
def _determine_mode(explicit_mode: str | None, api_key: str | None) -> str: |
|
|
"""Determine which mode to use. |
|
|
|
|
|
Priority: |
|
|
1. Explicit mode parameter |
|
|
2. Auto-detect based on available API keys |
|
|
|
|
|
Args: |
|
|
explicit_mode: Mode explicitly requested by caller |
|
|
api_key: API key provided by caller |
|
|
|
|
|
Returns: |
|
|
Effective mode string: "simple", "advanced", or "hierarchical" |
|
|
""" |
|
|
if explicit_mode: |
|
|
if explicit_mode in ("magentic", "advanced"): |
|
|
return "advanced" |
|
|
if explicit_mode == "hierarchical": |
|
|
return "hierarchical" |
|
|
return "simple" |
|
|
|
|
|
|
|
|
if settings.has_openai_key or (api_key and api_key.startswith("sk-")): |
|
|
return "advanced" |
|
|
|
|
|
return "simple" |
|
|
|