18 KiB
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, поэтому следующий шаг должен быть не только про качество поиска, но и про разбиение кода на понятные модули
Что уже очевидно сломано или недоделано
P0. Исправить критические дефекты в search
- В
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 на него ссылается. - Планирование размазано по нескольким файлам вместо одного документа.
Что нужно сделать
1. Рефакторинг search
Сначала разбей search/main.py на несколько логических частей. Минимально:
search/config.py: env, валидация конфигурации, auth-настройкиsearch/schemas.py: pydantic-модели запросов и ответовsearch/query_builder.py: сборка dense/sparse запросов изquestionsearch/retrieval.py:Qdrantprefetch, filters, fusionsearch/rerank.py: вызов reranker и работа с rerank-кандидатамиsearch/aggregation.py: дедуп message ids, score aggregation, top-50search/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
- агрегировать score по
Отдельно:
- убери импорт-тайм побочный эффект
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 payloadindex/chunking.py: сборка окон сообщений и overlap по сообщениямindex/sparse.py: локальная sparse-эмбеддинг логикаindex/main.py: только FastAPI wiring
Что должно измениться по логике индексации:
- базовая единица чанка: сообщение, а не кусок строки
- окно чанка должно учитывать:
- число сообщений
- суммарную длину
- time gap между сообщениями
- границы thread/forward/quote, если они явно ломают контекст
- overlap должен повторять последние сообщения, а не последние символы
render_message()должен материализовать:- автора сообщения
- mentions
- quote / forward маркеры
file_snippetsmember_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 очистки:
-
Извлечение raw сигналов:
textpartsmentionsmember_eventfile_snippetssender_id- флаги
is_system,is_forward,is_quote
-
Unicode и whitespace normalization:
- удалить
\u200b,\u200c,\u200d,\ufeff - унифицировать переводы строк
- схлопнуть лишние пробелы и пустые строки
- не удалять email, URL, версии, имена файлов и технические токены
- удалить
-
Нормализация
parts:mediaType = text: включать как основной контентmediaType = quote: явно материализовать какquote_from+quote_textmediaType = forward: явно материализовать какforwarded_from+forward_text- неизвестные типы не выбрасывать, а сохранять как маркированные текстовые блоки
-
Нормализация системных событий:
member_eventпревращать в индексируемый текст- минимум поддержать
addMembers - для неизвестных event type сохранять тип и payload в безопасной текстовой форме
-
Нормализация файлов:
- распарсить
file_snippetsлокально из JSON-строки - вытащить
name,mime,original_url,date_create - при невалидном JSON не падать, а сохранять
attachment_raw
- распарсить
-
Правило пропуска:
- выбрасывать сообщение только если после очистки пусты и
text, иparts, иmember_event, иfile_snippets
- выбрасывать сообщение только если после очистки пусты и
Развести три представления текста:
page_content: читаемый текст чанка для payloaddense_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_snippetsindex/rendering.py: сообщение сparts,quote,forward,mentions,file_snippets,member_eventindex/chunking.py: chunking по сообщениям, time gap, overlap по сообщениямsearch/query_builder.py:search_text, fallback наtext,variants,hyde,keywords, entities, date rangesearch/aggregation.py: dedup, score aggregation,top-50
Smoke checks
python3 -m py_compile index/main.py search/main.pydocker compose config- локальный запуск через
docker compose up --build, если заполнен.env - ручной smoke
curlна/health,/index,/search
Regression set
Зафиксируй отдельный markdown-файл с контрольными вопросами. Включи хотя бы такие классы запросов:
- кто что писал
- кого упоминали
- что писали про документ, файл или ссылку
- что обсуждали в конкретный период
- что было в пересланных сообщениях и цитатах
- что было в системных событиях и прикреплениях
Используй data/Go Nova.json как локальную fixture-основу.
Порядок внедрения
- Сначала внедрить и протестировать локальную очистку сообщений в
index/cleaning.pyна кейсах изdata/Go Nova.json. - Переделать
indexна message-based chunking и разныеpage_content/dense_content/sparse_content. - После стабилизации входного текста починить P0 баги в
searchи добавить тесты на query builder и aggregation. - Вынести
searchиз монолитаmain.pyв модули без изменения API. - Подключить metadata-aware retrieval и тюнинг параметров.
- Синхронизировать 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обновляются по ходу работы, а не только в конце