Гайд · TNWS AI

Как написать потокобезопасный TracingProcessor для Agents SDK

6 мин

Реализуем полный интерфейс TracingProcessor с очередью, быстрыми callback и проверяем force_flush и shutdown.

Конкретная задача и применимость

Здесь настраивается экспортировать трассы во внутреннюю систему без блокировки agent run с помощью точного интерфейса TracingProcessor. Это материал для Python-сервиса на OpenAI Agents SDK, где одного факта «tracing включён» недостаточно: нужна наблюдаемая и проверяемая семантика. Общая трассировка включена SDK по умолчанию, но конкретная операция из этого гайда меняет корреляцию, состав данных или способ экспорта.

Документация проверена 12 сентября 2026 года. Фактическая основа — официальное руководство по tracing и официальный API reference. processor реализует шесть методов: on_trace_start/end, on_span_start/end, force_flush и shutdown; callbacks синхронные, должны быстро завершаться, быть потокобезопасными и сами обрабатывать ошибки. Цены, квоты и номера версий не приводятся: для этой операции они не нужны, а версия библиотеки должна фиксироваться lock-файлом проекта.

Результат, который будем доказывать

Готовность определяется не отсутствием исключения. Нужен проверяемый признак: конкретное поле trace, событие processor, отсутствие секретного маркера или завершённый экспорт. Сначала запускайте пример на staging-проекте. В metadata не помещайте prompt, email, телефон, токены и ответы tools. Для корреляции используйте внутренний непрямой ID.

До изменения сохраните контрольную trace со стандартной конфигурацией и запишите её структуру: trace_id, имена spans, parent_id и число событий. Это baseline. После изменения сравнивайте именно ожидаемую разницу. Если одновременно обновить SDK, exporter и конфигурацию, причину изменения определить будет трудно.

Пошаговая настройка

  1. Создайте виртуальное окружение, установите openai-agents и зафиксируйте разрешённую версию в lock-файле.
  2. Настройте ключи через переменные окружения или secret manager. Не вставляйте рабочий ключ в код, trace metadata и тестовый отчёт.
  3. Скопируйте пример ниже в отдельный интеграционный тест. Имена workflow и атрибуты замените на значения вашей предметной области.
  4. Выполните один контрольный запуск и сохраните только технические идентификаторы. Текст сообщения нужен лишь локальному assert и не должен попадать в общий лог.
  5. Проверьте позитивный критерий из раздела примера. Для processor считайте callbacks; для редактирования сериализуйте событие в тестовый sink; для flush проверяйте порядок закрытия контекста.
  6. Выполните негативный сценарий. Он должен отличать ошибочную конфигурацию от временной задержки backend.
  7. Запустите два параллельных trace. Их trace_id должны различаться, parent_id не должны пересекать workflows, а общий group_id допускается только если это один диалог.
  8. Добавьте cleanup: flush для критичного короткого job, shutdown для собственного processor и очистку тестовых данных по политике проекта.

Готовый пример

from queue import Queue, Empty
from agents import add_trace_processor, flush_traces, trace
from agents.tracing import TracingProcessor

class QueueProcessor(TracingProcessor):
    def __init__(self): self.q=Queue(); self.exported=[]
    def on_trace_start(self,t): self.q.put(("trace_start",t.trace_id))
    def on_trace_end(self,t): self.q.put(("trace_end",t.trace_id))
    def on_span_start(self,s): self.q.put(("span_start",s.span_id))
    def on_span_end(self,s): self.q.put(("span_end",s.span_id))
    def force_flush(self):
        while True:
            try: self.exported.append(self.q.get_nowait())
            except Empty: break
    def shutdown(self): self.force_flush()

p=QueueProcessor(); add_trace_processor(p)
with trace("queue_processor"): pass
flush_traces()
assert any(x[0]=="trace_end" for x in p.exported)
print(len(p.exported))

В примере нет универсального «попробуйте посмотреть dashboard»: состояние проверяется программно. Для реального exporter дополните тест контролируемым sink, потому что появление записи в веб-интерфейсе может быть отложено фоновой пакетной отправкой. Не делайте сетевой запрос внутри синхронного callback processor — складывайте событие в очередь.

Реалистичный вход и ожидаемый результат

Вход: Одна trace queue_processor; callbacks складывают события в потокобезопасную Queue.

Ожидаемый результат: После flush очередь пуста, exported содержит trace_start и trace_end, а callback не выполнял сетевой ввод-вывод.

