| """ |
| 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 |
|
|