NODEDC_1C/IN/TZ_LLM_Normalizer_v2.md

586 lines
20 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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. 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 без пояснительного мяса.