24 KiB
План реализации фич Telegram Helper Bot
Документ создан: 28 февраля 2025
Ветка:dev-*
Статус: План утверждён
Обзор фич
- Пагинация заблокированных пользователей — сортировка по дате бана, единая логика текста и кнопок
- Обогащение сообщений пользователей админам — данные о пользователе при обращении в поддержку
- Причина бана «Последний пост» — замена «Спам» на «Последний пост» при быстром бане из поста
- Похожие посты за 24ч — проверка на дубликаты через RAG (threshold >0.9)
- Авто-публикация/отклонение по RAG — задел на будущее (>0.8 publish, <0.4 decline)
- ML Scoring Статистика — восстановить полный вывод (модель, примеры, device) вместо fallback (API URL, статус)
Решения по уточняющим вопросам
| Вопрос | Решение |
|---|---|
| Пагинация | Единый источник данных, единый items_per_page |
| message_id при forward | Осознанный workaround (n+1). При переходе на send — использовать returned_message.message_id |
| RAG similar | Новый endpoint в RAG. Нужна отдельная коллекция для submitted-постов (не позитив/негатив) |
| Скор для авто-решений | Только rag_score |
| Ветки | dev-* |
Проверка: message_id при forward
Telegram Bot API: forwardMessage возвращает объект Message — это новое сообщение в целевом чате со своим message_id. Telegram присваивает message_id в целевом чате — он не обязан быть n+1 от исходного.
Если всё работает — возможно, бот единственный отправитель в group_for_message, и id часто идут подряд. Рекомендация: при переходе на send_message обязательно сохранять message_id из возвращаемого объекта.
RAG: коллекция для похожих постов
- Позитив/негатив — примеры для скоринга модерации
- Похожие посты — сравнение с другими submitted-постами за 24ч
Endpoint /similar должен искать по отдельной коллекции submitted-постов (с created_at), а не по позитиву/негативу. Нужно:
- Новая коллекция в RAG (например,
posts_submitted) с полями:text,vector,created_at,post_id - Endpoint
POST /similar:{"text": "...", "threshold": 0.9, "hours": 24}→ список похожих постов - При каждом suggest — добавлять пост в эту коллекцию (или вызывать endpoint RAG для индексации)
Пошаговый план реализации
Этап 0: Подготовка
| Шаг | Действие |
|---|---|
| 0.1 | Создать ветку dev-N (N — следующий номер) от main |
| 0.2 | Убедиться, что локально проходит make code-quality (или isort, black, flake8, pytest) |
Этап 1: Пагинация заблокированных пользователей
| Шаг | Действие |
|---|---|
| 1.1 | В BlacklistRepository: добавить ORDER BY created_at DESC в get_all_users и get_all_users_no_limit |
| 1.2 | Ввести единый items_per_page = 9 в create_keyboard_with_pagination и во всех местах пагинации |
| 1.3 | Создать единую функцию get_banned_users_data(page, items_per_page) в AdminService или helper_func, возвращающую (message_text, buttons_list) для заданной страницы |
| 1.4 | Обновить get_banned_users_list и get_banned_users_buttons — использовать общий источник и пагинацию |
| 1.5 | Обновить admin_handlers.get_banned_users и callback_handlers.change_page — вызывать единую функцию с page |
| 1.6 | Прогнать тесты test_keyboards_and_filters, test_callback_handlers, test_admin_handlers |
Затронутые файлы:
database/repositories/blacklist_repository.pyhelper_bot/keyboards/keyboards.pyhelper_bot/utils/helper_func.pyhelper_bot/handlers/admin/services.pyhelper_bot/handlers/admin/admin_handlers.pyhelper_bot/handlers/callback/callback_handlers.py
Этап 2: Обогащение сообщений пользователей админам
| Шаг | Действие |
|---|---|
| 2.1 | Добавить в AsyncBotDB/репозитории методы: get_posts_count_by_author, get_last_post_by_author, get_ban_history_count, get_last_ban_info, get_user_date_added |
| 2.2 | Добавить в BlacklistHistoryRepository методы для истории банов (количество, последний бан) |
| 2.3 | В UserService или отдельном сервисе: метод format_user_message_for_admins(user_id, message_text) — собирает текст с данными пользователя |
| 2.4 | В resend_message_in_group_for_message: заменить forward на send_message с обогащённым текстом (имя, ник, id, посты, последний пост, баны, дата регистрации) |
| 2.5 | Сохранять message_id из результата send_message в user_messages (вместо message.message_id + 1) |
| 2.6 | Проверить, что get_user_by_message_id по-прежнему возвращает user_id для ответа админа |
| 2.7 | Обновить/добавить тесты для resend_message_in_group_for_message и AdminReplyService |
Данные для обогащения:
- Количество постов от пользователя
- Последний пост пользователя (текст)
- Количество банов
- Дата и причина последнего бана (если был)
- Дата создания пользователя в БД (первый контакт с ботом)
- Имя, ник, id пользователя (обязательно при send вместо forward)
Шаблон итогового сообщения для админов:
👤 От: Иван Петров (@ivan_petrov) | ID: 123456789
📊 Постов в базе: 5
📝 Последний пост: "Привет, хочу поделиться мыслями о..."
📅 В боте с: 15.01.2025
🚫 Банов: 2
Последний: 20.02.2025, причина «Спам», истёк 27.02.2025
---
**Сообщение пользователя:**
**Почему удалили мой пост?**
Правила форматирования:
- Секции «Банов: 0» и «Последний: …» — показывать только если были баны
- «Последний пост» — обрезать до ~80 символов + «…» если длиннее; если постов нет — «Нет постов»
- Даты в формате
DD.MM.YYYYилиDD.MM.YYYY HH:MMдля разбана - Разделитель
---перед текстом сообщения пользователя
Для тестов: использовать этот шаблон как эталон; проверять наличие всех секций, порядок полей, экранирование HTML в имени/нике/тексте.
Затронутые файлы:
database/async_db.pydatabase/repositories/post_repository.pydatabase/repositories/blacklist_history_repository.pydatabase/repositories/user_repository.pyhelper_bot/handlers/private/private_handlers.pyhelper_bot/handlers/private/services.pyhelper_bot/handlers/group/services.py
Этап 3: Причина бана «Последний пост»
| Шаг | Действие |
|---|---|
| 3.1 | В callback/services.py в ban_user_from_post: заменить message_for_user="Спам" на message_for_user="Последний пост" |
| 3.2 | Обновить тесты, где ожидается «Спам» |
Затронутые файлы:
helper_bot/handlers/callback/services.pytests/test_callback_services.py(если есть)
Этап 4: Похожие посты (RAG + бот)
Разделение работ:
- RAG сервис — промпт в
.cursor/prompt-stage-4-similar-posts.md - Подключение в боте — ниже, раздел 4.2
4.0. Текущая архитектура RAG сервиса
Путь: /Users/andrejkatyhin/Work/PycharmProjects/rag-service
| Компонент | Описание |
|---|---|
VectorStore (app/storage/vector_store.py) |
In-memory хранилище векторов. _positive_vectors / _negative_vectors — для модерации. Персистентность: positive_embeddings.npy, negative_embeddings.npy или vectors.npz. Косинусное сходство через np.dot для нормализованных векторов. |
RAGService (app/services/rag_service.py) |
Модель sentence-transformers/all-MiniLM-L12-v2 (384 dim). get_embedding(text), calculate_score(text), add_positive_example, add_negative_example. |
API (app/api/routes.py) |
POST /api/v1/score, POST /api/v1/examples/positive, POST /api/v1/examples/negative, GET /api/v1/stats, POST /api/v1/warmup, GET/PUT /api/v1/scoring/params. |
Config (app/config.py) |
RAG_VECTORS_PATH, RAG_MAX_EXAMPLES, vector_dim=384. |
Важно: Позитив/негатив — отдельные коллекции без created_at. Для похожих постов нужна третья коллекция с временными метками.
4.1. Работы в RAG сервисе
4.1.1. Расширить VectorStore
Файл: app/storage/vector_store.py
Добавить третью коллекцию для submitted-постов:
# Новые атрибуты (аналогично _positive_vectors)
self._submitted_vectors: list = []
self._submitted_hashes: list = []
self._submitted_created_at: list = [] # Unix timestamps
self._submitted_post_ids: list = [] # post_id из бота
self._submitted_texts: list = [] # текст поста (для возврата в similar)
self._submitted_rag_scores: list = [] # rag_score на момент добавления
Новые методы:
| Метод | Описание |
|---|---|
add_submitted(vector, text_hash, created_at, post_id=None, text="", rag_score=None) |
Добавить пост в коллекцию submitted. FIFO при превышении max_submitted (новый лимит, например 5000). |
find_similar_submitted(vector, threshold, hours) |
Возвращает: List[dict] с полями similarity, created_at, post_id, text, rag_score. Фильтрует по created_at >= now - hours*3600. Сравнивает с _submitted_vectors через np.dot. Возвращает только те, где similarity >= threshold. |
Персистентность: Добавить в save_to_disk / _load_from_disk сохранение submitted-коллекции. Файл submitted_embeddings.npz с полями: vectors, hashes, created_at, post_ids, texts, rag_scores.
Конфиг: Добавить RAG_MAX_SUBMITTED (default 5000), RAG_SUBMITTED_PATH (путь к файлу submitted).
4.1.2. Расширить RAGService
Файл: app/services/rag_service.py
| Метод | Описание |
|---|---|
add_submitted_post(text, post_id=None, rag_score=None) |
Очистить текст, получить embedding, add_submitted в vector_store. Сохраняет text и rag_score для возврата в similar. Вызывается при каждом suggest (бот передаёт rag_score из скоринга). |
find_similar_posts(text, threshold=0.9, hours=24) |
Получить embedding, вызвать vector_store.find_similar_submitted. Вернуть список похожих с полями: similarity, created_at, post_id, text, rag_score. |
4.1.3. Добавить API endpoint
Файл: app/api/routes.py
POST /api/v1/similar
Возвращает: количество похожих постов, текст каждого, similarity (косинусное сходство), присвоенный rag_score на момент добавления.
# Request
class SimilarRequest(BaseModel):
text: str = Field(..., min_length=1)
threshold: float = Field(default=0.9, ge=0.0, le=1.0)
hours: int = Field(default=24, ge=1, le=168) # 1ч–7дней
# Response
class SimilarPostItem(BaseModel):
similarity: float # косинусное сходство (0.0–1.0)
created_at: int # Unix timestamp
post_id: Optional[int] = None
text: str # текст похожего поста
rag_score: Optional[float] = None # rag_score на момент добавления
class SimilarResponse(BaseModel):
similar_count: int
similar_posts: List[SimilarPostItem]
POST /api/v1/submitted
# Request
class SubmittedRequest(BaseModel):
text: str = Field(..., min_length=1)
post_id: Optional[int] = None
rag_score: Optional[float] = None # для возврата в similar
# Response
class SubmittedResponse(BaseModel):
success: bool
message: str
submitted_count: int
Примечание: POST /submitted вызывается ботом при каждом suggest (после сохранения поста в БД). POST /similar вызывается ботом перед отправкой в группу модерации — чтобы проверить, есть ли похожие посты за последние сутки.
4.1.4. Схемы и исключения
Файл: app/schemas.py — добавить SimilarRequest, SimilarResponse, SimilarPostItem, SubmittedRequest, SubmittedResponse.
Файл: app/exceptions.py — при необходимости добавить SubmittedStoreError (если коллекция пуста и т.п.).
4.1.5. Автоочистка submitted (опционально)
В autosave_loop или отдельно: периодически удалять из _submitted_* записи старше N часов (например, 48), чтобы не раздувать память.
4.2. Подключение в Telegram Helper Bot
RAG сервис реализуется отдельно (промпт:
.cursor/prompt-stage-4-similar-posts.md). Ниже — интеграция в бота.
4.2.1. RagApiClient (helper_bot/services/scoring/rag_client.py)
Добавить методы:
find_similar_posts(text, threshold=0.9, hours=24)— POST на{api_url}/similar, body{"text": text, "threshold": threshold, "hours": hours}. ВернутьSimilarResponse(или dataclass/dict) илиNoneпри ошибке.add_submitted_post(text, post_id=None, rag_score=None)— POST на{api_url}/submitted, body{"text": text, "post_id": post_id, "rag_score": rag_score}. При ошибке — логировать, не падать.
Оба метода проверяют self._enabled и не делают запросы, если RAG отключён.
4.2.2. PostService (helper_bot/handlers/private/services.py)
В _process_post_background и _process_media_group_background:
Порядок вызовов:
- Получить скоры (
_get_scores_with_error_handling) — уже естьrag_score. - Перед отправкой: вызвать
find_similar_posts(original_raw_text, 0.9, 24). Если RAG недоступен или ошибка — не падать, пропустить. - Если
similar_count > 0: добавить вpost_textстроку\n\n⚠️ Похожий пост за последние 24ч (совпадение {max_similarity:.0%}). - Отправить пост в группу модерации.
- Сохранить в БД.
- После успешной отправки: вызвать
add_submitted_post(original_raw_text, sent_message.message_id, rag_score)— в фоне.rag_scoreиз шага 1.
Важно: Проверка similar — до добавления текущего поста в submitted.
4.2.3. Доступ к RagApiClient
RagApiClient создаётся через ScoringManager или BaseDependencyFactory. PostService должен иметь доступ к rag_client (или scoring_manager). При необходимости добавить методы в ScoringManager как прокси к RAG.
4.2.4. Обработка ошибок
При недоступности RAG — не падать, не добавлять предупреждение и не индексировать.
4.3. Порядок вызовов в боте
1. Пользователь отправляет пост
2. PostService._process_post_background:
a) Получить скоры (rag_score, confidence, ...)
b) find_similar_posts(text, 0.9, 24) — есть ли похожие? (возвращает count, text, similarity, rag_score)
c) Если да — добавить предупреждение в post_text
d) Отправить пост в группу модерации
e) Сохранить в БД (add_post)
f) add_submitted_post(text, message_id, rag_score) — индексировать в RAG
Важно: Проверка similar делается до добавления текущего поста в submitted, иначе пост будет похож сам на себя.
4.4. Затронутые файлы
| Репозиторий | Файлы |
|---|---|
| rag-service | app/storage/vector_store.py, app/services/rag_service.py, app/api/routes.py, app/schemas.py, app/config.py, app/main.py (описание в docs) |
| telegram-helper-bot | helper_bot/services/scoring/rag_client.py, helper_bot/handlers/private/services.py |
Этап 5: Авто-публикация/отклонение (задел на будущее)
| Шаг | Действие |
|---|---|
| 5.1 | В PostService._process_post_background: после получения rag_score проверять пороги |
| 5.2 | Если rag_score >= 0.8: не показывать кнопки модерации, сразу публиковать (или вызывать логику publish) |
| 5.3 | Если rag_score <= 0.4: сразу отклонять (decline) |
| 5.4 | Добавить флаги в .env (например, AUTO_PUBLISH_ENABLED, AUTO_DECLINE_ENABLED) — по умолчанию false |
| 5.5 | Реализацию оформить как выключенную по умолчанию; включение — через конфиг |
Затронутые файлы:
helper_bot/handlers/private/services.pyhelper_bot/config/или.env
Этап 5.5: ML Scoring Статистика — восстановить полный вывод
Проблема: Раньше «📊 ML Статистика» показывала детали (модель, device, кол-во примеров, размерность). Теперь только API URL и статус.
Причина: Бот использует fallback (get_stats_sync) когда RagApiClient.get_stats() возвращает пустой результат. Это происходит при:
- 401/403 (ошибка авторизации)
- таймауте или ошибке соединения
- неверном формате ответа от API
Задачи:
| Шаг | Действие |
|---|---|
| 5.5.1 | Проверить, что RAG API GET /stats доступен с бота (сеть, CORS, API key). |
| 5.5.2 | Убедиться, что RagApiClient.get_stats() передаёт заголовок X-API-Key и корректно обрабатывает 200. |
| 5.5.3 | Проверить контракт ответа: RAG возвращает model_name, model_loaded, device, vector_store (positive_count, negative_count, total_count, vector_dim, max_examples). |
| 5.5.4 | При ошибке API — логировать причину (status, body) и при необходимости улучшить fallback-сообщение (например, «API недоступен: …»). |
| 5.5.5 | Добавить тесты для get_ml_stats с моком API (успешный ответ и fallback). |
Затронутые файлы:
helper_bot/handlers/admin/admin_handlers.py(get_ml_stats)helper_bot/services/scoring/rag_client.py(get_stats, get_stats_sync)helper_bot/services/scoring/scoring_manager.py(get_stats)
Этап 6: Тесты и качество кода
| Шаг | Действие |
|---|---|
| 6.1 | Прогнать все тесты: pytest tests/ -v |
| 6.2 | make code-quality (или isort, black, flake8) |
| 6.3 | При необходимости обновить моки и фикстуры |
Этап 7: Release Notes и деплой
| Шаг | Действие |
|---|---|
| 7.1 | Создать docs/RELEASE_NOTES_DEV-N.md по шаблону из .cursor/rules/release-notes-template.md |
| 7.2 | Коммиты в формате: feat:, fix:, refactor: |
| 7.3 | Push в dev-N → CI (тесты, code quality) |
| 7.4 | Создать/обновить PR в main |
| 7.5 | После мержа — деплой по deploy.yml (если настроен в prod) |
Зависимости между этапами
Этап 0 → Этап 1, 2, 3 (можно параллельно)
Этап 1, 2, 3 → Этап 6
Этап 4 зависит от RAG (отдельный сервис)
Этап 5 можно делать после 4 или независимо
Этап 5.5 — независимо (можно параллельно с 1–3)
Рекомендуемый порядок реализации
- Этап 0 — подготовка
- Этапы 1, 2, 3 — независимо, можно в любом порядке
- Этап 4 — после готовности RAG
- Этап 5 — после 4 или параллельно с 1–3
- Этап 5.5 — разобраться с ML Scoring Статистикой (можно параллельно)
- Этап 6 — перед PR
- Этап 7 — после ревью и мержа
Ссылки на документацию проекта
.cursor/rules/my-custom-rule.mdc— общие правила.cursor/rules/architecture.md— архитектура.cursor/rules/handlers-patterns.md— паттерны handlers.cursor/rules/release-notes-template.md— шаблон Release Notesprod/.cursor/rules/my-custom-rule.mdc— CI/CD, ветки, деплой