Менеджер отзывов · модуль контент-завода
Машина отвечает на отзывы, но не на все
Четыре заведения, тысячи отзывов на Яндексе и в 2ГИС. Руками на каждый не ответишь, а если молчать, гости уходят. Я собрал систему, которая сама собирает отзывы, понимает их тон, пишет ответ голосом заведения и публикует его, если это безопасно.
2 470отзывов с Яндекса собрано в первый же полный проход
4заведения, у каждого свой голос и подпись
30 миншаг цикла: сбор, разбор, черновики, публикация, жалобы
0автоответов на негатив, на него отвечает человек
01 · Устройство
Пять шагов
за один проход
У каждого шага есть лимит на проход, иначе один большой завал съел бы лимиты модели на весь день.
| Шаг | Что происходит | Лимит за проход |
| Сбор | Яндекс собирается через браузер (Playwright), 2ГИС через публичный виджет без логина | 50 |
| Разбор | Gemini определяет тон, есть ли реальная проблема, нужен ли владелец, есть ли повод для жалобы | 8 |
| Черновик | Ответ голосом конкретного заведения, со своей подписью | 6 |
| Публикация | Позитивные и нейтральные уходят автоматически, если не нужен человек | 10 |
| Жалоба | Подаётся на «пустой» негатив без реального опыта, по шаблону через кабинет площадки | 10 |
PythonGemini 2.5 FlashPlaywrightSQLiteFernetЯндекс Бизнес2ГИС
Инструкции для модели на английском, хотя отзывы русские: так она точнее держит формат ответа. Жалобы собираются по шаблону, без модели. Творчество там не нужно, а квота целее.
Пароли от кабинетов площадок лежат в базе зашифрованными. Для жалоб есть стоп-кран: переключаешь один флаг в панели, и система перестаёт их подавать, а очередь продолжает копиться.
02 · Граница
Когда машине
можно отвечать самой
Писать ответы модель умеет. Сложнее решить, когда её ответ можно отправить без человека. Это условие ниже, дословно из кода.
01
Жалуемся только на пустой негатив
- Правило
- Жаловаться можно только на отзыв с одной-двумя звёздами, если у жалобы есть основание, в отзыве нет реальной проблемы и владельцу смотреть не нужно. Все три условия сразу.
- Откуда оно
- Появилось после 22 июня. Тогда система отправила в жалобы 106 реальных развёрнутых негативов одного ресторана, хотя их нужно было читать.
reviews_manager/orchestrator.py · дословно
# Жалоба ТОЛЬКО на «пустой» негатив без опыта. Развёрнутый реальный негатив
# (has_real_issues) или требующий владельца (needs_human_review) → НЕ жалуемся,
# а отвечаем/на ревью (корень инцидента 22.06: 106 реальных негативов Makhall в жалобы).
if (cls.complaint_ground and cls.complaint_ground != "concerts"
and not cls.has_real_issues and not cls.needs_human_review):
item.status = "needs_complaint"
02
Автоответ ждал флага, который модель почти не ставила
- Симптом
- Система писала хорошие ответы на позитив, но ни один не уходил сам.
- Причина
- Условие требовало, чтобы модель отметила ответ как «безопасный». А она перестраховывалась: из 367 позитивных отзывов безопасными отметила только 72.
Проверку перевёл на другой флаг, «нужен ли человек». Угрозы, инциденты и подозрение на фейк отсекает как раз он. Сразу после этого одобрилось 183 черновика, которые копились без движения.
reviews_manager/orchestrator.py · дословно
# ГЕЙТ безопасности = needs_human_review (он отсекает угрозы/инциденты/
# подозрение на фейк). Флаг is_safe_to_auto_reply НЕ требуем: классификатор
# ставит его в False даже на нормальных позитивах (перестраховка) → авто не
# срабатывало. На позитив/нейтрал без needs_human ответ безопасен по определению.
auto = False
if item.sentiment == "positive" and config.auto_publish_positive() and not cls.needs_human_review:
auto = True
elif item.sentiment == "neutral" and config.auto_publish_neutral() and not cls.needs_human_review:
auto = True
Что держал один флаг
Позитивных отзывовв выборке
367
«Безопасных»по мнению модели
72
Одобрено сразупосле смены условия
183
Черновики, которые копились без движения, одобрились, как только проверка перешла на флаг «нужен ли человек».
03 · Разбор
Ещё два урока
Оба про то, что данные устроены не так, как кажется.
03
Гостинице отвечала «Команда клуба»
- Симптом
- На отзыв о номере в гостинице ушёл ответ с подписью ночного клуба.
- Причина
- Под одним аккаунтом Яндекса жили семь карточек разных заведений, а сборщик записывал все отзывы в одно.
Сделал карту «карточка → заведение» и отдельное поле в базе. Задним числом переразметил 104 отзыва.
04
Модель держала базу 200 секунд
- Симптом
- Во время разбора отзывов остальные службы завода получали «база занята».
- Причина
- Транзакция оставалась открытой, пока модель пишет ответ. А писать в SQLite может только кто-то один, остальные ждут.
Коммит теперь идёт до вызова модели. Транзакция закрывается раньше, и база занята меньше.