diff --git a/py/src/braintrust/integrations/auto_test_scripts/test_auto_openai.py b/py/src/braintrust/integrations/auto_test_scripts/test_auto_openai.py index 00cbf44e9..dfdfad1f2 100644 --- a/py/src/braintrust/integrations/auto_test_scripts/test_auto_openai.py +++ b/py/src/braintrust/integrations/auto_test_scripts/test_auto_openai.py @@ -3,6 +3,7 @@ import inspect import openai +from braintrust import wrap_openai from braintrust.auto import auto_instrument from braintrust.integrations.test_utils import autoinstrument_test_context from wrapt import FunctionWrapper @@ -49,4 +50,20 @@ def _is_braintrust_wrapped(resource) -> bool: assert span["metadata"]["provider"] == "openai" assert "gpt-4o-mini" in span["metadata"]["model"] +# 5. Manual wrapping after auto-instrumentation must not duplicate the span or tokens. +with autoinstrument_test_context("test_openai_responses_metrics", integration="openai") as memory_logger: + client = wrap_openai(openai.OpenAI()) + response = client.responses.create( + model="gpt-4o-mini", + input="What's 12 + 12?", + instructions="Just the number please", + ) + assert response.output + + spans = memory_logger.pop() + assert len(spans) == 1, f"Expected 1 Responses span, got {len(spans)}" + assert spans[0]["span_attributes"]["name"] == "openai.responses.create" + assert spans[0]["metrics"]["prompt_tokens"] == response.usage.input_tokens + assert spans[0]["metrics"]["completion_tokens"] == response.usage.output_tokens + print("SUCCESS") diff --git a/py/src/braintrust/integrations/openai/patchers.py b/py/src/braintrust/integrations/openai/patchers.py index 532e97c60..0bc69fa4e 100644 --- a/py/src/braintrust/integrations/openai/patchers.py +++ b/py/src/braintrust/integrations/openai/patchers.py @@ -82,6 +82,8 @@ def _make_method_patchers( "name": wrap_name, "target_path": method, "wrapper": wrapper, + # The instance marker differs from the class-level setup markers. + "setup_patchers": (sync_patcher, async_patcher), }, ) return sync_patcher, async_patcher, instance_patcher @@ -479,7 +481,7 @@ class methods. Only Braintrust's own patch marker should make ``wrap_openai()`` skip instance-level instrumentation. """ cls_attr = inspect.getattr_static(type(resource), method_name, None) - return patcher.has_patch_marker(cls_attr) + return any(setup_patcher.has_patch_marker(cls_attr) for setup_patcher in patcher.setup_patchers) def _delegates_to_wrapped_method(resource: Any, method_name: str) -> bool: