""" Configuration management for Enhanced Brief Processing System Loads environment variables and provides configuration validation """ import os from typing import List, Dict, Any, Optional from dotenv import load_dotenv # Load environment variables from .env file load_dotenv() class Config: """Centralized configuration management""" # API Keys — support both docker-compose names and legacy names OPENAI_API_KEY: str = os.getenv('OPENAI_API_KEY', '') ANTHROPIC_API_KEY: str = os.getenv('ANTHROPIC_API_KEY', '') # GEMINI_API_KEY is the docker-compose / .env name; GOOGLE_API_KEY is the legacy name GOOGLE_API_KEY: str = os.getenv('GEMINI_API_KEY') or os.getenv('GOOGLE_API_KEY', '') # LLAMA_CLOUD_API_KEY is the docker-compose / official SDK name LLAMACLOUD_API_KEY: str = os.getenv('LLAMA_CLOUD_API_KEY') or os.getenv('LLAMACLOUD_API_KEY', '') # OpenAI Configuration OPENAI_MODEL: str = os.getenv('OPENAI_MODEL', 'gpt-5.1') OPENAI_REASONING_EFFORT: str = os.getenv('OPENAI_REASONING_EFFORT', 'medium') OPENAI_TIMEOUT: int = int(os.getenv('OPENAI_TIMEOUT', '3600')) OPENAI_MAX_RETRIES: int = int(os.getenv('OPENAI_MAX_RETRIES', '2')) # Google Configuration GOOGLE_MODEL: str = os.getenv('GOOGLE_MODEL', 'gemini-3.1-pro-preview') GOOGLE_TEMPERATURE: float = float(os.getenv('GOOGLE_TEMPERATURE', '0.1')) GOOGLE_MAX_OUTPUT_TOKENS: int = int(os.getenv('GOOGLE_MAX_OUTPUT_TOKENS', '8192')) GOOGLE_THINKING_BUDGET: int = int(os.getenv('GOOGLE_THINKING_BUDGET', '12000')) GOOGLE_TIMEOUT: int = int(os.getenv('GOOGLE_TIMEOUT', '300')) # Anthropic Configuration ANTHROPIC_MODEL_OPUS: str = os.getenv('ANTHROPIC_MODEL_OPUS', 'claude-opus-4-5-20251101') ANTHROPIC_MODEL_SONNET: str = os.getenv('ANTHROPIC_MODEL_SONNET', 'claude-sonnet-4-5-20250929') ANTHROPIC_TEMPERATURE: float = float(os.getenv('ANTHROPIC_TEMPERATURE', '0.1')) ANTHROPIC_MAX_TOKENS: int = int(os.getenv('ANTHROPIC_MAX_TOKENS', '32000')) ANTHROPIC_THINKING_BUDGET: int = int(os.getenv('ANTHROPIC_THINKING_BUDGET', '12000')) ANTHROPIC_TIMEOUT: int = int(os.getenv('ANTHROPIC_TIMEOUT', '300')) # Processing Configuration DEFAULT_PRIMARY_MODELS: str = os.getenv('DEFAULT_PRIMARY_MODELS', 'openai-gpt51,anthropic-sonnet45,google-gemini31') DEFAULT_CONSOLIDATION_MODEL: str = os.getenv('DEFAULT_CONSOLIDATION_MODEL', 'openai-gpt51') MINIMUM_SUCCESS_THRESHOLD: int = int(os.getenv('MINIMUM_SUCCESS_THRESHOLD', '1')) ENABLE_COST_ESTIMATION: bool = os.getenv('ENABLE_COST_ESTIMATION', 'true').lower() == 'true' MAX_PROCESSING_COST_USD: float = float(os.getenv('MAX_PROCESSING_COST_USD', '10.00')) # Model Pricing (per 1M tokens) PRICING = { 'openai-gpt51': { 'input': 1.25, 'cached_input': 0.625, 'output': 10.00 }, 'anthropic-opus45': { 'input': 5.00, 'output': 25.00 }, 'anthropic-sonnet45': { 'input': 3.00, 'output': 15.00 }, 'google-gemini31': { 'input': 1.25, 'output': 5.00 } } # Model mappings for CLI compatibility MODEL_MAPPINGS = { 'openai-gpt51': ('openai', OPENAI_MODEL), 'anthropic-opus45': ('anthropic', ANTHROPIC_MODEL_OPUS), 'anthropic-sonnet45': ('anthropic', ANTHROPIC_MODEL_SONNET), 'google-gemini31': ('google', GOOGLE_MODEL) } @classmethod def validate_api_keys(cls) -> Dict[str, bool]: """Validate that required API keys are set""" return { 'openai': bool(cls.OPENAI_API_KEY and cls.OPENAI_API_KEY != 'your-openai-api-key-here'), 'anthropic': bool(cls.ANTHROPIC_API_KEY and cls.ANTHROPIC_API_KEY != 'your-anthropic-api-key-here'), 'google': bool(cls.GOOGLE_API_KEY and cls.GOOGLE_API_KEY != 'your-google-api-key-here'), 'llamacloud': bool(cls.LLAMACLOUD_API_KEY and cls.LLAMACLOUD_API_KEY != 'your-llamacloud-api-key-here') } @classmethod def get_provider_config(cls, provider: str) -> Dict[str, Any]: """Get configuration for a specific provider""" if provider == 'openai': return { 'api_key': cls.OPENAI_API_KEY, 'model': cls.OPENAI_MODEL, 'reasoning_effort': cls.OPENAI_REASONING_EFFORT, 'timeout': cls.OPENAI_TIMEOUT, 'max_retries': cls.OPENAI_MAX_RETRIES } elif provider == 'google': return { 'api_key': cls.GOOGLE_API_KEY, 'model': cls.GOOGLE_MODEL, 'temperature': cls.GOOGLE_TEMPERATURE, 'max_output_tokens': cls.GOOGLE_MAX_OUTPUT_TOKENS, 'thinking_budget': cls.GOOGLE_THINKING_BUDGET, 'timeout': cls.GOOGLE_TIMEOUT } elif provider == 'anthropic': return { 'api_key': cls.ANTHROPIC_API_KEY, 'model_opus': cls.ANTHROPIC_MODEL_OPUS, 'model_sonnet': cls.ANTHROPIC_MODEL_SONNET, 'temperature': cls.ANTHROPIC_TEMPERATURE, 'max_tokens': cls.ANTHROPIC_MAX_TOKENS, 'thinking_budget': cls.ANTHROPIC_THINKING_BUDGET, 'timeout': cls.ANTHROPIC_TIMEOUT } else: raise ValueError(f"Unknown provider: {provider}") @classmethod def get_default_primary_models(cls) -> List[str]: """Get default list of primary analysis models""" return cls.DEFAULT_PRIMARY_MODELS.split(',') @classmethod def get_model_info(cls, model_key: str) -> tuple: """Get provider and model name for a model key""" if model_key not in cls.MODEL_MAPPINGS: raise ValueError(f"Unknown model key: {model_key}. Available: {list(cls.MODEL_MAPPINGS.keys())}") return cls.MODEL_MAPPINGS[model_key] @classmethod def estimate_cost(cls, model_key: str, input_tokens: int, output_tokens: int, cached_tokens: int = 0) -> float: """Estimate processing cost for a model""" if model_key not in cls.PRICING: return 0.0 pricing = cls.PRICING[model_key] input_cost = (input_tokens / 1_000_000) * pricing['input'] output_cost = (output_tokens / 1_000_000) * pricing['output'] cached_cost = (cached_tokens / 1_000_000) * pricing.get('cached_input', pricing['input']) return input_cost + output_cost + cached_cost # Global config instance config = Config()