Агент — чёрный ящик

Пока граф из двух узлов — всё понятно. Но реальный агент за один invoke может сделать десятки шагов: спросить LLM, вызвать инструмент, снова спросить, переключить ветку, уйти в сабграф. Когда результат неверный, вопросов масса: на каком узле сломалось? какой именно промпт ушёл в модель? почему она выбрала этот инструмент? сколько это стоило и где тормозит?

Отлаживать это через print() и логи — мучение: они плоские, теряют структуру вложенных вызовов и не показывают полные промпты. Нужен инструмент, который записывает выполнение как дерево и даёт кликнуть в любой шаг. Это и есть трейсинг, а LangSmith — его реализация для нашего стека.

ℹ️ Observability — не роскошь, а необходимость

У агентов недетерминированное поведение: один и тот же запрос идёт разными путями. Без записи трейсов воспроизвести и понять баг почти невозможно. Поэтому наблюдаемость для LLM-приложений — такая же база, как логирование для обычного бэкенда.

Включение: только переменные окружения

Главное преимущество: LangChain и LangGraph уже умеют отправлять трейсы — нужно лишь включить это переменными окружения. Ни строчки в коде графа менять не надо.

Включение трейсинга через env
bash
# .env или окружение процесса
export LANGSMITH_TRACING=true
export LANGSMITH_API_KEY="lsv2_..."        # ключ из настроек LangSmith
export LANGSMITH_PROJECT="my-agent"        # необязательно (иначе проект "default")

# Ключи провайдера LLM — как обычно
export OPENAI_API_KEY="sk-..."
Дальше — обычный код, трейсы пишутся сами
python
# Никаких импортов 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 и инструментов. По каждому шагу видно вход, выход, время, токены, стоимость и ошибки.

100%
колёсико — масштаб  ·  зажать и тянуть — перемещение
Трейс (дерево runs) ▼ agent.invoke 2.4s · 1180 tok · $0.001 весь запуск графа ▼ узел agent 0.9s ChatOpenAI 0.8s · 420 tok ▼ узел tools 0.1s get_weather ✓ ▼ узел agent 0.7s ChatOpenAI 0.7s · 380 tok Шаг: ChatOpenAI INPUT (messages) system: Ты помощник... user: Погода в Москве? OUTPUT tool_call: get_weather( city="Москва") МЕТРИКИ tokens: 420 · latency: 0.8s · $0.0003 клик по шагу слева → полные детали справа

Что показывает каждый шаг трейса:

  • Вход и выход — точный промпт, отправленный в LLM, и её полный ответ (включая tool_calls). Видно ровно то, что «видела» модель.
  • Время — сколько занял каждый шаг; сразу понятно, где бутылочное горлышко.
  • Токены и стоимость — расход по каждому вызову и суммарно.
  • Ошибки — упавший шаг подсвечен, виден traceback и вход, на котором сломалось.

Отладка по трейсу

Трейс превращает расследование бага в клики. Типичные сценарии в агентах LangGraph:

не тот инструмент
Открой LLM-шаг и прочитай промпт: модель видела то, что ты думаешь? Часто причина — кривой системный промпт или описание инструмента.
зацикливание
В дереве видно повтор узлов agent↔tools. Сразу ясно, на каком шаге петля не разрывается и почему router не вернул END.
дорого / медленно
Сортировка по токенам и времени показывает самый прожорливый шаг — туда и оптимизация (модель поменьше, короче контекст).
Трейсы дружат с human-in-the-loop

Для агентов с прерываниями (этот раздел) трейс особенно полезен: в нём видно, на каком узле граф встал на паузу, какое состояние было на момент interrupt и что человек прислал в resume. Связка checkpoint (что сохранилось) + трейс (как к этому пришли) закрывает почти любой разбор инцидента.

Теги, имена и метаданные

Когда трейсов тысячи, нужна навигация. Через config запуска можно навесить понятное имя, теги и метаданные — потом по ним фильтровать в LangSmith (например, найти все трейсы конкретного пользователя или только продовые).

Обогащаем запуск контекстом через config
python
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 — он оборачивает обычную функцию в шаг трейса.

@traceable — трейсинг своей функции
python
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.

Типичные ошибки

Ошибка 1: ждать трейсы без LANGSMITH_TRACING=true

Один LANGSMITH_API_KEY ничего не включает — нужен именно флаг LANGSMITH_TRACING=true (или legacy LANGCHAIN_TRACING_V2). Нет флага — нет трейсов.

Ошибка 2: всё валится в проект default

Без LANGSMITH_PROJECT все трейсы идут в общий «default» и перемешиваются. Задавай проект на сервис/окружение, чтобы прод и дев не сливались.

Ошибка 3: секреты в промптах → в облаке

Трейс сохранит то, что ушло в модель. Если в промпт подставляются токены/PII — они окажутся в LangSmith. Маскируй чувствительное до отправки.

Ошибка 4: правят код ради трейсинга

Обмазывать узлы ручным логированием не нужно — базовый трейсинг включается окружением и работает сам. Код трогаем только для @traceable своих функций и тегов через config.

Шпаргалка

LangSmith для LangGraph — всё в одном месте
python
# 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-агента

  1. Заведи бесплатный аккаунт LangSmith, получи API-ключ и выставь LANGSMITH_TRACING=true, LANGSMITH_API_KEY, LANGSMITH_PROJECT="lab".
  2. Запусти ReAct-агента с инструментом (из урока про conditional edges) — без единой правки кода. Открой трейс в LangSmith.
  3. Найди шаг ChatOpenAI, где модель решила вызвать инструмент, и прочитай точный промпт и ответ с tool_calls.
  4. Посмотри суммарные токены и время запуска; определи самый дорогой шаг.
  5. Добавь в config run_name, tags и metadata с user_id; сделай два запуска с разными user_id и отфильтруй их в UI по метаданным.
  6. Со звёздочкой: оберни вспомогательную функцию в @traceable и убедись, что она появилась в дереве трейса отдельным шагом.

Что дальше

Это последний урок раздела «Human-in-the-loop». Ты умеешь ставить агента на паузу, давать человеку контроль и теперь — наблюдать за всем происходящим через трейсы. Дальше — раздел про готовые паттерны агентов на LangGraph.