NODEDC_1C/docs/TECH/1CLLMARCH.md

1352 lines
54 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.

Да — помочь могу. И нет, у тебя не “всё в корне неправильно”. По форме ты уже описал вполне нормальный паттерн: **augmented LLM** с инструментами/MCP, **prompt chaining** по шагам и отдельный проход на проверку качества перед финальным ответом. Это очень похоже на то, что в официальных гайдах описывают как рабочую основу: сначала простые композиционные паттерны, потом только при необходимости — более автономные agent-циклы. И OpenAI, и Anthropic отдельно советуют сначала выжать максимум из **одного агента с инструментами**, а не городить много агентов слишком рано. ([Anthropic][1])
Но у тебя, по описанию, смешаны в одну массу **четыре разные роли**:
1. разговор с пользователем,
2. семантическое понимание вопроса,
3. походы в 1С/MCP,
4. объяснение результата человеку.
Когда это всё делает одна и та же “мыслящая каша”, система и становится сухой, тупой и неясной в дебаге. Для таких систем проблема обычно не в том, что “мало LLM”, а в том, что нет **жёстких контрактов состояния** между шагами. Anthropic прямо разделяет **workflow** как предопределённые кодовые этапы и **agent** как динамический loop; для запросов в 1С у тебя, по сути, большая часть работы должна быть именно workflow, а не полностью свободный агент. ([Anthropic][1])
Самая важная мысль: **не надо “сильно упрощать” вопрос пользователя и выбрасывать оригинал**. Это одна из типовых ловушек. Нужно не simplification, а **semantic unpacking**: хранить рядом
* исходный вопрос,
* нормализованную формулировку,
* список неоднозначностей,
* гипотезы домена,
* контракт ответа: что именно надо доказать, чтобы считать вопрос отвеченным.
Иначе ты рано убиваешь нюанс, а потом уже собираешь “правильные” данные не на тот вопрос. Это особенно критично в длинных agent-пайплайнах: актуальная практика сейчас вообще больше про **context engineering**, то есть про управление состоянием контекста, а не про красивый системный промпт. Контекст конечен, и чем больше ты тащишь в модель сырых логов/кусков, тем выше риск потери фокуса. ([Anthropic][2])
Я бы собрал тебе архитектуру так:
```text
Пользователь
A. Dialogue Interpreter (LLM)
B. Query Frame Builder (LLM, strict JSON)
C. Planner / Resolver (LLM + код)
D. MCP / 1C Executor (только код и инструменты)
E. Coverage Critic (LLM + правила)
F. Answer Composer (LLM)
Пользовательский ответ + инженерный trace
```
Ключевая идея тут в том, что **LLM не должна быть источником истины**. Истина — это 1С/MCP и детерминированные результаты вызовов. LLM делает интерпретацию, план, проверку покрытия и человеческую упаковку ответа. А всё, что связано с фактом “нашлось / не нашлось / есть противоречие / не удалось привязать сущность”, должно жить в машинном объекте состояния, а не в свободном тексте. Такой подход прямо согласуется с current best practices: модели + tools + state/memory + orchestration, с loop-выходами и внятными stop conditions. ([OpenAI Developers][3])
### Что должно появиться между шагами
Не “много промптов”, а **несколько обязательных артефактов**.
**1. QueryFrame** — как система поняла вопрос.
```json
{
"original_user_question": "...",
"normalized_question": "...",
"business_intent": "settlement_tail_check",
"requested_answer_shape": "list_with_explanation",
"time_scope": {
"explicit": null,
"inferred": "current_as_of_date",
"ambiguity": true
},
"entity_candidates": [
{"surface": "поставщики", "type": "counterparty_group"},
{"surface": "хвосты", "type": "informal_term", "needs_domain_resolution": true}
],
"ambiguities": [
"что считать хвостами",
"на какую дату смотреть",
"по какому контуру: расчеты, акты, оплаты, закрытие документов"
],
"success_criteria": [
"определено доменное значение 'хвостов'",
"выбран период/as_of",
"собран список контрагентов с доказательством"
]
}
```
**2. ExecutionPlan** — не просто “сходить в 1С”, а конкретный план доказательства.
```json
{
"plan_steps": [
"resolve_domain_term('хвосты')",
"resolve_counterparty_scope('поставщики')",
"resolve_as_of_date()",
"query_open_settlements()",
"query_unclosed_docs_if_needed()"
],
"tool_calls_expected": [...],
"fallbacks": [...],
"stop_conditions": [
"all success criteria covered",
"hard blocker",
"ambiguity requires user clarification"
]
}
```
**3. EvidenceBundle** — весь ground truth.
```json
{
"facts": [...],
"source_refs": [...],
"tool_runs": [...],
"entity_bindings": [...],
"not_found": [...],
"conflicts": [...],
"execution_errors": [...]
}
```
**4. CoverageReport** — самый недостающий у вас слой.
```json
{
"answerability": "full | partial | insufficient",
"covered_points": [...],
"uncovered_points": [...],
"why_partial": [...],
"next_best_actions": [...]
}
```
**5. FinalAnswerPackage** — уже для человека.
```json
{
"short_answer": "...",
"detailed_explanation": "...",
"evidence_summary": "...",
"limitations": "...",
"clarifying_question_if_needed": "..."
}
```
Вот это и даёт тебе одновременно и хороший UX, и инженерную диагностируемость.
### Где у вас сейчас, скорее всего, ломается качество
Первая ошибка: **слишком ранняя компрессия смысла**.
Не надо превращать вопрос пользователя в один “упрощённый” вариант и жить с ним дальше. Нужен пакет из: оригинал + нормализация + неоднозначности + критерии ответа.
Вторая ошибка: **LLM сама решает, достаточно ли она ответила**.
Нужен отдельный слой **coverage critic**, который сверяет:
* что хотел пользователь,
* что реально доказано evidenceом,
* какие дыры остались.
Это очень похоже на паттерн **evaluator-optimizer**, который Anthropic отдельно рекомендует для задач, где качество растёт от дополнительной критики и итеративной доработки. ([Anthropic][1])
Третья ошибка: **инженерный trace не оформлен как продукт системы**.
У тебя он, судя по описанию, где-то размазан по логам. А он должен быть первоклассным объектом: чтобы и пользователь, и инженер могли понять, почему ответ полный/частичный/невозможный.
### Как сделать ассистента “человечным”, а не сухим
Сухость почти никогда не лечится “более креативным промптом”. Она лечится **формой финального ответа**.
Финальный ответ у такого ассистента должен всегда иметь 5 смысловых слоёв:
1. **Короткий вывод** — прямой ответ на вопрос.
2. **Что именно проверено** — какие регистры/документы/сущности смотрели.
3. **Что найдено**с привязкой к фактам.
4. **Что не удалось установить** — честно, без стыда.
5. **Что нужно уточнить или что система сделает следующим лучшим шагом**.
То есть не “вот ссылка на документ, иди сам разбирайся”, а:
“Под хвостами в текущем прогоне система проверила незакрытые расчёты с поставщиками на дату X. Найдены такие-то контрагенты. Для двух контрагентов есть движения без подтверждающего закрывающего документа, поэтому ответ частичный. Чтобы дать окончательный список хвостов, нужно уточнить: считать ли хвостами только просроченные расчёты или любые незакрытые остатки.”
Вот это уже и человеку полезно, и инженеру показывает, **какого доменного определения не хватает**.
### Практическое правило №1
**Не спрашивай LLM: “ответь на вопрос”**.
Спрашивай по шагам:
* “Собери QueryFrame”
* “Собери ExecutionPlan”
* “Оцени CoverageReport”
* “Собери человеческий ответ по пакету evidence + coverage”
То есть LLM не пишет свободный роман, а закрывает **конкретный контракт шага**.
### Практическое правило №2
Для внутренних шагов используй **строгие структурированные выходы**, а не просто JSON “на честном слове”. В официальных гайдах OpenAI это отдельно вынесено как способ добиваться строгого совпадения со схемой при tool calling / structured outputs. Даже если у тебя не OpenAI-стек, сам принцип тот же: каждый шаг должен возвращать объект по схеме, иначе ретрай/repair loop. ([OpenAI][4])
### Практическое правило №3
**Не пихай в финальную LLM сырые результаты всех MCP-вызовов.**
Перед финальным проходом делай **evidence compaction**:
* только нужные факты,
* только привязанные сущности,
* только важные ошибки,
* только конфликты,
* только краткие source refs.
Иначе у тебя начинается классическая деградация от перегруженного контекста. Это сейчас уже прямо рассматривается как отдельная инженерная дисциплина — context engineering. ([Anthropic][2])
### Практическое правило №4
Держи **два параллельных выхода** на каждый turn:
**Пользовательский**
* короткий вывод,
* объяснение,
* ограничения,
* следующий вопрос/шаг.
**Инженерный**
* QueryFrame,
* Plan,
* Tool trace,
* EvidenceBundle,
* CoverageReport,
* reason codes.
Например reason codes:
* `missing_period`
* `ambiguous_business_term`
* `entity_not_resolved`
* `insufficient_1c_coverage`
* `conflicting_documents`
* `tool_execution_error`
Это резко упрощает и улучшение доменов, и анализ того, почему “ответ тупой”.
### Практическое правило №5
Введи **claim-to-evidence binding**.
Любое содержательное утверждение в финальном ответе должно быть либо:
* доказано конкретным evidence id,
* либо помечено как inference,
* либо помечено как unknown.
Это очень сильно отрезает галлюцинации в бухгалтерских и 1С-задачах.
### Что я бы сделал именно в твоём кейсе с 1С
Не строил бы “свободного умного агента”, который сам решает, куда идти по 1С. Я бы сделал **manager-style orchestrator** с небольшим числом жёстко описанных инструментов и доменных процедур. OpenAI отдельно отмечает, что чаще проблема не в количестве инструментов, а в том, что они похожи, пересекаются и плохо описаны; если инструменты чёткие, один агент часто справляется лучше и проще в сопровождении. ([OpenAI][5])
То есть не 40 мутных MCP-тулов вроде:
* `search_docs`
* `find_data`
* `get_entries`
А доменно:
* `resolve_counterparty`
* `resolve_period_or_asof`
* `get_open_settlements`
* `get_unclosed_documents`
* `get_payments_for_counterparty`
* `get_document_movements`
* `explain_domain_term`
И каждому — чёткое описание входа, выхода, ограничений и типичных ошибок. И Anthropic, и OpenAI отдельно подчёркивают, что надёжность агента очень сильно зависит от качества инструментов и их документации. ([Anthropic][1])
### Про Qwen 2.5 отдельно
Если ты сидишь на локальном Qwen 2.5 и у тебя tool calling/MCP местами ведёт себя странно, это может быть не только “модель тупая”, а ещё и проблема **шаблона/парсера tool calls**. В `llama.cpp` прямо указана поддержка native tool call formats для **Qwen 2.5** и generic fallback, причём generic менее эффективен. У текущего `Qwen-Agent` функция tool calling и MCP есть, но сам репозиторий уже ориентирован на Qwen>=3.0; при этом в changelog есть отдельные упоминания адаптаций под Qwen2.5 general models в 2025 году. То есть сам подход рабочий, но стек надо очень внимательно проверять на уровне parser/template/runtime, а не только промпта. ([GitHub][6])
Итоговый тезис такой:
**вам не нужен “ещё более агентный агент”. Вам нужен доказательный диалоговый workflow с отдельным critic-слоем и явными артефактами состояния.**
Тогда система станет:
* человечнее для пользователя,
* полезнее для инженера,
* понятнее в дебаге,
* устойчивее к доменным дырам,
* и при этом не обязательно сложнее по коду.
[1]: https://www.anthropic.com/research/building-effective-agents "Building Effective AI Agents \ Anthropic"
[2]: https://www.anthropic.com/engineering/effective-context-engineering-for-ai-agents "Effective context engineering for AI agents \ Anthropic"
[3]: https://developers.openai.com/tracks/building-agents "Building agents"
[4]: https://openai.com/index/introducing-structured-outputs-in-the-api/ "Introducing Structured Outputs in the API | OpenAI"
[5]: https://openai.com/business/guides-and-resources/a-practical-guide-to-building-ai-agents/ "A practical guide to building agents | OpenAI"
[6]: https://github.com/ggml-org/llama.cpp/blob/master/docs/function-calling.md "llama.cpp/docs/function-calling.md at master · ggml-org/llama.cpp · GitHub"
______
Ниже — ТЗ в прикладном виде под **диалогового 1С-ассистента с MCP**, где приоритет — **максимальное качество, объяснимость, удержание контекста и инженерная диагностируемость**, а не скорость.
---
# ТЗ
## Диалоговый LLM-ассистент для 1С через MCP
### Версия: Draft v1
### Назначение: архитектура качественного, объяснимого и инженерно прозрачного ассистента
## 1. Цель
Разработать диалоговый ассистент для работы с данными 1С через MCP, который:
* корректно понимает пользовательский вопрос в бизнес-терминах;
* удерживает смысл и контекст вопроса без грубой потери нюансов;
* умеет декомпозировать вопрос на доменные сущности и проверяемые подзадачи;
* выполняет доказательный проход по 1С через MCP-инструменты;
* формирует **человеческий**, а не сухой ответ;
* явно сообщает, **что найдено, что не найдено, чего не хватает, где неоднозначность**;
* одновременно помогает **инженерам системы** понимать, почему ответ получился полным, частичным или не получился.
---
## 2. Проблема текущего состояния
Текущее поведение ассистента описывается следующими симптомами:
* ответы сухие и мало полезные для пользователя;
* система рано “сжимает” вопрос и теряет часть смысла;
* механика маршрутизации, извлечения данных и ответа человеку смешаны в одну логическую массу;
* отсутствует явный слой, который проверяет: **действительно ли собранные данные покрывают исходный вопрос**;
* отсутствует прозрачный reason trace для инженеров;
* при невозможности ответа система не объясняет полезно и предметно, **почему именно** ответ неполный.
Следствие: ассистент выглядит тупым не только из-за модели, а в первую очередь из-за **неразделённых ролей и отсутствия промежуточных артефактов состояния**.
---
## 3. Основной архитектурный принцип
Ассистент должен быть построен не как “одна умная LLM, которая делает всё”, а как **жёсткий workflow из нескольких этапов**, где каждый этап производит **структурированный артефакт**, а не свободный текст.
### Ключевой принцип
Каждый шаг обязан оставлять после себя:
* машинно проверяемый результат;
* объяснимую причину выбранного пути;
* список неопределённостей и ограничений.
### Базовое разделение ролей
Нужно развести четыре логики:
1. **Диалоговая интерпретация** — что пользователь на самом деле хочет узнать.
2. **Доменная нормализация и планирование** — какие сущности, периоды, типы объектов и операции нужны.
3. **Детерминированное извлечение данных** — походы в 1С через MCP.
4. **Человеческая упаковка ответа** — как сформулировать результат полезно, мягко и честно.
---
## 4. Границы этапа
### Входит в этап
* архитектура пайплайна запроса;
* контракты промежуточных артефактов;
* правила диалогового ответа;
* правила неполного ответа;
* reason codes;
* логирование и трассировка;
* требования к памяти контекста;
* требования к качеству и acceptance.
### Не входит в этап
* обучение собственной модели;
* полная реализация семантического поиска по документам вне 1С;
* универсальный open-ended autonomous agent;
* полный онтологический слой предприятия;
* автоматическое исправление данных в 1С.
---
## 5. Целевой результат
После внедрения этапа система должна уметь:
* отвечать на вопрос не “как LLM”, а **как доказательный доменный ассистент**;
* возвращать не только ответ, но и полезное пояснение, почему ответ именно такой;
* разделять:
* уверенно доказанное,
* вероятностный вывод,
* пробел данных,
* пробел доменной формализации,
* ошибку инструмента;
* удерживать контекст диалога между репликами;
* накапливать инженерную телеметрию для улучшения маршрутов и доменов.
---
# 6. Общая архитектура
## 6.1. Логическая схема
```text
Пользователь
1. Dialogue Interpreter
2. Query Frame Builder
3. Domain Resolver / Entity Resolver
4. Execution Planner
5. MCP / 1C Executor
6. Evidence Builder
7. Coverage Critic
8. Answer Composer
Пользовательский ответ + инженерный trace
```
---
## 6.2. Назначение блоков
### 1. Dialogue Interpreter
Задача:
* понять намерение пользователя;
* сохранить исходную формулировку;
* выявить скрытые неоднозначности;
* определить ожидаемую форму ответа.
Важно:
**Нельзя заменять исходный вопрос одним “упрощённым” вопросом и жить только с ним.**
Нужно сохранять:
* оригинал;
* нормализованный вариант;
* доменные гипотезы;
* список неоднозначностей.
---
### 2. Query Frame Builder
Формирует структурированный каркас запроса — QueryFrame.
В QueryFrame должны быть:
* исходный вопрос;
* нормализованный вопрос;
* intent;
* предполагаемый домен;
* сущности-кандидаты;
* временной контекст;
* ожидаемый тип ответа;
* критерии успешности ответа.
---
### 3. Domain Resolver / Entity Resolver
Задача:
* привязать пользовательские слова к доменным сущностям;
* разрешить разговорные термины;
* проверить, что именно имеется в виду под неоднозначными словами.
Примеры:
* “хвосты” → незакрытые остатки / просроченные расчёты / не закрытые документами движения;
* “поставщики” → контрагенты конкретного типа;
* “на конец месяца” → требуется точная дата отсечения;
* “договоры висят” → проверить логику “остаток по расчётам”, “незакрытые акты”, “непроведённые взаиморасчёты” и т.д.
На этом этапе нельзя молча принимать одну трактовку, если есть несколько бизнес-валидных.
---
### 4. Execution Planner
Строит **план доказательства**, а не просто список тулов.
План должен отвечать на вопросы:
* что надо доказать;
* какие сущности обязательны;
* какие поля/регистры/документы нужны;
* какие шаги обязательны;
* какие шаги fallback;
* при каких условиях можно завершить ответ;
* когда нужно остановиться и запросить уточнение.
---
### 5. MCP / 1C Executor
Это детерминированный контур.
Требования:
* никакой свободной импровизации LLM при исполнении;
* строго типизированные вызовы;
* прозрачные ошибки;
* возвращаемый результат должен быть нормализован;
* каждый вызов должен иметь идентификатор, статус, параметры, источник, время выполнения.
---
### 6. Evidence Builder
Собирает результат исполнений в единый **EvidenceBundle**:
* факты;
* привязки сущностей;
* источники;
* найденные документы;
* найденные движения;
* конфликты;
* пробелы;
* ошибки исполнения.
EvidenceBundle — это основной ground truth для ответа.
---
### 7. Coverage Critic
Самый важный слой.
Проверяет:
* что хотел пользователь;
* что действительно найдено;
* что доказано;
* что покрыто частично;
* что не покрыто вообще;
* какая причина неполноты.
Этот блок не должен “додумывать”, он должен **оценивать покрытие**.
---
### 8. Answer Composer
Формирует финальный ответ человеку.
Ответ должен быть:
* прямым;
* полезным;
* объясняющим;
* честным по ограничениям;
* пригодным и для пользователя, и для инженера в разборе кейса.
---
# 7. Контракты данных
## 7.1. QueryFrame
```json
{
"request_id": "uuid",
"conversation_id": "uuid",
"turn_id": 12,
"original_user_question": "По каким поставщикам висят хвосты?",
"normalized_question": "Определить список поставщиков с незавершенными расчетами или иными незакрытыми остатками на целевую дату.",
"business_intent": "detect_open_supplier_settlements",
"expected_answer_shape": "list_with_explanation",
"domain_candidates": [
"supplier_settlements",
"document_closure",
"mutual_reconciliations"
],
"time_scope": {
"explicit_period": null,
"as_of_date": null,
"needs_resolution": true
},
"entity_candidates": [
{
"surface": "поставщики",
"type": "counterparty_group",
"confidence": 0.96
},
{
"surface": "хвосты",
"type": "informal_business_term",
"confidence": 0.41,
"requires_domain_resolution": true
}
],
"ambiguities": [
"Не определено, что считать 'хвостами'",
"Не определена дата отсечения",
"Не определен точный контур проверки"
],
"success_criteria": [
"Определить трактовку термина 'хвосты'",
"Определить дату/период",
"Найти подтверждаемые остатки или незакрытые документы",
"Сформировать список контрагентов с обоснованием"
]
}
```
---
## 7.2. ExecutionPlan
```json
{
"request_id": "uuid",
"plan_version": 1,
"goal": "Подтвердить наличие незакрытых обязательств/остатков по поставщикам",
"steps": [
{
"step_id": "S1",
"type": "resolve_business_term",
"input": "хвосты",
"required": true
},
{
"step_id": "S2",
"type": "resolve_as_of_date",
"required": true
},
{
"step_id": "S3",
"type": "resolve_counterparty_scope",
"input": "поставщики",
"required": true
},
{
"step_id": "S4",
"type": "query_open_settlements",
"required": true
},
{
"step_id": "S5",
"type": "query_supporting_documents",
"required": false
}
],
"fallbacks": [
"Если термин 'хвосты' не резолвится однозначно — проверить 2-3 допустимые трактовки и явно показать пользователю",
"Если дата отсутствует — использовать рабочую дату с явной пометкой",
"Если список поставщиков не выделяется напрямую — применить фильтр по типу контрагента"
],
"stop_conditions": [
"success_criteria_fully_covered",
"critical_ambiguity_requires_user_input",
"tooling_failure_no_evidence",
"max_execution_budget_reached"
]
}
```
---
## 7.3. ToolRun
```json
{
"tool_run_id": "uuid",
"tool_name": "get_open_settlements",
"status": "success",
"input": {
"counterparty_type": "supplier",
"as_of_date": "2026-04-10"
},
"started_at": "2026-04-10T12:01:15Z",
"finished_at": "2026-04-10T12:01:16Z",
"duration_ms": 842,
"rows_returned": 17,
"error_code": null,
"error_message": null
}
```
---
## 7.4. EvidenceBundle
```json
{
"request_id": "uuid",
"facts": [
{
"fact_id": "F1",
"type": "open_balance",
"entity": "ООО Альфа",
"value": 152340.55,
"currency": "RUB",
"as_of_date": "2026-04-10",
"source_ref": "SRC12"
}
],
"entity_bindings": [
{
"surface": "поставщики",
"resolved_to": "counterparty.role=supplier",
"confidence": 0.97
}
],
"source_refs": [
{
"source_ref": "SRC12",
"kind": "1C_register_extract",
"object_type": "settlement_register",
"object_id": "..."
}
],
"documents": [],
"conflicts": [],
"not_found": [],
"execution_errors": []
}
```
---
## 7.5. CoverageReport
```json
{
"request_id": "uuid",
"answerability": "partial",
"covered_points": [
"Сформирован список поставщиков с открытыми остатками",
"Определена дата отсечения"
],
"uncovered_points": [
"Не доказано, считать ли все остатки именно 'хвостами' в бизнес-смысле",
"Не подтверждено закрытие документами по каждому случаю"
],
"why_partial": [
{
"reason_code": "AMBIGUOUS_BUSINESS_TERM",
"message": "Термин 'хвосты' не имеет единственной фиксированной трактовки"
},
{
"reason_code": "EVIDENCE_DEPTH_LIMIT",
"message": "Для части контрагентов есть остатки, но нет дополнительной проверки закрывающих документов"
}
],
"next_best_actions": [
"Уточнить, считать ли хвостами любые незакрытые остатки или только просроченные/неподтвержденные",
"Запустить углубленный проход по документам"
]
}
```
---
## 7.6. FinalAnswerPackage
```json
{
"short_answer": "На текущую дату выявлены поставщики с незакрытыми остатками.",
"user_facing_answer": "Проверка выполнена по контуру расчетов с поставщиками...",
"evidence_summary": [
"Найдены открытые остатки по 7 контрагентам",
"Для 2 случаев есть недостающая глубина подтверждения"
],
"limitations": [
"Термин 'хвосты' интерпретирован как незакрытые остатки по расчетам"
],
"clarifying_question": "Нужно ли считать хвостами только просроченные случаи, или любые незакрытые остатки?"
}
```
---
# 8. Пайплайн обработки запроса
## 8.1. Этап 1. Приём и первичная интерпретация
Вход: пользовательская реплика.
Выход: QueryFrame draft.
Обязательные действия:
* сохранить оригинальную формулировку;
* выделить intent;
* выделить сущности;
* выявить разговорные/неформальные термины;
* определить недостающие обязательные параметры;
* определить ожидаемый формат ответа.
---
## 8.2. Этап 2. Разрешение доменной неоднозначности
Вход: QueryFrame draft.
Выход: QueryFrame resolved.
Обязательные действия:
* резолвить бизнес-термины;
* резолвить типы сущностей;
* резолвить временные параметры;
* не делать скрытых предположений без пометки.
Если есть несколько допустимых трактовок:
* либо выбрать одну с явным дисклеймером;
* либо проверить несколько трактовок;
* либо задать уточнение, если без этого ответ потенциально вводит в заблуждение.
---
## 8.3. Этап 3. Построение плана
Вход: QueryFrame resolved.
Выход: ExecutionPlan.
Требования:
* шаги должны быть атомарными;
* обязательные и необязательные шаги должны быть разделены;
* должен быть определён критерий остановки;
* должен быть определён fallback.
---
## 8.4. Этап 4. Исполнение плана
Вход: ExecutionPlan.
Выход: ToolRun[].
Требования:
* каждый вызов независим и логируется;
* ошибка не должна теряться;
* если инструмент вернул пустой результат, это отдельное состояние, а не “успех без данных”.
---
## 8.5. Этап 5. Сбор доказательной базы
Вход: ToolRun[].
Выход: EvidenceBundle.
Правила:
* факты должны быть нормализованы;
* дубликаты должны быть схлопнуты;
* противоречия должны быть вынесены отдельно;
* важна не полнота сырых данных, а качественная сборка доказательной базы.
---
## 8.6. Этап 6. Проверка покрытия
Вход: QueryFrame + EvidenceBundle.
Выход: CoverageReport.
CoverageReport обязателен всегда, даже если ответ “полный”.
---
## 8.7. Этап 7. Формирование ответа
Вход: QueryFrame + EvidenceBundle + CoverageReport.
Выход: FinalAnswerPackage.
---
# 9. Правила ответа пользователю
## 9.1. Общая форма ответа
Каждый ответ должен содержать до пяти смысловых блоков:
1. **Прямой ответ**
2. **Что именно проверено**
3. **Что найдено**
4. **Что не удалось установить**
5. **Что нужно уточнить / какой следующий шаг наиболее полезен**
---
## 9.2. Требования к стилю ответа
Ответ должен:
* быть человеческим;
* не быть “канцелярской выгрузкой”;
* не быть пустым перечислением ссылок и документов;
* объяснять связь между вопросом и найденными данными.
Плохой ответ:
> Найден документ списания с расчетного счета. Ссылка: ...
Хороший ответ:
> Вопрос интерпретирован как проверка незакрытых расчетов с поставщиками на дату X.
> По данным 1С найдены такие-то контрагенты с открытыми остатками.
> Один из найденных документов относится к списанию средств, но сам по себе не доказывает наличие “хвоста”, поэтому использован только как вспомогательный источник, а не как основное основание ответа.
---
## 9.3. Правила неполного ответа
Если ответ частичный, система обязана не просто написать “недостаточно данных”, а объяснить:
* чего конкретно не хватает;
* это проблема данных, маршрута или доменного определения;
* какой следующий шаг даст наибольшую пользу.
Пример:
* “Найдены открытые остатки, но для части случаев не выполнена проверка закрывающих документов.”
* “Термин ‘хвосты’ не имеет фиксированной трактовки в текущем домене.”
* “Вопрос можно доуточнить по дате отсечения, иначе используется рабочая дата.”
---
# 10. Reason codes
Нужно ввести единый справочник причин неполноты, ошибок и спорных состояний.
## 10.1. Минимальный набор reason codes
### Домен и интерпретация
* `AMBIGUOUS_BUSINESS_TERM`
* `MISSING_PERIOD`
* `MISSING_AS_OF_DATE`
* `MULTIPLE_VALID_INTERPRETATIONS`
* `ENTITY_NOT_RESOLVED`
* `LOW_CONFIDENCE_ENTITY_BINDING`
### Данные и покрытие
* `NO_MATCH_IN_1C`
* `PARTIAL_MATCH_IN_1C`
* `INSUFFICIENT_DOCUMENT_DEPTH`
* `CONFLICTING_EVIDENCE`
* `MISSING_SUPPORTING_DOCUMENT`
* `EVIDENCE_DEPTH_LIMIT`
### Инструменты и исполнение
* `TOOL_EXECUTION_ERROR`
* `MCP_TIMEOUT`
* `MCP_SCHEMA_ERROR`
* `INVALID_TOOL_OUTPUT`
* `ROUTE_EXECUTION_ABORTED`
### Диалог и UX
* `QUESTION_UNDER_SPECIFIED`
* `NEEDS_USER_CONFIRMATION`
* `ASSUMPTION_APPLIED_EXPLICITLY`
---
# 11. Память и удержание контекста
## 11.1. Требование
Ассистент должен работать не как набор независимых реплик, а как **диалоговая система с состоянием**.
## 11.2. Что хранить между ходами
Нужно хранить ConversationState:
```json
{
"conversation_id": "uuid",
"active_domain": "supplier_settlements",
"active_entities": [
{"type": "counterparty_group", "value": "suppliers"}
],
"active_time_scope": {
"as_of_date": "2026-04-10"
},
"resolved_business_terms": [
{
"surface": "хвосты",
"resolved_to": "open_settlement_balance",
"confidence": 0.72
}
],
"last_clarifications": [],
"open_ambiguities": [],
"last_evidence_refs": ["SRC12", "SRC19"]
}
```
## 11.3. Правила использования контекста
* контекст не должен бесконтрольно разрастаться;
* в следующий ход передаются только релевантные выжимки;
* старые гипотезы не должны silently переезжать в “истину”;
* каждая предпосылка должна иметь пометку:
* подтверждена,
* унаследована,
* предположена.
---
# 12. Требования к инструментальному контуру
## 12.1. MCP-инструменты должны быть доменно гранулированы
Плохо:
* `search_data`
* `find_docs`
* `query_1c_anything`
Хорошо:
* `resolve_counterparty`
* `resolve_period_or_asof`
* `get_open_supplier_settlements`
* `get_unclosed_documents_for_counterparty`
* `get_document_movements`
* `get_payment_chain`
* `get_balance_as_of`
* `get_related_closing_documents`
---
## 12.2. Каждый инструмент обязан иметь
* чёткое описание назначения;
* входную схему;
* выходную схему;
* перечень типовых ошибок;
* ограничения;
* политику пустого ответа.
---
## 12.3. Политика пустого результата
Пустой результат не равен успешному ответу пользователю.
Нужно различать:
* данных нет;
* данных не найдено по текущему маршруту;
* сущность не привязана;
* фильтр слишком узкий;
* инструмент вернул ноль из-за ошибки/схемы;
* вопрос некорректно поставлен.
---
# 13. Требования к LLM-слоям
## 13.1. Общий принцип
LLM не должна быть единственным источником решения.
LLM должна выполнять следующие типы задач:
* интерпретация вопроса;
* построение QueryFrame;
* планирование;
* оценка покрытия;
* человекоориентированная сборка ответа.
LLM не должна:
* выдумывать факты из 1С;
* подменять инструментальный ответ домыслом;
* молча трактовать неопределённый бизнес-термин как однозначный.
---
## 13.2. Формат взаимодействия с LLM
Для внутренних шагов использовать только:
* строгие схемы JSON;
* validator;
* repair/retry loop при нарушении схемы.
---
## 13.3. Отдельные проходы LLM
Нужно разделить как минимум на 4 разных промптовых режима:
1. **Interpreter Prompt**
Понимание вопроса и неоднозначностей.
2. **Planner Prompt**
Построение плана извлечения.
3. **Critic Prompt**
Проверка покрытия и полноты.
4. **Answer Prompt**
Формирование финального ответа человеку.
Запрещается использовать один и тот же общий промпт для всех режимов.
---
# 14. Требования к инженерной диагностике
## 14.1. По каждому запросу сохранять
* QueryFrame;
* ExecutionPlan;
* ToolRun[];
* EvidenceBundle;
* CoverageReport;
* FinalAnswerPackage;
* latency по этапам;
* reason codes;
* summary failure class.
---
## 14.2. Нужен отдельный debug-вывод
Инженерный trace должен быть читаемым.
Минимальная структура debug view:
* как понят вопрос;
* какие сущности распознаны;
* какие сущности не распознаны;
* какой маршрут выбран;
* какие тулзы вызваны;
* что реально найдено;
* почему ответ полный/частичный/ошибочный.
---
## 14.3. Классы итогов запроса
На верхнем уровне запрос должен классифицироваться как:
* `FULLY_ANSWERED`
* `PARTIALLY_ANSWERED`
* `BLOCKED_BY_AMBIGUITY`
* `BLOCKED_BY_MISSING_DATA`
* `BLOCKED_BY_TOOLING`
* `MISROUTED`
* `FAILED_TO_BIND_ENTITIES`
---
# 15. Acceptance criteria
Система считается принятой на этапе, если:
## 15.1. По качеству ответа
* ответ всегда содержит прямой вывод;
* ответ не состоит из голого набора ссылок;
* ответ объясняет, почему найденные данные относятся к вопросу;
* неполный ответ объясняется предметно.
## 15.2. По объяснимости
* по каждому кейсу доступен CoverageReport;
* по каждому кейсу доступны reason codes;
* можно восстановить, почему выбран маршрут.
## 15.3. По устойчивости
* сломанный или пустой tool output не приводит к “уверенному бреду”;
* неоднозначные термины не замалчиваются;
* система не теряет исходный смысл вопроса после нормализации.
## 15.4. По контексту
* следующий ход корректно использует ранее разрешённые сущности и периоды;
* контекст не дрейфует в соседний домен без причины;
* предыдущие предположения не становятся “фактами” без подтверждения.
---
# 16. План внедрения
## Этап 1. QueryFrame и разделение ролей
Сделать:
* отдельный Interpreter;
* отдельный Planner;
* отдельный Critic;
* отдельный Answer Composer.
Результат:
* система перестаёт быть “одной кашей”.
---
## Этап 2. Ввести структурированные артефакты
Сделать:
* QueryFrame;
* ExecutionPlan;
* ToolRun;
* EvidenceBundle;
* CoverageReport.
Результат:
* появляется прозрачная трассировка.
---
## Этап 3. Ввести reason codes
Сделать:
* единый справочник;
* обязательную простановку причин неполноты.
Результат:
* становится видно, где проблема: домен, данные, маршрут или инструмент.
---
## Этап 4. Переделать финальный ответ
Сделать:
* новый шаблон ответа;
* вывод “что найдено / что не доказано / что уточнить”.
Результат:
* ответы становятся полезными и человеческими.
---
## Этап 5. Контекст и диалог
Сделать:
* ConversationState;
* наследование разрешённых сущностей и периода;
* учёт открытых неоднозначностей.
Результат:
* появляется реальный диалог, а не серия разрозненных вызовов.
---
## Этап 6. Оценка качества
Сделать:
* набор сценариев;
* ручную разметку;
* сравнение old vs new;
* сбор статистики по reason codes.
Результат:
* становится видно, что реально улучшилось.
---
# 17. Основные риски
## Риск 1. Ранняя потеря смысла
Причина: слишком сильная “нормализация”.
Снижение риска: всегда хранить original question + normalized question + ambiguities.
## Риск 2. Скрытые предположения
Причина: LLM молча выбирает трактовку.
Снижение риска: reason code + явная пометка assumption.
## Риск 3. Сырой контекст перегружает модель
Причина: в финальный шаг передаётся весь мусор.
Снижение риска: отдельный EvidenceBuilder и evidence compaction.
## Риск 4. Пустой output трактуется как ответ
Причина: тул вернул ноль, а система делает вывод.
Снижение риска: разделить no-data / no-match / tool-failure.
## Риск 5. Ответ красивый, но недоказательный
Причина: хороший prose без claim binding.
Снижение риска: каждое содержательное утверждение должно опираться на evidence.
---
# 18. Итоговое архитектурное решение
Для данной задачи рекомендуется не fully autonomous agent, а **жёсткий доказательный workflow с LLM-слоями**.
## Итоговая формула
Ассистент =
* LLM для понимания вопроса,
* LLM для планирования,
* код и MCP для извлечения фактов,
* LLM для проверки покрытия,
* LLM для человеческого ответа,
* обязательные промежуточные артефакты,
* обязательные reason codes,
* обязательный инженерный trace.
---
# 19. Коротко: что именно надо сделать в первую очередь
Если совсем приземлённо, то первыми задачами должны стать вот эти:
1. Вынести отдельный `QueryFrame`.
2. Вынести отдельный `CoverageReport`.
3. Запретить финальному ответу формироваться без CoverageReport.
4. Ввести reason codes.
5. Сделать новый шаблон финального ответа.
6. Ввести `ConversationState`.
7. Перестать считать “упрощённый вопрос” заменой исходного вопроса.
---
# 20. Ожидаемый эффект после внедрения
После внедрения ассистент должен перестать выглядеть как:
* сухая LLM-выгрузка;
* случайный маршрутизатор по 1С;
* генератор ссылок без объяснения.
И должен начать выглядеть как:
* осмысленный доменный ассистент;
* понятный и честный собеседник;
* диагностируемая инженерная система;
* инструмент, который одновременно помогает пользователю и вскрывает пробелы домена/маршрутов.
---