vk_hackathon/doc/prompt.md

18 KiB
Raw Blame History

Prompt For Next Refactor Pass

Считай этот файл каноническим планом работ по репозиторию. Старые заметки в doc/todo.md, doc/todo_and_pipeline.md, doc/todo_people.md и doc/ai_update.md можно использовать как справку, но не как основной источник правды.

Контекст

В репозитории два сервиса:

  • index строит чанки для индексации
  • search получает вопрос и возвращает message_ids

Контракты POST /index, POST /sparse_embedding и POST /search менять нельзя.

Дополнительный контекст по текущему состоянию:

  • worktree уже грязный, не откатывай чужие правки
  • main отстает от origin/main на 3 коммита
  • .ai_update/ должен быть shared-state каталогом, но сейчас он игнорируется через .gitignore
  • реальная логика почти целиком живет в index/main.py и search/main.py, поэтому следующий шаг должен быть не только про качество поиска, но и про разбиение кода на понятные модули

Что уже очевидно сломано или недоделано

  • В search/main.py есть реальный баг: must_conditions: [] не создает список. При запросах с date_range или asker код упадет на .append(). Исправить первым коммитом.
  • Поиск использует только question.text, хотя схема уже содержит search_text, variants, hyde, keywords, entities, date_mentions, date_range.
  • После rerank теряется хвост retrieval-кандидатов.
  • Финальный список message_ids не дедуплицируется, не агрегируется по score и не ограничивается top-50, хотя метрика считается именно на K=50.
  • Внешние HTTP-вызовы dense/rerank не имеют нормальных timeout и retry.

P1. Перестроить индексацию под структуру чата

  • Сейчас index режет текст по символам, а не по сообщениям.
  • Overlap строится по хвосту строки, а не по границам сообщений.
  • page_content, dense_content и sparse_content сейчас одинаковые, хотя должны выполнять разные задачи.
  • В индекс почти не попадают важные сигналы: sender_id, mentions, file_snippets, member_event, thread_sn, маркеры quote и forward.

P2. Начать использовать metadata осмысленно

  • В README прямо указаны participants, mentions, contains_forward, contains_quote.
  • В search/main.py есть модель ChunkMetadata, но retrieval почти не использует metadata для фильтрации и буста.
  • Нужно поддержать фильтры/бусты по людям, mentions, thread, дате, quote/forward и не ломать контракт ответа.

P3. Привести инфраструктуру и документацию в порядок

  • docker-compose.yml, README.md, Makefile и doc/upload_to_docker.md частично расходятся по сценарию запуска и сборки.
  • В Makefile нет --platform linux/amd64, хотя в документации на загрузку образов это требуется.
  • В репозитории нет нормального bin/codex-start, хотя workflow на него ссылается.
  • Планирование размазано по нескольким файлам вместо одного документа.

Что нужно сделать

Сначала разбей search/main.py на несколько логических частей. Минимально:

  • search/config.py: env, валидация конфигурации, auth-настройки
  • search/schemas.py: pydantic-модели запросов и ответов
  • search/query_builder.py: сборка dense/sparse запросов из question
  • search/retrieval.py: Qdrant prefetch, filters, fusion
  • search/rerank.py: вызов reranker и работа с rerank-кандидатами
  • search/aggregation.py: дедуп message ids, score aggregation, top-50
  • search/main.py: только wiring FastAPI и вызовы сервисных функций

Что должно измениться по логике:

  • основной dense query: question.search_text.strip() с fallback на question.text.strip()
  • дополнительные dense query: question.variants, question.hyde
  • основной sparse query: keywords, а если их нет, то нормализованный базовый запрос
  • entity-сигналы: people, emails, documents, names, links использовать как lexical boost или metadata filter
  • date_range и, по возможности, date_mentions использовать для фильтрации по metadata.start / metadata.end
  • retrieval должен возвращать расширенный пул кандидатов
  • rerank должен сортировать top-N, но не уничтожать полностью хвост retrieval
  • финальный ответ должен:
    • агрегировать score по message_id
    • удалять дубликаты
    • ограничиваться top-50

