command_parser

Парсинг текстовых команд Яндекс Алисы в структурированные действия.

Преобразует естественно-языковые запросы пользователя (добавление, просмотр, выполнение, удаление и перенос задач) в объекты ParseResult с определённым действием и параметрами.

  1"""
  2Парсинг текстовых команд Яндекс Алисы в структурированные действия.
  3
  4Преобразует естественно-языковые запросы пользователя (добавление,
  5просмотр, выполнение, удаление и перенос задач) в объекты ParseResult
  6с определённым действием и параметрами.
  7"""
  8
  9import re
 10
 11from dataclasses import dataclass, field
 12from typing import Optional
 13from langfuse import observe
 14
 15
 16@dataclass
 17class ParseResult:
 18    """
 19    Результат парсинга команды пользователя.
 20
 21    Содержит идентификатор действия и сопутствующие параметры:
 22    текст задачи, ключевое слово даты, ID задачи и ключевое слово для поиска.
 23    """
 24
 25    action: str
 26    task_text: str = ""
 27    date_keyword: Optional[str] = None
 28    task_id: Optional[int] = None
 29    keyword: str = ""
 30
 31
 32class CommandParser:
 33    """
 34    Парсер голосовых команд для навыка управления задачами.
 35
 36    Анализирует входящий текст команды и оригинальное высказывание,
 37    определяет намерение пользователя и извлекает параметры
 38    (текст задачи, дату, ID задачи).
 39    """
 40
 41    @observe(name="Parse command")
 42    def parse(self, command: str, original_utterance: str) -> Optional[ParseResult]:
 43        """
 44        Анализирует команду пользователя и возвращает структурированный результат.
 45
 46        Определяет тип действия (ping, add_task, list_tasks, complete,
 47        delete, move_one, move_all, help, what) на основе регулярных выражений.
 48
 49        Args:
 50            command: Нормализованная команда (в нижнем регистре)
 51            original_utterance: Оригинальное высказывание пользователя
 52
 53        Returns:
 54            Optional[ParseResult]: Результат парсинга или None, если команда не распознана
 55        """
 56        if original_utterance == "ping":
 57            return ParseResult(action="ping")
 58
 59        if re.search(r"\b(добавь|добавить|создай|новая\s+задача)\b", command):
 60            return self._parse_add(command, original_utterance)
 61
 62        if re.search(r"\b(какие\s+задачи|покажи\s+список|список\s+задач)\b", command) or re.search(
 63            r"\b(дело|дела)\b", command
 64        ):
 65            return self._parse_list(command)
 66
 67        if re.search(r"\b(помощь|help)\b", command):
 68            return ParseResult(action="help")
 69
 70        if command.startswith("что "):
 71            return self._parse_what(command)
 72
 73        if re.search(r"\b(выполн(?:ен[аыо])?|сделал(?:а)?|готово|пометь|отметь)\b", command):
 74            return self._parse_task_id_command(command, action="complete")
 75
 76        if re.search(r"\b(удали|удалить|убери|убрать)\b", command):
 77            return self._parse_task_id_command(command, action="delete")
 78
 79        if re.search(r"\bперенеси\s+задачу|перенос\s+задачи\b", command):
 80            return self._parse_move_one(command)
 81
 82        if re.search(r"\bперенеси|перенос\b", command):
 83            return self._parse_move_all(command)
 84
 85        return None
 86
 87    def _parse_add(self, command: str, original_utterance: str) -> ParseResult:
 88        """
 89        Парсит команду добавления задачи.
 90
 91        Определяет дату (сегодня/завтра/послезавтра) и текст задачи
 92        из оригинального высказывания пользователя.
 93
 94        Args:
 95            command: Нормализованная команда
 96            original_utterance: Оригинальное высказывание
 97
 98        Returns:
 99            ParseResult: Действие add_task с текстом задачи и датой,
100                         либо no_task_text если текст задачи не обнаружен
101        """
102        date_keyword = None
103        task_text = ""
104
105        date_patterns = [
106            (r"\bна\s+завтра\b\s+(.+)", "завтра"),
107            (r"\bна\s+сегодня\b\s+(.+)", "сегодня"),
108            (r"\bна\s+послезавтра\b\s+(.+)", "послезавтра"),
109        ]
110
111        for pattern, date in date_patterns:
112            match = re.search(pattern, original_utterance)
113            if match:
114                date_keyword = date
115                task_text = match.group(1).strip()
116                break
117
118        if not task_text:
119            for trigger in ["добавь задачу", "добавить задачу", "создай задачу"]:
120                if trigger in original_utterance:
121                    remaining = original_utterance.split(trigger, 1)[1].strip()
122                    if remaining:
123                        task_text = remaining
124                    break
125            else:
126                task_text = command
127
128        task_text = re.sub(r"[.,!?]+$", "", task_text).strip()
129
130        if not task_text:
131            return ParseResult(action="no_task_text")
132
133        return ParseResult(action="add_task", task_text=task_text, date_keyword=date_keyword)
134
135    def _parse_list(self, command: str) -> ParseResult:
136        """
137        Парсит команду просмотра списка задач.
138
139        Определяет дату, на которую пользователь хочет увидеть задачи.
140
141        Args:
142            command: Нормализованная команда
143
144        Returns:
145            ParseResult: Действие list_tasks с указанной датой
146        """
147        date_keyword = "сегодня"
148        if re.search(r"\bна\s+завтра\b", command):
149            date_keyword = "завтра"
150        elif re.search(r"\bна\s+послезавтра\b", command):
151            date_keyword = "послезавтра"
152        return ParseResult(action="list_tasks", date_keyword=date_keyword)
153
154    def _parse_what(self, command: str) -> ParseResult:
155        """
156        Парсит команду поиска задач по ключевому слову (например "что купить?").
157
158        Извлекает ключевое слово (первое слово после "что") и дату
159        для фильтрации задач.
160
161        Args:
162            command: Нормализованная команда
163
164        Returns:
165            ParseResult: Действие what с ключевым словом и датой
166        """
167        date_keyword = "сегодня"
168        if re.search(r"\bна\s+завтра\b", command):
169            date_keyword = "завтра"
170        elif re.search(r"\bна\s+послезавтра\b", command):
171            date_keyword = "послезавтра"
172
173        parts = command.split()
174        keyword = parts[1] if len(parts) >= 2 else ""
175        return ParseResult(action="what", keyword=keyword, date_keyword=date_keyword)
176
177    def _parse_task_id_command(self, command: str, action: str) -> ParseResult:
178        """
179        Парсит команду, требующую ID задачи (выполнение/удаление).
180
181        Извлекает числовой ID задачи из команды.
182
183        Args:
184            command: Нормализованная команда
185            action: Тип действия (complete или delete)
186
187        Returns:
188            ParseResult: Действие с указанным типом и ID задачи,
189                         либо no_task_id если ID не найден
190        """
191        match = re.search(r"(\d+)", command)
192        if not match:
193            return ParseResult(action="no_task_id")
194        return ParseResult(action=action, task_id=int(match.group(1)))
195
196    def _parse_move_one(self, command: str) -> ParseResult:
197        """
198        Парсит команду переноса одной задачи на другую дату.
199
200        Извлекает ID задачи и целевую дату (завтра/послезавтра).
201
202        Args:
203            command: Нормализованная команда
204
205        Returns:
206            ParseResult: Действие move_one с ID задачи и датой,
207                         либо no_task_id если ID не найден
208        """
209        date_keyword = "завтра"
210        if re.search(r"\bна\s+завтра\b", command):
211            date_keyword = "завтра"
212        elif re.search(r"\bна\s+послезавтра\b", command):
213            date_keyword = "послезавтра"
214
215        match = re.search(r"(\d+)", command)
216        if not match:
217            return ParseResult(action="no_task_id")
218        return ParseResult(
219            action="move_one", task_id=int(match.group(1)), date_keyword=date_keyword
220        )
221
222    def _parse_move_all(self, command: str) -> ParseResult:
223        """
224        Парсит команду переноса всех задач на другую дату.
225
226        Определяет целевую дату (завтра/послезавтра) для массового переноса.
227
228        Args:
229            command: Нормализованная команда
230
231        Returns:
232            ParseResult: Действие move_all с целевой датой
233        """
234        date_keyword = "завтра"
235        if re.search(r"\bна\s+завтра\b", command):
236            date_keyword = "завтра"
237        elif re.search(r"\bна\s+послезавтра\b", command):
238            date_keyword = "послезавтра"
239        return ParseResult(action="move_all", date_keyword=date_keyword)
@dataclass
class ParseResult:
17@dataclass
18class ParseResult:
19    """
20    Результат парсинга команды пользователя.
21
22    Содержит идентификатор действия и сопутствующие параметры:
23    текст задачи, ключевое слово даты, ID задачи и ключевое слово для поиска.
24    """
25
26    action: str
27    task_text: str = ""
28    date_keyword: Optional[str] = None
29    task_id: Optional[int] = None
30    keyword: str = ""

