| |
|
|
| import os |
| from typing import Dict, Any, Optional |
| from dataclasses import dataclass |
| from enum import Enum |
|
|
| from ankigen_core.logging import logger |
|
|
|
|
| class AgentMode(Enum): |
| """Agent system operation modes""" |
| LEGACY = "legacy" |
| AGENT_ONLY = "agent_only" |
| HYBRID = "hybrid" |
| A_B_TEST = "a_b_test" |
|
|
|
|
| @dataclass |
| class AgentFeatureFlags: |
| """Feature flags for controlling agent system rollout""" |
| |
| |
| mode: AgentMode = AgentMode.LEGACY |
| |
| |
| enable_subject_expert_agent: bool = False |
| enable_pedagogical_agent: bool = False |
| enable_content_structuring_agent: bool = False |
| enable_generation_coordinator: bool = False |
| |
| |
| enable_content_accuracy_judge: bool = False |
| enable_pedagogical_judge: bool = False |
| enable_clarity_judge: bool = False |
| enable_technical_judge: bool = False |
| enable_completeness_judge: bool = False |
| enable_judge_coordinator: bool = False |
| |
| |
| enable_revision_agent: bool = False |
| enable_enhancement_agent: bool = False |
| |
| |
| enable_multi_agent_generation: bool = False |
| enable_parallel_judging: bool = False |
| enable_agent_handoffs: bool = False |
| enable_agent_tracing: bool = True |
| |
| |
| ab_test_ratio: float = 0.5 |
| ab_test_user_hash: Optional[str] = None |
| |
| |
| agent_timeout: float = 30.0 |
| max_agent_retries: int = 3 |
| enable_agent_caching: bool = True |
| |
| |
| min_judge_consensus: float = 0.6 |
| max_revision_iterations: int = 3 |
| |
| @classmethod |
| def from_env(cls) -> "AgentFeatureFlags": |
| """Load feature flags from environment variables""" |
| return cls( |
| mode=AgentMode(os.getenv("ANKIGEN_AGENT_MODE", "legacy")), |
| |
| |
| enable_subject_expert_agent=_env_bool("ANKIGEN_ENABLE_SUBJECT_EXPERT"), |
| enable_pedagogical_agent=_env_bool("ANKIGEN_ENABLE_PEDAGOGICAL_AGENT"), |
| enable_content_structuring_agent=_env_bool("ANKIGEN_ENABLE_CONTENT_STRUCTURING"), |
| enable_generation_coordinator=_env_bool("ANKIGEN_ENABLE_GENERATION_COORDINATOR"), |
| |
| |
| enable_content_accuracy_judge=_env_bool("ANKIGEN_ENABLE_CONTENT_JUDGE"), |
| enable_pedagogical_judge=_env_bool("ANKIGEN_ENABLE_PEDAGOGICAL_JUDGE"), |
| enable_clarity_judge=_env_bool("ANKIGEN_ENABLE_CLARITY_JUDGE"), |
| enable_technical_judge=_env_bool("ANKIGEN_ENABLE_TECHNICAL_JUDGE"), |
| enable_completeness_judge=_env_bool("ANKIGEN_ENABLE_COMPLETENESS_JUDGE"), |
| enable_judge_coordinator=_env_bool("ANKIGEN_ENABLE_JUDGE_COORDINATOR"), |
| |
| |
| enable_revision_agent=_env_bool("ANKIGEN_ENABLE_REVISION_AGENT"), |
| enable_enhancement_agent=_env_bool("ANKIGEN_ENABLE_ENHANCEMENT_AGENT"), |
| |
| |
| enable_multi_agent_generation=_env_bool("ANKIGEN_ENABLE_MULTI_AGENT_GEN"), |
| enable_parallel_judging=_env_bool("ANKIGEN_ENABLE_PARALLEL_JUDGING"), |
| enable_agent_handoffs=_env_bool("ANKIGEN_ENABLE_AGENT_HANDOFFS"), |
| enable_agent_tracing=_env_bool("ANKIGEN_ENABLE_AGENT_TRACING", default=True), |
| |
| |
| ab_test_ratio=float(os.getenv("ANKIGEN_AB_TEST_RATIO", "0.5")), |
| ab_test_user_hash=os.getenv("ANKIGEN_AB_TEST_USER_HASH"), |
| |
| |
| agent_timeout=float(os.getenv("ANKIGEN_AGENT_TIMEOUT", "30.0")), |
| max_agent_retries=int(os.getenv("ANKIGEN_MAX_AGENT_RETRIES", "3")), |
| enable_agent_caching=_env_bool("ANKIGEN_ENABLE_AGENT_CACHING", default=True), |
| |
| |
| min_judge_consensus=float(os.getenv("ANKIGEN_MIN_JUDGE_CONSENSUS", "0.6")), |
| max_revision_iterations=int(os.getenv("ANKIGEN_MAX_REVISION_ITERATIONS", "3")), |
| ) |
| |
| def should_use_agents(self) -> bool: |
| """Determine if agents should be used based on current mode""" |
| if self.mode == AgentMode.LEGACY: |
| return False |
| elif self.mode == AgentMode.AGENT_ONLY: |
| return True |
| elif self.mode == AgentMode.HYBRID: |
| |
| return ( |
| self.enable_subject_expert_agent or |
| self.enable_pedagogical_agent or |
| self.enable_content_structuring_agent or |
| any([ |
| self.enable_content_accuracy_judge, |
| self.enable_pedagogical_judge, |
| self.enable_clarity_judge, |
| self.enable_technical_judge, |
| self.enable_completeness_judge, |
| ]) |
| ) |
| elif self.mode == AgentMode.A_B_TEST: |
| |
| if self.ab_test_user_hash: |
| |
| import hashlib |
| hash_value = int(hashlib.md5(self.ab_test_user_hash.encode()).hexdigest(), 16) |
| return (hash_value % 100) < (self.ab_test_ratio * 100) |
| else: |
| |
| import random |
| return random.random() < self.ab_test_ratio |
| |
| return False |
| |
| def get_enabled_agents(self) -> Dict[str, bool]: |
| """Get a dictionary of all enabled agents""" |
| return { |
| "subject_expert": self.enable_subject_expert_agent, |
| "pedagogical": self.enable_pedagogical_agent, |
| "content_structuring": self.enable_content_structuring_agent, |
| "generation_coordinator": self.enable_generation_coordinator, |
| "content_accuracy_judge": self.enable_content_accuracy_judge, |
| "pedagogical_judge": self.enable_pedagogical_judge, |
| "clarity_judge": self.enable_clarity_judge, |
| "technical_judge": self.enable_technical_judge, |
| "completeness_judge": self.enable_completeness_judge, |
| "judge_coordinator": self.enable_judge_coordinator, |
| "revision_agent": self.enable_revision_agent, |
| "enhancement_agent": self.enable_enhancement_agent, |
| } |
| |
| def to_dict(self) -> Dict[str, Any]: |
| """Convert to dictionary for logging/debugging""" |
| return { |
| "mode": self.mode.value, |
| "enabled_agents": self.get_enabled_agents(), |
| "workflow_features": { |
| "multi_agent_generation": self.enable_multi_agent_generation, |
| "parallel_judging": self.enable_parallel_judging, |
| "agent_handoffs": self.enable_agent_handoffs, |
| "agent_tracing": self.enable_agent_tracing, |
| }, |
| "ab_test_ratio": self.ab_test_ratio, |
| "performance_config": { |
| "timeout": self.agent_timeout, |
| "max_retries": self.max_agent_retries, |
| "caching": self.enable_agent_caching, |
| }, |
| "quality_thresholds": { |
| "min_judge_consensus": self.min_judge_consensus, |
| "max_revision_iterations": self.max_revision_iterations, |
| } |
| } |
|
|
|
|
| def _env_bool(env_var: str, default: bool = False) -> bool: |
| """Helper to parse boolean environment variables""" |
| value = os.getenv(env_var, str(default)).lower() |
| return value in ("true", "1", "yes", "on", "enabled") |
|
|
|
|
| |
| _global_flags: Optional[AgentFeatureFlags] = None |
|
|
|
|
| def get_feature_flags() -> AgentFeatureFlags: |
| """Get the global feature flags instance""" |
| global _global_flags |
| if _global_flags is None: |
| _global_flags = AgentFeatureFlags.from_env() |
| logger.info(f"Loaded agent feature flags: {_global_flags.mode.value}") |
| logger.debug(f"Feature flags config: {_global_flags.to_dict()}") |
| return _global_flags |
|
|
|
|
| def set_feature_flags(flags: AgentFeatureFlags): |
| """Set global feature flags (for testing or runtime reconfiguration)""" |
| global _global_flags |
| _global_flags = flags |
| logger.info(f"Updated agent feature flags: {flags.mode.value}") |
|
|
|
|
| def reset_feature_flags(): |
| """Reset feature flags (reload from environment)""" |
| global _global_flags |
| _global_flags = None |