"""LLM provider abstraction. Each provider wraps one vendor SDK.""" import os from rich.console import Console _console = Console() class Provider: name: str def chat(self, messages: list[dict], system: str, max_tokens: int = 4096) -> str: raise NotImplementedError class AnthropicProvider(Provider): name = "Claude" def __init__(self): import anthropic self._client = anthropic.Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"]) def chat(self, messages: list[dict], system: str, max_tokens: int = 4096) -> str: response = self._client.messages.create( model="claude-sonnet-4-6", max_tokens=max_tokens, system=system, messages=messages, ) return response.content[0].text class OpenAIProvider(Provider): name = "ChatGPT" def __init__(self): from openai import OpenAI self._client = OpenAI(api_key=os.environ["OPENAI_API_KEY"]) def chat(self, messages: list[dict], system: str, max_tokens: int = 4096) -> str: oai_messages = [{"role": "system", "content": system}] + messages response = self._client.chat.completions.create( model="gpt-4o", max_tokens=max_tokens, messages=oai_messages, ) return response.choices[0].message.content class GeminiProvider(Provider): name = "Gemini" def __init__(self): import google.generativeai as genai genai.configure(api_key=os.environ["GOOGLE_API_KEY"]) self._genai = genai def chat(self, messages: list[dict], system: str, max_tokens: int = 4096) -> str: model = self._genai.GenerativeModel( model_name="gemini-2.0-flash", system_instruction=system, ) # Convert role "assistant" → "model" for Gemini contents = [ {"role": "model" if m["role"] == "assistant" else "user", "parts": [m["content"]]} for m in messages ] response = model.generate_content( contents, generation_config=self._genai.types.GenerationConfig(max_output_tokens=max_tokens), ) return response.text class MistralProvider(Provider): name = "Mistral" def __init__(self): from mistralai import Mistral self._client = Mistral(api_key=os.environ["MISTRAL_API_KEY"]) def chat(self, messages: list[dict], system: str, max_tokens: int = 4096) -> str: mistral_messages = [{"role": "system", "content": system}] + messages response = self._client.chat.complete( model="mistral-large-latest", max_tokens=max_tokens, messages=mistral_messages, ) return response.choices[0].message.content _REGISTRY = [ ("ANTHROPIC_API_KEY", AnthropicProvider), ("OPENAI_API_KEY", OpenAIProvider), ("GOOGLE_API_KEY", GeminiProvider), ("MISTRAL_API_KEY", MistralProvider), ] def get_configured() -> list[Provider]: """Return instantiated providers for every API key that is set. Skips on init error.""" result = [] for env_var, cls in _REGISTRY: if os.environ.get(env_var): try: result.append(cls()) except Exception as e: _console.print(f"[yellow][Warnung] {cls.__name__} konnte nicht initialisiert werden: {e}[/yellow]") return result