Моніторинг затримок агентів

Допомагає відстежувати час відповіді та затримки виконання.
На цій сторінці
  1. Ідея за 30 секунд
  2. Основна проблема
  3. Як це працює
  4. Типові production-метрики latency
  5. Як читати latency-layer
  6. Коли використовувати
  7. Приклад реалізації
  8. Investigation
  9. Типові помилки
  10. Дивляться тільки на середню latency
  11. Немає розбиття latency по tools і step type
  12. Ігнорується queue time
  13. Немає timeout-rate і retry-overhead метрик
  14. Немає алертів на p95/p99 і timeout spikes
  15. Самоперевірка
  16. FAQ
  17. Пов'язані сторінки

Ідея за 30 секунд

Latency monitoring для AI-агентів показує, де саме система сповільнюється: у LLM-кроках, tools, чергах або повторних ітераціях.

Без цього складно зрозуміти, чому користувач чекає довше, навіть коли run формально завершується успішно.

Основна проблема

Один run може завершитися коректно, але бути занадто повільним для production.

Два запити з однаковою відповіддю можуть мати різну latency: через довший reasoning-ланцюг, повільний tool або retries. Без latency monitoring це зазвичай видно тільки після скарг користувачів.

Далі розберемо, як читати ці сигнали і знаходити, що саме сповільнює run.

У production це часто виглядає так:

  • середня latency виглядає нормально, але p95 вже росте;
  • один tool непомітно стає bottleneck;
  • retries додають секунди без помітного зростання трафіку;
  • команда бачить проблему вже після часткового збою.

Саме тому latency-layer варто моніторити окремо, а не лише через загальні run-метрики.

Як це працює

Моніторинг затримок будується навколо двох типів сигналів:

  • runtime signals (queue_time, step_latency, tool_latency, ttft);
  • service signals (run_latency_p50/p95/p99, timeout_rate, retry_overhead_ms).

Ці метрики відповідають на питання «де і чому система сповільнюється в часі». Логи й трейсинг потрібні, щоб пояснити конкретний повільний run.

Latency ≠ user experience. Користувачі не бачать середнє — вони відчувають p95/p99. Саме повільні запити визначають сприйняття системи. Latency часто прямо пов'язана з витратами. Довші runs означають більше токенів, більше tool-викликів і більше retries, що напряму збільшує cost.

Типові production-метрики latency

МетрикаЩо показуєНавіщо потрібна
run_latency_p50типовий час виконання runбазовий контроль швидкості
run_latency_p95 / p99«довгі» і найповільніші runраннє виявлення деградації
step_latency_p95які кроки агента сповільнюютьсялокалізація проблемного етапу
tool_latency_p95затримка конкретних toolsпошук зовнішніх bottleneck
ttft_p95time-to-first-token для LLMконтроль швидкості першої відповіді
queue_time_p95скільки run чекає до стартуконтроль навантаження і capacity
timeout_rateчастка кроків, що завершились timeoutранній сигнал нестабільності
retry_overhead_msскільки часу додають retriesоцінка впливу відновлення на latency

run_latency_p95 і run_latency_p99 зазвичай рахуються на рівні дашборду або запитів до метрик, а не окремим лічильником у коді.

Щоб метрики були практичними, їх зазвичай сегментують за release, model, tool і типом workflow.

Важливо: не додавай у labels висококардинальні поля (run_id, request_id, user_id), інакше сховище метрик швидко перевантажиться.

Як читати latency-layer

Де виникає затримка → як агент поводиться → що саме сповільнює run. Це три рівні, які завжди потрібно дивитися разом.

Важливо дивитися на тренди у часі і різницю між релізами, а не на одиничні значення.

Далі дивимось на комбінації сигналів:

  • run_latency_p95 ↑ + tool_latency_p95 ↑ → bottleneck у зовнішніх інструментах;
  • run_latency_p95 ↑ + step_count ↑ → агент робить зайві ітерації;
  • ttft_p95 ↑ + tool_latency_p95 ≈ стабільний → проблема в LLM-шарі, не в tools;
  • timeout_rate ↑ + retry_overhead_ms ↑ → retries маскують нестабільність і додають latency;
  • queue_time_p95 ↑ + run_count ↑ → системі бракує capacity.

Коли використовувати

Повний latency monitoring не завжди потрібен.

