""" Provider factory. This is the ONLY module the rest of the application should import to talk to an LLM. It picks the concrete provider based on config.LLM_PROVIDER and exposes a single generate(prompt) function. Swapping providers is a config change only. """ import config from logs.logger import get_logger logger = get_logger(__name__) _PROVIDER_SINGLETON = None def _build_provider(): provider_name = config.LLM_PROVIDER.lower() if provider_name == "local": from llm.local_provider import LocalMistralProvider return LocalMistralProvider() if provider_name == "groq": from llm.groq_provider import GroqProvider return GroqProvider() if provider_name == "openai": from llm.openai_provider import OpenAIProvider return OpenAIProvider() raise ValueError( f"Unknown LLM_PROVIDER={provider_name!r} in config.py. " "Expected one of: 'local', 'groq', 'openai'." ) def get_provider(): """Module-level singleton so the local model (if used) loads only once.""" global _PROVIDER_SINGLETON if _PROVIDER_SINGLETON is None: _PROVIDER_SINGLETON = _build_provider() logger.info("LLM provider initialized: %s", _PROVIDER_SINGLETON.name()) return _PROVIDER_SINGLETON def generate(prompt: str, stream: bool = False): """ The single entrypoint the rest of the app (Flask routes, agents, evaluation) should call. Never import a provider class directly outside of this module. """ provider = get_provider() return provider.generate(prompt, stream=stream) def reset_provider(): """Mainly for tests: force re-reading config.LLM_PROVIDER on next call.""" global _PROVIDER_SINGLETON _PROVIDER_SINGLETON = None