M.E.S.A._Intentions / fast_parser.py
Alex-Watchman's picture
Upload fast parser and ml evaluator
7d9276b verified
# 📄 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()