import os import asyncio from typing import Optional, List, Dict import traceback class LLMProvider: OPENAI = "openai" ANTHROPIC = "anthropic" GOOGLE = "google" def _detect_llm_provider() -> Optional[dict]: """Use only Google Gemini. No fallback providers.""" google_key = os.getenv("GOOGLE_API_KEY") if not google_key: raise ValueError( "GOOGLE_API_KEY is required. Please set it in .env file.\n" "Get your key at: https://aistudio.google.com/app/apikey" ) return { "provider": LLMProvider.GOOGLE, "api_key": google_key, "model": os.getenv("GOOGLE_MODEL", "gemini-2.0-flash-exp") } class UnifiedLLMService: @staticmethod async def generate_vision_completion( system_prompt: str, user_text: str, image_base64: str, media_type: str = "image/png", provider_override: Optional[Dict] = None, max_tokens: int = 8192 ) -> str: """ Sends a vision-based generation request to the active LLM provider. """ provider = provider_override or _detect_llm_provider() if not provider: raise ValueError("No LLM provider configuration found in environment variables.") print(f"[UnifiedLLMService] Utilizing {provider['provider']} ({provider.get('model', 'default')})") try: if provider["provider"] == LLMProvider.OPENAI: return await UnifiedLLMService._call_openai(provider, system_prompt, user_text, image_base64, media_type) elif provider["provider"] == LLMProvider.ANTHROPIC: return await UnifiedLLMService._call_anthropic(provider, system_prompt, user_text, image_base64, media_type, max_tokens) elif provider["provider"] == LLMProvider.GOOGLE: return await UnifiedLLMService._call_google(provider, system_prompt, user_text, image_base64, media_type) else: raise ValueError(f"Unsupported provider: {provider['provider']}") except Exception as e: print(f"[UnifiedLLMService] Error from {provider['provider']}: {e}") traceback.print_exc() raise Exception(f"Failed to generate completion using {provider['provider']}: {str(e)}") @staticmethod async def _call_openai(provider: dict, system_prompt: str, user_text: str, image_base64: str, media_type: str) -> str: from openai import OpenAI def _sync_call(): client = OpenAI(api_key=provider["api_key"]) data_url = f"data:{media_type};base64,{image_base64}" # Using standard chat completion API instead of beta responses API for better stability response = client.chat.completions.create( model=provider.get("model", "gpt-4o"), messages=[ {"role": "system", "content": system_prompt}, {"role": "user", "content": [ {"type": "image_url", "image_url": {"url": data_url}}, {"type": "text", "text": user_text} ]} ], max_tokens=8000 ) return response.choices[0].message.content or "" return await asyncio.to_thread(_sync_call) @staticmethod async def _call_anthropic(provider: dict, system_prompt: str, user_text: str, image_base64: str, media_type: str, max_tokens: int) -> str: import anthropic def _sync_call(): client = anthropic.Anthropic(api_key=provider["api_key"]) response = client.messages.create( model=provider.get("model", "claude-3-5-sonnet-20240620"), max_tokens=max_tokens, system=system_prompt, messages=[{ "role": "user", "content": [ {"type": "image", "source": { "type": "base64", "media_type": media_type, "data": image_base64, }}, {"type": "text", "text": user_text}, ], }], ) return response.content[0].text if response.content else "" return await asyncio.to_thread(_sync_call) @staticmethod async def _call_google(provider: dict, system_prompt: str, user_text: str, image_base64: str, media_type: str) -> str: import google.genai as genai def _sync_call(): client = genai.Client(api_key=provider["api_key"]) model_name = provider.get("model", "gemini-2.0-flash") response = client.models.generate_content( model=model_name, contents=[ system_prompt, {"inline_data": {"mime_type": media_type, "data": image_base64}}, user_text, ], ) return response.text or "" return await asyncio.to_thread(_sync_call) @staticmethod def clean_llm_code_output(text: str, lang_identifiers: List[str] = ["html", "tsx", "typescript", "javascript"]) -> str: """Removes markdown backticks and specific language identifiers from LLM output.""" cleaned = text for lang in lang_identifiers: cleaned = cleaned.replace(f"```{lang}", "") cleaned = cleaned.replace("```", "") return cleaned.strip() llm_service = UnifiedLLMService()