Отдельно:

  • убери импорт-тайм побочный эффект validate_required_env() и переведи его в более тестируемую точку старта
  • добавь явные timeout для httpx.AsyncClient
  • добавь retry-политику на ошибки сети и 5xx

2. Рефакторинг index

Разбей index/main.py хотя бы так:

  • index/schemas.py: request/response модели
  • index/rendering.py: извлечение и разметка текста сообщения
  • index/cleaning.py: локальная очистка и нормализация raw message payload
  • index/chunking.py: сборка окон сообщений и overlap по сообщениям
  • index/sparse.py: локальная sparse-эмбеддинг логика
  • index/main.py: только FastAPI wiring

Что должно измениться по логике индексации:

  • базовая единица чанка: сообщение, а не кусок строки
  • окно чанка должно учитывать:
    • число сообщений
    • суммарную длину
    • time gap между сообщениями
    • границы thread/forward/quote, если они явно ломают контекст
  • overlap должен повторять последние сообщения, а не последние символы
  • render_message() должен материализовать:
    • автора сообщения
    • mentions
    • quote / forward маркеры
    • file_snippets
    • member_event
    • при необходимости thread_sn

2.1. Локальная очистка сообщений по реальному формату data/Go Nova.json

Очистка должна происходить локально внутри index, без внешних API и без попытки делегировать нормализацию в dense/rerank сервисы.

Что показал реальный датасет:

  • значимая часть сообщений имеет пустой верхнеуровневый text
  • смысл часто лежит в parts[*].text
  • в parts[*] используется поле mediaType, а не type
  • встречаются mediaType = text, quote, forward
  • есть member_event без обычного текста
  • file_snippets приходит JSON-строкой
  • в данных встречаются URL, email и zero-width символы

Минимальный pipeline очистки:

  1. Извлечение raw сигналов:

    • text
    • parts
    • mentions
    • member_event
    • file_snippets
    • sender_id
    • флаги is_system, is_forward, is_quote
  2. Unicode и whitespace normalization:

    • удалить \u200b, \u200c, \u200d, \ufeff
    • унифицировать переводы строк
    • схлопнуть лишние пробелы и пустые строки
    • не удалять email, URL, версии, имена файлов и технические токены
  3. Нормализация parts:

    • mediaType = text: включать как основной контент
    • mediaType = quote: явно материализовать как quote_from + quote_text
    • mediaType = forward: явно материализовать как forwarded_from + forward_text
    • неизвестные типы не выбрасывать, а сохранять как маркированные текстовые блоки
  4. Нормализация системных событий:

    • member_event превращать в индексируемый текст
    • минимум поддержать addMembers
    • для неизвестных event type сохранять тип и payload в безопасной текстовой форме
  5. Нормализация файлов:

    • распарсить file_snippets локально из JSON-строки
    • вытащить name, mime, original_url, date_create
    • при невалидном JSON не падать, а сохранять attachment_raw
  6. Правило пропуска:

    • выбрасывать сообщение только если после очистки пусты и text, и parts, и member_event, и file_snippets

Развести три представления текста:

  • page_content: читаемый текст чанка для payload
  • dense_content: нормализованный текст с role-маркерами, авторами и служебным контекстом
  • sparse_content: keyword-heavy текст, куда попадают имена людей, mentions, email, документы, файлы, ссылки, важные термины

При этом:

  • не меняй внешний контракт POST /index
  • сохрани понятную привязку message_ids к каждому чанку
  • делай chunking объяснимым, а не магическим

3. Улучшить metadata-aware retrieval

После стабилизации search и index:

  • добавь boost/filter по participants
  • добавь boost/filter по mentions
  • используй contains_quote и contains_forward как вторичные сигналы ранжирования
  • если в payload есть thread_sn, учитывай его для вопросов про конкретную ветку обсуждения
  • подбери новые значения для DENSE_PREFETCH_K, SPRASE_PREFETCH_K, RETRIEVE_K, RERANK_LIMIT

