CoolFace
Apppublic

KashefTech/Data-Anonymization

sourceHugging Facemitupdated 7mo agoView on Hugging Face
0likes
llm_sender_unified.py384 linesDownload Raw Back to root
1"""2🤖 LLM Sender Unified Module3ماژول یکپارچه برای ارسال به ChatGPT و Grok4✨ با پشتیبانی از GPT-5 models و رفع مشکل temperature5"""6 7import requests8import os9import logging10from typing import Optional11import time12from abc import ABC, abstractmethod13 14logging.basicConfig(level=logging.INFO)15logger = logging.getLogger(__name__)16 17 18class LLMSender(ABC):19    """کلاس پایه برای ارسال به مدل‌های مختلف LLM"""20    21    def __init__(self, api_key: Optional[str] = None, model: str = None):22        self.api_key = api_key23        self.model = model24        self.base_url = ""25        26    @abstractmethod27    def get_default_model(self) -> str:28        """مدل پیش‌فرض"""29        pass30    31    @abstractmethod32    def get_base_url(self) -> str:33        """URL پایه API"""34        pass35    36    def set_api_key(self, api_key: str):37        """تنظیم کلید API"""38        self.api_key = api_key39        logger.info("✅ کلید API تنظیم شد")40    41    def set_model(self, model: str):42        """تغییر مدل"""43        self.model = model44        logger.info(f"✅ مدل تغییر یافت به: {model}")45    46    def _uses_max_completion_tokens(self) -> bool:47        """بررسی اینکه آیا مدل از max_completion_tokens استفاده می‌کند"""48        models_with_completion_tokens = [49            'gpt-5',        # تمام مدل‌های GPT-550            'gpt-5.1'       # GPT-5.151        ]52        return any(self.model.startswith(prefix) for prefix in models_with_completion_tokens)53    54    def _requires_default_temperature(self) -> bool:55        """بررسی اینکه آیا مدل فقط temperature=1 را قبول می‌کند"""56        models_requiring_default_temp = [57            'gpt-5',        # تمام مدل‌های GPT-558            'o1'            # تمام مدل‌های O159        ]60        return any(self.model.startswith(prefix) for prefix in models_requiring_default_temp)61    62    def send_simple(self, text: str, lang: str = 'fa') -> str:63        """ارسال ساده بدون system message سفارشی"""64        system_msg = (65            "شما یک تحلیلگر متخصص هستید. متن حاوی کدهای ناشناس است. "66            "به درخواست‌ها با دقت و حرفه‌ای پاسخ دهید."67            if lang == 'fa' 68            else "You are a professional analyst. The text contains anonymous codes. "69                 "Answer requests accurately and professionally."70        )71        72        return self.send(text, system_msg=system_msg, lang=lang)73    74    def send(75        self,76        text: str,77        system_msg: Optional[str] = None,78        max_tokens: int = 2000,79        temperature: float = 0.2,  # ✅ کاهش از 0.7 به 0.2 برای دقت بیشتر80        timeout: int = 60,81        lang: str = 'fa',82        retry_count: int = 383    ) -> str:84        """ارسال متن به LLM با کنترل کامل"""85        try:86            # بررسی اولیه87            if not text or not text.strip():88                error_msg = "متن خالی است!" if lang == 'fa' else "Text is empty!"89                logger.error(f"❌ {error_msg}")90                return f"❌ {error_msg}"91            92            if not self.api_key:93                error_msg = "کلید API تنظیم نشده است!" if lang == 'fa' else "API Key not configured!"94                logger.error(f"❌ {error_msg}")95                return f"❌ {error_msg}"96            97            # تنظیم system message پیش‌فرض98            if system_msg is None:99                system_msg = (100                    "شما یک تحلیلگر مالی حرفه‌ای هستید. متن حاوی کدهای ناشناس است. "101                    "به سوالات با دقت پاسخ دهید."102                    if lang == 'fa'103                    else "You are a professional financial analyst. The text contains anonymous codes. "104                         "Answer questions accurately."105                )106            107            # تهیه headers108            headers = {109                "Authorization": f"Bearer {self.api_key}",110                "Content-Type": "application/json"111            }112            113            # ✨ تنظیم temperature مناسب114            if self._requires_default_temperature():115                actual_temperature = 1.0116                if temperature != 1.0:117                    logger.info(f"⚠️ مدل {self.model} فقط temperature=1 را قبول می‌کند")118            else:119                actual_temperature = temperature120            121            # ساخت request body122            data = {123                "model": self.model,124                "messages": [125                    {"role": "system", "content": system_msg},126                    {"role": "user", "content": text}127                ],128                "temperature": actual_temperature129            }130            131            # ✨ انتخاب پارامتر مناسب برای max tokens132            if self._uses_max_completion_tokens():133                data["max_completion_tokens"] = max_tokens134            else:135                data["max_tokens"] = max_tokens136            137            # ارسال با retry mechanism138            for attempt in range(retry_count):139                try:140                    logger.info(f"📤 ارسال درخواست به {self.__class__.__name__} (تلاش {attempt + 1}/{retry_count})...")141                    142                    response = requests.post(143                        self.base_url,144                        headers=headers,145                        json=data,146                        timeout=timeout147                    )148                    149                    # پردازش پاسخ موفق150                    if response.status_code == 200:151                        result = response.json()152                        llm_response = result['choices'][0]['message']['content']153                        # ✅ حذف بلوک‌های thinking برای DeepInfra/Qwen3154                        if isinstance(self, DeepInfraSender):155                            llm_response = DeepInfraSender.strip_thinking(llm_response)156                        logger.info("✅ پاسخ دریافت شد")157                        return llm_response158                    159                    # پردازش خطاهای مختلف160                    elif response.status_code == 429:  # Rate limiting161                        wait_time = 5 * (attempt + 1)162                        logger.warning(f"⚠️  Rate limit | صبر: {wait_time} ثانیه")163                        if attempt < retry_count - 1:164                            time.sleep(wait_time)165                            continue166                        else:167                            return (168                                "❌ سهمیه API تمام شده است. لطفاً بعداً تلاش کنید."169                                if lang == 'fa'170                                else "❌ API quota exceeded. Please try later."171                            )172                    173                    elif response.status_code == 401:174                        return (175                            "❌ کلید API نامعتبر است!"176                            if lang == 'fa'177                            else "❌ Invalid API key!"178                        )179                    180                    elif response.status_code in [502, 503, 504]:  # Server errors181                        wait_time = 2 * (attempt + 1)182                        logger.warning(f"⚠️  Server error {response.status_code} | صبر: {wait_time} ثانیه")183                        if attempt < retry_count - 1:184                            time.sleep(wait_time)185                            continue186                        else:187                            return (188                                f"❌ خطای سرور: {response.status_code}"189                                if lang == 'fa'190                                else f"❌ Server error: {response.status_code}"191                            )192                    193                    else:194                        # خطای دیگر195                        try:196                            error_data = response.json() if response.content else {}197                            if isinstance(error_data, dict):198                                error_msg = error_data.get('error', {}).get('message', response.text)199                            else:200                                error_msg = str(error_data)201                        except:202                            error_msg = response.text[:200]203                        204                        logger.error(f"❌ API Error: {error_msg}")205                        return f"❌ API Error: {error_msg}"206                207                except requests.exceptions.Timeout:208                    logger.warning("⚠️  Timeout | صبر: 3 ثانیه و تلاش مجدد")209                    if attempt < retry_count - 1:210                        time.sleep(3)211                        continue212                    else:213                        return (214                            "❌ خطای اتصال: timeout"215                            if lang == 'fa'216                            else "❌ Connection error: timeout"217                        )218                219                except requests.exceptions.ConnectionError as e:220                    logger.warning("⚠️  Connection error | صبر: 2 ثانیه و تلاش مجدد")221                    if attempt < retry_count - 1:222                        time.sleep(2)223                        continue224                    else:225                        return (226                            f"❌ خطای اتصال: {str(e)}"227                            if lang == 'fa'228                            else f"❌ Connection error: {str(e)}"229                        )230        231        except Exception as e:232            logger.error(f"❌ خطای غیرمنتظره: {str(e)}")233            return (234                f"❌ خطا در ارتباط با LLM: {str(e)}"235                if lang == 'fa'236                else f"❌ Error connecting to LLM: {str(e)}"237            )238 239 240class ChatGPTSender(LLMSender):241    """کلاس برای ارسال به ChatGPT"""242    243    def __init__(self, api_key: Optional[str] = None, model: str = "gpt-4o-mini"):244        raw_key = api_key or os.getenv("OPENAI_API_KEY", "")245        cleaned_key = raw_key.strip() if raw_key else ""246        247        super().__init__(cleaned_key, model)248        self.base_url = self.get_base_url()249        250        if not self.api_key:251            logger.warning("⚠️  کلید OpenAI API تنظیم نشده است!")252    253    def get_default_model(self) -> str:254        return "gpt-4o-mini"255    256    def get_base_url(self) -> str:257        return "https://api.openai.com/v1/chat/completions"258 259 260class GrokSender(LLMSender):261    """کلاس برای ارسال به Grok (xAI)"""262    263    def __init__(self, api_key: Optional[str] = None, model: str = "grok-beta"):264        raw_key = api_key or os.getenv("XAI_API_KEY", "")265        cleaned_key = raw_key.strip() if raw_key else ""266        267        super().__init__(cleaned_key, model)268        self.base_url = self.get_base_url()269        270        if not self.api_key:271            logger.warning("⚠️  کلید xAI API تنظیم نشده است!")272    273    def get_default_model(self) -> str:274        return "grok-beta"275    276    def get_base_url(self) -> str:277        return "https://api.x.ai/v1/chat/completions"278 279 280class DeepInfraSender(LLMSender):281    """کلاس برای ارسال به DeepInfra"""282    283    def __init__(self, api_key: Optional[str] = None, model: str = "Qwen/Qwen3-14B"):284        raw_key = api_key or os.getenv("DEEPINFRA_API_KEY", "")285        cleaned_key = raw_key.strip() if raw_key else ""286        287        super().__init__(cleaned_key, model)288        self.base_url = self.get_base_url()289        290        if not self.api_key:291            logger.warning("⚠️  کلید DeepInfra API تنظیم نشده است!")292    293    def get_default_model(self) -> str:294        return "Qwen/Qwen3-14B"295    296    def get_base_url(self) -> str:297        return "https://api.deepinfra.com/v1/openai/chat/completions"298    299    def _uses_max_completion_tokens(self) -> bool:300        return False301    302    def _requires_default_temperature(self) -> bool:303        return False304 305    @staticmethod306    def strip_thinking(text: str) -> str:307        """✅ حذف بلوک‌های <think>...</think> که Qwen3 تولید می‌کند"""308        if not text:309            return text310        cleaned = re.sub(r"<think>.*?</think>", "", text, flags=re.DOTALL)311        return cleaned.strip()312 313 314def create_llm_sender(315    provider: str = "chatgpt",316    api_key: Optional[str] = None,317    model: Optional[str] = None318) -> LLMSender:319    """ایجاد LLM sender بر اساس provider"""320    provider = provider.lower()321    322    if provider == "chatgpt":323        if model is None:324            model = "gpt-4o-mini"325        return ChatGPTSender(api_key=api_key, model=model)326    327    elif provider == "grok":328        if model is None:329            model = "grok-beta"330        return GrokSender(api_key=api_key, model=model)331    332    elif provider == "deepinfra":333        if model is None:334            model = "Qwen/Qwen3-14B"335        return DeepInfraSender(api_key=api_key, model=model)336    337    else:338        raise ValueError(f"Provider نامعتبر: {provider}")339 340 341# ✅ مدل‌های موجود (به‌روزرسانی شده با DeepInfra)342AVAILABLE_MODELS = {343    "chatgpt": [344        "gpt-5.1",345        "gpt-5",346        "gpt-4.1",347        "gpt-4o-mini",348        "gpt-4o",349        "gpt-4-turbo",350        "gpt-3.5-turbo"351    ],352    "grok": [353        "grok-3-mini",354        "grok-3",355        "grok-2-1212"356    ],357    "deepinfra": [358        "Qwen/Qwen3-14B",359        "Qwen/Qwen3-32B",360        "Qwen/Qwen3-30B-A3B",361        "Qwen/Qwen2.5-72B-Instruct",362        "Qwen/Qwen2.5-14B-Instruct",363    ]364}365 366 367if __name__ == "__main__":368    print("=" * 60)369    print("🤖 LLM Sender - نسخه اصلاح شده")370    print("✨ رفع مشکل temperature برای GPT-5")371    print("=" * 60)372    373    # تست374    print("\n🧪 تست مدل‌ها:")375    test_models = ['gpt-5', 'gpt-5.1', 'gpt-4o']376    for model in test_models:377        sender = create_llm_sender("chatgpt", model=model)378        uses_completion = sender._uses_max_completion_tokens()379        requires_default_temp = sender._requires_default_temperature()380        381        print(f"\n  مدل: {model}")382        print(f"    • max_tokens: {'max_completion_tokens' if uses_completion else 'max_tokens'}")383        print(f"    • temperature: {'1.0 (default only)' if requires_default_temp else '0.7 (custom)'}")384