Перейти к содержанию
Новое AiManual теперь в MAX Подписаться
Публикация AiManual

Архитектура AI-агентов: от токенов LLM до ReAct-цикла — полный разбор

Разбираем архитектуру AI-агентов послойно: от токенизации и KV-кэша до ReAct-цикла и MCP-протокола. Как русский язык влияет на стоимость токенов, где искать баг

Коротко

Что будет в материале

  1. 01

    Фундамент: как LLM обрабатывает токены и почему это важно

  2. 02

    Слои обёртки: от промпта до HTTP-контракта

  3. 03

    Оркестрация агента: ReAct-цикл и протокол MCP

  4. 04

    Практика: отладка reasoning-моделей и поиск багов

Фундамент: как LLM обрабатывает токены и почему это важно

LLM не читает текст. Она переваривает последовательности чисел - токенов. От того, как исходный текст разбит на эти единицы, зависят скорость ответа, стоимость одного запроса и предельный объём контекста, который модель способна удержать. Для инженера, проектирующего агентную систему, токены - это валюта. Русский язык обходится дороже английского: одно и то же сообщение на русском занимает в среднем на 20–30% больше токенов. Причина - морфология и длина слов. Каждое склонение, каждый суффикс создают новую токенную единицу там, где английский обходится одним коротким корнем.

Токенизация: почему модель не видит слов

Токенизатор - это отдельный модуль, который работает до того, как текст попадёт в нейросеть. Он берёт строку и превращает её в список целых чисел по фиксированному словарю. Словарь этот строится один раз при обучении модели и больше не меняется. Английское «The quick brown fox» токенизатор GPT-4o превращает в четыре токена: ["The", " quick", " brown", " fox"]. Русское «Быстрая бурая лиса» в том же токенизаторе даст больше токенов, потому что «Быстрая» и «быстрый» - разные единицы, «бурая» и «бурый» - тоже. Модель не знает, что это однокоренные слова. Она видит разные ID.

Практический вывод: промпты на русском языке требуют больше токенов. При цене $15 за миллион входных токенов (GPT-4o, август 2025) разница в 25% на объёме 100 тысяч запросов в сутки превращается в тысячи долларов ежемесячно. Инструменты для подсчёта: tiktoken от OpenAI, метод tokenizer.encode() в библиотеке transformers, онлайн-калькуляторы. Перед запуском агента в production проверьте фактический расход токенов на типовом сценарии, а не опирайтесь на грубую оценку «слово ≈ 1.3 токена». Ошибка в 0.2 токена на слове при масштабировании даёт заметное отклонение бюджета.

KV-кэш: как LLM «помнит» предыдущие токены

Трансформер вычисляет для каждого токена три вектора: запрос (Query), ключ (Key) и значение (Value). При генерации следующего токена модель заново считает Query для нового токена, но Key и Value для всех предыдущих токенов можно не пересчитывать - они не изменились. KV-кэш сохраняет эти вычисленные пары и подставляет их в механизм внимания. Без KV-кэша каждый шаг генерации требовал бы повторного прогона всей последовательности через все слои. С кэшем сложность одного шага падает с O(n²) до O(n), где n - длина последовательности.

KV-кэш живёт в памяти GPU. Чем длиннее контекст, тем больше памяти он занимает. Модель с 32 слоями, 8 головами внимания и размерностью 128 на голову при длине контекста 4096 токенов хранит около 268 млн чисел в кэше - это примерно 1 ГБ в float16. Умножьте на batch size, и станет понятно, почему инференс на длинных диалогах упирается в память быстрее, чем в вычисления. Отсюда растут ноги у техник вроде Multi-Query Attention и Grouped-Query Attention - они сокращают число хранимых Key/Value-голов, уменьшая кэш в 4–8 раз без катастрофической потери качества.

Квантизация: меньше бит - больше скорость

