54 KiB
Да — помочь могу. И нет, у тебя не “всё в корне неправильно”. По форме ты уже описал вполне нормальный паттерн: augmented LLM с инструментами/MCP, prompt chaining по шагам и отдельный проход на проверку качества перед финальным ответом. Это очень похоже на то, что в официальных гайдах описывают как рабочую основу: сначала простые композиционные паттерны, потом только при необходимости — более автономные agent-циклы. И OpenAI, и Anthropic отдельно советуют сначала выжать максимум из одного агента с инструментами, а не городить много агентов слишком рано. (Anthropic)
Но у тебя, по описанию, смешаны в одну массу четыре разные роли:
- разговор с пользователем,
- семантическое понимание вопроса,
- походы в 1С/MCP,
- объяснение результата человеку. Когда это всё делает одна и та же “мыслящая каша”, система и становится сухой, тупой и неясной в дебаге. Для таких систем проблема обычно не в том, что “мало LLM”, а в том, что нет жёстких контрактов состояния между шагами. Anthropic прямо разделяет workflow как предопределённые кодовые этапы и agent как динамический loop; для запросов в 1С у тебя, по сути, большая часть работы должна быть именно workflow, а не полностью свободный агент. (Anthropic)
Самая важная мысль: не надо “сильно упрощать” вопрос пользователя и выбрасывать оригинал. Это одна из типовых ловушек. Нужно не simplification, а semantic unpacking: хранить рядом
- исходный вопрос,
- нормализованную формулировку,
- список неоднозначностей,
- гипотезы домена,
- контракт ответа: что именно надо доказать, чтобы считать вопрос отвеченным. Иначе ты рано убиваешь нюанс, а потом уже собираешь “правильные” данные не на тот вопрос. Это особенно критично в длинных agent-пайплайнах: актуальная практика сейчас вообще больше про context engineering, то есть про управление состоянием контекста, а не про красивый системный промпт. Контекст конечен, и чем больше ты тащишь в модель сырых логов/кусков, тем выше риск потери фокуса. (Anthropic)
Я бы собрал тебе архитектуру так:
Пользователь
↓
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)
Что должно появиться между шагами
Не “много промптов”, а несколько обязательных артефактов.
1. QueryFrame — как система поняла вопрос.
{
"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С”, а конкретный план доказательства.
{
"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.
{
"facts": [...],
"source_refs": [...],
"tool_runs": [...],
"entity_bindings": [...],
"not_found": [...],
"conflicts": [...],
"execution_errors": [...]
}
4. CoverageReport — самый недостающий у вас слой.
{
"answerability": "full | partial | insufficient",
"covered_points": [...],
"uncovered_points": [...],
"why_partial": [...],
"next_best_actions": [...]
}
5. FinalAnswerPackage — уже для человека.
{
"short_answer": "...",
"detailed_explanation": "...",
"evidence_summary": "...",
"limitations": "...",
"clarifying_question_if_needed": "..."
}
Вот это и даёт тебе одновременно и хороший UX, и инженерную диагностируемость.
Где у вас сейчас, скорее всего, ломается качество
Первая ошибка: слишком ранняя компрессия смысла. Не надо превращать вопрос пользователя в один “упрощённый” вариант и жить с ним дальше. Нужен пакет из: оригинал + нормализация + неоднозначности + критерии ответа.
Вторая ошибка: LLM сама решает, достаточно ли она ответила. Нужен отдельный слой coverage critic, который сверяет:
- что хотел пользователь,
- что реально доказано evidence’ом,
- какие дыры остались. Это очень похоже на паттерн evaluator-optimizer, который Anthropic отдельно рекомендует для задач, где качество растёт от дополнительной критики и итеративной доработки. (Anthropic)
Третья ошибка: инженерный trace не оформлен как продукт системы. У тебя он, судя по описанию, где-то размазан по логам. А он должен быть первоклассным объектом: чтобы и пользователь, и инженер могли понять, почему ответ полный/частичный/невозможный.
Как сделать ассистента “человечным”, а не сухим
Сухость почти никогда не лечится “более креативным промптом”. Она лечится формой финального ответа.
Финальный ответ у такого ассистента должен всегда иметь 5 смысловых слоёв:
- Короткий вывод — прямой ответ на вопрос.
- Что именно проверено — какие регистры/документы/сущности смотрели.
- Что найдено — с привязкой к фактам.
- Что не удалось установить — честно, без стыда.
- Что нужно уточнить или что система сделает следующим лучшим шагом.
То есть не “вот ссылка на документ, иди сам разбирайся”, а: “Под хвостами в текущем прогоне система проверила незакрытые расчёты с поставщиками на дату X. Найдены такие-то контрагенты. Для двух контрагентов есть движения без подтверждающего закрывающего документа, поэтому ответ частичный. Чтобы дать окончательный список хвостов, нужно уточнить: считать ли хвостами только просроченные расчёты или любые незакрытые остатки.”
Вот это уже и человеку полезно, и инженеру показывает, какого доменного определения не хватает.
Практическое правило №1
Не спрашивай LLM: “ответь на вопрос”. Спрашивай по шагам:
- “Собери QueryFrame”
- “Собери ExecutionPlan”
- “Оцени CoverageReport”
- “Собери человеческий ответ по пакету evidence + coverage”
То есть LLM не пишет свободный роман, а закрывает конкретный контракт шага.
Практическое правило №2
Для внутренних шагов используй строгие структурированные выходы, а не просто JSON “на честном слове”. В официальных гайдах OpenAI это отдельно вынесено как способ добиваться строгого совпадения со схемой при tool calling / structured outputs. Даже если у тебя не OpenAI-стек, сам принцип тот же: каждый шаг должен возвращать объект по схеме, иначе ретрай/repair loop. (OpenAI)
Практическое правило №3
Не пихай в финальную LLM сырые результаты всех MCP-вызовов. Перед финальным проходом делай evidence compaction:
- только нужные факты,
- только привязанные сущности,
- только важные ошибки,
- только конфликты,
- только краткие source refs.
Иначе у тебя начинается классическая деградация от перегруженного контекста. Это сейчас уже прямо рассматривается как отдельная инженерная дисциплина — context engineering. (Anthropic)
Практическое правило №4
Держи два параллельных выхода на каждый turn:
Пользовательский
- короткий вывод,
- объяснение,
- ограничения,
- следующий вопрос/шаг.
Инженерный
- QueryFrame,
- Plan,
- Tool trace,
- EvidenceBundle,
- CoverageReport,
- reason codes.
Например reason codes:
missing_periodambiguous_business_termentity_not_resolvedinsufficient_1c_coverageconflicting_documentstool_execution_error
Это резко упрощает и улучшение доменов, и анализ того, почему “ответ тупой”.
Практическое правило №5
Введи claim-to-evidence binding. Любое содержательное утверждение в финальном ответе должно быть либо:
- доказано конкретным evidence id,
- либо помечено как inference,
- либо помечено как unknown.
Это очень сильно отрезает галлюцинации в бухгалтерских и 1С-задачах.
Что я бы сделал именно в твоём кейсе с 1С
Не строил бы “свободного умного агента”, который сам решает, куда идти по 1С. Я бы сделал manager-style orchestrator с небольшим числом жёстко описанных инструментов и доменных процедур. OpenAI отдельно отмечает, что чаще проблема не в количестве инструментов, а в том, что они похожи, пересекаются и плохо описаны; если инструменты чёткие, один агент часто справляется лучше и проще в сопровождении. (OpenAI)
То есть не 40 мутных MCP-тулов вроде:
search_docsfind_dataget_entries
А доменно:
resolve_counterpartyresolve_period_or_asofget_open_settlementsget_unclosed_documentsget_payments_for_counterpartyget_document_movementsexplain_domain_term
И каждому — чёткое описание входа, выхода, ограничений и типичных ошибок. И Anthropic, и OpenAI отдельно подчёркивают, что надёжность агента очень сильно зависит от качества инструментов и их документации. (Anthropic)
Про 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)
Итоговый тезис такой: вам не нужен “ещё более агентный агент”. Вам нужен доказательный диалоговый workflow с отдельным critic-слоем и явными артефактами состояния. Тогда система станет:
- человечнее для пользователя,
- полезнее для инженера,
- понятнее в дебаге,
- устойчивее к доменным дырам,
- и при этом не обязательно сложнее по коду.
Ниже — ТЗ в прикладном виде под диалогового 1С-ассистента с MCP, где приоритет — максимальное качество, объяснимость, удержание контекста и инженерная диагностируемость, а не скорость.
ТЗ
Диалоговый LLM-ассистент для 1С через MCP
Версия: Draft v1
Назначение: архитектура качественного, объяснимого и инженерно прозрачного ассистента
1. Цель
Разработать диалоговый ассистент для работы с данными 1С через MCP, который:
- корректно понимает пользовательский вопрос в бизнес-терминах;
- удерживает смысл и контекст вопроса без грубой потери нюансов;
- умеет декомпозировать вопрос на доменные сущности и проверяемые подзадачи;
- выполняет доказательный проход по 1С через MCP-инструменты;
- формирует человеческий, а не сухой ответ;
- явно сообщает, что найдено, что не найдено, чего не хватает, где неоднозначность;
- одновременно помогает инженерам системы понимать, почему ответ получился полным, частичным или не получился.
2. Проблема текущего состояния
Текущее поведение ассистента описывается следующими симптомами:
- ответы сухие и мало полезные для пользователя;
- система рано “сжимает” вопрос и теряет часть смысла;
- механика маршрутизации, извлечения данных и ответа человеку смешаны в одну логическую массу;
- отсутствует явный слой, который проверяет: действительно ли собранные данные покрывают исходный вопрос;
- отсутствует прозрачный reason trace для инженеров;
- при невозможности ответа система не объясняет полезно и предметно, почему именно ответ неполный.
Следствие: ассистент выглядит тупым не только из-за модели, а в первую очередь из-за неразделённых ролей и отсутствия промежуточных артефактов состояния.
3. Основной архитектурный принцип
Ассистент должен быть построен не как “одна умная LLM, которая делает всё”, а как жёсткий workflow из нескольких этапов, где каждый этап производит структурированный артефакт, а не свободный текст.
Ключевой принцип
Каждый шаг обязан оставлять после себя:
- машинно проверяемый результат;
- объяснимую причину выбранного пути;
- список неопределённостей и ограничений.
Базовое разделение ролей
Нужно развести четыре логики:
- Диалоговая интерпретация — что пользователь на самом деле хочет узнать.
- Доменная нормализация и планирование — какие сущности, периоды, типы объектов и операции нужны.
- Детерминированное извлечение данных — походы в 1С через MCP.
- Человеческая упаковка ответа — как сформулировать результат полезно, мягко и честно.
4. Границы этапа
Входит в этап
- архитектура пайплайна запроса;
- контракты промежуточных артефактов;
- правила диалогового ответа;
- правила неполного ответа;
- reason codes;
- логирование и трассировка;
- требования к памяти контекста;
- требования к качеству и acceptance.
Не входит в этап
- обучение собственной модели;
- полная реализация семантического поиска по документам вне 1С;
- универсальный open-ended autonomous agent;
- полный онтологический слой предприятия;
- автоматическое исправление данных в 1С.
5. Целевой результат
После внедрения этапа система должна уметь:
-
отвечать на вопрос не “как LLM”, а как доказательный доменный ассистент;
-
возвращать не только ответ, но и полезное пояснение, почему ответ именно такой;
-
разделять:
- уверенно доказанное,
- вероятностный вывод,
- пробел данных,
- пробел доменной формализации,
- ошибку инструмента;
-
удерживать контекст диалога между репликами;
-
накапливать инженерную телеметрию для улучшения маршрутов и доменов.
6. Общая архитектура
6.1. Логическая схема
Пользователь
↓
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
{
"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
{
"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
{
"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
{
"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
{
"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
{
"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. Общая форма ответа
Каждый ответ должен содержать до пяти смысловых блоков:
- Прямой ответ
- Что именно проверено
- Что найдено
- Что не удалось установить
- Что нужно уточнить / какой следующий шаг наиболее полезен
9.2. Требования к стилю ответа
Ответ должен:
- быть человеческим;
- не быть “канцелярской выгрузкой”;
- не быть пустым перечислением ссылок и документов;
- объяснять связь между вопросом и найденными данными.
Плохой ответ:
Найден документ списания с расчетного счета. Ссылка: ...
Хороший ответ:
Вопрос интерпретирован как проверка незакрытых расчетов с поставщиками на дату X. По данным 1С найдены такие-то контрагенты с открытыми остатками. Один из найденных документов относится к списанию средств, но сам по себе не доказывает наличие “хвоста”, поэтому использован только как вспомогательный источник, а не как основное основание ответа.
9.3. Правила неполного ответа
Если ответ частичный, система обязана не просто написать “недостаточно данных”, а объяснить:
- чего конкретно не хватает;
- это проблема данных, маршрута или доменного определения;
- какой следующий шаг даст наибольшую пользу.
Пример:
- “Найдены открытые остатки, но для части случаев не выполнена проверка закрывающих документов.”
- “Термин ‘хвосты’ не имеет фиксированной трактовки в текущем домене.”
- “Вопрос можно доуточнить по дате отсечения, иначе используется рабочая дата.”
10. Reason codes
Нужно ввести единый справочник причин неполноты, ошибок и спорных состояний.
10.1. Минимальный набор reason codes
Домен и интерпретация
AMBIGUOUS_BUSINESS_TERMMISSING_PERIODMISSING_AS_OF_DATEMULTIPLE_VALID_INTERPRETATIONSENTITY_NOT_RESOLVEDLOW_CONFIDENCE_ENTITY_BINDING
Данные и покрытие
NO_MATCH_IN_1CPARTIAL_MATCH_IN_1CINSUFFICIENT_DOCUMENT_DEPTHCONFLICTING_EVIDENCEMISSING_SUPPORTING_DOCUMENTEVIDENCE_DEPTH_LIMIT
Инструменты и исполнение
TOOL_EXECUTION_ERRORMCP_TIMEOUTMCP_SCHEMA_ERRORINVALID_TOOL_OUTPUTROUTE_EXECUTION_ABORTED
Диалог и UX
QUESTION_UNDER_SPECIFIEDNEEDS_USER_CONFIRMATIONASSUMPTION_APPLIED_EXPLICITLY
11. Память и удержание контекста
11.1. Требование
Ассистент должен работать не как набор независимых реплик, а как диалоговая система с состоянием.
11.2. Что хранить между ходами
Нужно хранить ConversationState:
{
"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_datafind_docsquery_1c_anything
Хорошо:
resolve_counterpartyresolve_period_or_asofget_open_supplier_settlementsget_unclosed_documents_for_counterpartyget_document_movementsget_payment_chainget_balance_as_ofget_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 разных промптовых режима:
-
Interpreter Prompt Понимание вопроса и неоднозначностей.
-
Planner Prompt Построение плана извлечения.
-
Critic Prompt Проверка покрытия и полноты.
-
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_ANSWEREDPARTIALLY_ANSWEREDBLOCKED_BY_AMBIGUITYBLOCKED_BY_MISSING_DATABLOCKED_BY_TOOLINGMISROUTEDFAILED_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. Коротко: что именно надо сделать в первую очередь
Если совсем приземлённо, то первыми задачами должны стать вот эти:
- Вынести отдельный
QueryFrame. - Вынести отдельный
CoverageReport. - Запретить финальному ответу формироваться без CoverageReport.
- Ввести reason codes.
- Сделать новый шаблон финального ответа.
- Ввести
ConversationState. - Перестать считать “упрощённый вопрос” заменой исходного вопроса.
20. Ожидаемый эффект после внедрения
После внедрения ассистент должен перестать выглядеть как:
- сухая LLM-выгрузка;
- случайный маршрутизатор по 1С;
- генератор ссылок без объяснения.
И должен начать выглядеть как:
- осмысленный доменный ассистент;
- понятный и честный собеседник;
- диагностируемая инженерная система;
- инструмент, который одновременно помогает пользователю и вскрывает пробелы домена/маршрутов.