English translation is not available yet. Showing Russian content.

Интегрировать OpenTelemetry в агента

ТЕХНИЧЕСКОЕ ЗАДАНИЕ: Интегрировать OpenTelemetry в агента

1. Цель задачи

Научиться инструментировать feedback|агентный цикл (thought → actionobservation) с помощью OpenTelemetry для сквозной трассировки. После выполнения вы обеспечите полную наблюдаемость каждого шага агента в Jaeger UI, включая корректные parent-child связи и атрибуты (время мысли, вызванный инструмент, результат наблюдения). Ключевой результат В Jaeger виден полный граф трассировки одного запроса, где каждый этап (thought, action, observation) представлен отдельным спаном с корректными временными метками и связями.


2. Исходные данные

Перед началом необходимо иметь:

Что нужноОткуда взять
Агентный код (Python) на базе LangChain / llama-index / собственный циклРепозиторий pet-проекта (или пример из курса)
Docker (или podman) для запуска Jaegerdocker pull jaegertracing/all-in-one
Python 3.10+ с установленным pipЛокальное окружение
Доступ к сети для загрузки библиотекpip install opentelemetry-*

Если нет реального агента — симулируем:

  1. Склонируйте репозиторий-шаблон с простым агентом на LangChain (один инструмент search и один calculator).
  2. Убедитесь, что агент запускается и выполняет цепочку из 2–3 шагов на тестовом запросе («Сколько будет 2+2, а затем умножь на 3»).
  3. Подготовьте Python-виртуальное окружение и активируйте его.

3. Технологический стек

