""" 🔧 توابع کمکی عمومی Utility functions for text processing """ import re import logging from typing import List logger = logging.getLogger(__name__) def count_tokens(text: str, method: str = 'simple') -> int: """ شمارش تعداد tokens در متن Args: text: متن ورودی method: روش شمارش - 'simple' یا 'accurate' Returns: تعداد tokens تخمینی Examples: >>> count_tokens("این یک متن تست است") 6 """ if not text or not text.strip(): return 0 if method == 'simple': # تخمین ساده: 1 token ≈ 4 کاراکتر # این تخمین برای فارسی و انگلیسی کار می‌کند return max(1, len(text) // 4) elif method == 'accurate': try: import tiktoken encoding = tiktoken.get_encoding("cl100k_base") return len(encoding.encode(text)) except ImportError: logger.warning("⚠️ tiktoken not installed, falling back to simple method") return max(1, len(text) // 4) except Exception as e: logger.error(f"❌ Error in tiktoken: {e}") return max(1, len(text) // 4) else: raise ValueError(f"Invalid method: {method}. Use 'simple' or 'accurate'") def should_use_chunking(text: str, threshold: int = 6000) -> bool: """ تصمیم‌گیری: آیا نیاز به chunking داریم؟ Args: text: متن ورودی threshold: حد آستانه (tokens) - پیش‌فرض 6000 Returns: True اگر تعداد tokens بیشتر از threshold باشد Examples: >>> should_use_chunking("متن کوتاه", threshold=100) False """ if not text or not text.strip(): return False token_count = count_tokens(text) if token_count > threshold: logger.info( f"📊 متن بلند تشخیص داده شد: {token_count} tokens > {threshold} " f"→ استفاده از chunking" ) return True else: logger.info( f"📊 متن کوتاه: {token_count} tokens ≤ {threshold} " f"→ بدون chunking" ) return False def split_sentences(text: str) -> List[str]: """ تقسیم متن به جملات از الگوهای regex برای تشخیص پایان جملات فارسی استفاده می‌کند Args: text: متن ورودی Returns: لیست جملات Examples: >>> split_sentences("جمله اول. جمله دوم؟ جمله سوم!") ['جمله اول', 'جمله دوم', 'جمله سوم'] """ if not text or not text.strip(): return [] # الگوهای پایان جمله در فارسی # . ! ? ؟ و همچنین نسخه‌های فارسی آن‌ها pattern = r'[.!?؟]\s+' sentences = re.split(pattern, text) # حذف جملات خالی و فضای خالی اضافی sentences = [s.strip() for s in sentences if s.strip()] return sentences def get_last_n_tokens(text: str, n: int) -> str: """ استخراج n توکن آخر از متن (برای ایجاد overlap در chunking) Args: text: متن ورودی n: تعداد tokens مورد نظر Returns: بخش آخر متن که تقریباً n توکن دارد Examples: >>> get_last_n_tokens("این یک متن بلند است", 2) 'است' # تقریباً 2 توکن آخر """ if not text or n <= 0: return "" # تقریب: هر token ≈ 4 کاراکتر approx_chars = n * 4 if len(text) <= approx_chars: return text return text[-approx_chars:] def get_first_n_tokens(text: str, n: int) -> str: """ استخراج n توکن اول از متن Args: text: متن ورودی n: تعداد tokens مورد نظر Returns: بخش اول متن که تقریباً n توکن دارد """ if not text or n <= 0: return "" # تقریب: هر token ≈ 4 کاراکتر approx_chars = n * 4 if len(text) <= approx_chars: return text return text[:approx_chars] def clean_text(text: str) -> str: """ پاکسازی و نرمال‌سازی اولیه متن Args: text: متن ورودی Returns: متن پاکسازی شده """ if not text: return "" # حذف فضاهای خالی اضافی text = re.sub(r'\s+', ' ', text) # حذف فضای خالی ابتدا و انتها text = text.strip() return text # ✅ تست‌های سریع if __name__ == "__main__": print("=" * 60) print("🧪 Testing Utils Module") print("=" * 60) # تست 1: Token counting test_text = "این یک متن تست برای بررسی تعداد توکن‌ها است." tokens = count_tokens(test_text) print(f"\n📊 Test 1: Token Counting") print(f" Text: {test_text}") print(f" Tokens: {tokens}") # تست 2: Chunking decision short_text = "متن کوتاه" long_text = "متن بلند " * 1000 print(f"\n📊 Test 2: Chunking Decision") print(f" Short text ({count_tokens(short_text)} tokens): {should_use_chunking(short_text)}") print(f" Long text ({count_tokens(long_text)} tokens): {should_use_chunking(long_text)}") # تست 3: Sentence splitting multi_sentence = "جمله اول. جمله دوم؟ جمله سوم! چطور هستید؟" sentences = split_sentences(multi_sentence) print(f"\n📊 Test 3: Sentence Splitting") print(f" Input: {multi_sentence}") print(f" Sentences: {sentences}") print(f" Count: {len(sentences)}") # تست 4: Last n tokens sample_text = "این یک متن نمونه برای تست است که می‌خواهیم بخش آخر آن را بگیریم" last_part = get_last_n_tokens(sample_text, 5) print(f"\n📊 Test 4: Get Last N Tokens") print(f" Original: {sample_text}") print(f" Last 5 tokens (~20 chars): {last_part}") print("\n" + "=" * 60) print("✅ All tests completed!") print("=" * 60)