NODEDC_1C/IN/TZ_LLM_Normalizer_v2.md

20 KiB
Raw Blame History

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.

Ключевая идея

На выходе нужен объект вида:

{
  "schema_version": "normalized_query_v2",
  "message_in_scope": true,
  "scope_confidence": "high",
  "fragments": [...],
  "discarded_fragments": [...],
  "global_notes": {...}
}

4.3. Фрагменты

Каждый фрагмент — это самостоятельный кандидат на задачу.

Пример:

{
  "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. Верхний уровень

{
  "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

Для каждого фрагмента:

{
  "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 = truelive_mcp_drilldown
  • если asks_for_ranking_or_top = true или asks_for_period_summary = truebatch_refresh_then_store
  • если has_multi_entity_scope = true и asks_for_chain_explanation = truehybrid_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. 23 живых примера длинного “потока”
  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 без пояснительного мяса.