Для простого прототипу інколи достатньо базового часу відповіді.

Але детальний моніторинг затримок стає критичним, коли:

  • система вже в production і має SLO/SLA по швидкості;
  • агент використовує кілька зовнішніх tools і залежностей;
  • релізи виходять часто й потрібно бачити latency-регресії;
  • у workflow є черги, retries або багатокрокові reasoning-цикли.

Приклад реалізації

Нижче — спрощений приклад інструментації latency metrics у стилі Prometheus. Приклад показує базовий контроль: run latency, кроки, tools, timeouts і retry overhead.

PYTHON
import time
from prometheus_client import Counter, Histogram

# perf_counter() використовується замість time.time(),
# щоб отримати точні монотонні вимірювання latency
RUN_TOTAL = Counter(
    "agent_run_total",
    "Total number of agent runs",
    ["status", "stop_reason", "release"],
)

RUN_LATENCY_MS = Histogram(
    "agent_run_latency_ms",
    "Run latency in milliseconds",
    ["release"],
    buckets=(100, 250, 500, 1000, 2000, 5000, 10000, 20000),
)

STEP_LATENCY_MS = Histogram(
    "agent_step_latency_ms",
    "Latency by step type in milliseconds",
    ["step_type", "release"],
    buckets=(20, 50, 100, 250, 500, 1000, 2000, 5000),
)

TOOL_LATENCY_MS = Histogram(
    "agent_tool_latency_ms",
    "Tool latency in milliseconds",
    ["tool", "release"],
    buckets=(20, 50, 100, 250, 500, 1000, 2000, 5000),
)

QUEUE_TIME_MS = Histogram(
    "agent_queue_time_ms",
    "Queue wait time before run start",
    ["release"],
    buckets=(0, 20, 50, 100, 250, 500, 1000, 2000),
)

TTFT_MS = Histogram(
    "agent_ttft_ms",
    "Time to first token in milliseconds",
    ["model", "release"],
    buckets=(50, 100, 200, 400, 800, 1500, 3000),
)

TIMEOUT_TOTAL = Counter(
    "agent_timeout_total",
    "Total timeout errors by layer",
    ["layer", "release"],
)

RETRY_OVERHEAD_MS = Histogram(
    "agent_retry_overhead_ms",
    "Added latency from retries",
    ["release"],
    buckets=(0, 50, 100, 250, 500, 1000, 2000, 5000),
)


def run_agent(agent, task, queue_time_ms=0, release="2026-03-22"):
    run_status = "ok"
    stop_reason = "max_steps"
    started_at = time.perf_counter()

    if queue_time_ms > 0:
        QUEUE_TIME_MS.labels(release=release).observe(queue_time_ms)

    try:
        for step in agent.iter(task):
            step_type = step.type
            step_started_at = time.perf_counter()

            try:
                result = step.execute()
            except TimeoutError:
                run_status = "error"
                if step_type == "tool_call":
                    stop_reason = "tool_timeout"
                elif step_type == "llm_generate":
                    stop_reason = "llm_timeout"
                else:
                    stop_reason = "step_timeout"
                layer = (
                    "tool"
                    if step_type == "tool_call"
                    else "llm"
                    if step_type == "llm_generate"
                    else "runtime"
                )
                TIMEOUT_TOTAL.labels(layer=layer, release=release).inc()
                raise
            except Exception:
                run_status = "error"
                if step_type == "tool_call":
                    stop_reason = "tool_error"
                elif step_type == "llm_generate":
                    stop_reason = "llm_error"
                else:
                    stop_reason = "step_error"
                raise
            finally:
                step_latency_ms = (time.perf_counter() - step_started_at) * 1000
                STEP_LATENCY_MS.labels(step_type=step_type, release=release).observe(step_latency_ms)
                if step_type == "tool_call":
                    TOOL_LATENCY_MS.labels(
                        tool=getattr(step, "tool_name", "unknown"),
                        release=release,
                    ).observe(step_latency_ms)
                # retry overhead може бути навіть якщо крок завершився помилкою
                retry_overhead_ms = float(getattr(step, "retry_overhead_ms", 0) or 0)
                if retry_overhead_ms > 0:
                    RETRY_OVERHEAD_MS.labels(release=release).observe(retry_overhead_ms)

            if step_type == "llm_generate":
                model = getattr(step, "model", "unknown")
                ttft_ms = float(getattr(result, "ttft_ms", 0) or 0)
                if ttft_ms > 0:
                    TTFT_MS.labels(model=model, release=release).observe(ttft_ms)

            if result and result.is_final:
                stop_reason = "completed"
                break
    finally:
        run_latency_ms = (time.perf_counter() - started_at) * 1000
        RUN_LATENCY_MS.labels(release=release).observe(run_latency_ms)
        RUN_TOTAL.labels(status=run_status, stop_reason=stop_reason, release=release).inc()

