From 3b3d507e3fc2652c0e9a305351c153e478772976 Mon Sep 17 00:00:00 2001 From: serhiizghama Date: Sun, 5 Apr 2026 04:22:42 +0700 Subject: [PATCH] fix: support openai SDK v2 by wrapping parse() on non-beta path In openai >= 2.0, the structured output .parse() method moved from openai.resources.beta.chat.completions to openai.resources.chat.completions. The old code wrapped all methods in a single try/except, so a ModuleNotFoundError on the beta path would silently skip the Responses API wrappers too. Changes: - Try chat.completions.parse first (current SDK location) - Fall back to beta.chat.completions.parse for openai < 2.0 compatibility - Split into separate try/except blocks so a failure in one group doesn't prevent other wrappers (e.g. Responses API) from being installed Fixes #1260 --- .../providers/openai/instrumentor.py | 43 ++++++++++++++----- 1 file changed, 32 insertions(+), 11 deletions(-) diff --git a/agentops/instrumentation/providers/openai/instrumentor.py b/agentops/instrumentation/providers/openai/instrumentor.py index 9497d7252..9875ef838 100644 --- a/agentops/instrumentation/providers/openai/instrumentor.py +++ b/agentops/instrumentation/providers/openai/instrumentor.py @@ -92,11 +92,8 @@ def _initialize(self, **kwargs): def _custom_wrap(self, **kwargs): """Add custom wrappers for streaming functionality.""" if is_openai_v1() and self._tracer: - # from wrapt import wrap_function_wrapper - # # Add streaming wrappers for v1 + # Chat completion streaming wrappers (create) try: - # Chat completion streaming wrappers - wrap_function_wrapper( "openai.resources.chat.completions", "Completions.create", @@ -108,21 +105,45 @@ def _custom_wrap(self, **kwargs): "AsyncCompletions.create", async_chat_completion_stream_wrapper(self._tracer), ) + except Exception as e: + logger.warning(f"[OPENAI INSTRUMENTOR] Error wrapping chat.completions.create: {e}") - # Beta chat completion streaming wrappers + # Chat completion parse wrappers — try the current SDK path first, + # then fall back to the legacy beta path used in openai < 2.0. + _parse_wrapped = False + try: wrap_function_wrapper( - "openai.resources.beta.chat.completions", + "openai.resources.chat.completions", "Completions.parse", chat_completion_stream_wrapper(self._tracer), ) - wrap_function_wrapper( - "openai.resources.beta.chat.completions", + "openai.resources.chat.completions", "AsyncCompletions.parse", async_chat_completion_stream_wrapper(self._tracer), ) - - # Responses API streaming wrappers + _parse_wrapped = True + except Exception: + pass + + if not _parse_wrapped: + # Legacy path: openai < 2.0 exposed .parse() under beta.chat.completions + try: + wrap_function_wrapper( + "openai.resources.beta.chat.completions", + "Completions.parse", + chat_completion_stream_wrapper(self._tracer), + ) + wrap_function_wrapper( + "openai.resources.beta.chat.completions", + "AsyncCompletions.parse", + async_chat_completion_stream_wrapper(self._tracer), + ) + except Exception as e: + logger.debug(f"[OPENAI INSTRUMENTOR] beta.chat.completions.parse not available: {e}") + + # Responses API streaming wrappers + try: wrap_function_wrapper( "openai.resources.responses", "Responses.create", @@ -135,7 +156,7 @@ def _custom_wrap(self, **kwargs): async_responses_stream_wrapper(self._tracer), ) except Exception as e: - logger.warning(f"[OPENAI INSTRUMENTOR] Error setting up OpenAI streaming wrappers: {e}") + logger.warning(f"[OPENAI INSTRUMENTOR] Error wrapping responses API: {e}") else: if not is_openai_v1(): logger.debug("[OPENAI INSTRUMENTOR] Skipping custom wrapping - not using OpenAI v1")