Skip to content

Commit 5bebe3d

Browse files
authored
fix: make OpenAI wrapping compatible with ddtrace (#370)
1 parent de41845 commit 5bebe3d

5 files changed

Lines changed: 416 additions & 100 deletions

File tree

py/noxfile.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,14 @@ def test_openai_http2_streaming(session, version):
195195
_run_tests(session, f"{INTEGRATION_DIR}/openai/test_openai_http2.py", version=version)
196196

197197

198+
@nox.session()
199+
def test_openai_ddtrace(session):
200+
_install_test_deps(session)
201+
_install_matrix_dep(session, "openai", LATEST)
202+
_install_group_locked(session, "test-openai-ddtrace")
203+
_run_tests(session, f"{INTEGRATION_DIR}/openai/test_openai_ddtrace.py", version=LATEST)
204+
205+
198206
OPENAI_AGENTS_VERSIONS = _get_matrix_versions("openai-agents")
199207

200208

py/pyproject.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,11 @@ test-openai-http2 = [
108108
"h2==4.3.0",
109109
]
110110

111+
test-openai-ddtrace = [
112+
{include-group = "test"},
113+
"ddtrace==4.1.0",
114+
]
115+
111116
test-openai-agents = [
112117
{include-group = "test"},
113118
# openai is an auxiliary dep (matrix dep is openai-agents); pin here.

py/src/braintrust/integrations/openai/patchers.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -434,15 +434,20 @@ class _WrapResponsesRaw(CompositeFunctionWrapperPatcher):
434434
# ---------------------------------------------------------------------------
435435

436436

437-
def _is_class_method_wrapped(resource: Any, method_name: str) -> bool:
437+
def _is_class_method_wrapped(
438+
resource: Any,
439+
method_name: str,
440+
patcher: type[FunctionWrapperPatcher],
441+
) -> bool:
438442
"""Return ``True`` if *method_name* on the **class** of *resource* is
439-
already a wrapt ``FunctionWrapper`` (i.e. patched by ``setup()``).
443+
already wrapped by the matching Braintrust ``setup()`` patcher.
440444
441-
This prevents double-tracing when both ``setup()`` and ``wrap_openai()``
442-
are active for the same client.
445+
Other libraries (for example ddtrace) may also use wrapt wrappers on OpenAI
446+
class methods. Only Braintrust's own patch marker should make
447+
``wrap_openai()`` skip instance-level instrumentation.
443448
"""
444449
cls_attr = inspect.getattr_static(type(resource), method_name, None)
445-
return isinstance(cls_attr, FunctionWrapper)
450+
return patcher.has_patch_marker(cls_attr)
446451

447452

448453
def _delegates_to_wrapped_method(resource: Any, method_name: str) -> bool:
@@ -480,7 +485,7 @@ def _wrap_resource(
480485
# level patchers are active and we can skip instance-level wrapping.
481486
for sub in patcher.sub_patchers:
482487
attr = sub.target_path.rsplit(".", 1)[-1]
483-
if _is_class_method_wrapped(resource, attr):
488+
if _is_class_method_wrapped(resource, attr, sub):
484489
return
485490
if attr_path.endswith("with_raw_response") and _delegates_to_wrapped_method(resource, attr):
486491
return
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
"""Compatibility tests for Braintrust OpenAI wrapping with ddtrace."""
2+
3+
import importlib
4+
import inspect
5+
6+
import openai
7+
from braintrust import wrap_openai
8+
from braintrust.integrations.openai.patchers import _wrap_chat_create
9+
from wrapt import FunctionWrapper
10+
11+
12+
def test_wrap_openai_wraps_instance_when_ddtrace_patched_class():
13+
ddtrace = importlib.import_module("ddtrace")
14+
15+
ddtrace.patch(openai=True)
16+
17+
class_create = inspect.getattr_static(openai.resources.chat.completions.Completions, "create")
18+
assert isinstance(class_create, FunctionWrapper)
19+
20+
client = wrap_openai(openai.OpenAI())
21+
22+
assert _wrap_chat_create.has_patch_marker(client.chat.completions)

0 commit comments

Comments
 (0)