Игрок пишет: «Пока орк отвлечён, я тихо выхватываю меч и прыгаю в окно». Для человека за столом это один ход. Для бота — разбор фразы на атомы, зависимости и броски, иначе «выхватываю» случится после «прыгаю» и всё запутается. Ниже — как ИИ превращает такую реплику в исполняемый граф действий.Эта статья — разбор архитектурного пайплайна для детерминированной обработки свободного текста. В качестве кейса — ДнД‑бот, где вместо надежды на «умную LLM» я использую атомизацию, граф зависимостей и специализированных агентов. Будет полезно всем, кто проектирует сложные LLM‑системы обработки запросов и пытается победить галлюцинации и высокую стоимость токенов.Однако я постарался использовать максимально простой язык, чтобы рассказать не связанным с разработкой игрокам, как ДнД‑бот из их запроса создаёт логичный и лаконичный ответ мастера.Итак, поехали! Читать далее

Игрок пишет: «Пока орк отвлечён, я тихо выхватываю меч и прыгаю в окно». Для человека за столом это один ход. Для бота — разбор фразы на атомы, зависимости и броски, иначе «выхватываю» случится после «прыгаю» и всё запутается. Ниже — как ИИ превращает такую реплику в исполняемый граф действий.
Эта статья — разбор архитектурного пайплайна для детерминированной обработки свободного текста. В качестве кейса — ДнД‑бот, где вместо надежды на «умную LLM» я использую атомизацию, граф зависимостей и специализированных агентов. Будет полезно всем, кто проектирует сложные LLM‑системы обработки запросов и пытается победить галлюцинации и высокую стоимость токенов.
Однако я постарался использовать максимально простой язык, чтобы рассказать не связанным с разработкой игрокам, как ДнД‑бот из их запроса создаёт логичный и лаконичный ответ мастера.
Итак, поехали!
На самом деле, если закинуть в ИИ (я так буду называть LLM для простоты) запрос игрока и попросить обработать, как настоящий ДнД мастер, ИИ прекрасно справится. Любой может убедиться в этом, открыв ИИ‑ассистента типа ChatGPT или Gemini и попытавшись поиграть в ДнД.
Моя первая версия бота по сути и представляла собой обёртку вокруг одной‑единственной модели, которой я передавал данные сеттинга, немного истории ходов и запрос игрока. Если довольствоваться малым, это работало, и с запросом игрока ничего делать не надо было.
Потом архитектура немного усложнилась, и я задался рядом вопросов. Например:
а если напишут спам?
а если я добавлю команды типа «/help» — их же надо отдельно обрабатывать?
а зачем простые вопросы про правила прогонять через движок бота?
Так родилась идея LLM‑классификатора, который на вход получает запрос игрока и определяет, что это. Если игровое действие — направляем запрос дальше в движок. Если спам — игнорируем. Если вопрос про правила — отдаём другой небольшой LLM, которая просто ответит про правила. А если игрок напишет «ещё раз» — находим предыдущее сообщение и скармливаем его движку. В общем, классификация стала первым шагом на пути анализа запроса.

Условная схема работы классификатора в N8N
Наличие одного лишь классификатора какое‑то время меня устраивало. Именно на модели «классификатор + 3 ключевых LLM» я запустил первый закрытый альфа тест с игроками 1 июня, и это тоже работало. Но тест выявил ряд проблем, а именно, невозможность обработки сложных запросов. Вайбом свободы действий в рамках правил ДнД, которую я мечтал реализовать, даже не пахло. Тогда я принял решение полностью переделать архитектуру под «параллельный пайплайн» (чем и продолжаю заниматься спустя 2 месяца).
Идея в том, чтобы разные LLM параллельно обрабатывали запрос игрока, а на выходе некий арбитр склеивал бы разрозненные ответы в единый и непротиворечивый результат. То есть игрок пишет: «Я подбегаю и атакую орка» — а отдельные LLM‑специалисты смотрят: а не сработает ли какая ловушка? а как просчитать атаку? а вдруг игрок бежит по льду, и поэтому надо сделать проверку атлетики? Подробно обо всём этом я писал вот в этой статье; там же объясняется, почему одна модель не справится с такой задачей.
Так вот. Для такой архитектуры нужен роутер, который бы определял, какого специалиста когда вызывать. Конечно, можно не париться и каждый запрос отправлять всем специалистам сразу. Но это будет дорого по токенам. Поэтому — роутер.
Задача роутера очень похожа на задачу классификатора: взять запрос и понять, к каким доменам он относится (то есть каких специалистов вызывать для его обработки). Например, боёвка, социалка или импровизированные действия.

