Классическое расписание задают пятью числами со звёздочками. Это точно, воспроизводимо и совершенно нечитаемо: строку, написанную полгода назад, приходится расшифровывать заново. Естественная формулировка читается сразу — и ровно поэтому неоднозначна.
«Напомни в понедельник» — в ближайший или через неделю, если сегодня понедельник? «Через неделю» — в тот же день недели или ровно через семь суток от текущего момента? «В конце месяца» — тридцатого, тридцать первого или в последний рабочий день? Человек уточнит. Программа обязана выбрать заранее и делать это одинаково.
#Короткий ответ
Разбор естественной формулировки в расписание — это не «понимание», а сопоставление с набором правил. Работающая реализация решает три подзадачи по отдельности:
- Найти дату или период в свободном тексте.
- Отделить их от текста задачи — то, что останется, и есть напоминание.
- Явно назначить поведение в неоднозначных случаях и сообщить о нём пользователю.
Третий пункт пропускают чаще всего, и именно он определяет, будет ли пользователь доверять напоминаниям через месяц.
#Как это устроено
Два подхода, и выбор между ними не про качество, а про предсказуемость.
Детерминированные правила. Набор шаблонов: дни недели, «завтра», «через N дней», абсолютная дата цифрами. Быстро, бесплатно, покрывается тестами, работает одинаково каждый раз. Не понимает того, чего в шаблонах нет.
Разбор моделью. Понимает произвольные формулировки, включая «в предпоследнюю пятницу квартала». Стоит вызова модели, работает медленнее и — главное — может дать разный ответ на одну и ту же фразу в разные дни.
Практичная схема — правила как основной путь, модель как необязательное усиление для того, что правила не разобрали. Так это и сделано в Persona: разбор напоминаний в app/chat/reminder_nl.py построен на детерминированных правилах и покрыт тестами, а обращение к локальной модели — дополнительный, необязательный слой для размытых формулировок.
#Таблица разбора
Что происходит с типовыми формулировками. Столбец «результат» показывает поведение реализации на детерминированных правилах, где базой считается текущая дата.
| Формулировка | Что распознаётся | Результат | Однозначно |
|---|---|---|---|
| напомни завтра позвонить маме | «завтра» | дата +1 день, тело «позвонить маме» | ✓ |
| через 3 дня оплатить хостинг | «через 3 дня» | дата +3 дня | ✓ |
| в пятницу созвон | день недели | ближайшая пятница | ✗ |
| 20.06 подать отчёт | абсолютная дата | 20 июня | ✓ |
| через неделю проверить | «через неделю» | дата +7 дней | ✗ |
| напомни в 15:00 позвонить | время суток | сегодня, «в 15:00» остаётся в тексте | ✗ |
| в конце месяца | не распознаётся | сегодня, пометка «дата не найдена» | ✗ |
| каждый вторник | не распознаётся | сегодня, пометка «дата не найдена» | ✗ |
Две последние строки — не недоработка описания, а точное положение дел, и о нём стоит сказать прямо. Повторяющихся расписаний в Persona нет. Разбор извлекает одну дату для одного напоминания; конструкции вида «каждый вторник» правилами не покрыты и приведут к напоминанию на сегодня с пометкой, что дата не найдена. Если вам нужен повторяющийся цикл — это не наш сценарий, и лучше знать об этом до, а не после.
Строка про время суток тоже требует пояснения. В нашей схеме напоминание хранит дату без времени, поэтому «в 15:00» остаётся частью текста напоминания и не превращается в момент срабатывания. Это осознанное упрощение хранилища, а не ошибка разбора.
#Неоднозначные случаи
Список формулировок, где двух разумных людей можно поставить по разные стороны. Для каждой нужно принять решение и записать его.
«В пятницу», когда сегодня пятница. Сегодня или через неделю? Распространённое решение — ближайшая будущая, то есть через неделю; но пользователь, пишущий это утром, обычно имеет в виду сегодня.
«Через неделю». Ровно семь суток от текущего момента или тот же день недели на следующей? Для дат без времени разница исчезает, для расписаний с временем — нет.
«В конце месяца». Последнее число, последний рабочий день или условное «после 25-го»? Универсального ответа нет, выбирать приходится вам.
Часовой пояс. Самая дорогая неоднозначность. Пользователь пишет «в 9 утра», сервер живёт в UTC, пользователь в другом поясе, а зимой ещё и переводит часы. Правило, которое спасает: хранить момент срабатывания в абсолютном времени, а пояс пользователя применять только при отображении и при разборе ввода.
Дата в прошлом. «Напомни 5 марта», когда сегодня 20 марта. Следующий год, ошибка или ближайшее 5 марта? Молчаливый выбор здесь особенно неприятен.
Общее правило для всего списка: если формулировка неоднозначна, покажите результат разбора пользователю в момент создания. «Напоминание на 27 августа: подать отчёт» — одна строка, которая снимает большую часть будущих претензий. Именно так наш инструмент и отвечает: подтверждением с датой и текстом, а не молчаливым «готово».
#Что из этого следует на практике
Тело напоминания надо чистить от даты и слов-триггеров. Иначе в списке дел вы увидите «напомни завтра позвонить маме» вместо «позвонить маме». Разбор обязан вырезать распознанную дату и вводные слова.
Ненайденная дата — это не ошибка, а состояние. Правильная реакция — создать напоминание на сегодня и явно пометить, что дата не распознана, а не отказаться или молча угадать.
Разбор должен быть проверяем тестами на фиксированной дате. Тест, зависящий от сегодняшнего дня, будет падать по вторникам. База отсчёта передаётся параметром, а не берётся из системных часов.
Создание напоминания — изменяющее действие. Оно пишет в базу, а значит не относится к тем, что модель может совершать без явного участия человека. В нашей классификации рисков создание напоминания отнесено к изменяющим инструментам именно поэтому.
Разбор дат в вопросах — отдельная задача. «Что я делал на прошлой неделе» — это не расписание, а диапазон для поиска. У нас это отдельный модуль со своим набором русских и английских формулировок; смешивать его с разбором напоминаний не стоит, у них разные требования.
Как это выглядит в связке с проактивностью и утренними сводками — в руководстве по брифингу и напоминаниям на естественном языке и в отдельном материале про напоминания на естественном языке.
#Где интуиция подводит
«Модель разберёт любую формулировку». Разберёт, но не гарантирует одинаковый ответ на одну фразу дважды. Для расписания воспроизводимость важнее покрытия: лучше честно не понять, чем понять по-разному в понедельник и в среду.
«Если не поняли — надо переспросить». Переспрашивание на каждое второе напоминание раздражает сильнее, чем неточная дата, которую видно в подтверждении. Показать результат разбора дешевле, чем спросить.
«Часовой пояс можно взять из браузера». Можно, и он будет верным для веб-интерфейса и неверным для напоминания, созданного из мессенджера с другого устройства. Пояс — свойство пользователя, а не сессии.
«Повторяющиеся напоминания — это тот же разбор, только с "каждый"». Совсем нет. Однократное напоминание — это одна дата в таблице. Повторяющееся — это правило, состояние «когда было в прошлый раз», обработка пропущенных срабатываний и вопрос, что делать после недели простоя. Отсюда и разница в объёме работы, и наше текущее отсутствие такой функции.
#Когда естественный язык не нужен
Если расписание задаётся один раз и работает годами — берите точный формат. Пять чисел со звёздочками читаются хуже, но не оставляют места для интерпретации, а перечитывать их раз в год не проблема.
Естественная формулировка окупается там, где задач много и они одноразовые: бытовые напоминания, разовые дела, «не забыть про». Именно в этом сценарии выигрыш от того, что не нужно открывать календарь, перевешивает риск неточного разбора.
Как напоминания встраиваются в проактивное поведение ассистента — в разборе проактивного агента и его бюджета внимания. Как это работает через мессенджер — в руководстве по агенту в Telegram. Где в общей архитектуре живут планировщики — в разборе агентной архитектуры Persona.
#Частые вопросы
#Можно ли задать повторяющееся напоминание фразой?
В Persona — нет: разбор извлекает одну дату для одного напоминания, конструкции вида «каждый вторник» не распознаются, и такое напоминание будет создано на сегодня с пометкой, что дата не найдена. Повторяющиеся расписания требуют отдельного механизма с состоянием и обработкой пропусков.
#Что происходит, если дату не удалось распознать?
Напоминание всё равно создаётся — на сегодня, с явной пометкой, что дата не найдена. Это лучше двух альтернатив: отказаться и потерять задачу или угадать дату молча и создать ложную уверенность.
#Как быть с часовыми поясами?
Хранить момент срабатывания в абсолютном времени, а пояс пользователя применять только при разборе ввода и при показе. Пояс — свойство пользователя, а не устройства: напоминание, созданное из мессенджера, и напоминание из браузера должны сработать одинаково.
#Почему в тексте напоминания остаётся время?
Потому что хранилище может хранить только дату. В нашем случае это так: момент срабатывания — календарный день, а «в 15:00» остаётся частью текста. Ограничение честное, но его надо знать: напоминание сработает днём, а не в указанный час.
#Что надёжнее — правила или модель?
Правила надёжнее по воспроизводимости и бесплатны, модель шире по покрытию и дороже. Рабочая схема — правила основным путём, модель необязательной подстраховкой для того, что правила не разобрали, с обязательным показом результата пользователю.
#Как проверить, что разбор работает правильно?
Тестами с фиксированной базовой датой, передаваемой параметром, а не системными часами. Иначе тесты начнут падать в определённые дни недели, и вы будете чинить не разбор, а календарь.
#Вывод
Разбор естественной формулировки — это не понимание, а набор правил плюс явно принятые решения для неоднозначных случаев. Правила дают воспроизводимость, модель — покрытие, показ результата пользователю — доверие.
Если расписание постоянное и живёт годами — задавайте его точным форматом. Если вам нужны повторяющиеся напоминания — учитывайте, что в Persona их сегодня нет: разбор рассчитан на одно напоминание с одной датой.