# πŸ“„ src/core/intent_parser/fast_parser.py import re import logging from typing import Dict, Tuple, Optional from dataclasses import dataclass @dataclass class ParsedIntent: """Π£Π½ΠΈΠ²Π΅Ρ€ΡΠ°Π»ΡŒΠ½Ρ‹ΠΉ ΠΊΠΎΠ½Ρ‚Π΅ΠΉΠ½Π΅Ρ€ для распознанного намСрСния""" intent: str confidence: float original_text: str normalized_text: str parameters: Dict[str, any] source: str = "fast_parser" class FastIntentParser: """ Быстрый парсСр Π½Π°ΠΌΠ΅Ρ€Π΅Π½ΠΈΠΉ Π½Π° основС ΠΊΠ»ΡŽΡ‡Π΅Π²Ρ‹Ρ… слов ΠΈ ΠΏΡ€Π°Π²ΠΈΠ». ΠžΠ±Ρ€Π°Π±Π°Ρ‚Ρ‹Π²Π°Π΅Ρ‚ 80-90% Ρ‚ΠΈΠΏΠΈΡ‡Π½Ρ‹Ρ… запросов Π±Π΅Π· использования ML. """ def __init__(self): self.logger = logging.getLogger(__name__) self._setup_domains() self._setup_synonyms() self._setup_patterns() def _setup_domains(self): """Настройка Π΄ΠΎΠΌΠ΅Π½ΠΎΠ² ΠΈ ΠΊΠ»ΡŽΡ‡Π΅Π²Ρ‹Ρ… слов""" self.domains = { 'greeting': { 'keywords': ['ΠΏΡ€ΠΈΠ²Π΅Ρ‚', 'здравствуй', 'Π΄ΠΎΠ±Ρ€Ρ‹ΠΉ', 'Ρ…Π°ΠΉ', 'ΡΠ°Π»ΡŽΡ‚', 'Π·Π΄Π°Ρ€ΠΎΠ²'], 'priority': 1, 'response_templates': [ "ΠŸΡ€ΠΈΠ²Π΅Ρ‚! Π“ΠΎΡ‚ΠΎΠ² ΠΊ Ρ€Π°Π±ΠΎΡ‚Π΅.", "ЗдравствуйтС! Π§Π΅ΠΌ ΠΌΠΎΠ³Ρƒ ΠΏΠΎΠΌΠΎΡ‡ΡŒ?", "ΠŸΡ€ΠΈΠ²Π΅Ρ‚ΡΡ‚Π²ΡƒΡŽ! Ariel Π½Π° связи." ] }, 'system': { 'keywords': ['Π±ΡƒΠ΄ΠΈΠ»ΡŒΠ½ΠΈΠΊ', 'Ρ‚Π°ΠΉΠΌΠ΅Ρ€', 'ΠΎΡ‚ΠΊΡ€ΠΎΠΉ', 'запусти', 'Π²Ρ‹ΠΊΠ»ΡŽΡ‡ΠΈ', 'Π³Ρ€ΠΎΠΌΠΊΠΎΡΡ‚ΡŒ'], 'priority': 2, 'subdomains': { 'alarm': ['Π±ΡƒΠ΄ΠΈΠ»ΡŒΠ½ΠΈΠΊ', 'Ρ€Π°Π·Π±ΡƒΠ΄ΠΈ', 'Π½Π°ΠΏΠΎΠΌΠ½ΠΈ'], 'app_launch': ['ΠΎΡ‚ΠΊΡ€ΠΎΠΉ', 'запусти', 'Π²ΠΊΠ»ΡŽΡ‡ΠΈ'], 'system_control': ['Π²Ρ‹ΠΊΠ»ΡŽΡ‡ΠΈ', 'ΠΏΠ΅Ρ€Π΅Π·Π°Π³Ρ€ΡƒΠ·ΠΈ', 'Π³Ρ€ΠΎΠΌΠΊΠΎΡΡ‚ΡŒ'] } }, 'visualization': { 'keywords': ['Π³Ρ€Π°Ρ„ΠΈΠΊ', 'Π΄ΠΈΠ°Π³Ρ€Π°ΠΌΠΌ', 'схСм', 'Π²ΠΈΠ·ΡƒΠ°Π»ΠΈΠ·ΠΈΡ€', 'построй', 'нарисуй'], 'priority': 3, 'subdomains': { 'plot': ['Π³Ρ€Π°Ρ„ΠΈΠΊ', 'построй'], 'chart': ['Π΄ΠΈΠ°Π³Ρ€Π°ΠΌΠΌ', 'гистограмм'], 'scheme': ['схСм', 'Π±Π»ΠΎΠΊ-схСм'] } }, 'knowledge': { 'keywords': ['Ρ‡Ρ‚ΠΎ Ρ‚Π°ΠΊΠΎΠ΅', 'ΠΊΠ°ΠΊ Ρ€Π°Π±ΠΎΡ‚Π°Π΅Ρ‚', 'объясни', 'Π½Π°ΠΉΠ΄ΠΈ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ', 'Π±Π°Π·Π° Π΄Π°Π½Π½Ρ‹Ρ…'], 'priority': 4 }, 'creative': { 'keywords': ['расскаТи', 'ΠΏΠΎΡˆΡƒΡ‚ΠΈ', 'ΠΏΡ€ΠΈΠ΄ΡƒΠΌΠ°ΠΉ', 'Ρ€Π΅ΠΊΠΎΠΌΠ΅Π½Π΄ΡƒΠΉ', 'совСтуй'], 'priority': 5 }, 'help': { 'keywords': ['ΠΏΠΎΠΌΠΎΡ‰ΡŒ', 'ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹', 'Ρ‡Ρ‚ΠΎ Ρ‚Ρ‹ ΡƒΠΌΠ΅Π΅ΡˆΡŒ', 'справка'], 'priority': 6 } } def _setup_synonyms(self): """Настройка синонимов для Π½ΠΎΡ€ΠΌΠ°Π»ΠΈΠ·Π°Ρ†ΠΈΠΈ тСкста""" self.synonyms = { 'ΠΏΡ€ΠΈΠ²Π΅Ρ‚': ['ΡΠ°Π»ΡŽΡ‚', 'Π·Π΄Π°Ρ€ΠΎΠ²', 'Ρ…Π΅ΠΉ', 'Ρ…Π°ΠΉ', 'Π·Π΄ΠΎΡ€ΠΎΠ²ΠΎ', 'Π΄ΠΎΠ±Ρ€Ρ‹ΠΉ дСнь'], 'Π±ΡƒΠ΄ΠΈΠ»ΡŒΠ½ΠΈΠΊ': ['Π±ΡƒΠ΄ΠΈΠ»ΡŒΠ½ΠΈΡ‡Π΅ΠΊ', 'Π½Π°ΠΏΠΎΠΌΠΈΠ½Π°Π½ΠΈΠ΅', 'ΠΎΠΏΠΎΠ²Π΅Ρ‰Π΅Π½ΠΈΠ΅', 'Π·Π²ΠΎΠ½ΠΎΠΊ'], 'ΠΏΠΎΡΡ‚Π°Π²ΡŒ': ['Π·Π°Π²Π΅Π΄ΠΈ', 'установи', 'создай', 'Π°ΠΊΡ‚ΠΈΠ²ΠΈΡ€ΡƒΠΉ'], 'ΠΎΡ‚ΠΊΡ€ΠΎΠΉ': ['запусти', 'Π²ΠΊΠ»ΡŽΡ‡ΠΈ', 'ΠΎΡ‚ΠΊΡ€ΠΎΠΉ', 'запусти'], 'Π³Ρ€Π°Ρ„ΠΈΠΊ': ['Π³Ρ€Π°Ρ„ΠΈΠΊΠΈ', 'Π³Ρ€Π°Ρ„ΠΈΡ‡Π΅ΠΊ', 'ΠΏΠ»ΠΎΡ‚ΠΈΠΊ'], 'ΠΏΠΎΠΌΠΎΡ‰ΡŒ': ['справка', 'Ρ…Π΅Π»ΠΏ', 'ΠΏΠΎΠΌΠΎΠ³ΠΈ', 'подскаТи'] } def _setup_patterns(self): """Настройка regex-ΠΏΠ°Ρ‚Ρ‚Π΅Ρ€Π½ΠΎΠ² для слоТных случаСв""" self.patterns = { 'system': [ # Π‘ΡƒΠ΄ΠΈΠ»ΡŒΠ½ΠΈΠΊΠΈ ΠΈ Ρ‚Π°ΠΉΠΌΠ΅Ρ€Ρ‹ r'(ΠΏΠΎΡΡ‚Π°Π²ΡŒ|Π·Π°Π²Π΅Π΄ΠΈ|установи).*Π±ΡƒΠ΄ΠΈΠ»ΡŒΠ½ΠΈΠΊ.*(\d{1,2}:\d{2})', r'Π±ΡƒΠ΄ΠΈΠ»ΡŒΠ½ΠΈΠΊ.*(\d{1,2}).*(ΡƒΡ‚Ρ€Π°|Π²Π΅Ρ‡Π΅Ρ€Π°|часов|час)', r'Ρ€Π°Π·Π±ΡƒΠ΄ΠΈ.*(\d{1,2}).*(ΡƒΡ‚Ρ€Π°|Π²Π΅Ρ‡Π΅Ρ€Π°)', # Запуск ΠΏΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠΉ r'(ΠΎΡ‚ΠΊΡ€ΠΎΠΉ|запусти).*(Π±Ρ€Π°ΡƒΠ·Π΅Ρ€|chrome|Ρ…Ρ€ΠΎΠΌ|firefox|файрфокс)', r'(ΠΎΡ‚ΠΊΡ€ΠΎΠΉ|запусти).*(Ρ‚Π΅Ρ€ΠΌΠΈΠ½Π°Π»|cmd|ΠΊΠΎΠΌΠ°Π½Π΄Π½ΡƒΡŽ строку)', # Π£ΠΏΡ€Π°Π²Π»Π΅Π½ΠΈΠ΅ систСмой r'(Π²Ρ‹ΠΊΠ»ΡŽΡ‡ΠΈ|ΠΏΠ΅Ρ€Π΅Π·Π°Π³Ρ€ΡƒΠ·ΠΈ).*(ΠΊΠΎΠΌΠΏΡŒΡŽΡ‚Π΅Ρ€|систСму)', r'(сдСлай|ΠΏΠΎΡΡ‚Π°Π²ΡŒ).*(Π³Ρ€ΠΎΠΌΡ‡Π΅|Ρ‚ΠΈΡˆΠ΅)' ], 'visualization': [ r'построй.*Π³Ρ€Π°Ρ„ΠΈΠΊ.*(\w+).*ΠΎΡ‚.*(\d+).*Π΄ΠΎ.*(\d+)', r'Π³Ρ€Π°Ρ„ΠΈΠΊ.*(sin|синус|cos|косинус|tan|тангСнс)', r'Π΄ΠΈΠ°Π³Ρ€Π°ΠΌΠΌ.*(ΠΊΡ€ΡƒΠ³ΠΎΠ²Π°|столбчата|гистограмм)', r'нарисуй.*схСм.*Ρ€Π°Π±ΠΎΡ‚Ρ‹' ], 'knowledge': [ r'Ρ‡Ρ‚ΠΎ Ρ‚Π°ΠΊΠΎΠ΅ (\w+)', r'ΠΊΠ°ΠΊ Ρ€Π°Π±ΠΎΡ‚Π°Π΅Ρ‚ (\w+)', r'объясни.*(\w+)', r'Π½Π°ΠΉΠ΄ΠΈ.*ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ.*ΠΎ (\w+)' ] } def normalize_text(self, text: str) -> str: """Нормализация тСкста: ΠΏΡ€ΠΈΠ²Π΅Π΄Π΅Π½ΠΈΠ΅ ΠΊ Π½ΠΈΠΆΠ½Π΅ΠΌΡƒ рСгистру ΠΈ Π·Π°ΠΌΠ΅Π½Π° синонимов""" if not text: return "" text_lower = text.lower().strip() # Π—Π°ΠΌΠ΅Π½Π° синонимов Π½Π° основныС Ρ„ΠΎΡ€ΠΌΡ‹ for main_word, synonyms in self.synonyms.items(): for synonym in synonyms: if synonym in text_lower: text_lower = text_lower.replace(synonym, main_word) self.logger.debug(f"Π—Π°ΠΌΠ΅Π½Π΅Π½ синоним '{synonym}' -> '{main_word}'") return text_lower def extract_parameters(self, domain: str, text: str) -> Dict[str, any]: """Π˜Π·Π²Π»Π΅Ρ‡Π΅Π½ΠΈΠ΅ ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€ΠΎΠ² ΠΈΠ· тСкста ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹""" normalized_text = self.normalize_text(text) parameters = {} if domain == 'system': # Π˜Π·Π²Π»Π΅Ρ‡Π΅Π½ΠΈΠ΅ Π²Ρ€Π΅ΠΌΠ΅Π½ΠΈ для Π±ΡƒΠ΄ΠΈΠ»ΡŒΠ½ΠΈΠΊΠΎΠ² time_match = re.search(r'(\d{1,2})(?::(\d{2}))?\s*(ΡƒΡ‚Ρ€Π°|Π²Π΅Ρ‡Π΅Ρ€Π°|часов|час)?', normalized_text) if time_match: hour = int(time_match.group(1)) minute = int(time_match.group(2) or "0") period = time_match.group(3) or "" # ΠšΠΎΠ½Π²Π΅Ρ€Ρ‚Π°Ρ†ΠΈΡ Π² 24-часовой Ρ„ΠΎΡ€ΠΌΠ°Ρ‚ if period == 'Π²Π΅Ρ‡Π΅Ρ€Π°' and hour < 12: hour += 12 parameters['time'] = f"{hour:02d}:{minute:02d}" parameters['period'] = period # Π˜Π·Π²Π»Π΅Ρ‡Π΅Π½ΠΈΠ΅ названия прилоТСния app_matches = re.findall(r'(Π±Ρ€Π°ΡƒΠ·Π΅Ρ€|Ρ…Ρ€ΠΎΠΌ|chrome|Ρ‚Π΅Ρ€ΠΌΠΈΠ½Π°Π»|cmd)', normalized_text) if app_matches: parameters['app'] = app_matches[0] elif domain == 'visualization': # Π˜Π·Π²Π»Π΅Ρ‡Π΅Π½ΠΈΠ΅ матСматичСской Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΈ func_match = re.search(r'(sin|синус|cos|косинус|tan|тангСнс|x\^2)', normalized_text) if func_match: func_map = {'синус': 'sin', 'косинус': 'cos', 'тангСнс': 'tan'} parameters['function'] = func_map.get(func_match.group(1), func_match.group(1)) # Π˜Π·Π²Π»Π΅Ρ‡Π΅Π½ΠΈΠ΅ Π΄ΠΈΠ°ΠΏΠ°Π·ΠΎΠ½Π° range_match = re.search(r'ΠΎΡ‚\s*(\d+)\s*Π΄ΠΎ\s*(\d+)', normalized_text) if range_match: parameters['x_range'] = [float(range_match.group(1)), float(range_match.group(2))] elif domain == 'knowledge': # Π˜Π·Π²Π»Π΅Ρ‡Π΅Π½ΠΈΠ΅ Ρ‚Π΅ΠΌΡ‹ для поиска topic_match = re.search(r'Ρ‡Ρ‚ΠΎ Ρ‚Π°ΠΊΠΎΠ΅\s+(\w+)', normalized_text) if not topic_match: topic_match = re.search(r'ΠΊΠ°ΠΊ Ρ€Π°Π±ΠΎΡ‚Π°Π΅Ρ‚\s+(\w+)', normalized_text) if not topic_match: topic_match = re.search(r'объясни\s+(\w+)', normalized_text) if topic_match: parameters['topic'] = topic_match.group(1) return parameters def parse(self, text: str) -> Optional[ParsedIntent]: """ Основной ΠΌΠ΅Ρ‚ΠΎΠ΄ парсинга намСрСния ΠΈΠ· тСкста. Π’ΠΎΠ·Π²Ρ€Π°Ρ‰Π°Π΅Ρ‚ ParsedIntent ΠΈΠ»ΠΈ None Ссли Π½Π°ΠΌΠ΅Ρ€Π΅Π½ΠΈΠ΅ Π½Π΅ распознано. """ if not text or not text.strip(): return None normalized_text = self.normalize_text(text) self.logger.debug(f"ΠŸΠ°Ρ€ΡΠΈΠ½Π³ тСкста: '{text}' -> '{normalized_text}'") # Π‘Π½Π°Ρ‡Π°Π»Π° провСряСм regex-ΠΏΠ°Ρ‚Ρ‚Π΅Ρ€Π½Ρ‹ (Π±ΠΎΠ»Π΅Π΅ Ρ‚ΠΎΡ‡Π½Ρ‹Π΅) domain_from_patterns = self._check_patterns(normalized_text) if domain_from_patterns: domain, subdomain, confidence = domain_from_patterns parameters = self.extract_parameters(domain, normalized_text) return ParsedIntent( intent=domain, confidence=confidence, original_text=text, normalized_text=normalized_text, parameters=parameters ) # Π—Π°Ρ‚Π΅ΠΌ провСряСм ΠΊΠ»ΡŽΡ‡Π΅Π²Ρ‹Π΅ слова domain_from_keywords = self._check_keywords(normalized_text) if domain_from_keywords: domain, subdomain, confidence = domain_from_keywords parameters = self.extract_parameters(domain, normalized_text) return ParsedIntent( intent=domain, confidence=confidence, original_text=text, normalized_text=normalized_text, parameters=parameters ) # НС распознано self.logger.debug(f"НС ΡƒΠ΄Π°Π»ΠΎΡΡŒ Ρ€Π°ΡΠΏΠΎΠ·Π½Π°Ρ‚ΡŒ Π½Π°ΠΌΠ΅Ρ€Π΅Π½ΠΈΠ΅: '{text}'") return None def _check_patterns(self, text: str) -> Optional[Tuple[str, str, float]]: """ΠŸΡ€ΠΎΠ²Π΅Ρ€ΠΊΠ° тСкста ΠΏΠΎ regex-ΠΏΠ°Ρ‚Ρ‚Π΅Ρ€Π½Π°ΠΌ""" for domain, pattern_list in self.patterns.items(): for pattern in pattern_list: if re.search(pattern, text): self.logger.debug(f"НайдСн ΠΏΠ°Ρ‚Ρ‚Π΅Ρ€Π½ '{pattern}' для Π΄ΠΎΠΌΠ΅Π½Π° '{domain}'") return domain, None, 0.95 # Высокая ΡƒΠ²Π΅Ρ€Π΅Π½Π½ΠΎΡΡ‚ΡŒ для ΠΏΠ°Ρ‚Ρ‚Π΅Ρ€Π½ΠΎΠ² return None def _check_keywords(self, text: str) -> Optional[Tuple[str, str, float]]: """ΠŸΡ€ΠΎΠ²Π΅Ρ€ΠΊΠ° тСкста ΠΏΠΎ ΠΊΠ»ΡŽΡ‡Π΅Π²Ρ‹ΠΌ словам""" found_domains = [] for domain, domain_config in self.domains.items(): for keyword in domain_config['keywords']: if keyword in text: confidence = 0.9 if len(keyword) > 3 else 0.7 found_domains.append((domain, None, confidence)) self.logger.debug(f"НайдСно ΠΊΠ»ΡŽΡ‡Π΅Π²ΠΎΠ΅ слово '{keyword}' для Π΄ΠΎΠΌΠ΅Π½Π° '{domain}'") if not found_domains: return None # Π’ΠΎΠ·Π²Ρ€Π°Ρ‰Π°Π΅ΠΌ Π΄ΠΎΠΌΠ΅Π½ с Π½Π°ΠΈΠ²Ρ‹ΡΡˆΠΈΠΌ ΠΏΡ€ΠΈΠΎΡ€ΠΈΡ‚Π΅Ρ‚ΠΎΠΌ found_domains.sort(key=lambda x: self.domains[x[0]]['priority']) return found_domains[0] # Π€Π°Π±Ρ€ΠΈΠΊΠ° для создания парсСра def create_fast_parser() -> FastIntentParser: """Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅ ΠΈ настройка быстрого парсСра""" return FastIntentParser()