Веса модели по умолчанию хранятся в float16 или bfloat16 - 2 байта на параметр. Модель на 70 млрд параметров требует 140 ГБ видеопамяти только под веса. Квантизация переводит веса в формат с меньшей разрядностью: int8 (1 байт) или int4 (0.5 байта). Та же 70B-модель в int4 занимает 35 ГБ и помещается на одну A100 с 80 ГБ памяти. Цена - небольшое падение точности. Для большинства агентных задач, где модель вызывает инструменты и рассуждает по шаблону, а не пишет поэму, разница в качестве незаметна.

Методы квантизации делятся на две группы: посттренировочная (GPTQ, AWQ) и с дообучением (QLoRA). Первая применяется к готовой модели, вторая требует калибровочных данных. В production-среде агентов чаще используют GPTQ или AWQ - они дают предсказуемый результат без дополнительного обучения. Формат GGUF (используется в llama.cpp) идёт дальше: он позволяет указать битность для каждого типа тензора отдельно. На практике это означает, что можно запустить Llama-3.1-70B на одном GPU с 48 ГБ памяти и получить скорость 20–30 токенов в секунду - достаточно для интерактивного агента.

Слои обёртки: от промпта до HTTP-контракта

Сырой текст, который пользователь вводит в чат, проходит несколько слоёв трансформации, прежде чем стать тензором на входе модели. Ошибка на любом из этих слоёв приводит к тому, что модель получает некорректный контекст и выдаёт мусор. Разработчики агентов часто списывают такие сбои на «глупость модели», тогда как проблема лежит в неправильно собранном промпте.

Chat template и Jinja: как промпт становится понятным модели

Базовая LLM обучена продолжать текст. Она не знает, что такое «роль пользователя» или «роль ассистента». Chat template - это Jinja-шаблон, который превращает структурированный список сообщений в плоскую строку со специальными маркерами. Например, для Llama-3 шаблон выглядит так:

<|begin_of_text|><|start_header_id|>user<|end_header_id|>

Привет, как дела?<|eot_id|><|start_header_id|>assistant<|end_header_id|>

Mistral использует другой формат: [INST] ... [/INST]. Если применить шаблон Llama к модели Mistral, она проигнорирует маркеры и начнёт генерировать текст, не понимая границ сообщений. Результат: ассистент «отвечает» за пользователя, путает роли, теряет инструкции. В агентных системах, где промпт собирается динамически из десятков сообщений и результатов вызова инструментов, ошибка в шаблоне ломает всю цепочку рассуждений.

Jinja в шаблонах чата позволяет использовать условную логику. Например, добавлять системный промпт только если он передан, или обрабатывать вызовы инструментов особым образом. Типичный шаблон проверяет наличие поля tool_calls в сообщении ассистента и форматирует их в синтаксис, понятный конкретной модели. Платформы вроде HuggingFace хранят chat template внутри файла tokenizer_config.json каждой модели. При загрузке через transformers.AutoTokenizer шаблон применяется автоматически - но только если вы используете метод apply_chat_template(). Ручная сборка промпта конкатенацией строк - источник трудноуловимых багов.

HTTP-контракты: унификация доступа к LLM

OpenAI задал стандарт де-факто для API языковых моделей. Запрос - это JSON с полями model, messages, tools, temperature и другими. Ответ содержит choices[0].message с текстом и опциональным tool_calls. Большинство провайдеров - Anthropic, Groq, Together, локальные серверы вроде vLLM - реализуют совместимость с этим контрактом.

Для агента критичны два поля: tools и tool_calls. В tools агент передаёт описание доступных функций в формате JSON Schema. Модель возвращает tool_calls - список вызовов с именем функции и аргументами. Слой оркестрации перехватывает этот ответ, выполняет функцию и добавляет результат обратно в историю сообщений как role: "tool". Этот цикл повторяется, пока модель не решит, что пора дать финальный ответ. Подробно этот механизм разобран в статье про самостоятельную разработку AI-агента на Python - там есть метрики latency и reliability для разных подходов к оркестрации.

Оркестрация агента: ReAct-цикл и протокол MCP

