Ідея за 30 секунд
Agent tracing показує повний шлях виконання одного run.
Трейс складається зі спанів (spans): кожен спан — це окремий крок, наприклад reasoning, tool call або LLM-генерація.
Це дає видимість на рівні кроків і значно спрощує debugging у production.
Основна проблема
У багатьох системах логують лише початок і завершення run.
Для агентів цього недостатньо: між стартом і фінальною відповіддю можуть бути десятки кроків. Без трейсингу важко зрозуміти, що саме зробив агент і на якому кроці виникла проблема.
Один і той самий запит може пройти по-різному: інша кількість кроків, інші інструменти, інша затримка (latency).
Без трейсингу складно відповісти навіть на базові питання:
- Який крок був найповільніший?
- Чому агент повторно викликав інструмент?
- Де саме з'явилася помилка?
- Чому виросли токени в конкретному run?
Саме тому трейсинг важливий: він показує повний шлях виконання run, а не лише фінальний результат.
Як це працює
У трейсингу є дві базові сутності:
trace— увесь шлях одного runspan— один крок усередині цього trace
На практиці step у runtime часто відповідає одному span, але не завжди.
Складний крок може містити вкладені spans — наприклад, виклик інструмента, який усередині робить кілька HTTP-запитів або звернень до бази.
Кожен span зазвичай має базові поля:
trace_idіrun_idдля кореляціїspan_id(і за потребиparent_span_id)step_type(reasoning,tool_call,llm_generate)latency_msіstatus(ok/error)
Ця структура (trace_id, span_id) базується на стандарті OpenTelemetry (OTel), який є фундаментом для більшості сучасних систем моніторингу.
Поле parent_span_id — частина OTel-моделі ієрархічних спанів, яка дозволяє будувати дерево виконання (trace tree).
Існують спеціалізовані інструменти для трейсингу агентів (наприклад LangSmith, Langfuse, Arize Phoenix), але ці принципи однакові незалежно від платформи.
Як виглядає трейс одного run
Найпростіше зрозуміти трейсинг на прикладі одного запиту:
У реальних системах кожен span-event містить trace_id, span_id і часто parent_span_id.
У прикладі нижче ці поля опущені для простоти.
trace_id: tr_9fd2
run_id: run_9fd2
user_query: "Find recent research about battery recycling"
span 1 llm_reasoning 320ms status=ok
span 2 tool_call: search 410ms status=ok
span 3 llm_reasoning 180ms status=ok
span 4 tool_call: fetch 260ms status=error
stop_reason: tool_error
Такий трейс одразу показує:
- які кроки пройшов агент;
- які інструменти були викликані;
- скільки часу зайняв кожен крок;
- де саме виникла затримка або помилка.
Трейси корисні не лише для debugging. Вони також важливі для evaluations і автоматичної перевірки проміжних кроків: без них складно перевірити, чи агент діяв правильно, а не лише чи дав правильну фінальну відповідь.
Коли використовувати
Трейсинг не завжди потрібен.
Для простого сценарію — один виклик LLM без tools і без циклу виконання — часто вистачає базового логування.
Але якщо run містить кілька кроків, виклики інструментів або повторні ітерації, без трейсингу стає складно:
- дебажити поведінку агента;
- контролювати latency і витрати;
- пояснювати, чому система прийняла конкретне рішення.
Приклад реалізації
Нижче — спрощений приклад runtime instrumentation для trace і spans. Такий підхід використовують у LangGraph, CrewAI і в кастомних agent runtime. У цьому прикладі весь run також оформлений як root span, а окремі кроки агента логуються як вкладені spans.
import contextvars
import logging
import time
import uuid
logger = logging.getLogger("agent")
trace_id_ctx = contextvars.ContextVar("trace_id", default=None)
def start_span(run_id, step_type, tool=None, parent_span_id=None):
span_id = str(uuid.uuid4())
started_at = time.time()
logger.info(
"span_started",
extra={
"trace_id": trace_id_ctx.get(),
"run_id": run_id,
"span_id": span_id,
"parent_span_id": parent_span_id,
"step_type": step_type,
"tool": tool,
},
)
return span_id, started_at
def finish_span(
run_id,
span_id,
step_type,
started_at,
status,
tool=None,
parent_span_id=None,
error=None,
):
logger.info(
"span_finished",
extra={
"trace_id": trace_id_ctx.get(),
"run_id": run_id,
"span_id": span_id,
"parent_span_id": parent_span_id,
"step_type": step_type,
"tool": tool,
"status": status,
"latency_ms": int((time.time() - started_at) * 1000),
"error": error,
},
)
def run_agent(agent, task):
trace_id = str(uuid.uuid4())
run_id = str(uuid.uuid4()) # у multi-agent системах один trace_id може містити кілька різних run_id
token = trace_id_ctx.set(trace_id)
logger.info("trace_started", extra={"trace_id": trace_id, "run_id": run_id, "task": task})
stop_reason = "max_steps"
step_count = 0
root_span_id, root_started_at = start_span(run_id, "run", parent_span_id=None)
try:
# у цьому прикладі всі кроки — дочірні до root span (без глибокої вкладеності)
for step in agent.iter(task): # step: reasoning або tool execution
step_count += 1
step_type = step.type # reasoning | tool_call | llm_generate
tool_name = getattr(step, "tool_name", None)
span_id, started_at = start_span(
run_id,
step_type,
tool=tool_name,
parent_span_id=root_span_id,
)
try:
result = step.execute()
finish_span(
run_id,
span_id,
step_type,
started_at,
status="ok",
tool=tool_name,
parent_span_id=root_span_id,
)
except Exception as error:
finish_span(
run_id,
span_id,
step_type,
started_at,
status="error",
tool=tool_name,
parent_span_id=root_span_id,
error=str(error),
)
stop_reason = "tool_error"
raise
if result.is_final:
stop_reason = "completed"
break
finally:
if stop_reason == "completed":
root_status = "ok"
elif stop_reason == "max_steps":
root_status = "error" # у цьому прикладі вважаємо як error для простоти
else:
root_status = "error"
finish_span(
run_id,
root_span_id,
"run",
root_started_at,
status=root_status,
error=None if root_status == "ok" else stop_reason,
)
logger.info(
"trace_finished",
extra={
"trace_id": trace_id,
"run_id": run_id,
"steps": step_count,
"stop_reason": stop_reason,
},
)
trace_id_ctx.reset(token)
У реальних системах trace_id і run_id потрібно прокидати через увесь ланцюг викликів.
У Python для цього часто використовують contextvars, щоб не передавати ідентифікатор у кожну функцію вручну.
Наприклад, один span у структурованому логу може виглядати так:
{
"timestamp": "2026-03-21T15:17:00Z",
"event": "span_finished",
"trace_id": "tr_9fd2",
"run_id": "run_9fd2",
"span_id": "sp_21ab",
"parent_span_id": "sp_root_01",
"step_type": "tool_call",
"tool": "search_docs",
"latency_ms": 410,
"status": "ok"
}
Типові помилки
Навіть якщо трейсинг уже додано, системи часто залишаються складними для діагностики через типові помилки нижче.
Трейс лише на рівні run, без спанів
Якщо логується тільки старт і фініш run, трейс фактично втрачає цінність: проміжні кроки не видно, а затримку або помилку майже неможливо локалізувати.
Відсутній trace_id у частині подій
Коли частина логів не має trace_id або run_id, події не складаються в один ланцюг.
Через це debugging займає більше часу навіть для простих інцидентів.
Не трасуються виклики інструментів
Інструменти часто є найповільнішою частиною run. Якщо tool calls не потрапляють у trace, важко знайти причину затримки і повторів. У production це може маскувати збій інструмента або спам інструментами.
Немає stop_reason і статусу span
Без stop_reason і status складно зрозуміти, run завершився успішно чи зупинився через обмеження або помилку.
У результаті важко відтворити інцидент і правильно налаштувати алерти.
Самоперевірка
Нижче — короткий checklist базового трейсингу перед релізом.
Прогрес: 0/9
⚠ Бракує базової observability
Систему буде складно дебажити в production. Почніть з run_id, structured logs і tracing tool calls.
FAQ
Q: Чим трейс відрізняється від звичайних логів?
A: Логи відповідають на питання «що сталося». Трейс показує послідовність кроків одного run і допомагає зрозуміти, «як саме це сталося».
Q: Що варто впровадити першим для трейсингу агента?
A: Мінімум: trace_id, run_id, span_id, тип кроку, latency, status і stop_reason. Цього вже достатньо для базового debugging.
Q: Чи обов'язково одразу підключати зовнішній tracing-інструмент?
A: Ні. Можна почати з власного instrumentation і JSON-логів. Зовнішні платформи стають особливо корисними, коли росте кількість run і команд.
Q: Коли повний трейсинг може бути зайвим?
A: Для простих single-shot сценаріїв без tools і без циклу виконання часто вистачає базового логування. Повний трейсинг стає особливо корисним, коли run містить кілька кроків, зовнішні інструменти або повторні ітерації.
Пов'язані сторінки
Далі за темою:
- Observability для AI-агентів — базова модель traces, logs і metrics.
- Розподілений трейсинг агентів — як пов'язувати трейси між кількома сервісами.
- Дебаг запусків агентів — як розбирати проблемні run покроково.
- Логування агентів — які події фіксувати в логах.
- Метрики агентів — які показники відслідковувати у production.