Pull Request: dev-15 #17
@@ -1,428 +0,0 @@
|
||||
# План реализации фич Telegram Helper Bot
|
||||
|
||||
> Документ создан: 28 февраля 2025
|
||||
> Ветка: `dev-*`
|
||||
> Статус: План утверждён
|
||||
|
||||
---
|
||||
|
||||
## Обзор фич
|
||||
|
||||
1. **Пагинация заблокированных пользователей** — сортировка по дате бана, единая логика текста и кнопок
|
||||
2. **Обогащение сообщений пользователей админам** — данные о пользователе при обращении в поддержку
|
||||
3. **Причина бана «Последний пост»** — замена «Спам» на «Последний пост» при быстром бане из поста
|
||||
4. **Похожие посты за 24ч** — проверка на дубликаты через RAG (threshold >0.9)
|
||||
5. **Авто-публикация/отклонение по RAG** — задел на будущее (>0.8 publish, <0.4 decline)
|
||||
6. **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.py`
|
||||
- `helper_bot/keyboards/keyboards.py`
|
||||
- `helper_bot/utils/helper_func.py`
|
||||
- `helper_bot/handlers/admin/services.py`
|
||||
- `helper_bot/handlers/admin/admin_handlers.py`
|
||||
- `helper_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)
|
||||
|
||||
**Шаблон итогового сообщения для админов:**
|
||||
|
||||
```text
|
||||
👤 От: Иван Петров (@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.py`
|
||||
- `database/repositories/post_repository.py`
|
||||
- `database/repositories/blacklist_history_repository.py`
|
||||
- `database/repositories/user_repository.py`
|
||||
- `helper_bot/handlers/private/private_handlers.py`
|
||||
- `helper_bot/handlers/private/services.py`
|
||||
- `helper_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.py`
|
||||
- `tests/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-постов:
|
||||
|
||||
```python
|
||||
# Новые атрибуты (аналогично _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 на момент добавления.
|
||||
|
||||
```python
|
||||
# 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**
|
||||
|
||||
```python
|
||||
# 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`:
|
||||
|
||||
**Порядок вызовов:**
|
||||
1. Получить скоры (`_get_scores_with_error_handling`) — уже есть `rag_score`.
|
||||
2. **Перед** отправкой: вызвать `find_similar_posts(original_raw_text, 0.9, 24)`. Если RAG недоступен или ошибка — не падать, пропустить.
|
||||
3. Если `similar_count > 0`: добавить в `post_text` строку `\n\n⚠️ Похожий пост за последние 24ч (совпадение {max_similarity:.0%})`.
|
||||
4. Отправить пост в группу модерации.
|
||||
5. Сохранить в БД.
|
||||
6. **После** успешной отправки: вызвать `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. Порядок вызовов в боте
|
||||
|
||||
```text
|
||||
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.py`
|
||||
- `helper_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)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Рекомендуемый порядок реализации
|
||||
|
||||
1. Этап 0 — подготовка
|
||||
2. Этапы 1, 2, 3 — независимо, можно в любом порядке
|
||||
3. Этап 4 — после готовности RAG
|
||||
4. Этап 5 — после 4 или параллельно с 1–3
|
||||
5. Этап 5.5 — разобраться с ML Scoring Статистикой (можно параллельно)
|
||||
6. Этап 6 — перед PR
|
||||
7. Этап 7 — после ревью и мержа
|
||||
|
||||
---
|
||||
|
||||
## Ссылки на документацию проекта
|
||||
|
||||
- `.cursor/rules/my-custom-rule.mdc` — общие правила
|
||||
- `.cursor/rules/architecture.md` — архитектура
|
||||
- `.cursor/rules/handlers-patterns.md` — паттерны handlers
|
||||
- `.cursor/rules/release-notes-template.md` — шаблон Release Notes
|
||||
- `prod/.cursor/rules/my-custom-rule.mdc` — CI/CD, ветки, деплой
|
||||
@@ -1,126 +0,0 @@
|
||||
# Промпт: Реализация Этапа 4 — RAG сервис (похожие посты)
|
||||
|
||||
Скопируй этот промпт нейросети для реализации фичи «похожие посты» в RAG сервисе.
|
||||
|
||||
> Подключение к Telegram боту описано в `.cursor/implementation-plan-features.md` (Этап 4, раздел 4.2).
|
||||
|
||||
---
|
||||
|
||||
## Задача
|
||||
|
||||
Добавить в RAG сервис третью коллекцию для submitted-постов. Endpoints:
|
||||
- `POST /similar` — поиск похожих постов за N часов (threshold, text)
|
||||
- `POST /submitted` — добавление поста в коллекцию (для индексации ботом)
|
||||
|
||||
---
|
||||
|
||||
## Контекст
|
||||
|
||||
**Путь:** `/Users/andrejkatyhin/Work/PycharmProjects/rag-service`
|
||||
|
||||
RAG уже имеет:
|
||||
- `VectorStore` с `_positive_vectors` / `_negative_vectors` — для модерации (score)
|
||||
- `RAGService` с `get_embedding`, `calculate_score`, `add_positive_example`, `add_negative_example`
|
||||
- API: `POST /score`, `POST /examples/positive`, `POST /examples/negative`, `GET /stats`
|
||||
|
||||
Нужно добавить **третью коллекцию** для submitted-постов (с `created_at`, `text`, `rag_score`).
|
||||
|
||||
---
|
||||
|
||||
## 1. VectorStore (`app/storage/vector_store.py`)
|
||||
|
||||
Добавь коллекцию submitted:
|
||||
|
||||
```python
|
||||
self._submitted_vectors: list = []
|
||||
self._submitted_hashes: list = []
|
||||
self._submitted_created_at: list = [] # Unix timestamps
|
||||
self._submitted_post_ids: list = []
|
||||
self._submitted_texts: list = []
|
||||
self._submitted_rag_scores: list = []
|
||||
```
|
||||
|
||||
**Методы:**
|
||||
- `add_submitted(vector, text_hash, created_at, post_id=None, text="", rag_score=None)` — добавить пост. FIFO при превышении `max_submitted` (новый параметр в конструкторе, default 5000).
|
||||
- `find_similar_submitted(vector, threshold, hours)` — вернуть `List[dict]` с полями `similarity`, `created_at`, `post_id`, `text`, `rag_score`. Фильтр: `created_at >= now - hours*3600`. Сравнение через `np.dot` (как для positive/negative). Только те, где similarity >= threshold.
|
||||
|
||||
**Персистентность:** Сохранять/загружать submitted в отдельный файл (например, `submitted_embeddings.npz`). Поля: `vectors`, `hashes`, `created_at`, `post_ids`, `texts`, `rag_scores`. Используй `np.array(..., dtype=object)` для строк и `allow_pickle=True` при необходимости.
|
||||
|
||||
---
|
||||
|
||||
## 2. Config (`app/config.py`)
|
||||
|
||||
Добавь:
|
||||
- `RAG_MAX_SUBMITTED` (default 5000)
|
||||
- `RAG_SUBMITTED_PATH` (default `data/vectors/submitted.npz`)
|
||||
|
||||
---
|
||||
|
||||
## 3. RAGService (`app/services/rag_service.py`)
|
||||
|
||||
- `add_submitted_post(text, post_id=None, rag_score=None)` — очистить текст, получить embedding, вызвать `vector_store.add_submitted` с `created_at=int(time.time())`, `text`, `rag_score`.
|
||||
- `find_similar_posts(text, threshold=0.9, hours=24)` — получить embedding, вызвать `vector_store.find_similar_submitted`, вернуть результат.
|
||||
|
||||
---
|
||||
|
||||
## 4. Схемы (`app/schemas.py`)
|
||||
|
||||
```python
|
||||
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)
|
||||
|
||||
class SimilarPostItem(BaseModel):
|
||||
similarity: float
|
||||
created_at: int
|
||||
post_id: Optional[int] = None
|
||||
text: str
|
||||
rag_score: Optional[float] = None
|
||||
|
||||
class SimilarResponse(BaseModel):
|
||||
similar_count: int
|
||||
similar_posts: List[SimilarPostItem]
|
||||
|
||||
class SubmittedRequest(BaseModel):
|
||||
text: str = Field(..., min_length=1)
|
||||
post_id: Optional[int] = None
|
||||
rag_score: Optional[float] = None
|
||||
|
||||
class SubmittedResponse(BaseModel):
|
||||
success: bool
|
||||
message: str
|
||||
submitted_count: int
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. API (`app/api/routes.py`)
|
||||
|
||||
- `POST /api/v1/similar` — принять `SimilarRequest`, вызвать `service.find_similar_posts`, вернуть `SimilarResponse`.
|
||||
- `POST /api/v1/submitted` — принять `SubmittedRequest`, вызвать `service.add_submitted_post`, вернуть `SubmittedResponse`.
|
||||
|
||||
---
|
||||
|
||||
## 6. Автосохранение
|
||||
|
||||
В `autosave_loop` или при `save_vectors` — сохранять submitted-коллекцию. При загрузке — загружать submitted из файла в `_load_from_disk` или отдельном методе.
|
||||
|
||||
---
|
||||
|
||||
## Требования
|
||||
|
||||
- Не ломать существующий функционал: score, examples, stats работают как раньше.
|
||||
- Следовать стилю кода проекта (Black, isort, type hints).
|
||||
- Добавить тесты для новых методов и endpoints.
|
||||
|
||||
---
|
||||
|
||||
## Файлы для изменения
|
||||
|
||||
- `app/storage/vector_store.py`
|
||||
- `app/services/rag_service.py`
|
||||
- `app/api/routes.py`
|
||||
- `app/schemas.py`
|
||||
- `app/config.py`
|
||||
- `app/main.py` (при необходимости — lifespan для autosave submitted)
|
||||
Reference in New Issue
Block a user