Агент — чёрный ящик
Пока граф из двух узлов — всё понятно. Но реальный агент за один invoke может сделать десятки шагов: спросить LLM, вызвать инструмент, снова спросить, переключить ветку, уйти в сабграф. Когда результат неверный, вопросов масса: на каком узле сломалось? какой именно промпт ушёл в модель? почему она выбрала этот инструмент? сколько это стоило и где тормозит?
Отлаживать это через print() и логи — мучение: они плоские, теряют структуру вложенных вызовов и не показывают полные промпты. Нужен инструмент, который записывает выполнение как дерево и даёт кликнуть в любой шаг. Это и есть трейсинг, а LangSmith — его реализация для нашего стека.
У агентов недетерминированное поведение: один и тот же запрос идёт разными путями. Без записи трейсов воспроизвести и понять баг почти невозможно. Поэтому наблюдаемость для LLM-приложений — такая же база, как логирование для обычного бэкенда.
Включение: только переменные окружения
Главное преимущество: LangChain и LangGraph уже умеют отправлять трейсы — нужно лишь включить это переменными окружения. Ни строчки в коде графа менять не надо.
# .env или окружение процесса
export LANGSMITH_TRACING=true
export LANGSMITH_API_KEY="lsv2_..." # ключ из настроек LangSmith
export LANGSMITH_PROJECT="my-agent" # необязательно (иначе проект "default")
# Ключи провайдера LLM — как обычно
export OPENAI_API_KEY="sk-..."
# Никаких импортов LangSmith и правок графа — просто запускаем как всегда
result = graph.invoke({"messages": [{"role": "user", "content": "Погода в Москве?"}]})
# Каждый узел, каждый вызов LLM и инструмента автоматически
# записались как трейс в проект my-agent на smith.langchain.com
Раньше переменные назывались LANGCHAIN_TRACING_V2, LANGCHAIN_API_KEY, LANGCHAIN_PROJECT — они всё ещё работают. Новый префикс — LANGSMITH_*. Не смешивай оба набора в одном окружении, чтобы не запутаться, какой из них реально применился.
Анатомия трейса
Трейс (trace) — это запись одного запуска графа в виде дерева вложенных шагов (их называют runs). Верхний узел — весь invoke, внутри — узлы графа, внутри них — вызовы LLM и инструментов. По каждому шагу видно вход, выход, время, токены, стоимость и ошибки.
Что показывает каждый шаг трейса:
- Вход и выход — точный промпт, отправленный в LLM, и её полный ответ (включая
tool_calls). Видно ровно то, что «видела» модель. - Время — сколько занял каждый шаг; сразу понятно, где бутылочное горлышко.
- Токены и стоимость — расход по каждому вызову и суммарно.
- Ошибки — упавший шаг подсвечен, виден traceback и вход, на котором сломалось.
Отладка по трейсу
Трейс превращает расследование бага в клики. Типичные сценарии в агентах LangGraph:
Для агентов с прерываниями (этот раздел) трейс особенно полезен: в нём видно, на каком узле граф встал на паузу, какое состояние было на момент interrupt и что человек прислал в resume. Связка checkpoint (что сохранилось) + трейс (как к этому пришли) закрывает почти любой разбор инцидента.
Теги, имена и метаданные
Когда трейсов тысячи, нужна навигация. Через config запуска можно навесить понятное имя, теги и метаданные — потом по ним фильтровать в LangSmith (например, найти все трейсы конкретного пользователя или только продовые).
config = {
"run_name": "support-agent", # читаемое имя трейса
"tags": ["prod", "support"], # фильтры в UI
"metadata": {"user_id": "u-42", # произвольные поля для поиска
"version": "1.3"},
"configurable": {"thread_id": "t-1"}, # это уже из урока про threads
}
graph.invoke({"messages": [{"role": "user", "content": "..."}]}, config)
# В LangSmith трейс получит имя support-agent, теги и метаданные — по ним ищем
Для произвольных функций (не узлов графа), которые тоже хочется видеть в трейсе, есть декоратор @traceable из пакета langsmith — он оборачивает обычную функцию в шаг трейса.
from langsmith import traceable
@traceable
def rerank(query: str, docs: list[str]) -> list[str]:
# своя логика — попадёт в трейс отдельным шагом со входом/выходом
return sorted(docs, key=lambda d: score(query, d), reverse=True)
От отладки к оценке
LangSmith — не только просмотр трейсов. Понравившиеся (или сломанные) запуски можно сохранять в датасеты и прогонять на них оценку (evaluation): автоматически проверять качество ответов агента — правилами или через LLM-as-judge. Так разовая отладка превращается в регрессионное тестирование: поменял промпт — прогнал по датасету — увидел, не стало ли хуже.
Датасеты и оценка агентов — материал для отдельного блока (оценка качества LLM-приложений). Здесь важно знать, что трейсы из отладки бесшовно переходят в тесты: кнопка «добавить в датасет» прямо в трейсе. LLM-as-judge и метрики мы разберём в соответствующем модуле.
Приватность и альтернативы
Трейсинг отправляет входы и выходы (включая промпты и ответы) в облако LangSmith. Для чувствительных данных это нужно учитывать: предусмотрены self-hosted/enterprise-варианты и возможность не логировать определённые поля. Трейсинг полностью опциональный — выключи переменную LANGSMITH_TRACING, и ничего никуда не уходит.
Поскольку трейс хранит входы/выходы, туда могут попасть персональные данные, ключи, токены из промптов. На проде продумай маскирование чувствительных полей и доступы к проекту. Для локальной разработки и визуализации графа без облака есть LangGraph Studio.
Типичные ошибки
Один LANGSMITH_API_KEY ничего не включает — нужен именно флаг LANGSMITH_TRACING=true (или legacy LANGCHAIN_TRACING_V2). Нет флага — нет трейсов.
Без LANGSMITH_PROJECT все трейсы идут в общий «default» и перемешиваются. Задавай проект на сервис/окружение, чтобы прод и дев не сливались.
Трейс сохранит то, что ушло в модель. Если в промпт подставляются токены/PII — они окажутся в LangSmith. Маскируй чувствительное до отправки.
Обмазывать узлы ручным логированием не нужно — базовый трейсинг включается окружением и работает сам. Код трогаем только для @traceable своих функций и тегов через config.
Шпаргалка
# 1. Включение — ТОЛЬКО окружение, код не трогаем:
# LANGSMITH_TRACING=true
# LANGSMITH_API_KEY=lsv2_...
# LANGSMITH_PROJECT=my-agent # иначе "default"
# (legacy: LANGCHAIN_TRACING_V2 / LANGCHAIN_API_KEY / LANGCHAIN_PROJECT)
graph.invoke(inp) # трейс пишется автоматически
# 2. Контекст запуска через config:
config = {"run_name": "support-agent",
"tags": ["prod"],
"metadata": {"user_id": "u-42"},
"configurable": {"thread_id": "t-1"}}
graph.invoke(inp, config)
# 3. Трейсинг своей функции:
from langsmith import traceable
@traceable
def my_step(x): ...
# Что даёт трейс: дерево узлов → LLM/tool-вызовов с input/output,
# временем, токенами, стоимостью и ошибками.
# Отладка: открой LLM-шаг → прочитай реальный промпт и ответ.
# Приватность: трейсы уходят в облако — маскируй секреты; флаг отключает всё.
Практическое задание
Посмотри на своего агента изнутри:
Задание: трейсинг ReAct-агента
- Заведи бесплатный аккаунт LangSmith, получи API-ключ и выставь
LANGSMITH_TRACING=true,LANGSMITH_API_KEY,LANGSMITH_PROJECT="lab". - Запусти ReAct-агента с инструментом (из урока про conditional edges) — без единой правки кода. Открой трейс в LangSmith.
- Найди шаг
ChatOpenAI, где модель решила вызвать инструмент, и прочитай точный промпт и ответ сtool_calls. - Посмотри суммарные токены и время запуска; определи самый дорогой шаг.
- Добавь в
configrun_name,tagsиmetadataсuser_id; сделай два запуска с разнымиuser_idи отфильтруй их в UI по метаданным. - Со звёздочкой: оберни вспомогательную функцию в
@traceableи убедись, что она появилась в дереве трейса отдельным шагом.
Что дальше
Это последний урок раздела «Human-in-the-loop». Ты умеешь ставить агента на паузу, давать человеку контроль и теперь — наблюдать за всем происходящим через трейсы. Дальше — раздел про готовые паттерны агентов на LangGraph.