Skip to content

Commit e103c98

Browse files
authored
fix(openai): filter omit sentinel from metadata (#426)
Treat OpenAI Omit values like other omitted-parameter sentinels when prettifying traced request metadata, so omitted tools do not appear as object reprs in spans. Add VCR-backed regression coverage for chat completions on OpenAI 2.x and keep older 1.x sessions skipped because that SDK version does not omit Omit from requests. resolves #414
1 parent c614e30 commit e103c98

3 files changed

Lines changed: 151 additions & 3 deletions

File tree

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
interactions:
2+
- request:
3+
body: '{"messages":[{"role":"user","content":"What''s 12 + 12?"}],"model":"gpt-4o-mini","temperature":0.5}'
4+
headers:
5+
Accept:
6+
- application/json
7+
Accept-Encoding:
8+
- gzip, deflate
9+
Connection:
10+
- keep-alive
11+
Content-Length:
12+
- '98'
13+
Content-Type:
14+
- application/json
15+
Host:
16+
- api.openai.com
17+
User-Agent:
18+
- OpenAI/Python 2.36.0
19+
X-Stainless-Arch:
20+
- arm64
21+
X-Stainless-Async:
22+
- 'false'
23+
X-Stainless-Lang:
24+
- python
25+
X-Stainless-OS:
26+
- MacOS
27+
X-Stainless-Package-Version:
28+
- 2.36.0
29+
X-Stainless-Runtime:
30+
- CPython
31+
X-Stainless-Runtime-Version:
32+
- 3.12.12
33+
x-stainless-read-timeout:
34+
- '600'
35+
x-stainless-retry-count:
36+
- '0'
37+
method: POST
38+
uri: https://api.openai.com/v1/chat/completions
39+
response:
40+
body:
41+
string: "{\n \"id\": \"chatcmpl-Deqr3AS7sfalyDh0zUbV7GTbfJ6FW\",\n \"object\":
42+
\"chat.completion\",\n \"created\": 1778628665,\n \"model\": \"gpt-4o-mini-2024-07-18\",\n
43+
\ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\":
44+
\"assistant\",\n \"content\": \"12 + 12 equals 24.\",\n \"refusal\":
45+
null,\n \"annotations\": []\n },\n \"logprobs\": null,\n
46+
\ \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\":
47+
14,\n \"completion_tokens\": 8,\n \"total_tokens\": 22,\n \"prompt_tokens_details\":
48+
{\n \"cached_tokens\": 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\":
49+
{\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\":
50+
0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\":
51+
\"default\",\n \"system_fingerprint\": \"fp_026a2c69f4\"\n}\n"
52+
headers:
53+
Access-Control-Expose-Headers:
54+
- CF-Ray
55+
CF-RAY:
56+
- 9fad36ffb950ebc0-YYZ
57+
Connection:
58+
- keep-alive
59+
Content-Type:
60+
- application/json
61+
Date:
62+
- Tue, 12 May 2026 23:31:05 GMT
63+
Server:
64+
- cloudflare
65+
Strict-Transport-Security:
66+
- max-age=31536000; includeSubDomains; preload
67+
Transfer-Encoding:
68+
- chunked
69+
X-Content-Type-Options:
70+
- nosniff
71+
alt-svc:
72+
- h3=":443"; ma=86400
73+
cf-cache-status:
74+
- DYNAMIC
75+
content-length:
76+
- '824'
77+
openai-organization:
78+
- braintrust-data
79+
openai-processing-ms:
80+
- '361'
81+
openai-project:
82+
- proj_vsCSXafhhByzWOThMrJcZiw9
83+
openai-version:
84+
- '2020-10-01'
85+
set-cookie:
86+
- __cf_bm=REd5IGOCpA5St.7gaT4pJICQVAr4JZiYdU25UG5p2xk-1778628664.2733116-1.0.1.1-nGe1F_BTvWxO_eU6nY7JccQmtUHd4b4g8fMcFAwRFJNRPDTkvb6VzkeO7mtA0pGKwCIjuJGBX84ijewVEXCxE5BEDqOG8tjxG6p9YIRyEhDhGkiyC8xsZryTsIMIxTUP;
87+
HttpOnly; SameSite=None; Secure; Path=/; Domain=api.openai.com; Expires=Wed,
88+
13 May 2026 00:01:05 GMT
89+
x-openai-proxy-wasm:
90+
- v0.1
91+
x-ratelimit-limit-requests:
92+
- '30000'
93+
x-ratelimit-limit-tokens:
94+
- '150000000'
95+
x-ratelimit-remaining-requests:
96+
- '29999'
97+
x-ratelimit-remaining-tokens:
98+
- '149999995'
99+
x-ratelimit-reset-requests:
100+
- 2ms
101+
x-ratelimit-reset-tokens:
102+
- 0s
103+
x-request-id:
104+
- req_e4e8696b1b1b4b9abc9713aaddc43464
105+
status:
106+
code: 200
107+
message: OK
108+
version: 1

py/src/braintrust/integrations/openai/test_openai.py

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@
2020
from braintrust.span_types import SpanTypeAttribute
2121
from braintrust.test_helpers import assert_dict_matches, init_test_logger
2222
from openai import AsyncOpenAI
23-
from openai._types import NOT_GIVEN
23+
from openai._types import NOT_GIVEN, Omit
24+
from packaging.version import Version
2425
from pydantic import BaseModel
2526

2627

@@ -1568,6 +1569,45 @@ def test_openai_not_given_filtering(memory_logger):
15681569
assert k not in meta
15691570

15701571

1572+
@pytest.mark.skipif(Version(openai.__version__) < Version("2.0.0"), reason="openai.Omit is not omitted by OpenAI 1.x")
1573+
@pytest.mark.vcr
1574+
def test_openai_omit_filtering(memory_logger):
1575+
"""Test that Omit values are filtered out of logged inputs but API call still works."""
1576+
assert not memory_logger.pop()
1577+
1578+
client = wrap_openai(openai.OpenAI())
1579+
1580+
response = client.chat.completions.create(
1581+
model=TEST_MODEL,
1582+
messages=[{"role": "user", "content": TEST_PROMPT}],
1583+
temperature=0.5,
1584+
tools=Omit(),
1585+
)
1586+
1587+
assert response
1588+
assert response.choices[0].message.content
1589+
assert "24" in response.choices[0].message.content or "twenty-four" in response.choices[0].message.content.lower()
1590+
1591+
spans = memory_logger.pop()
1592+
assert len(spans) == 1
1593+
span = spans[0]
1594+
1595+
assert_dict_matches(
1596+
span,
1597+
{
1598+
"input": [{"role": "user", "content": TEST_PROMPT}],
1599+
"metadata": {
1600+
"model": TEST_MODEL,
1601+
"provider": "openai",
1602+
"temperature": 0.5,
1603+
},
1604+
},
1605+
)
1606+
meta = span["metadata"]
1607+
assert "Omit" not in str(meta)
1608+
assert "tools" not in meta
1609+
1610+
15711611
@pytest.mark.vcr
15721612
def test_openai_responses_not_given_filtering(memory_logger):
15731613
"""Test that NOT_GIVEN values are filtered out of logged inputs for responses API."""

py/src/braintrust/integrations/utils.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -482,15 +482,15 @@ def _extract_audio_output(
482482

483483

484484
def _is_not_given(value: object) -> bool:
485-
"""Return ``True`` when *value* is a provider ``NOT_GIVEN`` sentinel.
485+
"""Return ``True`` when *value* is a provider omitted-parameter sentinel.
486486
487487
Works by type-name inspection so that Braintrust does not need a
488488
direct import dependency on any provider SDK.
489489
"""
490490
if value is None:
491491
return False
492492
try:
493-
return type(value).__name__ == "NotGiven"
493+
return type(value).__name__ in {"NotGiven", "Omit"}
494494
except Exception:
495495
return False
496496

0 commit comments

Comments
 (0)