Ідея за 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_p95 | time-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.
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-метрик.
Ось як ці метрики виглядають разом у реальному дашборді:
| Segment | p50 latency | p95 latency | timeout_rate | Статус |
|---|---|---|---|---|
| gpt-4.1 + tools | 1.1s | 4.8s | 2.9% | critical: SLO risk |
| mini-model + cache | 420ms | 1.2s | 0.4% | ok |
| research workflow | 1.7s | 6.1s | 1.8% | warning: p95 росте |
Investigation
Коли спрацьовує latency-алерт:
- знайти сегмент з аномалією (
release,tool,model); - подивитись повільні runs у трейсингу;
- перевірити в логах retries, timeout і stop_reason;
- знайти 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-шарі.
Пов'язані сторінки
Далі за темою:
- Метрики агентів — загальна модель метрик для агентних систем.
- Метрики використання інструментів — як ізолювати latency на рівні tools.
- Моніторинг витрат агентів — як latency пов'язана з витратами.
- Трейсинг агента — як знайти повільний крок у конкретному run.
- Алерти у AI-агентах — як побудувати ранні сповіщення.