Одиночный вызов LLM - это просто текстовая генерация. Агент начинается там, где модель получает возможность действовать: вызывать API, читать файлы, запускать код, искать в базе данных. Оркестрация - это слой, который управляет циклом «мысль - действие - наблюдение» и принимает решение, продолжать или остановиться.

ReAct-цикл: как агент думает и делает

ReAct (Reasoning + Acting) - паттерн, опубликованный Google Research в 2022 году и ставший стандартом для агентных систем. Схема цикла:

  1. Thought - модель рассуждает о текущей ситуации и решает, что делать дальше.
  2. Action - модель выбирает инструмент и параметры его вызова.
  3. Observation - результат выполнения инструмента добавляется в контекст.
  4. Цикл повторяется с шага 1, пока не будет достигнут Final Answer.

Пример: пользователь спрашивает «Какая погода в Токио и сколько это в долларах, если я заплачу 10000 йен?». Агент делает Thought: «Нужно узнать погоду и курс валют. Начну с погоды». Action: вызов get_weather("Tokyo"). Observation: «+28°C, солнечно». Thought: «Теперь курс». Action: вызов get_exchange_rate("JPY", "USD"). Observation: «0.0067 USD за JPY». Thought: «10000 × 0.0067 = 67 USD. Данные собраны». Final Answer: «В Токио солнечно и +28°C. 10000 йен - это примерно 67 долларов США».

На практике ReAct-цикл подвержен зацикливанию. Модель может бесконечно вызывать инструменты, не приближаясь к ответу, или повторять одно и то же действие с разными формулировками. Оркестратор должен иметь ограничение на число итераций (обычно 10–30) и механизм детекции петель - сравнение последних N действий на предмет повторов. В статье про архитектурные ставки обвязки кодинг-агентов показано, что смена обвязки влияет на результат в 7.8 раза сильнее, чем смена модели. ReAct-цикл - это именно та часть обвязки, которую стоит считать несущей архитектурой, а не временным костылём.

MCP: единый протокол для инструментов агента

Model Context Protocol (MCP) предложен Anthropic в конце 2024 года как открытый стандарт подключения внешних данных и инструментов к LLM. До MCP каждый фреймворк изобретал свой способ описания инструментов: LangChain использовал декораторы, AutoGen - свои обёртки, кастомные решения - произвольные JSON-схемы. MCP унифицирует этот зоопарк.

Архитектура MCP состоит из трёх компонентов:

  • MCP Host - приложение, в котором работает агент (Claude Desktop, VS Code, Kimi Code CLI).
  • MCP Client - прослойка внутри хоста, которая управляет соединениями с серверами.
  • MCP Server - процесс, предоставляющий инструменты и ресурсы (файловая система, база данных, API).

Сервер описывает свои возможности в стандартизованном формате. Клиент при старте получает список инструментов с их схемами и передаёт их модели в поле tools. Когда модель решает вызвать инструмент, клиент маршрутизирует вызов к нужному серверу и возвращает результат. Kimi Code CLI поддерживает MCP из коробки: достаточно прописать путь к серверу в .kimi/config.json, и агент получит доступ к файловой системе, поиску или кастомному API без написания кода на стороне оркестратора.

Безопасность и контроль: guardrails и эскалация

Агент, способный вызывать произвольные функции, опасен. Он может удалить файлы, отправить письмо не тому адресату, потратить бюджет на бесконечные вызовы платного API. Guardrails - это правила, которые ограничивают действия агента до безопасного множества. Они бывают двух типов: превентивные (запрет на определённые вызовы до выполнения) и детективные (проверка результата после выполнения).

