Гайд · TNWS AI
Как протестировать NoOpSpan при отключённом tracing
Отключаем DefaultTraceProvider, создаём NoOpSpan, проверяем no-op идентификаторы, контекст и отсутствие export.
Практическая задача и применимость
Этот гайд решает конкретную задачу: доказать, что instrumented код продолжает работать при выключенном tracing и ничего не экспортирует. Он предназначен для разработчиков Python-сервисов, которые уже используют OpenAI Agents SDK и хотят проверять наблюдаемость кодом, а не только визуально в панели. Основной интерфейс материала — DefaultTraceProvider.set_disabled(True).
Функция нужна на границе приложения и tracing-инфраструктуры: в bootstrap-модуле, worker, собственном runner-адаптере или интеграционном тесте. Не добавляйте такую настройку в каждый обработчик HTTP-запроса. Глобальные provider и processors должны иметь одного владельца жизненного цикла.
Что подтверждено официальной документацией
Сигнатуры и поведение проверены 13 сентября 2026 года по руководству OpenAI Agents SDK, общему API reference tracing и профильному reference. при отключённом provider create_span возвращает NoOpSpan: trace_id и span_id равны no-op, error и export возвращают None. Цены, тарифы и неподтверждённые лимиты в материале не используются. Версию зависимости фиксируйте в lock-файле и повторяйте контрактные тесты после обновления.
Важно разделять бизнес-успех и успех telemetry. Ошибка exporter не должна превращать уже выполненную бизнес-операцию в повторную, иначе возможен дубль платежа или задания. Одновременно нельзя молча объявлять tracing надёжным: измеряйте созданные, завершённые, отправленные и отброшенные события отдельно.
Архитектура решения
В примере есть четыре слоя. Первый создаёт или получает provider. Второй формирует trace/span data только из разрешённых технических полей. Третий управляет start, finish, flush и shutdown. Четвёртый проверяет результат через свойства объекта или export. Такая схема отделяет конфигурацию от предметного кода и делает ошибки воспроизводимыми.
Не записывайте в name динамические идентификаторы пользователей: используйте стабильное имя операции, а корреляцию храните в безопасном group_id либо ограниченной metadata. Input и output могут содержать чувствительные данные, поэтому примеры используют исключительно искусственные значения. Для production создайте allowlist полей и тест с секретным маркером.
Пошаговая инструкция
- Создайте отдельное виртуальное окружение и установите пакет openai-agents; точную версию зафиксируйте тем же инструментом зависимостей, который использует проект.
- Вынесите tracing bootstrap в один модуль. Зафиксируйте, кто вызывает shutdown при завершении процесса.
- Импортируйте только официальные классы и helpers, указанные в reference. Не обращайтесь к приватным полям с подчёркиванием в production-коде.
- Подготовьте искусственный вход без токенов, email, текста реального клиента и содержимого внутренних документов.
- Вызовите
DefaultTraceProvider.set_disabled(True)и сохраните возвращённый объект, если его свойства участвуют в контрактной проверке. - Для ручного lifecycle используйте try/finally. Если объект становится текущим, при завершении обязательно восстановите contextvar.
- Проверьте конкретные поля: формат ID, parent_id, span_data, started_at/ended_at, export либо identity provider.
- Запустите негативный сценарий. Успех без такого теста не доказывает, что конфигурация ловит неправильную связь или утечку контекста.
- В отдельном concurrency-тесте запустите две asyncio-задачи и убедитесь, что их current trace/span не смешиваются.
- Перед выпуском вызовите flush там, где процесс короткоживущий, затем shutdown владельца processor/provider.
Готовый код
from agents.tracing.provider import DefaultTraceProvider
from agents.tracing.span_data import CustomSpanData
provider = DefaultTraceProvider()
provider.set_disabled(True)
span = provider.create_span(CustomSpanData("disabled_step", {"safe": True}))
with span as current:
assert current.span_id == "no-op"
assert current.trace_id == "no-op"
assert provider.get_current_span() is current
assert provider.get_current_span() is None
assert span.parent_id is None
assert span.error is None
assert span.export() is None
print("noop_contract_verified")
Код можно скопировать в отдельный тестовый файл. Assertions являются частью решения: они превращают предположение о tracing в проверяемый контракт. Если пример использует тестовый ключ или домен example.test, не заменяйте их реальными значениями в репозитории. Секрет передавайте через менеджер секретов или переменную окружения только во время выполнения.
Реалистичный пример входа и результата
Вход: Provider отключён до создания технического span disabled_step с безопасным тестовым флагом.
Ожидаемый результат: Context manager работает, ID равны no-op, после выхода current span очищен, экспорт отсутствует.
Проверяйте структуру, а не случайное значение сгенерированного ID или микросекунды времени. Для идентификатора важны префикс, формат, уникальность и сохранение связи. Для lifecycle важны наличие отметок и очистка contextvar. Для exporter важны полученная пачка, отсутствие секретов и корректное закрытие клиента.
Негативный тест
После теста включите provider и создайте новый span внутри реальной trace. Он не должен иметь no-op ID; так обнаруживается забытый глобальный disabled.
Негативный сценарий должен быть автоматическим. Если он лишь описан в документации команды, регрессия останется незаметной. Поместите его рядом с позитивным тестом и запускайте в CI без реального сетевого экспорта. Для сетевой части используйте управляемый mock endpoint, который записывает количество запросов, заголовки без вывода секрета и последовательность кодов ответа.
Диагностика типичных ошибок
Если parent_id пуст, сначала проверьте, был ли родитель текущим в момент создания span или был ли он передан явно. Если current object остаётся после исключения, ищите ручной start без finish(reset_current=True). Если export равен None, проверьте disabled/no-op режим до анализа processor. Если события дублируются, убедитесь, что processor не зарегистрирован и глобально, и повторно внутри запроса.
Если процесс завершается раньше фонового экспорта, добавьте force_flush перед shutdown, но не вызывайте его после каждого запроса. Если тест зависит от порядка глобальных provider, восстанавливайте прежнее значение в finally. Тесты, которые меняют глобальную конфигурацию, нельзя бездумно запускать параллельно в одном процессе.
Копируемый шаблон внедрения
Операция: DefaultTraceProvider.set_disabled(True)
Владелец lifecycle: <bootstrap/worker/test fixture>
Причина настройки: <одна конкретная задача>
Стабильное имя workflow/span: <значение>
Разрешённые metadata: <список>
Запрещённые данные: токены, PII, полный пользовательский ввод
Проверяемые свойства: <точные поля>
Ожидаемый parent: <trace/span/None>
Позитивный assert: <условие>
Негативный сценарий: <условие>
Поведение при исключении: finish/reset/restore
Поведение при shutdown: flush затем закрытие
Источник: https://openai.github.io/openai-agents-python/ref/tracing/spans/
Дата проверки: 13 сентября 2026 года
Контроль качества перед production
Сначала выполните unit-тест без сети. Затем интеграционный тест с тестовым processor или mock exporter. После этого создайте одну staging trace и сопоставьте её trace_id с техническим журналом. Проверьте, что ни сериализованный payload, ни stdout, ни сообщение об ошибке не содержат тестовый секретный маркер.
Отдельно смоделируйте исключение между start и finish, повторную инициализацию bootstrap и завершение короткой job. После каждого сценария current trace/span должен соответствовать ожидаемому объекту либо None. Зафиксируйте эти проверки в CI; ручной просмотр панели оставьте дополнительной, а не единственной проверкой.
Чек-лист финальной проверки
- Сигнатура сверена с официальным reference 13 сентября 2026 года.
- Title операции и имена spans имеют низкую кардинальность.
- Пример использует искусственные данные.
- В metadata и payload нет токенов и PII.
- Глобальная конфигурация создаётся один раз.
- Ручной start всегда парен с finish в finally.
- Contextvar восстанавливается после выхода и исключения.
- Проверяются конкретные свойства или export.
- Негативный тест действительно падает при неверной настройке.
- Параллельные контексты изолированы.
- Короткоживущий процесс выполняет flush.
- Владелец ресурсов вызывает shutdown/close.
FAQ
Нужно ли тестировать tracing, если бизнес-функция работает?
Да. Бизнес-тест не обнаружит неверный parent_id, утечку contextvar, дубли processor или потерю буфера при завершении процесса. Проверяйте эти свойства отдельно.
Можно ли использовать ConsoleSpanExporter в production?
Для постоянной production-настройки это рискованно: stdout может получить содержимое span. Он удобен локально с искусственными данными и явной проверкой редактирования.
Почему нельзя регистрировать processor на каждый запрос?
Глобальный provider отправляет события всем зарегистрированным processors. Повторная регистрация создаёт дубли, расходует ресурсы и усложняет корректный shutdown.
Что делать, если tracing отключён?
Код должен корректно работать с no-op объектами и export=None. Не считайте отсутствие исключения доказательством того, что событие было отправлено.
Когда нужен force_flush?
Перед завершением короткоживущего worker/job или в контролируемом тесте немедленной доставки. В обычном серверном запросе полагайтесь на фоновый processor и общий lifecycle приложения.
Читайте также
Как генерировать group_id через gen_group_id в Agents SDK
Генерируем group_id официальным helper, связываем две trace одного диалога и проверяем формат и корреляцию.
Как генерировать корректные trace_id и span_id в Agents SDK
Используем gen_trace_id и gen_span_id, проверяем префиксы, длину, уникальность и передаём ID в ручную trace/span.
Как настроить очередь BatchTraceProcessor в Agents SDK
Настраиваем max_queue_size, max_batch_size, schedule_delay и export_trigger_ratio у BatchTraceProcessor и проверяем flush.
Комментарии
Пока тихо. Скажите первое слово