- Главная
- Блог
- «После майских, ближе к обеду в четверг»: как мы учили ИИ-агента-помощника парсить русские даты в реальный timestamp
«После майских, ближе к обеду в четверг»: как мы учили ИИ-агента-помощника парсить русские даты в реальный timestamp

Обычное утро, менеджер разбирает переписку. В чат секретаря падает сообщение от клиента: «Давайте созвонимся после праздников, где-то ближе к обеду, лучше в четверг». Через несколько минут менеджер открывает CRM и видит: «Встреча запланирована на 1 января 2027, 12:00». Январь. Через семь месяцев. Спасибо, дорогой ИИ.
Ситуация не выдуманная — реальный случай из нашей практики. Клиент имел в виду четверг после майских. Модель честно поняла слово «праздники», нашла ближайшие крупные («Новый год же главный!») и уверенно вернула timestamp. Менеджер не перепроверил — он привык доверять. Встречу пришлось переназначать, извиняться, ловить клиента ещё раз, и это в тот момент, когда мы всем командой уже полгода рассказывали заказчикам, какие у нас крутые готовые ИИ-агенты.
Я сел разбираться. И довольно быстро понял, что задача «русская дата → timestamp» — это не пара строк промпта, а отдельный инженерный слой.
Почему просто «верни ISO-дату» не работает
Первый подход был наивным, каким и бывает первый подход. Промпт: «Извлеки из сообщения дату и время встречи. Верни JSON: {"datetime": "2026-05-14T12:00:00+03:00"}». GigaChat Pro, YandexGPT — без разницы, любая приличная модель такое умеет.
Прогнал двести живых сообщений из переписки. На глаз — прилично, где-то четыре из пяти попадают в точку. Но «где-то четыре из пяти» на практике означает катастрофу: каждое пятое назначение встречи мимо. Секретарю на таком фоне вообще нельзя доверять расписание.
Разбор ошибок показал три класса проблем.
Первое — галлюцинация праздников. Модели не знают актуальный календарь. Спрашиваешь «после праздников» — отвечают «после Нового года», даже если на дворе апрель. Производственный календарь РФ с переносами выходных, длинными майскими и ноябрьскими они держат в голове плохо. Оно и понятно: обучали их не под нас.
Второе — арифметика с датами. Модель «знает», что сегодня, условно, 28 июня. Спрашиваешь «через две недели в пятницу». Считает: +14 дней = 12 июля, суббота. Дальше начинается фантазия: то «ближайшая пятница до», то «ближайшая после», то просто 12 июля и забить на день недели.
Третье — часовые пояса. Клиент с Дальнего Востока пишет «в три дня». Модель не знает, какой пояс у клиента, и подставляет либо UTC, либо московское, либо часовой пояс самого менеджера. Когда «в три дня» на самом деле означает раннее утро по Москве — встречу очень удобно проспать.
Двухэтажная схема: LLM понимает, код считает
Решение получилось скучным и надёжным — как обычно и бывает, когда перестаёшь ждать от нейросети чуда. Я разделил задачу на два слоя.
Этаж первый — LLM извлекает не дату, а компоненты намерения. Не datetime, а структуру:
{
"reference": "after_holidays",
"weekday": "thursday",
"time_hint": "around_noon",
"time_range": [11, 14],
"duration_minutes": 60,
"ambiguity": "high",
"raw": "после праздников ближе к обеду в четверг"
}
Промпт стал строгим и коротким: не вычисляй ничего, только классифицируй. Модель отлично умеет различать «после праздников», «в начале месяца», «к концу квартала», «как сможете». Если просишь её только маркировать, а не считать, ошибается редко.
Этаж второй — детерминированный Python-парсер. На вход — структура от LLM и контекст: текущая дата, часовой пояс клиента (вытащен из карточки CRM), календарь рабочих дней.
def resolve_datetime(
intent: DateIntent,
now: datetime,
tz: ZoneInfo,
calendar: ProductionCalendar
) -> ResolvedDate:
base = _apply_reference(intent.reference, now, calendar)
if intent.weekday:
base = _next_weekday(base, intent.weekday)
hour = _resolve_time_hint(intent.time_hint, intent.time_range)
return ResolvedDate(
ts=base.replace(hour=hour, tzinfo=tz),
confidence=_score(intent),
needs_confirmation=intent.ambiguity == "high"
)
«После праздников» превращается в «первый рабочий день после ближайшего длинного блока выходных». «К концу квартала» — в «последний рабочий день квартала минус три дня». «Утром» — в 10:00, «к обеду» — в 12:00, «после обеда» — в 15:00, «вечером» — в 18:00. Если модель отдала диапазон времени — берём середину. Скучно, зато предсказуемо.
Производственный календарь — отдельная боль
Календарь рабочих дней РФ живёт в трёх местах: постановление правительства, всякие сводки в интернете с переносами и наш собственный JSON с правками под клиентов («у крупного банка 31 декабря не рабочий, а у вас рабочий — учитывайте»). Первое время я честно думал, что где-то есть один правильный API. Нет, нету.
Раз в год скрипт парсит официальное постановление, накладывает корпоративные исключения и складывает результат в таблицу production_calendar(date, day_type, region_code). День бывает четырёх типов: рабочий, выходной, праздник, сокращённый. Парсер дат лезет за этим в Postgres, не в LLM — модель к календарю вообще не прикасается.
Длинные выходные определяем эвристикой: блок из трёх и более нерабочих дней подряд. После него «после праздников» = первый ближайший рабочий день. До майских «после праздников» — это первый рабочий день после них. Проходят майские — фраза автоматически перестраивается на ноябрьские. Пользователь ничего не замечает, а нам не нужно раз в квартал переписывать промпт.
Часовой пояс клиента — поле в CRM, а не догадка
Завёл в карточке контакта поле timezone с дефолтом по региону компании. Если компания зарегистрирована на Дальнем Востоке — ставим Asia/Vladivostok. Если регион пуст — Europe/Moscow и пометка «уточнить». Дальше любой парсинг времени идёт с явным ZoneInfo, никаких naive datetime в коде. Один раз обжёгся, больше не хочется.
Дополнительный нюанс: если в сообщении явно «в 15:00 по Москве» — это маркер, который тоже извлекает LLM на первом этаже. Парсер видит флаг explicit_tz="Europe/Moscow" и игнорирует пояс клиента для этого случая. Мелочь, но именно на такой мелочи всё и держится.
Confidence и human-in-the-loop
Не все сообщения одинаково однозначны, и делать вид, что ИИ-агент справится со всем сам, — самообман. «Встретимся завтра в 15:00» — очевидно, парсер ставит высокий confidence, секретарь сразу шлёт приглашение в календарь. «Давайте на следующей неделе как-нибудь» — confidence низкий, секретарь отвечает уточняющим сообщением: «Удобно во вторник в 11:00 или в четверг в 15:00?».
Confidence считается грубо, но честно: высокий, если есть и день, и время; средний — если только день или только время; низкий — если намерение вида «созвонимся ближе к концу месяца». Менеджер всегда видит исходную формулировку рядом с распарсенной датой — одно касание, и если что-то не так, правит до отправки приглашения. Это тот случай, когда небольшая доля ручной работы окупается спокойствием: никто не проспал звонок, никто не назначил встречу на прошлое воскресенье.
Как это встраивается в сеть ИИ-агентов
У нас парсер дат — не отдельная поделка, а один из инструментов в общей сети ИИ-агентов: секретарь, разборщик писем, ассистент по документам. Все они дёргают резолвер дат через единый интерфейс, чтобы «завтра к обеду» у всех означало одно и то же. Инструмент подключён по протоколу MCP — ИИ-агент MCP-клиент видит его как обычный tool, не думая о том, что внутри Python и Postgres. Заводить в каждом агенте свою логику парсинга дат — прямой путь к тому, что через полгода никто не поймёт, почему секретарь и почтовый бот расходятся на сутки.
Тесты как страховка
Снапшот-тесты на нескольких сотнях примеров: реальные сообщения из истории плюс синтетика. Каждый пример — пара «вход, ожидаемый timestamp относительно фиксированной даты now». Прогон через парсер занимает считаные секунды, гоняем в CI на каждый PR.
Особый класс тестов — граничные. Что будет, если сообщение пришло 30 декабря и человек пишет «после праздников»? Должны вернуть первый рабочий день после январских каникул. А «в среду на следующей неделе» 31 декабря? Первая среда января. Эти кейсы неочевидны и легко ломаются от рефакторинга — их лучше зафиксировать один раз и больше не думать.
Ещё одна категория — снапшоты ошибок. Каждый раз, когда менеджер правит дату вручную в CRM, исходное сообщение и правка падают в таблицу date_parse_corrections. Раз в неделю смотрю топ расхождений, дописываю правила или примеры в промпт LLM. Обратная связь от живых людей работает лучше, чем любая внутренняя интуиция.
Что получилось по ощущениям
До переделки картина была такая: каждое пятое сообщение с датой парсилось неправильно, и часть ошибок была критическая — расхождение на сутки и больше. Менеджеры перестали доверять секретарю и начали перепроверять каждую встречу, что убивало саму идею автоматизации.
После — критических промахов почти нет. Остаются редкие сообщения вида «созвонимся как обычно», где извлечь дату в принципе неоткуда, и мы честно уходим в уточняющий вопрос. Менеджеры перестали перепроверять — начали жаловаться на другие вещи, что для нас как раз хороший знак: значит, эту проблему они уже не считают проблемой.
Стоимость одного парсинга тоже заметно просела. Раньше через большую модель прогонялось всё сообщение целиком с просьбой вернуть timestamp — токенов уходило много. Теперь LLM получает короткий вход и возвращает компактную структуру, а дальнейшая арифметика — это Python, за неё никому не платим.
Мораль
LLM прекрасно понимает человеческий язык, но плохо считает дни и не знает, какое сегодня число. Если в задаче есть детерминированная составляющая — арифметика, календарь, бизнес-правила — выносите её из промпта в код. Модели оставьте то, что она умеет лучше всего: распознать намерение, классифицировать формулировку, разметить нюансы.
«После майских ближе к обеду в четверг» — это не одна задача для AI. Это пять разных задач, и только одна из них требует модели. Именно поэтому, когда мы разрабатываем ИИ-агентов для клиентов, первым делом смотрим, что из промпта можно вытащить в обычный код. Готовые ИИ-агенты хорошо продаются на слайдах, но в проде выживают только те, у которых под капотом честная инженерия, а не одна большая надежда на нейросеть.
Похожие статьи
- Как мы научили ИИ-агента на GigaChat Pro не путать живого клиента с ночным батчем
- Как мы настроили ИИ-агента для поиска в 1С: MCP-сервер вместо метаний между окнами
- Один вопрос — три темы: как мы научили ИИ-агента разбирать составные запросы
- Text2SQL для 340 таблиц: как мы собирали ИИ-агента для анализа корпоративных данных на GigaChat Pro




