""" AI Client - AI 프로바이더 관리 및 통합 인터페이스 사용 예시: from app.ai.ai_client import AIClient, AIProviderType # 클라이언트 생성 client = AIClient() # 프로바이더 설정 client.set_provider(AIProviderType.OPENAI, api_key="sk-...") # 또는 설정에서 자동 로드 client.load_from_settings() # 채팅 요청 response = client.chat("안녕하세요!") """ from typing import Optional, Dict, List, Type from .base import BaseAIProvider, AIProviderType, AIMessage, AIResponse from .providers import OpenAIProvider, OpenRouterProvider, GeminiProvider, XAIProvider class AIClient: """ AI 클라이언트 매니저 여러 AI 프로바이더를 관리하고 통합 인터페이스를 제공합니다. """ # 프로바이더 타입별 클래스 매핑 PROVIDER_CLASSES: Dict[AIProviderType, Type[BaseAIProvider]] = { AIProviderType.OPENAI: OpenAIProvider, AIProviderType.OPENROUTER: OpenRouterProvider, AIProviderType.GEMINI: GeminiProvider, AIProviderType.XAI: XAIProvider, } def __init__(self): self._current_provider: Optional[BaseAIProvider] = None self._providers: Dict[AIProviderType, BaseAIProvider] = {} self._settings = None @property def current_provider(self) -> Optional[BaseAIProvider]: """현재 활성화된 프로바이더""" return self._current_provider @property def current_provider_type(self) -> Optional[AIProviderType]: """현재 프로바이더 타입""" if self._current_provider: return self._current_provider.provider_type return None @property def available_provider_types(self) -> List[AIProviderType]: """사용 가능한 프로바이더 타입 목록""" return list(self.PROVIDER_CLASSES.keys()) def get_provider_name(self, provider_type: AIProviderType) -> str: """프로바이더 타입에 해당하는 표시 이름 반환""" names = { AIProviderType.OPENAI: "OpenAI", AIProviderType.OPENROUTER: "OpenRouter", AIProviderType.GEMINI: "Google Gemini", AIProviderType.XAI: "xAI (Grok)", } return names.get(provider_type, provider_type.value) def set_provider( self, provider_type: AIProviderType, api_key: str, model: Optional[str] = None ) -> bool: """ 프로바이더 설정 및 활성화 Args: provider_type: 프로바이더 타입 api_key: API 키 model: 사용할 모델 (None이면 기본 모델) Returns: 초기화 성공 여부 """ provider_class = self.PROVIDER_CLASSES.get(provider_type) if not provider_class: raise ValueError(f"지원하지 않는 프로바이더: {provider_type}") # 프로바이더 인스턴스 생성 provider = provider_class(api_key=api_key, model=model) # 초기화 시도 if provider.initialize(): self._providers[provider_type] = provider self._current_provider = provider return True return False def switch_provider(self, provider_type: AIProviderType) -> bool: """ 이미 등록된 프로바이더로 전환 Args: provider_type: 전환할 프로바이더 타입 Returns: 전환 성공 여부 """ if provider_type in self._providers: self._current_provider = self._providers[provider_type] return True return False def update_api_key(self, provider_type: AIProviderType, api_key: str) -> bool: """ 프로바이더의 API 키 업데이트 Args: provider_type: 프로바이더 타입 api_key: 새 API 키 Returns: 업데이트 및 재초기화 성공 여부 """ if provider_type in self._providers: provider = self._providers[provider_type] provider.update_api_key(api_key) return provider.initialize() return False def update_model(self, model: str) -> bool: """ 현재 프로바이더의 모델 변경 Args: model: 새 모델명 Returns: 변경 성공 여부 """ if self._current_provider: self._current_provider.model = model return True return False def get_available_models(self) -> List[str]: """현재 프로바이더의 사용 가능한 모델 목록""" if self._current_provider: return self._current_provider.available_models return [] def load_from_settings(self, settings: 'AISettings') -> bool: """ 설정에서 프로바이더 정보 로드 Args: settings: AISettings 인스턴스 Returns: 로드 성공 여부 """ self._settings = settings # 현재 선택된 프로바이더 가져오기 current_type = settings.get_current_provider() if not current_type: return False # API 키 가져오기 api_key = settings.get_api_key(current_type) if not api_key: return False # 모델 가져오기 model = settings.get_model(current_type) return self.set_provider(current_type, api_key, model) def chat( self, user_message: str, system_prompt: Optional[str] = None, temperature: float = 0.7, max_tokens: Optional[int] = None, **kwargs ) -> AIResponse: """ 간편 채팅 메서드 (동기) Args: user_message: 사용자 메시지 system_prompt: 시스템 프롬프트 (선택) temperature: 응답 다양성 max_tokens: 최대 토큰 Returns: AI 응답 """ if not self._current_provider: raise RuntimeError("프로바이더가 설정되지 않았습니다.") messages = [] if system_prompt: messages.append(AIMessage(role="system", content=system_prompt)) messages.append(AIMessage(role="user", content=user_message)) return self._current_provider.chat_sync( messages=messages, temperature=temperature, max_tokens=max_tokens, **kwargs ) async def chat_async( self, user_message: str, system_prompt: Optional[str] = None, temperature: float = 0.7, max_tokens: Optional[int] = None, **kwargs ) -> AIResponse: """ 간편 채팅 메서드 (비동기) Args: user_message: 사용자 메시지 system_prompt: 시스템 프롬프트 (선택) temperature: 응답 다양성 max_tokens: 최대 토큰 Returns: AI 응답 """ if not self._current_provider: raise RuntimeError("프로바이더가 설정되지 않았습니다.") messages = [] if system_prompt: messages.append(AIMessage(role="system", content=system_prompt)) messages.append(AIMessage(role="user", content=user_message)) return await self._current_provider.chat( messages=messages, temperature=temperature, max_tokens=max_tokens, **kwargs ) def chat_with_history( self, messages: List[AIMessage], temperature: float = 0.7, max_tokens: Optional[int] = None, **kwargs ) -> AIResponse: """ 대화 히스토리와 함께 채팅 (동기) Args: messages: 메시지 리스트 temperature: 응답 다양성 max_tokens: 최대 토큰 Returns: AI 응답 """ if not self._current_provider: raise RuntimeError("프로바이더가 설정되지 않았습니다.") return self._current_provider.chat_sync( messages=messages, temperature=temperature, max_tokens=max_tokens, **kwargs ) async def chat_with_history_async( self, messages: List[AIMessage], temperature: float = 0.7, max_tokens: Optional[int] = None, **kwargs ) -> AIResponse: """ 대화 히스토리와 함께 채팅 (비동기) Args: messages: 메시지 리스트 temperature: 응답 다양성 max_tokens: 최대 토큰 Returns: AI 응답 """ if not self._current_provider: raise RuntimeError("프로바이더가 설정되지 않았습니다.") return await self._current_provider.chat( messages=messages, temperature=temperature, max_tokens=max_tokens, **kwargs ) def is_ready(self) -> bool: """프로바이더가 사용 준비되었는지 확인""" return self._current_provider is not None and self._current_provider._is_initialized def get_status(self) -> Dict: """현재 상태 정보 반환""" if self._current_provider: return { "ready": self.is_ready(), "provider": self._current_provider.provider_name, "model": self._current_provider.model, "api_key": self._current_provider.api_key, # 마스킹됨 } return { "ready": False, "provider": None, "model": None, "api_key": None, } # 싱글톤 인스턴스 _ai_client_instance: Optional[AIClient] = None def get_ai_client() -> AIClient: """AI 클라이언트 싱글톤 인스턴스 반환""" global _ai_client_instance if _ai_client_instance is None: _ai_client_instance = AIClient() return _ai_client_instance