Фундамент: как 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 году и ставший стандартом для агентных систем. Схема цикла:
- Thought - модель рассуждает о текущей ситуации и решает, что делать дальше.
- Action - модель выбирает инструмент и параметры его вызова.
- Observation - результат выполнения инструмента добавляется в контекст.
- Цикл повторяется с шага 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-моделей и поиск багов
Агентная система состоит из трёх независимо отказывающих компонентов: модель, слой оркестрации, инструменты. Когда агент выдаёт неверный ответ, первая реакция - «модель ошиблась». На практике модель ошибается реже, чем кажется. Чаще проблема в том, что оркестратор передал модели некорректный контекст или инструмент вернул данные в неожиданном формате.
Где искать баг: модель, оркестрация или инструмент?
Алгоритм локализации:
- Изолируйте модель. Возьмите ровно тот промпт, который был отправлен на последнем шаге, и подайте его в ту же модель напрямую через API, минуя оркестратор. Если ответ корректен - проблема в оркестрации.
- Проверьте историю сообщений. Часто оркестратор неправильно форматирует результаты вызова инструментов: добавляет лишние поля, обрезает вывод, нарушает очерёдность ролей. Модель видит битый контекст и «глупеет».
- Проверьте инструменты. Запустите их с теми же параметрами вручную. Инструмент может возвращать пустой ответ, ошибку в нестандартном формате или данные, которые модель не способна интерпретировать.
- Ищите петли в 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-модели или воспроизводится на любой архитектуре. Детальный разбор того, как скорость генерации кода агентами создаёт иллюзию продуктивности, скрывая рост сложности поддержки, дан в статье про кодинг-агентов и когнитивную ловушку.