Треугольник: скорость, цена, качество
Первое, что нужно принять: универсальной «оптимизации» не существует. Три величины связаны, и улучшение одной обычно ухудшает другую.
Ключевой вывод из анатомии: у агента почти всё время съедают LLM-вызовы, а внутри вызова — генерация выходных токенов (она идёт последовательно, токен за токеном). Значит, оптимизировать свой Python-код бессмысленно, а по-настоящему работают три рычага: меньше последовательных вызовов, короче выход, дешевле/быстрее модель там, где можно.
Оптимизация «на глаз» почти всегда бьёт мимо: разработчик ускоряет свой код, который занимает 2% времени, пока 90% уходит в лишний reasoning-шаг. У тебя уже есть инструменты из раздела Observability: открой трейс и посмотри, сколько заняли LLM-вызовы, сколько инструменты, сколько шагов сделал агент, и p95 из дашборда. Оптимизируй самый жирный кусок, а не тот, который проще починить.
Ускорение: меньше шагов и короче ответ
Раз время = (число последовательных вызовов) × (время вызова), атакуем оба множителя.
- Параллелизация независимых шагов. Если агент делает три поиска, не зависящих друг от друга, — запусти их одновременно (
asyncio.gather), а не по очереди. Это паттерн parallelization из модуля 05. - Убрать лишние шаги. Каждый дополнительный «подумай ещё раз» стоит секунд. Reflection-цикл полезен, но не для каждого запроса — включай его только там, где он реально повышает качество.
- Короче выход. Выходные токены генерируются последовательно и стоят дороже входных: попроси модель отвечать без воды, ограничь
max_tokens. - Меньше вход. Prefill тоже время — это прямая выгода от context engineering и prompt caching из прошлых уроков.
- Стриминг. Не сокращает общее время, но радикально улучшает воспринимаемую скорость (TTFT) — самый дешёвый «прирост» из всех.
import asyncio
# ❌ последовательно: 1.5 + 1.2 + 1.8 = 4.5с
async def gather_slow(topic):
docs = await search_docs(topic)
prices = await get_prices(topic)
reviews = await get_reviews(topic)
return docs, prices, reviews
# ✅ параллельно: max(1.5, 1.2, 1.8) = 1.8с — втрое быстрее
async def gather_fast(topic):
docs, prices, reviews = await asyncio.gather(
search_docs(topic), get_prices(topic), get_reviews(topic),
return_exceptions=True, # один упал — не рушим всё (graceful degradation)
)
return docs, prices, reviews
# работает ТОЛЬКО для независимых шагов: если запрос B зависит
# от результата A — параллелить нечего, останется последовательность
Удешевление: правильная модель и кэш
Стоимость агента = токены × цена модели × число вызовов. Самый большой рычаг — не платить премиальной моделью за простую работу.
| Приём | Суть | Эффект |
|---|---|---|
| Модель под задачу | Классификация/роутинг/извлечение — дешёвой моделью, сложный reasoning — сильной | Кратное снижение счёта |
| LLM-роутинг | Дешёвая модель определяет сложность и отдаёт сильной только трудное | Экономия на массе простых запросов |
| Prompt caching | Не платить за повторяющийся префикс (прошлый урок) | До ~90% на входе |
| Семантический кэш | Похожий вопрос уже задавали → отдать сохранённый ответ | Ноль токенов на попадании |
| Batch API | Неспешные задачи — пакетом со скидкой | Дешевле, но с задержкой |
Самая частая незамеченная переплата: агент использует топовую модель для всех вызовов, включая «определи язык запроса», «выбери инструмент», «переформулируй запрос». Это как возить хлеб на грузовике. Раздели шаги: рутинные — дешёвая быстрая модель, финальный ответ и сложный reasoning — сильная. Только проверяй качество на своём датасете (раздел оценки): деградация должна быть измерена, а не предположена.
Кэш «по смыслу» (эмбеддинги вопроса, поиск похожего — модуль 04) даёт мгновенный бесплатный ответ, но у него есть опасность: при слишком мягком пороге похожести «Какая погода в Москве?» и «Какая погода в Минске?» могут схлопнуться в один кэш-хит. Ставь строгий порог, учитывай пользователя/контекст в ключе кэша и никогда не кэшируй персонализированные или быстро устаревающие ответы. Неверный ответ из кэша хуже, чем медленный правильный.
Пропускная способность
Третий вопрос — сколько параллельных пользователей мы выдержим. Здесь важно понять специфику: агент почти всё время ждёт сеть, а не считает. Значит, узкое место обычно не CPU твоего сервера.
Что НЕ ограничивает throughput агента: CPU сервера — агент 95% времени просто ЖДЁТ ответа LLM Что РЕАЛЬНО ограничивает: 1. Лимиты провайдера (RPM/TPM) ← чаще всего именно это, «потолок» снаружи 2. Блокирующий код в async → event loop встал, конкурентность = 1 3. Синхронная архитектура (поток на запрос) → упор в память/потоки 4. Общие ресурсы: пул соединений к БД, локи → Масштабирование агента = async + больше квоты у провайдера, а НЕ «добавить процессорных ядер»
Отсюда практический порядок действий: сначала убедись, что весь путь асинхронный (нет блокирующих вызовов — урок про FastAPI), затем что ты не упираешься в лимиты провайдера (rate limiting из раздела надёжности) и при необходимости подними tier. Горизонтальное масштабирование (больше реплик) помогает только после этого — иначе десять реплик просто быстрее упрутся в тот же общий лимит провайдера.
1) Измерь по трейсам, где время и деньги. 2) Убери лишние шаги и распараллель независимые. 3) Включи prompt caching и подрежь контекст. 4) Раздели модели по сложности шагов. 5) Добавь кэш там, где безопасно. 6) Только потом масштабируй инфраструктуру. И после каждого шага — прогон offline-оценки: оптимизация не должна тихо съесть качество.
Типичные ошибки
Ускоряют свой код (2% времени), пока 90% уходит в лишний LLM-шаг. Сначала трейс и p95, потом оптимизация самого жирного куска.
Премиальной моделью выполняются «выбери инструмент» и «определи язык». Дели шаги по сложности — экономия кратная.
Три поиска по очереди вместо asyncio.gather — время складывается вместо максимума. Параллель всё, что не зависит друг от друга.
Урезали шаги и модель — стало быстро и дёшево, но агент отвечает хуже, и это заметили пользователи, а не вы. После каждой оптимизации — offline-оценка.
Агент ждёт сеть, а не считает: реплики не помогут, если код блокирующий или упёрлись в RPM/TPM провайдера. Сначала async и лимиты, потом масштаб.
Шпаргалка
ТРЕУГОЛЬНИК: скорость ↔ цена ↔ КАЧЕСТВО — улучшение одного ухудшает другое
универсальной оптимизации НЕТ, выбирай точку осознанно под сценарий
АНАТОМИЯ ЗАДЕРЖКИ:
LLM-вызовы = 80–95% времени · инструменты · RAG · свой код (~0 → оптимизировать бесполезно)
внутри вызова: prefill (вход, лечится caching) + ГЕНЕРАЦИЯ (выход, последовательно)
ГЛАВНЫЙ РЫЧАГ = число ПОСЛЕДОВАТЕЛЬНЫХ LLM-вызовов (5 шагов × 3с = 15с)
⚠️ СНАЧАЛА ИЗМЕРЬ (трейс + p95), потом оптимизируй — жирный кусок, а не удобный
УСКОРЕНИЕ: параллелить независимые шаги (asyncio.gather) · убрать лишние шаги
(reflection не на каждый запрос) · короче выход (max_tokens) · меньше вход
(context engineering + caching) · СТРИМИНГ (не сокращает время, но чинит TTFT)
УДЕШЕВЛЕНИЕ: стоимость = токены × цена модели × число вызовов
★ МОДЕЛЬ ПОД ЗАДАЧУ: рутина (роутинг/классификация) — дешёвой, reasoning — сильной
LLM-роутинг по сложности · prompt caching (~90% входа) · семантический кэш · Batch API
⚠️ семантический кэш: строгий порог, юзер в ключе, НЕ кэшировать персональное/устаревающее
(Москва/Минск схлопнутся при мягком пороге — неверный ответ хуже медленного)
THROUGHPUT: агент 95% времени ЖДЁТ сеть → узкое место НЕ CPU
реально ограничивают: лимиты провайдера (RPM/TPM) · блокирующий код в async ·
синхронная архитектура · общие пулы/локи
→ async + квоты, а не «больше ядер» (10 реплик упрутся в тот же лимит)
ПОРЯДОК: измерь → убрать/параллелить шаги → caching+контекст → модели по сложности →
кэш → масштаб · ПОСЛЕ КАЖДОГО ШАГА — offline-оценка качества
ГРАБЛИ: без измерения · одна топ-модель на всё · последовательные независимые ·
без проверки качества · железо вместо async/квот
Практическое задание
Задание: оптимизируй агента по числам
- Открой трейс агента из модулей 03–05 и разложи время: сколько на LLM-вызовы, инструменты, свой код? Сколько последовательных LLM-вызовов на один запрос?
- Найди независимые шаги, которые сейчас идут по очереди. Распараллель через
asyncio.gather— насколько упало общее время? - Найди шаг, где используется топовая модель, но задача рутинная (роутинг, классификация, извлечение). Замени на дешёвую и прогони offline-оценку: просело ли качество?
- Посчитай стоимость одного запроса до и после включения prompt caching и подрезки контекста. Во сколько раз изменилась?
- Прикинь throughput: что упрётся первым — твой сервер или лимиты провайдера? Есть ли в коде блокирующие вызовы, ограничивающие конкурентность?
- Для своего продукта выбери точку в треугольнике: что важнее — скорость, цена или качество? Какие компромиссы ты готов принять и почему?
- Со звёздочкой: добавь семантический кэш для частых запросов. Подбери порог похожести так, чтобы не склеивались разные вопросы, и замерь hit rate и экономию.
Что дальше
Агент оптимизирован по скорости, цене и пропускной способности. Остался последний шаг — вывести его в мир: упаковать, развернуть, настроить окружение и автоматизировать выкатку. Финальный урок раздела и модуля — деплой: Docker, Railway, Modal, AWS Lambda и как выбрать площадку под свой агент.