Зафиксируйте expected как автоматический критерий. Если сравнивается trace_id, проверяйте формат и уникальность, но не жёстко заданное случайное значение. Если сравнивается набор spans, используйте тип и имя, а не время выполнения: длительность зависит от среды и сети. Если проверяется отсутствие данных, ищите уникальные маркеры во всём сериализованном событии, включая error и metadata.

Негативный тест

Замените Queue обычным разделяемым списком и запустите параллельные traces как нагрузочный тест. Такое изменение не должно приниматься без отдельной синхронизации.

Негативный тест важен, потому что tracing не должен ломать основной ответ агента. Сбой дополнительного exporter следует журналировать технически и обрабатывать внутри processor. При этом тест конфигурации обязан падать, если ожидаемая защита данных отключена или события отправляются не в тот проект. Разделяйте доступность observability и корректность бизнес-операции.

Производственная интеграция

Инициализируйте глобальные processors один раз при старте процесса. Повторная регистрация после каждого HTTP-запроса создаёт дубликаты. Конфигурацию отдельного run передавайте через RunConfig, когда поведение действительно относится только к нему. Глобальные set-функции меняют процесс целиком и требуют явного владельца в bootstrap-коде.

Собирайте минимальные metadata: environment, тип workflow, версия схемы, непрямой tenant ID. Полный пользовательский ввод остаётся в бизнес-системе с её правилами доступа. Для расследования связывайте записи по trace_id и внутреннему request_id, а не копируйте содержимое запроса во все системы.

Callback собственного processor должен быть потокобезопасным и быстрым. Правильная архитектура — bounded queue, фоновый exporter, счётчик отброшенных событий и ограниченное время shutdown. Не выдумывайте метрику успешности: считайте доставленные, повторённые и отброшенные записи отдельно. Ошибка очереди не должна зависать внутри Runner.

Шаблон проверки для pull request

Сценарий tracing: TracingProcessor
Workflow: <стабильное имя>
Trace/group/request ID: <непрямые ID без PII>
Ожидаемые spans/callbacks: <точный список>
Проверяемое изменение относительно baseline: <одно изменение>
Маркер чувствительных данных: <тестовая строка>
Есть ли маркер в экспортированном событии: <да/нет по политике>
Поведение при недоступном exporter: <ожидаемая ошибка/очередь>
Порядок flush/shutdown: <описание>
Повторная инициализация не дублирует processor: <проверено>
Ссылка на trace в staging: <внутренняя ссылка>

Этот шаблон можно копировать без изменения структуры. Он заставляет указать одно проверяемое изменение, границы данных и поведение отказа.

Чек-лист финальной проверки

  • Название workflow стабильно и понятно команде.
  • trace_id генерируется корректным helper или самим SDK.
  • group_id используется только для связанных запусков.
  • Metadata не содержат PII, prompt, tool output и секреты.
  • Позитивный критерий проверен кодом.
  • Негативный сценарий даёт ожидаемое отличие.
  • Глобальная конфигурация применяется один раз при старте.
  • Callback processor не выполняет долгий сетевой I/O.
  • Для короткой задачи определена политика flush.
  • Повторный запуск не создаёт дубликаты событий.
  • Отказ tracing не подменяет результат агента.
  • Источники повторно проверены 12 сентября 2026 года.

FAQ

Tracing включён автоматически?

Да, SDK включает его по умолчанию и создаёт spans для Runner, агента, генерации, tools, guardrails и handoffs. Но конкретные поля, sensitive data и processors всё равно требуют явной политики приложения.

Почему trace может появиться не сразу?

Стандартный BatchTraceProcessor отправляет данные фоновыми пачками. Если требуется гарантия сразу после job, завершите контекст trace и вызовите flush_traces. Для обычного долгоживущего процесса фоновая задержка допустима.

Можно ли использовать персональные данные как group_id?

Технически поле принимает строку, но так делать не следует. Используйте непрямой ID диалога, который раскрывается до пользователя только в вашей системе с контролем доступа.

Что произойдёт при set_trace_processors?

Текущий список processors будет заменён. Стандартный экспорт в OpenAI исчезнет, если вы явно не сохранили processor, который его выполняет. Для добавочного приёмника используйте add_trace_processor.

Доступен ли tracing при Zero Data Retention?

Официальное руководство указывает, что tracing недоступен организациям, использующим API OpenAI по политике ZDR. Не обещайте доступность, пока это не подтверждено настройками вашей организации.

Читайте также

Комментарии

Пока тихо. Скажите первое слово