14 KiB
рекомендации после первых двух этапов.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_VISIBLELIVE_QUERYABLELIVE_QUERYABLE_WITH_LIMITSREQUIRES_SPECIALIZED_RECIPEDEEP_ONLY
Это сразу уберет ложное ощущение, что “раз сущность в P0, значит можно уже уверенно спрашивать”.
2. Перестать пытаться вытащить open_contracts из одного movement-среза
Вот это сейчас, по-моему, ключевой архитектурный затык.
Для:
list_open_contractsopen_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_contractdocuments_forming_balancebank_operations_by_contractopen_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_LOOKUPOBJECT_LOOKUPDOCUMENT_LISTDRILLDOWN_REQUESTCOMPOUND_FACTUAL_QUERYVERIFY_FACTUALEXPLAIN_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_matchmissing_anchorrecipe_visibility_gap
3. Для 5–10 ключевых сценариев собрать live evidence pack
Не просто “PASS/limited”, а:
- какой recipe вызывался,
- какие поля реально вернулись,
- каких anchors не хватило,
- на каком шаге потерялась сущность.
Это сразу покажет, что именно ломается.
Sprint B — минимальное усиление без раздувания системы
Приоритетно добавить:
documents_by_counterpartydocuments_by_contractbank_operations_by_counterpartybank_operations_by_contractdocuments_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-рецептам.