OpenAI Presence - корпоративная платформа для развёртывания агентов поддержки - встраивает guardrails на уровне инфраструктуры. Агент не может напрямую выполнить возврат средств или изменить тарифный план. Вместо этого он формирует запрос, который проходит через систему правил, и при недостаточности прав эскалирует задачу человеку-оператору. Результат: 75% входящих запросов решаются без участия человека, но ни один критичный экшен не проходит мимо контроля. Для команд, строящих собственных агентов, этот же принцип реализуется через песочницу: агент работает в изолированном окружении с ограниченным набором разрешённых инструментов и жёстким лимитом на число вызовов. Подробнее о том, как неправильная архитектура агента ведёт к накоплению проблем, читайте в разборе технического долга в эпоху AI - токены маскируют, но не устраняют архитектурную энтропию.

Практика: отладка reasoning-моделей и поиск багов

Агентная система состоит из трёх независимо отказывающих компонентов: модель, слой оркестрации, инструменты. Когда агент выдаёт неверный ответ, первая реакция - «модель ошиблась». На практике модель ошибается реже, чем кажется. Чаще проблема в том, что оркестратор передал модели некорректный контекст или инструмент вернул данные в неожиданном формате.

Где искать баг: модель, оркестрация или инструмент?

Алгоритм локализации:

  1. Изолируйте модель. Возьмите ровно тот промпт, который был отправлен на последнем шаге, и подайте его в ту же модель напрямую через API, минуя оркестратор. Если ответ корректен - проблема в оркестрации.
  2. Проверьте историю сообщений. Часто оркестратор неправильно форматирует результаты вызова инструментов: добавляет лишние поля, обрезает вывод, нарушает очерёдность ролей. Модель видит битый контекст и «глупеет».
  3. Проверьте инструменты. Запустите их с теми же параметрами вручную. Инструмент может возвращать пустой ответ, ошибку в нестандартном формате или данные, которые модель не способна интерпретировать.
  4. Ищите петли в ReAct. Если агент повторяет одни и те же действия, логируйте хеш от комбинации «мысль + действие» и обрывайте цикл при повторе трёх итераций подряд.

Типичные симптомы по уровням:

  • Модель: ответ не соответствует формату (ожидался JSON, получен текст), игнорирование системного промпта, галлюцинации фактов.
  • Оркестрация: неверный порядок сообщений, потеря части контекста, необработанный tool_calls, бесконечный цикл.
  • Инструменты: таймауты, неожиданные коды ответа, изменение схемы данных без обновления описания.

Инструменты для трассировки и логирования

LangSmith от LangChain - наиболее зрелый инструмент для трассировки агентных цепочек. Он записывает каждый шаг: входной промпт, ответ модели, вызовы инструментов, их результаты. Трассировка визуализируется в виде дерева, где видно, на каком именно шаге агент свернул не туда. Аналогичную функциональность предоставляет W&B Weave.

Для небольших проектов работает ручное логирование: перед каждым вызовом LLM пишите в лог полный промпт и историю, после вызова - полный ответ. Добавляйте к каждой записи timestamp и идентификатор сессии. При разборе инцидента вы сможете восстановить точную последовательность событий. Минимальный формат лога:

{
  "session_id": "uuid",
  "step": 3,
  "action": "tool_call",
  "tool": "search",
  "args": {"query": "погода Токио"},
  "result": {"status": "ok", "data": "+28°C"},
  "timestamp": "2026-07-22T10:15:30Z"
}

Такой лог парсится jq и позволяет быстро найти, на каком шаге и почему агент отклонился от ожидаемого поведения. В production-системах этот же формат используется для мониторинга: если доля шагов с status: error превышает порог, триггерится алерт.

Отладка reasoning-моделей требует особого подхода. Модели вроде o1 или DeepSeek-R1 генерируют внутреннюю цепочку рассуждений, которая не всегда доступна через API. Если агент на такой модели ведёт себя нестабильно, попробуйте переключиться на обычную LLM с тем же промптом - это изолирует проблему: специфична ли она для reasoning-модели или воспроизводится на любой архитектуре. Детальный разбор того, как скорость генерации кода агентами создаёт иллюзию продуктивности, скрывая рост сложности поддержки, дан в статье про кодинг-агентов и когнитивную ловушку.

Подписаться на канал