рекомендации после первых двух этапов.md ## Что видно по текущему состоянию Не похоже, что у вас основная проблема в том, что “ассистент не понимает вопрос”. По текущим run-артефактам картина такая: * `address lane` **включается корректно**; * ранняя ветка в `AssistantService` **срабатывает правильно**; * `intent resolver` после перевода на token-based стал **стабильнее**; * `false factual rate = 0` — это хорошо. Но дальше почти все упирается в другое: * `mcp_call_status=empty`, либо * `no_contract_anchors_in_live_rows`, либо * controlled `LIMITED_WITH_REASON`. То есть **верхний слой уже более-менее жив**, а основной потолок сейчас — это **небогатый live-result на уровне рецептов и materialization**. --- ## Глобальный вывод Сейчас у вас смешались **два разных понятия готовности**: ### 1. “Сущность видна в snapshot” Это значит: * она существует в 2020 corpus; * у нее есть поля; * есть связи; * она красиво выглядит в entity map. ### 2. “По этой сущности можно стабильно отвечать в runtime через текущий live recipe” Это уже совсем другое: * есть нужные якоря в live-строках; * эти якоря доезжают до MCP-ответа; * по ним можно собрать уверенный factual answer. И вот по артефактам видно, что у вас **эти две вещи пока переоценены как будто они одинаковые**. А они не одинаковые. --- ## Самый важный сигнал из ваших документов В inventory у вас формально много P0-сущностей, но по факту: * для `account_balances` опора есть; * для `counterparties/contracts` картина значительно слабее; * `open_contracts` и часть `open_items` уже сейчас честно уходят в limited, потому что **в live rows не хватает договорных якорей**. Это очень хороший симптом с точки зрения честности системы. Но одновременно это показывает, что: **entity map сейчас структурно сильнее, чем runtime-ready model.** --- # Что я бы улучшал уже сейчас ## 1. Развести два уровня readiness Сейчас у вас полезно ввести не один `priority/readiness`, а два. ### A. Structural readiness Сущность: * есть в snapshot; * понятны поля; * понятны связи; * можно проектировать recipes. ### B. Runtime readiness Сущность: * реально доступна через текущий MCP/live маршрут; * по ней есть достаточные якоря; * можно собрать factual answer без натяжки. ### Практически Добавьте для каждой сущности и/или сценария отдельный статус: * `STRUCTURALLY_VISIBLE` * `LIVE_QUERYABLE` * `LIVE_QUERYABLE_WITH_LIMITS` * `REQUIRES_SPECIALIZED_RECIPE` * `DEEP_ONLY` Это сразу уберет ложное ощущение, что “раз сущность в P0, значит можно уже уверенно спрашивать”. --- ## 2. Перестать пытаться вытащить `open_contracts` из одного movement-среза Вот это сейчас, по-моему, ключевой архитектурный затык. Для: * `list_open_contracts` * `open_items_by_contract` * часть `open_items_by_counterparty_or_contract` движений из `Хозрасчетный` **недостаточно**, если в live строках не приезжает нормальный contract anchor. ### Что делать Для этих сценариев нужны **специализированные recipes**, а не просто еще одна вариация movement-query. Минимум отдельные live-рецепты на базе: * документов поставщиков, * документов покупателей, * банковских выписок, * договорных реквизитов документных линий, * актов сверки, * возможно, отдельных регистров взаиморасчетов, если через MCP они доступны лучше, чем бухрегистр. То есть: **для balances movement-рецепт ок, для contracts/open items нужен object-aware recipe.** --- ## 3. Ввести двухшаговый execution вместо одного прямого Сейчас у вас, судя по артефактам, часто логика такая: `intent -> recipe -> empty -> LIMITED` Это честно, но слишком грубо. Я бы сделал для части сценариев **controlled two-step plan**. ### Пример Для вопроса: “какие хвосты по договору X” не сразу идти в movement summary, а: #### Шаг 1. Anchor resolution * найти договор; * понять контрагента; * понять связанные документы/тип маршрута. #### Шаг 2. Focused recipe * уже по найденному anchor выполнять узкий live query. Это особенно важно для: * `by_contract` * `documents_forming_balance` * `bank_operations_by_contract` * `open_items_by_counterparty_or_contract` --- ## 4. Разделить `empty`, `blind`, `missing_anchor` Сейчас у вас многие ответы схлопываются в один limited-мод. А на деле это три разных случая: ### A. `EMPTY` Данные действительно не найдены по фильтру. ### B. `BLIND_RECIPE` Данные, возможно, есть, но текущий recipe их не видит. ### C. `MISSING_ANCHOR` Нет достаточного ключа для точного запроса. Это суперважно, потому что сейчас пользователь может видеть одинаковый ограниченный ответ, но причины там разные. ### Что это даст Тогда limited-ответ станет полезнее: * “По указанному фильтру данные не найдены.” * “Текущий live-рецепт не возвращает договорную аналитику для этого сценария.” * “Нужно уточнить договор или контрагента.” Это уже не просто safety, а нормальный рабочий UX. --- ## 5. Усилить слой anchor-first resolution раньше intent Сейчас вы уже улучшили token-based intent resolver — это хорошо. Но на раннем этапе я бы вообще сделал акцент не на intent-first, а на: **anchor-first + intent-second** То есть сначала пытаться вытащить: * счет, * контрагента, * договор, * тип документа, * период, * дату среза, а уже потом выбирать intent. Потому что в реальной короткой подаче пользователь часто говорит не “тип запроса”, а якорь: * “по альфе что висит” * “по договору 15 покажи хвост” * “60 счет на сегодня” * “платежки по бете за март” Именно anchor здесь важнее красивой intent-классификации. --- ## 6. Добавить `query shape classification` Нужно разделить не только по intent, но и по форме вопроса. Например: * `AGGREGATE_LOOKUP` * `OBJECT_LOOKUP` * `DOCUMENT_LIST` * `DRILLDOWN_REQUEST` * `COMPOUND_FACTUAL_QUERY` * `VERIFY_FACTUAL` * `EXPLAIN_OR_REASON` Это сильно поможет на раннем этапе, потому что многие “многочастные” вопросы на самом деле нормально раскладываются не через “один сложный intent”, а через форму запроса. --- ## 7. Для composer: limited-ответы сделать намного полезнее Сейчас safety у вас честная, но UX можно усилить. Когда система не может дать factual result, она уже сейчас должна возвращать не просто ограничение, а **полезный следующий шаг**. ### Например: вместо * “не найдено” лучше: * “Текущий маршрут не видит договорную аналитику в live-строках. Можно продолжить по одному из вариантов: по контрагенту, по счету 60/62/76 или по документам за период.” или * “Для точного поиска не хватает якоря. Можно уточнить контрагента, договор или дату.” То есть limited-mode должен стать **операционным**, а не просто защитным. --- # Что, скорее всего, “глобально происходит” на текущих двух этапах Если упрощенно, то так: ## Верхний этап Работает уже лучше, чем кажется: * классификация, * заход в address lane, * базовый intent/filter layer. ## Нижний этап Не дотягивает по фактической выразительности live-data: * текущие рецепты слишком общие, * movement layer беден для contract-aware сценариев, * многие кейсы рано упираются в `empty/limited`. То есть у вас сейчас проблема не “LLM тупой”, а **семантический слой уже обогнал реальную мощность live recipe layer**. Это нормальная ранняя стадия. Просто теперь нужно не бесконечно шлифовать классификатор, а **сильно усилить слой address recipes и anchor resolution**. --- # Что бы я делал следующим коротким шагом ## Sprint A — не расширение фич, а прояснение runtime-реальности ### 1. Ввести отдельную матрицу: `scenario -> structural readiness -> runtime readiness -> blocker` Для каждого P0/P1 сценария явно указать: * видна ли сущность в snapshot; * видна ли она через текущий live recipe; * чего не хватает; * нужен ли новый recipe. ### 2. Добавить 3 статуса failure mode: * `empty_match` * `missing_anchor` * `recipe_visibility_gap` ### 3. Для 5–10 ключевых сценариев собрать live evidence pack Не просто “PASS/limited”, а: * какой recipe вызывался, * какие поля реально вернулись, * каких anchors не хватило, * на каком шаге потерялась сущность. Это сразу покажет, что именно ломается. --- ## Sprint B — минимальное усиление без раздувания системы ### Приоритетно добавить: 1. `documents_by_counterparty` 2. `documents_by_contract` 3. `bank_operations_by_counterparty` 4. `bank_operations_by_contract` 5. `documents_forming_balance` Потому что эти маршруты обычно лучше дают якоря, чем голый movement slice. --- ## Sprint C — небольшой апгрейд NLU, но без фанатизма ### Что стоит сделать * account token extractor (`60`, `62`, `76`, `60.01`, `62.02`); * contract number recognizer; * counterparty fuzzy resolver; * short mobile phrase normalization: * “что висит” * “хвосты” * “по альфе” * “на сегодня” * “за март” * “проверь оплаты” ### Что пока не надо делать * тяжелую многошаговую reasoning-логику; * сложный free-form planner; * глубокий unified-intelligence слой. Рано. --- # Мой короткий вывод Сейчас у вас ситуация на самом деле хорошая. Потому что: * lane уже заведен; * deep-path не сломан; * false factual не генерируется; * система честно ограничивается там, где live-слой не тянет. Главное, что я бы зафиксировал: **узкое место сейчас не в понимании вопроса, а в несоответствии между “сущность есть в inventory” и “по ней можно стабильно ответить текущим live recipe”.** То есть следующий шаг — это не столько “еще умнее классификатор”, сколько: * отдельный runtime-readiness слой, * richer anchor resolution, * specialized recipes вместо общих movement-срезов, * и более полезный limited-mode. Если хочешь, следующим сообщением я могу собрать тебе уже **конкретное ТЗ для Codex на M2.1/M2.2**, именно на усиление runtime-ready слоя и evidence pack по live-рецептам.