TZ_LLM_Normalizer_v2.md Да, всё, картина понятна. И по тому, что ты сейчас написал, **ТЗ уже можно делать нормально**, без новых срезов 1С. Главное, что я из твоего сообщения беру как опору: * **стабильная база у вас — `v1.1.2`**; * `v1.1.2.1` показал, что бесконечный prompt-tuning — тупик; * **допустимый контур** у вас определяется не “всей бухгалтерией мира”, а так: * вопросы **по текущему предприятию**, * вопросы **по его данным**, * вопросы **по сущностям вашей онтологии / учётного контура**; * **недопустимый контур**: * абстрактная бухгалтерия, * законы, формы, “как вообще по ФСБУ/НК РФ”, * вопросы не про текущее предприятие, * вопросы не про данные этой базы; * если вопрос не по контуру — **в 1С не идём**; * если пользователь пишет “потоком”, система должна: * разложить это на куски, * отфильтровать мусор, * оставить только валидные task fragments, * уже из них собрать человекочитаемый ответ. Это уже достаточно, чтобы зафиксировать **правильное ТЗ на `Normalizer v2 / Decomposition-first Pre-Router`**. --- # ТЗ: `Normalizer v2` ## Decomposition-first pre-router для бухгалтерического ассистента --- ## 1. Цель этапа Перестроить текущий LLM-normalizer из схемы: **сырой вопрос → один intent_class → один route_hint** в более устойчивую схему: **сырой вопрос → decomposition → semantic fragments → route-critical flags → deterministic routing in code** Цель: * перестать бесконечно дотюнивать prompt под каждую новую пачку вопросов; * устойчиво обрабатывать: * свободную человеческую речь, * длинные сообщения, * смешанные запросы, * multi-intent сообщения, * “поток сознания” пользователя; * не допускать поход в бухгалтерский контур для вопросов вне допустимой области. Комментарий: Это уже не “ещё один prompt patch”, а **смена архитектуры нормализации**. --- ## 2. Главная идея новой схемы LLM больше **не должна пытаться угадать один главный класс вопроса** как основу всей логики. Вместо этого LLM должна: 1. определить, относится ли сообщение к допустимому бухгалтерскому контуру; 2. разложить сообщение на один или несколько **task fragments**; 3. для каждого фрагмента вернуть: * смысловые признаки, * флаги, * границы уверенности, * domain relevance; 4. передать это в код; 5. код уже сам решает: * пускать ли это в 1С-контур; * какому маршруту это соответствует; * нужно ли разбивать ответ на несколько частей; * нужно ли вернуть fallback/уточнение. Комментарий: То есть LLM здесь — **semantic parser**, а не “магический final decision engine”. --- ## 3. Что именно было не так в `v1.x` На `v1.x` одна модель одновременно пыталась: * понять intent; * понять causal; * понять route; * понять period; * понять output shape; * выбрать один taxonomy label. На стабильном типизированном наборе это работало хорошо. На новых 30 вопросах всё поплыло: * `intent_class_accuracy = 70` * `route_hint_accuracy = 80` * `causal_flag_accuracy = 60` Комментарий: Это не значит, что LLM плохая. Это значит, что **схема слишком хрупкая**: она слишком зависит от того, как именно человек сформулировал вопрос. --- ## 4. Новая архитектура v2 --- ## 4.1. Вход Входом является **сырое сообщение пользователя**, которое может быть: * коротким; * длинным; * многочастным; * неструктурированным; * частично бухгалтерским, частично нет; * содержать: * вопрос, * сомнение, * гипотезу, * просьбу проверить, * комментарий, * мусор, * лирическое отступление. --- ## 4.2. Выход LLM LLM должна возвращать **не один итоговый intent**, а structured decomposition. ### Ключевая идея На выходе нужен объект вида: ```json { "schema_version": "normalized_query_v2", "message_in_scope": true, "scope_confidence": "high", "fragments": [...], "discarded_fragments": [...], "global_notes": {...} } ``` --- ## 4.3. Фрагменты Каждый фрагмент — это самостоятельный кандидат на задачу. Пример: ```json { "fragment_id": "F1", "raw_fragment_text": "по поставщикам не бьются взаиморасчеты", "normalized_fragment_text": "Проверка расхождений по взаиморасчетам с поставщиками", "domain_relevance": "in_scope", "business_scope": "company_specific_accounting", "entity_hints": ["supplier", "settlements", "payments", "documents"], "account_hints": ["60"], "time_scope": { "type": "explicit", "value": "2020-06", "confidence": "medium" }, "flags": { "has_multi_entity_scope": true, "asks_for_chain_explanation": true, "asks_for_ranking_or_top": false, "asks_for_period_summary": false, "asks_for_rule_check": false, "asks_for_anomaly_scan": false, "asks_for_exact_object_trace": false, "asks_for_evidence": true }, "candidate_labels": ["cross_entity"], "confidence": "medium" } ``` Комментарий: Фрагмент — это не “ответ”, а **единица смыслового разбора**. --- # 5. Главный принцип: domain gating Это критично. ## 5.1. Что считается допустимым контуром Допустимыми считаются только вопросы: 1. про **текущее предприятие**; 2. про **его данные**; 3. про сущности и связи внутри вашей онтологии/учётного контура; 4. про: * документы, * проводки, * взаиморасчёты, * остатки, * хвосты, * закрытие периода, * аномалии, * правила учёта, * признаки ошибок, * проблемные связи в конкретной базе. Комментарий: То есть допустимость определяется **не общей темой “бухгалтерия”**, а связью с **данными и онтологией текущего предприятия**. ## 5.2. Что считается недопустимым контуром Недопустимыми считаются: * общие вопросы по бухгалтерии; * законы, кодексы, формы, инструкции “вообще”; * абстрактные вопросы без опоры на данные предприятия; * вопросы не про текущее предприятие; * бытовой трёп; * оффтоп; * всё, что не маппится в ваш учётный контур. ## 5.3. Обязательное правило Если фрагмент вне контура: * он **не должен** идти в 1С / retrieval / analytics pipeline; * по нему возвращается safe fallback. --- # 6. Что должно происходить с “потоком сознания” Если пользователь пишет длинно и хаотично, система не должна пытаться сделать вид, что это один аккуратный вопрос. Она должна: 1. разбить сообщение на фрагменты; 2. определить для каждого: * это task fragment или шум; * in-scope или out-of-scope; * требует ли похода в контур; 3. собрать **valid task set**; 4. выполнить только допустимые части; 5. на выходе сделать один человекочитаемый ответ, где: * каждая допустимая часть обработана; * недопустимые части вежливо отбиты. Комментарий: Да, конечный ответ должен быть **связанным по диалекту**, а не “пять сухих буллетов из ада”. Но это уже задача **response composer**, а не нормализатора. --- # 7. Новый output contract: `normalized_query_v2` --- ## 7.1. Верхний уровень ```json { "schema_version": "normalized_query_v2", "user_message_raw": "string", "message_in_scope": true, "scope_confidence": "high | medium | low", "contains_multiple_tasks": true, "fragments": [], "discarded_fragments": [], "global_notes": { "needs_clarification": false, "clarification_reason": null } } ``` --- ## 7.2. Поля fragment-level Для каждого фрагмента: ```json { "fragment_id": "F1", "raw_fragment_text": "string", "normalized_fragment_text": "string", "domain_relevance": "in_scope | out_of_scope | unclear", "business_scope": "company_specific_accounting | generic_accounting | offtopic | unclear", "entity_hints": ["string"], "account_hints": ["string"], "document_hints": ["string"], "register_hints": ["string"], "time_scope": { "type": "explicit | inferred | missing", "value": "string | null", "confidence": "high | medium | low" }, "flags": { "has_multi_entity_scope": true, "asks_for_chain_explanation": true, "asks_for_ranking_or_top": false, "asks_for_period_summary": false, "asks_for_rule_check": false, "asks_for_anomaly_scan": false, "asks_for_exact_object_trace": false, "asks_for_evidence": false, "mentions_period_close_context": false }, "candidate_labels": ["cross_entity", "period_close_risk"], "confidence": "high | medium | low" } ``` Комментарий: Обрати внимание: здесь **нет обязательного одного `intent_class`**. Есть `candidate_labels` и `flags`. Это ключевое изменение v2. --- # 8. Маршрутизация больше не выбирается LLM напрямую ## 8.1. Что делает LLM LLM только возвращает decomposition + flags. ## 8.2. Что делает код Код детерминированно выбирает route на основе flags. ### Пример логики * если `asks_for_exact_object_trace = true` → `live_mcp_drilldown` * если `asks_for_ranking_or_top = true` или `asks_for_period_summary = true` → `batch_refresh_then_store` * если `has_multi_entity_scope = true` и `asks_for_chain_explanation = true` → `hybrid_store_plus_live` * если `asks_for_rule_check = true` и нет causal chain → `store_feature_risk` * если `asks_for_anomaly_scan = true` и нет heavy/causal признаков → `store_feature_risk` * если fragment out-of-scope → no-route / fallback Комментарий: Это главный способ уйти от бесконечной гонки за “идеальным prompt”. --- # 9. Fallback policy Нужно формализовать три типа fallback. ## 9.1. Out-of-scope fallback Если вопрос не относится к данным текущего предприятия и его учётному контуру: Пример ответа: > Я работаю только с данными и бухгалтерическим контуром текущей компании. > Вопрос не относится к данным этого предприятия или не попадает в доступную предметную область. ## 9.2. Clarification fallback Если вопрос в контуре, но недостаточно определён: Пример ответа: > Могу проверить это в контуре компании, но нужно уточнить период, документ, счёт или участок, который нужно смотреть. ## 9.3. Partial fallback Если сообщение смешанное: * часть in-scope, * часть out-of-scope. Пример: > Проверю часть запроса, которая относится к данным компании. > Остальная часть выходит за пределы доступного бухгалтерического контура. Комментарий: Формулировки должны быть: * профессиональные, * не сухие как бетон, * без “писечки-попочки”, * но и без канцелярита. --- # 10. Что делать с сообщением, если там несколько задач Нужно в коде реализовать `message execution planner`. ## 10.1. Что он делает * получает fragments; * выбрасывает out-of-scope; * группирует in-scope fragments; * решает: * это один aggregated response, * или серия mini-answers; * передаёт дальше route decisions по каждому фрагменту. ## 10.2. Важное правило Если пользователь навалил 4 задачи в одном сообщении, система **не должна** насильно сводить это к одному intent. --- # 11. Что нужно реализовать в GUI / playground Текущую GUI можно использовать, но её надо расширить под v2. Нужны новые вкладки: ### A. Fragment View Показывает: * сколько фрагментов выделено; * тексты фрагментов; * какие отброшены; * какие in-scope. ### B. Scope View Показывает: * global in-scope / out-of-scope; * business_scope; * clarification need. ### C. Flags View Показывает по каждому фрагменту: * `has_multi_entity_scope` * `asks_for_chain_explanation` * `asks_for_ranking_or_top` * `asks_for_period_summary` * `asks_for_rule_check` * `asks_for_anomaly_scan` * `asks_for_exact_object_trace` * `asks_for_evidence` ### D. Route Simulation Показывает уже не LLM hint, а **что выбрал deterministic code**. --- # 12. Какие данные нужны для реализации ## Для написания ТЗ — достаточно текущего контекста Новых срезов базы не нужно. ## Для реализации желательно иметь 1. `normalizer_v1.1.2` prompt set 2. calibration set 3. новая challenge-30 4. 2–3 живых примера длинного “потока” 5. текущие fallback-тексты, если уже есть Комментарий: То есть не данные предприятия нужны, а **данные по самому языковому поведению пользователя**. --- # 13. Новый eval подход Нужно перестать мерить всё только через `intent_class_accuracy`. ## Основные метрики v2 * `schema_validation_pass_rate` * `scope_detection_accuracy` * `fragment_split_accuracy` * `out_of_scope_filter_accuracy` * `route_flag_accuracy` * `route_decision_accuracy` * `false_cross_entity_activation_rate` * `false_causal_activation_rate` * `multi-intent_handling_accuracy` Комментарий: Вот это уже будет реальная метрика устойчивости. --- # 14. Что нельзя делать Codex запрещено: 1. снова пытаться лечить всё одним prompt patch; 2. сохранять один mandatory `intent_class` как ядро всей логики; 3. отправлять out-of-scope вопросы в бухгалтерский контур; 4. смешивать decomposition и final user answer в один слой; 5. делать дорогие массовые прогоны на старте. --- # 15. Ограничения по бюджету Этап проектировать экономно. ## Допустимый режим * сначала сделать spec + schema + deterministic router rules; * потом сделать маленький pilot eval; * не жечь API на sweep’ы. Комментарий: Сейчас самый умный путь — **архитектурное переустройство**, а не дорогая перестрелка запросами. --- # 16. Артефакты, которые должен выдать Codex 1. `docs/normalizer_v2_spec.md` 2. `schemas/normalized_query_v2.json` 3. `docs/domain_scope_policy.md` 4. `docs/fallback_policy.md` 5. `docs/fragment_execution_policy.md` 6. `prompts/developer/normalizer_v2.txt` 7. `prompts/fewshot/normalizer_v2.txt` 8. `reports/v2_pilot_eval_plan.md` --- # 17. Ключевой смысл этапа Нужно уйти от философии: > “давайте подгоним модель под очередные 30 вопросов” к философии: > “давайте сделаем такую схему, где новые 30 вопросов не ломают систему, потому что система опирается не на один магический ярлык, а на decomposition + flags + deterministic routing.” --- # 18. Очень короткий вывод Сейчас **не надо** пытаться добить новую тридцатку patch’ем. Сейчас надо: * зафиксировать `v1.1.2` как стабильный baseline; * новую тридцатку считать challenge set; * перейти к `Normalizer v2`, где: * multi-label, * fragment decomposition, * scope filter, * deterministic routing after LLM. --- Если хочешь, следующим сообщением я могу дать тебе **ещё более жёсткую короткую версию этого ТЗ для Codex**, уже как job prompt без пояснительного мяса.