Если multi-query fusion в Qdrant начинает заметно улучшать recall, оставляй его. Если только усложняет код без эффекта, не тащи лишнюю сложность.

4. Навести порядок в repo hygiene

  • перестань держать .ai_update/ в .gitignore, если workflow действительно предполагает коммит этого каталога
  • либо добавь реальный bin/codex-start, либо убери ссылки на него из документации
  • приведи docker-compose.yml к тому же сценарию env, что и README.md
  • добавь --platform linux/amd64 в команды сборки из Makefile
  • оставь doc/prompt.md основным планом, а дублирующие todo-файлы сократи или архивируй

5. Логирование каждого изменения и отчетность

Во время следующей реализации нельзя ограничиваться только кодом. После каждого meaningful change нужно фиксировать, что именно сделано и что сохранено.

Обязательные действия:

  • после каждого существенного изменения обновлять .ai_update/changelog.md
  • поддерживать .ai_update/touched_files.md
  • обновлять .ai_update/current_status.md и .ai_update/handoff.md к концу сессии
  • вести doc/output.md как человекочитаемый отчет по ходу работ

Что писать в doc/output.md после каждой существенной правки:

  • дата/время
  • что изменено
  • какие файлы изменены
  • зачем это сделано
  • как это проверено
  • что осталось недоделанным или рискованным

Какие тесты и проверки нужны

Создай минимальный тестовый контур. Без этого рефакторинг превратится в угадывание.

Unit tests

  • index/cleaning.py: unicode/whitespace cleanup, mediaType, member_event, file_snippets
  • index/rendering.py: сообщение с parts, quote, forward, mentions, file_snippets, member_event
  • index/chunking.py: chunking по сообщениям, time gap, overlap по сообщениям
  • search/query_builder.py: search_text, fallback на text, variants, hyde, keywords, entities, date range
  • search/aggregation.py: dedup, score aggregation, top-50

Smoke checks

  • python3 -m py_compile index/main.py search/main.py
  • docker compose config
  • локальный запуск через docker compose up --build, если заполнен .env
  • ручной smoke curl на /health, /index, /search

Regression set

Зафиксируй отдельный markdown-файл с контрольными вопросами. Включи хотя бы такие классы запросов:

  • кто что писал
  • кого упоминали
  • что писали про документ, файл или ссылку
  • что обсуждали в конкретный период
  • что было в пересланных сообщениях и цитатах
  • что было в системных событиях и прикреплениях

Используй data/Go Nova.json как локальную fixture-основу.

Порядок внедрения

  1. Сначала внедрить и протестировать локальную очистку сообщений в index/cleaning.py на кейсах из data/Go Nova.json.
  2. Переделать index на message-based chunking и разные page_content / dense_content / sparse_content.
  3. После стабилизации входного текста починить P0 баги в search и добавить тесты на query builder и aggregation.
  4. Вынести search из монолита main.py в модули без изменения API.
  5. Подключить metadata-aware retrieval и тюнинг параметров.
  6. Синхронизировать docker/docs/workflow и убрать repo hygiene противоречия.

Критерий готовности

Можно считать работу завершенной только если одновременно выполнено все ниже:

  • search использует не только question.text
  • поиск не падает на date_range и asker
  • retrieval + rerank не теряют кандидатов бессмысленно
  • финальная выдача дедуплицирована и ограничена top-50
  • index режет по сообщениям, а не по символам как основной механизм
  • локальная очистка сообщений работает без внешних API и покрывает parts, member_event, file_snippets, URL и zero-width артефакты
  • page_content, dense_content, sparse_content различаются по назначению
  • в индекс и retrieval реально включены metadata и скрытые сигналы чата
  • локальная документация, compose и сборка образов не противоречат друг другу
  • история изменений и отчет в doc/output.md обновляются по ходу работы, а не только в конце