Результат парсинга команды пользователя.

Содержит идентификатор действия и сопутствующие параметры: текст задачи, ключевое слово даты, ID задачи и ключевое слово для поиска.

ParseResult( action: str, task_text: str = '', date_keyword: Optional[str] = None, task_id: Optional[int] = None, keyword: str = '')
action: str
task_text: str = ''
date_keyword: Optional[str] = None
task_id: Optional[int] = None
keyword: str = ''
class CommandParser:
 33class CommandParser:
 34    """
 35    Парсер голосовых команд для навыка управления задачами.
 36
 37    Анализирует входящий текст команды и оригинальное высказывание,
 38    определяет намерение пользователя и извлекает параметры
 39    (текст задачи, дату, ID задачи).
 40    """
 41
 42    @observe(name="Parse command")
 43    def parse(self, command: str, original_utterance: str) -> Optional[ParseResult]:
 44        """
 45        Анализирует команду пользователя и возвращает структурированный результат.
 46
 47        Определяет тип действия (ping, add_task, list_tasks, complete,
 48        delete, move_one, move_all, help, what) на основе регулярных выражений.
 49
 50        Args:
 51            command: Нормализованная команда (в нижнем регистре)
 52            original_utterance: Оригинальное высказывание пользователя
 53
 54        Returns:
 55            Optional[ParseResult]: Результат парсинга или None, если команда не распознана
 56        """
 57        if original_utterance == "ping":
 58            return ParseResult(action="ping")
 59
 60        if re.search(r"\b(добавь|добавить|создай|новая\s+задача)\b", command):
 61            return self._parse_add(command, original_utterance)
 62
 63        if re.search(r"\b(какие\s+задачи|покажи\s+список|список\s+задач)\b", command) or re.search(
 64            r"\b(дело|дела)\b", command
 65        ):
 66            return self._parse_list(command)
 67
 68        if re.search(r"\b(помощь|help)\b", command):
 69            return ParseResult(action="help")
 70
 71        if command.startswith("что "):
 72            return self._parse_what(command)
 73
 74        if re.search(r"\b(выполн(?:ен[аыо])?|сделал(?:а)?|готово|пометь|отметь)\b", command):
 75            return self._parse_task_id_command(command, action="complete")
 76
 77        if re.search(r"\b(удали|удалить|убери|убрать)\b", command):
 78            return self._parse_task_id_command(command, action="delete")
 79
 80        if re.search(r"\bперенеси\s+задачу|перенос\s+задачи\b", command):
 81            return self._parse_move_one(command)
 82
 83        if re.search(r"\bперенеси|перенос\b", command):
 84            return self._parse_move_all(command)
 85
 86        return None
 87
 88    def _parse_add(self, command: str, original_utterance: str) -> ParseResult:
 89        """
 90        Парсит команду добавления задачи.
 91
 92        Определяет дату (сегодня/завтра/послезавтра) и текст задачи
 93        из оригинального высказывания пользователя.
 94
 95        Args:
 96            command: Нормализованная команда
 97            original_utterance: Оригинальное высказывание
 98
 99        Returns:
100            ParseResult: Действие add_task с текстом задачи и датой,
101                         либо no_task_text если текст задачи не обнаружен
102        """
103        date_keyword = None
104        task_text = ""
105
106        date_patterns = [
107            (r"\bна\s+завтра\b\s+(.+)", "завтра"),
108            (r"\bна\s+сегодня\b\s+(.+)", "сегодня"),
109            (r"\bна\s+послезавтра\b\s+(.+)", "послезавтра"),
110        ]
111
112        for pattern, date in date_patterns:
113            match = re.search(pattern, original_utterance)
114            if match:
115                date_keyword = date
116                task_text = match.group(1).strip()
117                break
118
119        if not task_text:
120            for trigger in ["добавь задачу", "добавить задачу", "создай задачу"]:
121                if trigger in original_utterance:
122                    remaining = original_utterance.split(trigger, 1)[1].strip()
123                    if remaining:
124                        task_text = remaining
125                    break
126            else:
127                task_text = command
128
129        task_text = re.sub(r"[.,!?]+$", "", task_text).strip()
130
131        if not task_text:
132            return ParseResult(action="no_task_text")
133
134        return ParseResult(action="add_task", task_text=task_text, date_keyword=date_keyword)
135
136    def _parse_list(self, command: str) -> ParseResult:
137        """
138        Парсит команду просмотра списка задач.
139
140        Определяет дату, на которую пользователь хочет увидеть задачи.
141
142        Args:
143            command: Нормализованная команда
144
145        Returns:
146            ParseResult: Действие list_tasks с указанной датой
147        """
148        date_keyword = "сегодня"
149        if re.search(r"\bна\s+завтра\b", command):
150            date_keyword = "завтра"
151        elif re.search(r"\bна\s+послезавтра\b", command):
152            date_keyword = "послезавтра"
153        return ParseResult(action="list_tasks", date_keyword=date_keyword)
154
155    def _parse_what(self, command: str) -> ParseResult:
156        """
157        Парсит команду поиска задач по ключевому слову (например "что купить?").
158
159        Извлекает ключевое слово (первое слово после "что") и дату
160        для фильтрации задач.
161
162        Args:
163            command: Нормализованная команда
164
165        Returns:
166            ParseResult: Действие what с ключевым словом и датой
167        """
168        date_keyword = "сегодня"
169        if re.search(r"\bна\s+завтра\b", command):
170            date_keyword = "завтра"
171        elif re.search(r"\bна\s+послезавтра\b", command):
172            date_keyword = "послезавтра"
173
174        parts = command.split()
175        keyword = parts[1] if len(parts) >= 2 else ""
176        return ParseResult(action="what", keyword=keyword, date_keyword=date_keyword)
177
178    def _parse_task_id_command(self, command: str, action: str) -> ParseResult:
179        """
180        Парсит команду, требующую ID задачи (выполнение/удаление).
181
182        Извлекает числовой ID задачи из команды.
183
184        Args:
185            command: Нормализованная команда
186            action: Тип действия (complete или delete)
187
188        Returns:
189            ParseResult: Действие с указанным типом и ID задачи,
190                         либо no_task_id если ID не найден
191        """
192        match = re.search(r"(\d+)", command)
193        if not match:
194            return ParseResult(action="no_task_id")
195        return ParseResult(action=action, task_id=int(match.group(1)))
196
197    def _parse_move_one(self, command: str) -> ParseResult:
198        """
199        Парсит команду переноса одной задачи на другую дату.
200
201        Извлекает ID задачи и целевую дату (завтра/послезавтра).
202
203        Args:
204            command: Нормализованная команда
205
206        Returns:
207            ParseResult: Действие move_one с ID задачи и датой,
208                         либо no_task_id если ID не найден
209        """
210        date_keyword = "завтра"
211        if re.search(r"\bна\s+завтра\b", command):
212            date_keyword = "завтра"
213        elif re.search(r"\bна\s+послезавтра\b", command):
214            date_keyword = "послезавтра"
215
216        match = re.search(r"(\d+)", command)
217        if not match:
218            return ParseResult(action="no_task_id")
219        return ParseResult(
220            action="move_one", task_id=int(match.group(1)), date_keyword=date_keyword
221        )
222
223    def _parse_move_all(self, command: str) -> ParseResult:
224        """
225        Парсит команду переноса всех задач на другую дату.
226
227        Определяет целевую дату (завтра/послезавтра) для массового переноса.
228
229        Args:
230            command: Нормализованная команда
231
232        Returns:
233            ParseResult: Действие move_all с целевой датой
234        """
235        date_keyword = "завтра"
236        if re.search(r"\bна\s+завтра\b", command):
237            date_keyword = "завтра"
238        elif re.search(r"\bна\s+послезавтра\b", command):
239            date_keyword = "послезавтра"
240        return ParseResult(action="move_all", date_keyword=date_keyword)

