Настроить token budget для агента
ТЕХНИЧЕСКОЕ ЗАДАНИЕ: Настроить token budget для агента
1. Цель задачи
Научиться контролировать расход токенов в LLM-агенте, чтобы предотвратить неконтролируемые затраты (runaway costs). Реализовать механизм budget: установить лимит 5000 токенов на сессию агента и принудительно останавливать его работу при превышении. В процессе освоить методы мониторинга потребления токенов, интеграцию лимита в цикл агента]] и обработку граничных случаев.
Ключевой результат Агент гарантированно завершает сессию при достижении суммарного потребления токенов (вход + выход) 5000 токенов, а логи фиксируют точное потребление, момент остановки и причину. Стоимость сессии никогда не превысит заданный лимит.
2. Исходные данные
| Что нужно | Откуда взять |
|---|---|
| Рабочий агент (ReAct / tool-using) | Собственный проект LangChain / AutoGPT / sim-Agent (см. ниже) |
| Средство подсчёта токенов | Библиотека tiktoken (OpenAI) или transformers (Hugging Face) |
| Среда выполнения | Python 3.10+, Jupyter / локальный скрипт |
| Тестовые сценарии (5–10 запросов, разной сложности) | Придумать самостоятельно или взять из документов датасета |
Если нет реального агента — симулируем:
- Создаём простого ReAct-агента на langgraph или smolagents с 2–3 инструментами (калькулятор, поиск по Wikipedia, чтение файла).
- Подключаем LLM через OpenAI API или локальную модель (через Ollama / vLLM) — для отслеживания токенов важен любой подсчёт.
- Добавляем глобальный счётчик токенов, который суммирует input_tokens + output_tokens каждого вызова LLM.
Если API не возвращает число токенов (например, Ollama), используем tiktoken для подсчёта по строке запроса/ответа.
3. Технологический стек
| Компонент | Инструменты | Назначение |
|---|---|---|
| Агентный фреймворк | LangChain / LangGraph / smolagents | Базовое построение агента с инструментами |
| Подсчёт токенов | tiktoken + transformers.AutoTokenizer | Точный подсчёт токенов для любой модели |
| Асинхронное выполнение | asyncio / concurrent.futures | Управление временем жизни агента, принудительная остановка |
| Логирование | logging + structlog (опционально) | Запись потребления токенов, событий остановки |
| Тестирование | pytest | Автоматическая проверка работы budget |
| Мониторинг (опционально) | Prometheus-клиент / простой CSV | Сбор метрик для Grafana дашборда |
4. Этапы выполнения
Этап 1: Подготовка окружения и базовый подсчёт токенов (30 минут)
Действия
-
Установить зависимости:
pip install langchain langgraph openai tiktoken python-dotenv pytest -
Создать файл
token_counter.pyс функцией подсчёта токенов для строки:import tiktoken def count_tokens(text: str, model: str = "gpt-4") -> int: encoder = tiktoken.encoding_for_model(model) return len(encoder.encode(text)) -
Написать тест проверки подсчёта:
def test_count_tokens(): assert count_tokens("Hello, world") == 4 # зависит от модели -
Подготовить агента: если его нет, создать минимального агента с одним инструментом (например,
calculator). Убедиться, что каждый вызов LLM возвращает usage (OpenAI) или можно посчитать строки.
Ожидаемый результат этапа Работает функция count_tokens, агент способен выполнять простые запросы.
Этап 2: Внедрение глобального аккумулятора токенов (60 минут)
Действия
-
Добавить в агента класс TokenTracker:
from dataclasses import dataclass, field @dataclass class TokenTracker: budget: int = 5000 used: int = 0 stopped: bool = False def consume(self, tokens: int) -> bool: """Возвращает True, если budget превышен""" self.used += tokens if self.used >= self.budget: self.stopped = True return self.stopped -
Модифицировать функцию вызова LLM так, чтобы перед запросом и после ответа увеличивать счётчик:
def llm_call(prompt: str, tracker: TokenTracker) -> str: if tracker.stopped: raise RuntimeError("Token budget exceeded") # Подсчёт токенов запроса input_tokens = count_tokens(prompt) tracker.consume(input_tokens) if tracker.stopped: raise RuntimeError("Token budget exceeded (input)") # Вызов LLM (пример OpenAI) response = client.chat.completions.create(...) output_tokens = response.usage.completion_tokens # или count_tokens(response.choices[0].message.content) tracker.consume(output_tokens) if tracker.stopped: raise RuntimeError("Token budget exceeded (output)") return response.choices[0].message.content -
Интегрировать TokenTracker в цикл агента (например, в AgentExecutor LangChain через callback или кастомный
on_llm_start/on_llm_end).
Ожидаемый результат этапа Любой вызов LLM увеличивает счётчик; при превышении лимита выбрасывается исключение.
Этап 3: Остановка агента при превышении лимита (45 минут)
Действия
-
Написать декоратор или обёртку для
runметода агента, которая ловит исключение RuntimeError и корректно завершает сессию:def safe_run(agent, prompt: str, tracker: TokenTracker): try: result = agent.run(prompt, callbacks=[TrackerCallback(tracker)]) return result except RuntimeError as e: log.warning(f"Session stopped: {e}") return {"error": str(e), "used_tokens": tracker.used} -
В конце сессии (нормальной или прерванной) записать в лог:
- session_id (например, UUID)
total_tokens_usedstopped(True/False)- budget
-
Тест: отправить агенту запрос, который гарантированно превысит лимит (например, генерация длинного текста или многошаговая цепочка). Убедиться, что агент не "зависает" и завершается с ошибкой.
Ожидаемый результат этапа Агент корректно останавливается при превышении budget, лог содержит ключевую информацию.
Этап 4: Тестирование сценариев и граничные случаи (30 минут)
Действия
-
Подготовить минимум 5 тестовых сценариев:
- короткий запрос (должен пройти)
- длинный запрос (превышение на входе)
- короткий запрос + длинный ответ (превышение на выходе)
- итеративный агент с несколькими вызовами (суммарное превышение)
- запрос, требующий много шагов (проверка, что остановка происходит без потери данных)
-
Написать pytest-тесты:
def test_short_query(): tracker = TokenTracker(budget=5000) result = safe_run(agent, "Hello", tracker) assert result is not None assert result.get("error") is None assert tracker.used < 5000 -
Проверить, что после остановки агент не делает новых вызовов LLM (нет "траты токенов" после лимита).
Ожидаемый результат этапа Все тесты проходят, граничные случаи обработаны.
Этап 5: Мониторинг и экспорт метрик (30 минут)
Действия
-
Создать скрипт monitor.py, который в реальном времени читает логи и выводит:
Session 12345 | used: 1230 / 5000 | stopped: False Session 12346 | used: 5120 / 5000 | stopped: True (OVERRUN!) -
(Опционально) Экспорт в Prometheus с помощью prometheus_client:
from prometheus_client import Gauge, start_http_server token_gauge = Gauge('agent_token_usage', 'Current token usage', ['session_id']) -
Убедиться, что при превышении лимита в лог пишется сообщение с уровнем
WARNINGи указанием превышения.
Ожидаемый результат этапа Логи содержат метрики по каждой сессии, скрипт мониторинга показывает актуальное состояние.
5. Критерии приемки (Definition of Done)
- Агент не превышает 5000 токенов за сессию (сумма input + output).
- При достижении лимита сессия корректно останавливается (без lossy side-effects).
- Механизм не блокирует сессии, которые не достигают лимита.
- В логах каждой сессии присутствуют поля: session_id, total_tokens, budget,
stopped, timestamp. - Написан как минимум один тест, проверяющий превышение лимита.
- Написан тест, проверяющий, что нормальная сессия завершается успешно.
- Код покрыт комментариями и содержит README с инструкцией по запуску.
- Мониторинг (лог или Prometheus) показывает количество активных и остановленных сессий.
- Нет "утечки токенов" (token leak) — счётчик не сбрасывается раньше времени.
6. Ожидаемый результат
Основной файл agent_with_budget.py (или аналогичный), содержащий:
- класс
TokenTracker - модифицированный агент с поддержкой budget
- функцию
safe_run - примеры вызова
Дополнительно
tests/test_token_budget.py— набор тестов (минимум 5 тестов)monitor.py— скрипт мониторинга (опционально)requirements.txt— список зависимостейlogs/— пример записей лога с остановленной сессией
7. Возможные сложности и их решение
| Сложность | Решение |
|---|---|
| API не возвращает количество токенов | Использовать tiktoken для подсчёта по тексту запроса/ответа; учитывать, что у разных моделей разный encoding. |
| Токены, потраченные на инструменты (например, чтение файла) не учитываются | Включать в budget только токены LLM; инструменты считаются "бесплатными" (если не указано иное). |
| Асинхронный вызов — гонка данных | Использовать threading.Lock или asyncio.Lock при обновлении TokenTracker.used. |
| Остановка агента в середине вызова инструмента может оставить незавершённое состояние | Разработать механизм "graceful shutdown": завершить текущий вызов LLM, но не начинать новый. |
| Высокая частота вызовов — задержка от подсчёта токенов | Кэшировать tiktoken.Encoding в глобальной переменной; подсчёт занимает <1 ms. |
8. Бюджет времени (оценка)
| Этап | Время |
|---|---|
| Этап 1: Подготовка окружения и подсчёт токенов | 30 мин |
| Этап 2: Внедрение глобального аккумулятора | 60 мин |
| Этап 3: Остановка агента при превышении лимита | 45 мин |
| Этап 4: Тестирование сценариев и граничные случаи | 30 мин |
| Этап 5: Мониторинг и экспорт метрик | 30 мин |
| Итого | 3 ч 15 мин |
Примечание: При первом выполнении (знакомство с фреймворком агентов) рекомендуется добавить 1 час резерва.
9. Связанные вопросы из базы знаний
| Вопрос | Тема |
|---|---|
| 12 | Как организовать мониторинг затрат LLM в production? |
| 45 | Какие стратегии ограничения токенов для агентов существуют? |
| 78 | Как измерить потребление токенов в LangChain агентах? |
| 134 | Настроить token budget для агента (данное ТЗ) |
| 201 | Что такое semantic caching и как он снижает затраты? |
| 267 | Как реализовать fallback model при превышении budget? |
| 389 | Какие метрики cost efficiency нужно отслеживать? |
| 412 | Как работает tiktoken и какие encoding бывают? |
| 560 | Разница между input_tokens и output_tokens в ценообразовании |
| 789 | Graceful degradation в мультиагентных системах |
10. Чек-лист самопроверки
- Я подключил
tiktokenи проверил, что функция подсчёта даёт корректные значения для используемой модели. - Я добавил
TokenTrackerв цикл агента так, что каждый вызов LLM увеличивает счётчик. - Я реализовал исключение
RuntimeErrorпри превышении лимита и убедился, что агент не делает новых вызовов. - Я написал минимум 5 тестов, включая тест на превышение и на нормальное выполнение.
- Я проверил, что логи содержат все обязательные поля (
session_id,used,stopped). - Я протестировал асинхронный сценарий (если агент async) и убедился, что блокировки работают.
- Я описал в README как запустить агента и как интерпретировать логи.