КомпонентИнструментыНазначение
ТрассировкаOpenTelemetry Python SDK (opentelemetry-api, opentelemetry-sdk, opentelemetry-exporter-otlp)Создание и экспорт спанов
Бэкенд трейсовJaeger (all-in-one) через OTLP gRPCПриём и визуализация трейсов
Агентный фреймворкLangChain / llama-index (или собственный)Цикл thought-action-observation
МониторингDocker Compose (опционально)Управление Jaeger
ОтладкаJaeger UI (http://localhost:16686)Просмотр графа трассировки

4. Этапы выполнения

Этап 1: Запуск Jaeger и подключение OTLP-экспортера (30 минут)

Действия

  1. Запустите Jaeger all-in-one в Docker

    docker run -d --name jaeger \
      -p 16686:16686 \
      -p 4317:4317 \
      -p 4318:4318 \
      jaegertracing/all-in-one:latest
    
  2. Установите необходимые пакеты OpenTelemetry

    pip install opentelemetry-api \
                opentelemetry-sdk \
                opentelemetry-exporter-otlp \
                opentelemetry-instrumentation
    
  3. Создайте файл tracing_setup.py с конфигурацией трассировщика:

    from opentelemetry import trace
    from opentelemetry.sdk.trace import TracerProvider
    from opentelemetry.sdk.trace.export import BatchSpanProcessor
    from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
    
    def setup_tracer(service_name: str = "agent-service"):
        provider = TracerProvider()
        exporter = OTLPSpanExporter(endpoint="localhost:4317", insecure=True)
        processor = BatchSpanProcessor(exporter)
        provider.add_span_processor(processor)
        trace.set_tracer_provider(provider)
        return trace.get_tracer(__name__)
    
  4. Добавьте вызов setup_tracer() в точку входа агента (например, в main() или agent.run()).

Ожидаемый результат этапа Jaeger UI доступен по адресу http://localhost:16686, а при запуске агента в консоли не возникает ошибок подключения к OTLP.


Этап 2: Инструментирование цикла агента (1 час)

Действия

  1. Определите структуру трейса

    • Один span|root span на весь запрос (например, agent.run).
    • Внутри root span: последовательные child span'ы для каждого шага:
      • thought — генерация мысли;
      • action — вызов инструмента (с атрибутами: имя инструмента, аргументы);
      • observation — обработка результата инструмента.
  2. Оберните агентный цикл в root span:

    tracer = setup_tracer()
    with tracer.start_as_current_span("agent.run") as root_span:
        while not done:
            step_result = agent.step()  # ваш существующий цикл
    
  3. Внутри каждого шага создавайте child span:

    # Пример для thought
    with tracer.start_as_current_span("thought", context=trace.get_current_span().get_span_context()) as thought_span:
        thought_span.set_attribute("thought.text", thought_output)
        # ... выполнение кода мысли
    

    Аналогично для action и observation.

  4. Для action добавьте атрибуты

    • action.tool_name (строка)
    • action.input (строка)
    • action.output (строка, если доступно)
  5. Для observation

  6. Убедитесь, что контекст трассировки передаётся (используйте trace.get_current_span().get_span_context() при создании дочерних спанов).

Ожидаемый результат этапа Код агента модифицирован так, что каждый этап цикла порождает отдельный child span. Приложение не падает с ошибками OpenTelemetry.


Этап 3: Проверка экспорта и визуализации в Jaeger (30 минут)

Действия

  1. Запустите агента с тестовым запросом (например, «Сколько будет 20/4, умножь на 5»).
  2. Откройте Jaeger UI http://localhost:16686.
  3. В выпадающем списке Service выберите agent-service.
  4. Нажмите «Find Traces» — должен появиться один трейс (или несколько, если запускали многократно).
  5. Кликните на трейс Проверьте:
    • Root span agent.run.
    • Дочерние spans в правильной последовательности: thought, action, observation.
    • Наличие атрибутов (thought.text, action.tool_name и т.д.).
    • Временные метки корректны (order и duration).
  6. Сделайте скриншот полного графа для отчёта.

Ожидаемый результат этапа В Jaeger UI виден полный граф трассировки с parent-child связями.


Этап 4: Обработка ошибок и дополнительных атрибутов (30 минут)

Действия

  1. Добавьте обработку исключений в spans: при возникновении ошибки установите span.set_status(Status(StatusCode.ERROR)) и запишите exception в атрибуты.
  2. Добавьте атрибут error.message при сбое инструмента.
  3. Проверьте сценарий с ошибкой (например, попросите агента вызвать несуществующий инструмент) — убедитесь, что в Jaeger отображается красный статус у спана action.
  4. Добавьте атрибут agent.user_query в root span (исходный запрос пользователя).
  5. Экспериментально попробуйте увеличить/уменьшить количество шагов (2-3 шага), чтобы убедиться, что все они корректно трассируются.

Ожидаемый результат этапа Трейсы корректно отображают ошибки, а root span содержит дополнительную информацию о запросе.


Этап 5: Документирование и рефакторинг (20 минут)

Действия

  1. Вынесите конфигурацию трассировки в отдельный модуль (используйте tracing_setup.py).
  2. Добавьте комментарии в код с пояснением, почему используется trace.get_current_span().
  3. Напишите README с инструкциями:
    • как запустить Jaeger;
    • как включить/выключить трассировку через переменную окружения (например, ENABLE_TRACING=True);
    • пример скриншота графа.
  4. Убедитесь, что код не содержит захардкоженных endpoint'ов — вынесите в переменные окружения OTEL_EXPORTER_OTLP_ENDPOINT.

Ожидаемый результат этапа Чистый, документированный код, готовый к интеграции в production.


5. Критерии приемки (Definition of Done)

  • Jaeger all-in-one запущен и UI отвечает на localhost:16686.
  • При выполнении любого запроса к агенту в Jaeger появляется трейс с root span agent.run.
  • Каждый этап цикла (thought, action, observation) представлен отдельным дочерним span с корректным родителем.
  • Spans содержат атрибуты: thought.text, action.tool_name, action.input, observation.result.
  • В случае ошибки у соответствующего span установлен статус ERROR и атрибут error.message.
  • Трассировка не вызывает видимых slowdown (latency агента не увеличивается более чем на 10%).
  • Конфигурация вынесена в переменные окружения (OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_SERVICE_NAME).
  • README содержит инструкцию по запуску и скриншот графа из Jaeger.

6. Ожидаемый результат

Основной артефакт Модифицированный репозиторий агента с интегрированной OpenTelemetry трассировкой.

Содержимое

  • tracing_setup.py — инициализация tracer provider и экспортера.
  • Обновлённый файл агента (например, agent.py) с вызовами start_as_current_span.
  • docker-compose.yml (опционально) для запуска Jaeger.
  • README.md с документацией.

Дополнительно Скриншот Jaeger UI с полным графом трассировки по заданному запросу.


7. Возможные сложности и их решение

СложностьРешение
Jaeger не стартует из-за занятых портовСмените порты при запуске (-p 16687:16686 и т.д.), обновите endpoint в коде
OTLP экспорт не работает (gRPC refused)Убедитесь, что Jaeger слушает 4317. Проверьте docker logs jaeger. Используйте HTTP экспорт (порт 4318) как fallback
Spans в Jaeger отображаются без parent-child (все на одном уровне)Проверьте, что контекст передаётся через trace.get_current_span().get_span_context() при создании child span
Атрибуты не отображаютсяУбедитесь, что значения атрибутов — строки или числа; не используйте сложные объекты
Резкое замедление работы агентаУменьшите частоту экспорта (BatchSpanProcessor параметр max_export_batch_size или переключитесь на SimpleSpanProcessor для отладки)

8. Бюджет времени (оценка)

ЭтапВремя
Этап 1: Запуск Jaeger и подключение OTLP30 мин
Этап 2: Инструментирование цикла агента1 ч
Этап 3: Проверка экспорта и визуализации30 мин
Этап 4: Обработка ошибок и доп. атрибуты30 мин
Этап 5: Документирование и рефакторинг20 мин
Итого~3 ч

Примечание Для первого раза может потребоваться до 4 часов, если потребуется отладка OTLP-подключения или исправление ошибок контекста.


9. Связанные вопросы из базы знаний

ВопросТема
101Настройка OpenTelemetry Python
102Создание пользовательских спанов
103Текущая задача
107Экспорт трейсов в Jaeger
112Атрибуты спанов и лучшие практики
145Обработка ошибок в трейсах
201Инструментирование LangChain агентов
303Работа с Docker для инструментов наблюдаемости
410Построение графа зависимостей в Jaeger
502Переменные окружения для OpenTelemetry

10. Чек-лист самопроверки

  • Я запустил Jaeger через Docker и проверил доступность UI.
  • Я установил все необходимые opentelemetry-пакеты в виртуальное окружение.
  • Я модифицировал агентный цикл так, что каждый шаг (thought/action/observation) создаёт дочерний span.
  • Я протестировал сценарий с ошибкой и убедился, что в jaeger отображается ERROR статус.
  • Я зафиксировал скриншот полного графа и добавил его в README.
  • Я вынес настройки (OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_SERVICE_NAME) в переменные окружения.