# run_latency_p95 і run_latency_p99 зазвичай рахуються на рівні дашборду:
# histogram_quantile(...) за даними agent_run_latency_ms bucket-метрик.

Ось як ці метрики виглядають разом у реальному дашборді:

Segmentp50 latencyp95 latencytimeout_rateСтатус
gpt-4.1 + tools1.1s4.8s2.9%critical: SLO risk
mini-model + cache420ms1.2s0.4%ok
research workflow1.7s6.1s1.8%warning: p95 росте

Investigation

Коли спрацьовує latency-алерт:

  1. знайти сегмент з аномалією (release, tool, model);
  2. подивитись повільні runs у трейсингу;
  3. перевірити в логах retries, timeout і stop_reason;
  4. знайти root cause (tool, LLM, queue, логіка агента, зовнішній сервіс).

Типові помилки

Навіть коли latency metrics уже додані, вони часто не дають користі через типові помилки нижче.

Дивляться тільки на середню latency

Середнє значення часто приховує деградацію. Для production мінімум — p50 і p95, а для критичних сценаріїв ще й p99.

Немає розбиття latency по tools і step type

Без цього важко зрозуміти, що саме повільне: LLM, tool чи сам цикл агента. У такій ситуації складно швидко локалізувати збій інструмента.

Ігнорується queue time

Run може бути повільним ще до старту виконання. Без queue_time_p95 легко пропустити проблему capacity.

Немає timeout-rate і retry-overhead метрик

Retries можуть приховувати нестабільність і штучно роздувати latency. Це часто поєднується зі спамом інструментами.

Немає алертів на p95/p99 і timeout spikes

Без алертів команда дізнається про проблему запізно, коли вже порушено SLO.

Самоперевірка

Нижче — короткий checklist базового latency monitoring перед релізом.

Прогрес: 0/9

⚠ Бракує базової observability

Систему буде складно дебажити в production. Почніть з run_id, structured logs і tracing tool calls.

FAQ

Q: Чим latency monitoring відрізняється від звичайного моніторингу швидкості API?
A: Для агентів важливо моніторити не тільки загальний час відповіді, а й внутрішні кроки: reasoning, tools, retries, queue time.

Q: Який мінімум latency-метрик потрібен на старті?
A: Почни з run_latency_p50/p95, tool_latency_p95, timeout_rate і queue_time_p95.

Q: Чому p95 важливіший за середню latency?
A: Бо саме p95 показує, що відбувається з повільними запитами, які найчастіше помічають користувачі.

Q: Як відрізнити latency-проблему в tools від проблеми в LLM?
A: Порівнюй tool_latency_p95 і ttft_p95: якщо росте тільки tool-latency, bottleneck у tools; якщо росте ttft, проблема в LLM-шарі.

Пов'язані сторінки

Далі за темою:

⏱️ 7 хв читанняОновлено 22 березня 2026 р.Складність: ★★★
Інтегровано: продакшен-контрольOnceOnly
Додай guardrails до агентів з tool-calling
Зашип цей патерн з governance:
  • Бюджетами (кроки / ліміти витрат)
  • Дозволами на інструменти (allowlist / blocklist)
  • Kill switch та аварійна зупинка
  • Ідемпотентність і dedupe
  • Audit logs та трасування
Інтегрована згадка: OnceOnly — контрольний шар для продакшен агент-систем.

Автор

Микола — інженер, який будує інфраструктуру для продакшн AI-агентів.

Фокус: патерни агентів, режими відмов, контроль рантайму та надійність систем.

🔗 GitHub: https://github.com/mykolademyanov


Редакційна примітка

Ця документація підготовлена з допомогою AI, із людською редакторською відповідальністю за точність, ясність і продакшн-релевантність.

Контент базується на реальних відмовах, постмортемах та операційних інцидентах у розгорнутих AI-агентних системах.