Роутер и параллельный пайплайн в N8N
Вроде бы всё просто, но дьявол кроется в деталях. Оказалось, что при таком подходе и роутер, и каждый вызываемый специалист должны понять по контексту, а чего вообще хочет игрок и как это относится к их домену. Т.е. одно и то же действие делают, условно, на каждый ход сразу 5 моделей! Это оверхед и по стоимости токенов, и по времени ожидания ответа, и по качеству самого ответа. Для ДнД‑бота критично и то, и другое, и третье.
Так родилась идея нормализации запроса. Игрок пишет: «бью его». Кого? Чем? Нужно дать LLM историю переписки и попросить: напиши нормально, что хочет игрок. На выходе получается нормализованная фраза: «игрок бьёт мечом орка». Ага, супер! Вот эту фразу уже можно отдавать специалистам без всякой истории — им уже сразу понятно, что происходит.
Итак, имеем: классификация, роутер, нормализация. Уже немного сложнее, чем просто отдать весь запрос ИИ, но и работает качественнее.
Однако и этого недостаточно. Данная схема вполне подходит под простые запросы. Но что делать с запросами посложнее? Например, игрок пишет: «Я хватаю меч, приказываю врагу сдаваться и выбегаю за дверь». Когда вся эта «колбаса» попадает к специалисту, он путается. Он отвечает за социалку, например, и понятия не имеет, может ли игрок выхватить меч, существует ли вообще этот меч, и как переместить игрока в локацию за дверью.
И вот тут уже понадобятся атомарные действия. Их тоже нужно нормализовать, им нужно персонально назначать домены — и тогда их можно обрабатывать отдельно друг от друга. Если взять фразу из предыдущего абзаца, из неё можно получить 3 отдельных действия, каждое со своими доменами:
ID | Действие | Домены |
1 | Игрок хватает меч | свободное действие |
2 | Игрок приказывает гоблину сдаться | социалка |
3 | Игрок выбегает за дверь таверны | перемещение, ловушка |
Отлично, теперь мы точно знаем, что кому передавать. Тем самым параллельные специалисты разгружаются, время и токены экономятся, а фокус на своих задачах делает их ответы более точными (меньше лишнего контекста, меньше путаницы, меньше lost in the middle и тому подобное).
Очень крутой бонус, который даёт такой подход — возможность валидировать и обрабатывать результаты каждого действия отдельно. Например, первые два действия удались, а последнее — выбежать за дверь — нет, потому что дверь закрыта на замок. И именно такую цепочку вы можете выдать игроку — но не как общий ответ одной модели (где высока вероятность галлюцинаций), а как результат пошагового анализа каждого действия.
Второй бонус связан с тем, что одно атомарное действие может относиться сразу к нескольким доменам. В примере выше попытка убежать за дверь — это и перемещение, и ловушка — допустим, потому что дверь связана с ловушкой. И здесь возможна ситуация, что оба LLM‑специалиста выдадут противоречивые вердикты: один позволит пройти, другой бабахнет скрытую растяжку. Так вот, зная точно идентификатор действия, к которому относятся противоречивые суждения, вы можете изолированно разбираться с этим действием (заскриптованными правилами или через LLM — как удобно).
На этом этапе система уже выглядит громоздкой, но именно здесь появляется необходимость в графовой структуре.
"normalized_message": "Я выхватываю меч, подхожу к орку и бью его.",
"atomic_actions": [
{
"action_id": 1,
"normalized_action": "Игрок выхватывает меч",
"domains": [ "environment" ],
"depends_on": [ ]
},
{
"action_id": 2,
"normalized_action": "Игрок подходит к орку",
"domains": [ "transition" ],
"depends_on": [ ]
},
{
"action_id": 3,
"normalized_action": "Игрок бьёт орка мечом",
"domains": [ "combat" ],
"depends_on": [ 1, 2 ]
}
]Дело в том, что действия могут быть зависимые. «Я хватаю меч и бью орка». А что, если у игрока нет меча? Будь у нас одна умная модель, она бы справилась — сама бы провела параллели и решила, что если меча нет, то и атака на орка невозможна. Но мы пошли по пути пайплайна, и теперь просто некому связать эти действия. Классификатор скажет, что действие игровое. Роутер выберет домены специалистов. Специалисты провалидируют каждое свое действие. Типа так:
ID | Действие | Вердикт |
1 | Игрок хватает меч | 🚫 нет меча |
2 | Игрок бьёт орка мечом | ⚔️ минус 10 хп орку |
Как понять, что действие 2, на самом деле, невалидно? Вот тут и нужен граф, в данном примере (да и вообще на большинстве ходов) очень простой: 2 → 1 (то есть действие 2 зависит от действия 1). И тогда, получив вердикты по отдельным действиям, мы можем вывести простое правило: если исходное действие невозможно, проваливаем все последующие действия по каскаду. А независимые действия не трогаем.
Есть здесь ещё один момент. А что, если первое действие потребует броска? Например, игрок хочет перепрыгнуть расщелину и атаковать орка. А если не сможет перепрыгнуть? Опять же, раз у нас есть зависимости, можно что‑то придумать: попытка первого действия, результаты броска, а дальше либо провал — тогда блокировать второе действие, либо успех — тогда атаковать. Но это уже совсем отдельный топик — каскадные события.
Итак, если собрать всё вместе, получается, что одно‑единственное сообщение игрока проходит через следующие стадии: классификация, нормализация, разбиение на действия, присвоение действиям доменов, а также определение зависимостей. Просто чтобы обеспечить дальнейшую обработку.
А в результате рождается «магия». Игрок пишет запрос, а ИИ‑мастер выдаёт очень продуманный ответ. Например, так:
Вы тянетесь к мечу, но оказывается, что у вас его нет, и вам нечем атаковать орка. Однако вы смогли подбежать к двери… и не заметили ловушку! Сделайте спасбросок телосложения! 🎲
Та самая свобода в рамках правил, но созданная не абстрактным «ИИ», а архитектурными решениями.

