20 KiB
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 должна:
-
определить, относится ли сообщение к допустимому бухгалтерскому контуру;
-
разложить сообщение на один или несколько task fragments;
-
для каждого фрагмента вернуть:
- смысловые признаки,
- флаги,
- границы уверенности,
- domain relevance;
-
передать это в код;
-
код уже сам решает:
- пускать ли это в 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 = 70route_hint_accuracy = 80causal_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. Что считается допустимым контуром
Допустимыми считаются только вопросы:
-
про текущее предприятие;
-
про его данные;
-
про сущности и связи внутри вашей онтологии/учётного контура;
-
про:
- документы,
- проводки,
- взаиморасчёты,
- остатки,
- хвосты,
- закрытие периода,
- аномалии,
- правила учёта,
- признаки ошибок,
- проблемные связи в конкретной базе.
Комментарий: То есть допустимость определяется не общей темой “бухгалтерия”, а связью с данными и онтологией текущего предприятия.
5.2. Что считается недопустимым контуром
Недопустимыми считаются:
- общие вопросы по бухгалтерии;
- законы, кодексы, формы, инструкции “вообще”;
- абстрактные вопросы без опоры на данные предприятия;
- вопросы не про текущее предприятие;
- бытовой трёп;
- оффтоп;
- всё, что не маппится в ваш учётный контур.
5.3. Обязательное правило
Если фрагмент вне контура:
- он не должен идти в 1С / retrieval / analytics pipeline;
- по нему возвращается safe fallback.
6. Что должно происходить с “потоком сознания”
Если пользователь пишет длинно и хаотично, система не должна пытаться сделать вид, что это один аккуратный вопрос.
Она должна:
-
разбить сообщение на фрагменты;
-
определить для каждого:
- это task fragment или шум;
- in-scope или out-of-scope;
- требует ли похода в контур;
-
собрать valid task set;
-
выполнить только допустимые части;
-
на выходе сделать один человекочитаемый ответ, где:
- каждая допустимая часть обработана;
- недопустимые части вежливо отбиты.
Комментарий: Да, конечный ответ должен быть связанным по диалекту, а не “пять сухих буллетов из ада”. Но это уже задача 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 = 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_scopeasks_for_chain_explanationasks_for_ranking_or_topasks_for_period_summaryasks_for_rule_checkasks_for_anomaly_scanasks_for_exact_object_traceasks_for_evidence
D. Route Simulation
Показывает уже не LLM hint, а что выбрал deterministic code.
12. Какие данные нужны для реализации
Для написания ТЗ — достаточно текущего контекста
Новых срезов базы не нужно.
Для реализации желательно иметь
normalizer_v1.1.2prompt set- calibration set
- новая challenge-30
- 2–3 живых примера длинного “потока”
- текущие fallback-тексты, если уже есть
Комментарий: То есть не данные предприятия нужны, а данные по самому языковому поведению пользователя.
13. Новый eval подход
Нужно перестать мерить всё только через intent_class_accuracy.
Основные метрики v2
schema_validation_pass_ratescope_detection_accuracyfragment_split_accuracyout_of_scope_filter_accuracyroute_flag_accuracyroute_decision_accuracyfalse_cross_entity_activation_ratefalse_causal_activation_ratemulti-intent_handling_accuracy
Комментарий: Вот это уже будет реальная метрика устойчивости.
14. Что нельзя делать
Codex запрещено:
- снова пытаться лечить всё одним prompt patch;
- сохранять один mandatory
intent_classкак ядро всей логики; - отправлять out-of-scope вопросы в бухгалтерский контур;
- смешивать decomposition и final user answer в один слой;
- делать дорогие массовые прогоны на старте.
15. Ограничения по бюджету
Этап проектировать экономно.
Допустимый режим
- сначала сделать spec + schema + deterministic router rules;
- потом сделать маленький pilot eval;
- не жечь API на sweep’ы.
Комментарий: Сейчас самый умный путь — архитектурное переустройство, а не дорогая перестрелка запросами.
16. Артефакты, которые должен выдать Codex
docs/normalizer_v2_spec.mdschemas/normalized_query_v2.jsondocs/domain_scope_policy.mddocs/fallback_policy.mddocs/fragment_execution_policy.mdprompts/developer/normalizer_v2.txtprompts/fewshot/normalizer_v2.txtreports/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 без пояснительного мяса.