Да — помочь могу. И нет, у тебя не “всё в корне неправильно”. По форме ты уже описал вполне нормальный паттерн: **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С; * генератор ссылок без объяснения. И должен начать выглядеть как: * осмысленный доменный ассистент; * понятный и честный собеседник; * диагностируемая инженерная система; * инструмент, который одновременно помогает пользователю и вскрывает пробелы домена/маршрутов. ---