Процесс тестирования и отладки прямо в ТГ
Сейчас я как раз переписываю архитектуру движка под описанный пайплайн, каскад и граф зависимостей. Если вам интересна тема LLM‑пайплайнов, хочется следить за развитием проекта или принять участие в Alpha 2 — заходите в мой тг канал.
Буду рад любому фидбэку и инженерным идеям.
| # | Наименование новости | Тональность | Информативность | Дата публикации |
|---|---|---|---|---|
| 1 | ИИ‑мастер для настольной RPG: один запрос на ход, кубик до ответа и баг, который удваивал здоровье | 0 | 9 | 18-10-2026 |
| 2 | Книга: «Анализ данных с LLM. Текст, таблицы, изображения и аудио» | 0 | 7.03 | 23-06-2026 |
| 3 | Один интерфейс — девять LLM-провайдеров: как мы подружили Kimi K3, GLM-5.2, Claude и Ollama в одном терминальном агенте | 0 | 12.65 | 31-07-2026 |
| 4 | Практика программирования в эпоху ИИ (разбор реальной задачи на С++ с помощью ИИ агента) | 0 | 8.79 | 27-07-2026 |
| 5 | Книга: «Эффективный разговорный ИИ. Создаем чат-ботов, которые действительно работают» | 0 | 12.49 | 29-04-2026 |
| 6 | От болтливых LLM-агентов к управляемым системам | 0 | 13 | 08-08-2026 |
| 7 | В чём реальная проблема ЛЛМ | -5 | 7 | 03-07-2026 |
| 8 | [Перевод] Языковая Модель без магии: Крошечная Language Model на чистом Node.js | 0 | 8.77 | 27-07-2026 |
| 9 | [Перевод] Как на самом деле работают LLM | 0 | 7 | 07-07-2026 |
| 10 | Как ИИ не дал загнуться ещё одной ноунейм игре. Даже текстовой | 0 | 8.88 | 24-07-2026 |