Разработка ИИ-агентов · Модуль 4 · Урок 4.4
Наблюдаемость, логирование, стоимость, кеширование
Без наблюдаемости агент — чёрный ящик
Агент недетерминирован и многошагов: когда он «повёл себя странно», без следов вы не поймёте, почему. Поэтому наблюдаемость встраивают с самого начала, а не прикручивают после инцидента. Минимум, который стоит логировать на каждый запрос: входной вопрос, каждый ход модели (finish_reason), какие инструменты вызваны и с какими аргументами, что они вернули, число оборотов цикла, задержка и токены.
Удобно вести трассировку: один request_id, под которым видна вся цепочка шагов. Логи делайте структурированными (JSON-поля, а не свободный текст) — их проще фильтровать и агрегировать. Для распределённых систем пригодится OpenTelemetry, но начать можно с обычного slog в stdlib.
Стоимость: считать и показывать
Каждый оборот цикла — это оплаченные токены, и в агенте они складываются. Поэтому стоимость надо измерять: провайдер возвращает поле usage (prompt/completion токены, иногда — кэшированные); агрегируйте его по всем оборотам одного запроса и считайте приблизительную цену по карте цен модели. Это ровно то, что делает счётчик токенов в тьюторе этого курса.
Что отслеживать: токены и цену на запрос, среднее число оборотов, долю запросов с эскалацией/поиском, частоту срабатывания safety-cap. Эти метрики ловят регрессии («после правки промпта средняя стоимость выросла вдвое») и помогают выбирать модель: дешёвую — на простые шаги, дорогую — на сложные (см. идею модельных тиров).
Кеширование: платить за повтор один раз
Два уровня кеширования экономят заметно.
- Prompt caching (на стороне провайдера): большой стабильный префикс — системный промпт, длинные инструкции, справочные данные — помечается как кэшируемый, и его не тарифицируют заново по полной при повторных запросах. Держите стабильную часть в начале и неизменной — так она кешируется. Поле
cached_tokensв usage показывает, сколько токенов пришло из кеша. - Кеш результатов инструментов (на вашей стороне): если инструмент детерминирован и его вход повторяется (тот же запрос к справочнику), закешируйте результат по ключу-аргументам и не дёргайте внешний сервис снова. Для недетерминированных и изменяющих данные инструментов кеш, разумеется, неуместен.
Кеширование — это про деньги и задержку; наблюдаемость покажет, работает ли оно (растёт ли cached_tokens, падает ли число обращений к инструментам).
func logTurn(log *slog.Logger, reqID string, turn int, fr string,
tools []string, usage Usage, dur time.Duration) {
log.Info("agent_turn",
"req_id", reqID,
"turn", turn,
"finish_reason", fr,
"tools", tools,
"prompt_tokens", usage.PromptTokens,
"completion_tokens", usage.CompletionTokens,
"cached_tokens", usage.CachedTokens,
"latency_ms", dur.Milliseconds(),
)
}type toolCache struct {
mu sync.Mutex
m map[string]string // ключ: name+args -> результат
}
func (c *toolCache) call(name, argsJSON string, run func() string) string {
key := name + "|" + argsJSON
c.mu.Lock()
if v, ok := c.m[key]; ok {
c.mu.Unlock()
return v // попадание в кеш — внешний вызов не делаем
}
c.mu.Unlock()
v := run() // промах: выполняем инструмент
c.mu.Lock()
c.m[key] = v
c.mu.Unlock()
return v
}
// ВНИМАНИЕ: кешируем только детерминированные инструменты без побочных эффектов.Anti-patterns
| Анти-паттерн | Почему плохо | Как правильно |
|---|---|---|
| Запускать агента в прод без логов/трейсов | Инциденты невозможно разобрать | Структурные логи и трассировка с request_id с самого начала |
| Не считать токены/стоимость | Сюрприз в счёте, незаметные регрессии | Агрегировать usage по оборотам, считать цену, мониторить метрики |
| Логировать секреты/персональные данные | Утечка через логи | Маскировать ключи и чувствительные поля в логах |
| Кешировать недетерминированные/изменяющие инструменты | Устаревшие/неверные результаты | Кеш только для детерминированных read-only вызовов |
| Менять стабильный префикс ради мелочей | Сбивается prompt caching, растёт цена | Держать системный префикс неизменным и в начале |
Практическое задание
- Добавьте структурированное логирование (
slog) каждого оборота: инструменты,finish_reason, токены, задержку, request_id. - Агрегируйте
usageпо всем оборотам запроса и выведите приблизительную стоимость по карте цен. - Включите prompt caching на стабильный системный префикс и проверьте рост
cached_tokens. - Реализуйте кеш результатов для одного детерминированного инструмента и измерьте сокращение внешних вызовов.
- Убедитесь, что в логи не попадают ключи API и персональные данные.
Проверка знаний
Что минимально полезно логировать на каждом обороте агента?
Верный ответ: B
B верно. Такой структурный след позволяет разобрать поведение агента. C — секреты в логах недопустимы; D — без логов прод неотлаживаем.
Какие инструменты безопасно кешировать по аргументам?
Верный ответ: B
B верно. Кеш уместен для детерминированных вызовов без побочных эффектов. Изменяющие/недетерминированные инструменты (C) кешировать нельзя — получите устаревшие/неверные результаты.
Почему важно держать системный префикс стабильным и в начале запроса?
Верный ответ: B
B верно. Prompt caching кеширует стабильный префикс; его изменение сбивает кеш и повышает стоимость. cached_tokens показывает эффект.