# ТЗ: Семантический поиск и RAG по данным Telegram (Сноубайк Россия) ## Общее описание Система семантического поиска и RAG (Retrieval-Augmented Generation) по 155K сообщений Telegram-группы «Сноубайк Россия». Гибридный подход: Meilisearch (ключевые слова) + ChromaDB (семантика) + Sonnet (суммаризация). **Цель:** ответы на вопросы типа «какие масла рекомендуют для Polaris 850?» — не найти сообщение, а получить агрегированный ответ на основе всех данных. --- ## Исходные данные **Расположение:** `/home/node/.openclaw/workspace/data/telegram-collector/raw/1242788123/` **Структура:** ``` raw/1242788123/ ├── meta.json — метаданные канала (12 топиков) ├── 1/ — Основная (92K сообщений, 1.3 ГБ) ├── 63155/ — Барахолка (1.5K, 267 МБ) ├── 63467/ — Техничка (21.6K, 306 МБ) ├── 63469/ — Экип (3.6K, 57 МБ) ├── 64805/ — Обзоры (11K, 166 МБ) ├── 76611/ — Инструкции и 3D (96 msgs, 386 МБ) ├── 97494/ — Электрички (1.6K, 32 МБ) ├── 99795/ — Китай (15.7K, 213 МБ) ├── 103316/ — ОФФТОП (5.8K, 63 МБ) ├── 103317/ — Локации (1.6K, 55 МБ) ├── 117112/ — Опросы (24 msgs) └── 161840/ — Соревнования (24 msgs, 11 МБ) ``` **Формат сообщения (batch_NNNN.json):** ```json { "id": 165211, "date": "2026-03-24T17:55:39Z", "text": "Текст сообщения", "from_id": 5774548432, "reply_to_msg_id": null, "reply_to_top_id": null, "quote_text": null, "edit_date": null, "pinned": false, "media": null } ``` **Общий объём:** 2.9 ГБ, 155K сообщений, 12 топиков **Обновление:** инкрементальное, ежедневно в 00:00 МСК (cron `860e23a4`) --- ## Архитектура ``` Запрос пользователя │ ▼ ┌─────────────┐ │ Flask API │ ← HTTP сервер └──────┬──────┘ │ ┌─────┴─────┐ ▼ ▼ ┌─────────┐ ┌─────────┐ │Meili- │ │ChromaDB │ ← два индекса параллельно │search │ │(векторы)│ └────┬────┘ └────┬────┘ │ │ └─────┬─────┘ ▼ ┌─────────────┐ │ Объединение │ ← reranking контекста │ контекста │ └──────┬──────┘ ▼ ┌─────────────┐ │ Sonnet │ ← суммаризация + ответ │ (LLM) │ └──────┬──────┘ ▼ Ответ пользователю ``` --- ## Компоненты ### 1. Meilisearch (полнотекстовый поиск) **Назначение:** поиск по ключевым словам, допускающий опечатки **Роль:** быстрый отсев релевантных сообщений по точным словам **Дocker:** `getmeili/meilisearch:latest`, порт 7700 **Индекс:** `snowbike_messages` **Поля индекса:** - `id` — ID сообщения (уникальный) - `text` — текст сообщения (основное поле для поиска) - `date` — дата сообщения - `topic_id` — ID топика - `topic_title` — название топика - `from_id` — ID автора - `reply_to_msg_id` — ID сообщения, на которое отвечаем (для цепочек) **Настройки индекса:** - `filterableAttributes`: `["topic_id", "date"]` - `sortableAttributes`: `["date"]` - `typoTolerance`: `true` (по умолчанию) - `searchableAttributes`: `["text"]` - `stopWords`: `["и", "в", "на", "с", "для", "это", "что", "как", "не", "а"]` (русские стоп-слова) **Размер индекса:** ~200 МБ на 155K сообщений ### 2. ChromaDB (семантический поиск) **Назначение:** поиск по смыслу (не по словам) **Роль:** найти ответы, которые говорят о том же, но другими словами **Пакет:** `chromadb` (pip), без Docker **Коллекция:** `snowbike_embeddings` **Embeddings:** - **Модель:** `sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2` - Бесплатная, локальная, 384-мерные вектора - Поддержка русского языка - Размер модели: ~470 МБ (скачается при первом запуске) - Скорость: ~100 сообщений/сек на CPU - **Альтернатива:** OpenAI `text-embedding-3-small` ($0.02/1M токенов, ~$0.50 за все данные) **Структура записи в ChromaDB:** ``` id: str(message_id) embedding: List[float] (384-мерный вектор) metadata: { "topic_id": int, "topic_title": str, "date": str, "from_id": int } document: str(text) ``` **Размер коллекции:** ~500 МБ (155K × 384 × 4 байта + metadata) ### 3. Sonnet (суммаризация) **Назначение:** агрегация контекста и формирование ответа **Модель:** `openrouter/anthropic/claude-sonnet-4.6` (через OpenRouter) **Роль:** на основе найденных сообщений — сформировать полезный ответ **Промпт-шаблон:** ``` Ты — помощник по сноубайкам. На основе найденных сообщений ответь на вопрос. Если информации недостаточно — скажи об этом. Всегда указывай, откуда взята информация (дата, автор, топик). Вопрос: {question} Найденные сообщения: {context} Ответ: ``` --- ## Pipeline ### Шаг 1: Парсинг сырых данных **Скрипт:** `scripts/parse_messages.py` **Вход:** `/data/telegram-collector/raw/1242788123/{topic_id}/batch_*.json` **Выход:** плоский список сообщений (JSON lines) ```python for each topic_id in raw/1242788123/: for each batch_NNNN.json in topic_id/: for each message in batch: yield { "id": message["id"], "text": message["text"], "date": message["date"], "topic_id": topic_id, "topic_title": meta["topics"][topic_id], "from_id": message["from_id"], "reply_to_msg_id": message["reply_to_msg_id"], "media": bool(message["media"]) } ``` ### Шаг 2: Индексация в Meilisearch **Скрипт:** `scripts/index_meilisearch.py` **Вход:** парсированные сообщения **Действие:** batch upload в Meilisearch (по 1000 сообщений за раз) **Таймаут:** ~5 минут на все 155K сообщений ### Шаг 3: Генерация embeddings и запись в ChromaDB **Скрипт:** `scripts/index_chromadb.py` **Вход:** парсированные сообщения **Действие:** 1. Загрузить модель sentence-transformers 2. Сгенерировать embedding для каждого текста 3. Записать в коллекцию ChromaDB **Оптимизация:** - Батчинг: по 32 сообщения за раз - Фильтрация пустых сообщений (text = "") - Skip медиа-сообщений без текста **Время:** ~25 минут на CPU, ~5 минут на GPU ### Шаг 4: Поиск (основной flow) ```python def search(query: str, topic_ids: list[int] = None): # 1. Meilisearch — точные совпадения meili_results = meili_index.search(query, limit=20) # 2. ChromaDB — семантический поиск query_embedding = model.encode(query) chroma_results = collection.query( query_embeddings=[query_embedding], n_results=20 ) # 3. Объединение и дедупликация all_results = merge_and_deduplicate(meili_results, chroma_results) # 4. Реранкинг (по релевантности + дате) ranked = rerank(all_results, query) # 5. Формирование контекста context = format_context(ranked[:10]) # 6. LLM ответ answer = sonnet_summarize(query, context) return answer, sources ``` ### Шаг 5: API endpoint **Скрипт:** `server.py` **Стек:** Flask, порт 5557 **URL:** `/search?q={query}&topics={topic_ids}&limit={limit}` **Ответ:** ```json { "query": "какие масла рекомендуют для Polaris 850", "answer": "Для Polaris 850 рекомендуют...", "sources": [ {"id": 123456, "date": "2026-01-15", "topic": "Техничка", "author": "Иван"}, {"id": 789012, "date": "2026-02-20", "topic": "Техничка", "author": "Петр"} ], "count": 20, "time_ms": 1500 } ``` --- ## Технологии и зависимости ### Python пакеты (requirements.txt) ``` meilisearch==0.31.0 chromadb==0.4.22 sentence-transformers==2.3.1 flask==3.0.0 ``` ### Docker ``` getmeili/meilisearch:latest — порт 7700 ``` ### LLM API - OpenRouter (Sonnet 4.6) — через существующий ключ в `.env` --- ## Расположение файлов ``` tasks/snowbike-rag/ ├── TZ.md — это документ ├── scripts/ │ ├── parse_messages.py — парсинг сырых данных │ ├── index_meilisearch.py — загрузка в Meilisearch │ ├── index_chromadb.py — embeddings + ChromaDB │ └── search.py — поиск + LLM ├── server.py — Flask API ├── requirements.txt └── docker-compose.yml — Meilisearch ``` **Данные (только чтение):** - Сырые: `/data/telegram-collector/raw/1242788123/` - Мета: `/data/telegram-collector/raw/1242788123/meta.json` --- ## Инкрементальное обновление Ежедневно после cron-загрузки новых сообщений: 1. Парсинг только новых batch-файлов 2. Добавление в Meilisearch (add/update) 3. Генерация embeddings и добавление в ChromaDB 4. Индекс обновляется без прерывания поиска --- ## Ограничения - **Данные:** только текстовые сообщения, медиа не индексируются - **Embeddings:** локальная модель, ~25 минут на CPU (первый прогон) - **LLM:** стоимость ~$0.005 за запрос (Sonnet 4.6, ~5K токенов контекста) - **Память:** ~700 МБ для Meilisearch + ~500 МБ для ChromaDB + ~500 МБ для модели - **Язык:** данные на русском, модель многоязычная --- ## Стоимость - **Индексация:** бесплатно (локальная модель) - **Поиск (embeddings):** бесплатно (локальная модель) - **LLM ответ:** ~$0.005 за запрос (Sonnet 4.6) - **Docker:** бесплатно (Meilisearch community) --- ## Следующие шаги 1. Dev-агент: создать скрипты парсинга + индексации 2. Настроить Docker Meilisearch 3. Протестировать поиск на 5-10 запросах 4. Добавить Flask API 5. Настроить инкрементальное обновление