Парсер голосовых команд для навыка управления задачами.

Анализирует входящий текст команды и оригинальное высказывание, определяет намерение пользователя и извлекает параметры (текст задачи, дату, ID задачи).

@observe(name='Parse command')
def parse( self, command: str, original_utterance: str) -> Optional[ParseResult]:
42    @observe(name="Parse command")
43    def parse(self, command: str, original_utterance: str) -> Optional[ParseResult]:
44        """
45        Анализирует команду пользователя и возвращает структурированный результат.
46
47        Определяет тип действия (ping, add_task, list_tasks, complete,
48        delete, move_one, move_all, help, what) на основе регулярных выражений.
49
50        Args:
51            command: Нормализованная команда (в нижнем регистре)
52            original_utterance: Оригинальное высказывание пользователя
53
54        Returns:
55            Optional[ParseResult]: Результат парсинга или None, если команда не распознана
56        """
57        if original_utterance == "ping":
58            return ParseResult(action="ping")
59
60        if re.search(r"\b(добавь|добавить|создай|новая\s+задача)\b", command):
61            return self._parse_add(command, original_utterance)
62
63        if re.search(r"\b(какие\s+задачи|покажи\s+список|список\s+задач)\b", command) or re.search(
64            r"\b(дело|дела)\b", command
65        ):
66            return self._parse_list(command)
67
68        if re.search(r"\b(помощь|help)\b", command):
69            return ParseResult(action="help")
70
71        if command.startswith("что "):
72            return self._parse_what(command)
73
74        if re.search(r"\b(выполн(?:ен[аыо])?|сделал(?:а)?|готово|пометь|отметь)\b", command):
75            return self._parse_task_id_command(command, action="complete")
76
77        if re.search(r"\b(удали|удалить|убери|убрать)\b", command):
78            return self._parse_task_id_command(command, action="delete")
79
80        if re.search(r"\bперенеси\s+задачу|перенос\s+задачи\b", command):
81            return self._parse_move_one(command)
82
83        if re.search(r"\bперенеси|перенос\b", command):
84            return self._parse_move_all(command)
85
86        return None

Анализирует команду пользователя и возвращает структурированный результат.

Определяет тип действия (ping, add_task, list_tasks, complete, delete, move_one, move_all, help, what) на основе регулярных выражений.

Arguments:
  • command: Нормализованная команда (в нижнем регистре)
  • original_utterance: Оригинальное высказывание пользователя
Returns:

Optional[ParseResult]: Результат парсинга или None, если команда не распознана