Skip to content

Commit 49d4120

Browse files
feat(suppress): expose SUPPRESS_LANGUAGE_MODEL_INSTRUMENTATION as env var
Add SUPPRESS_LANGUAGE_MODEL_INSTRUMENTATION_ENV_VAR to util-genai attributes as the environment variable counterpart of the existing suppress_language_model_instrumentation OTel context key. The openai-v2 instrumentor now checks both surfaces via a single _is_instrumentation_suppressed() helper: 1. OTel context key (existing) — set per-request by the LangChain instrumentor to prevent duplicate LLM spans. 2. New env var — set globally for zero-code deployments together with OTEL_PYTHON_DISABLED_INSTRUMENTATIONS=openai. One concept, two surfaces. No new flags introduced in the LangChain package. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 17c6d96 commit 49d4120

7 files changed

Lines changed: 405 additions & 7 deletions

File tree

instrumentation-genai/opentelemetry-instrumentation-openai-v2/CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## Unreleased
99

10+
### Added
11+
- **`SUPPRESS_LANGUAGE_MODEL_INSTRUMENTATION` env var** — Global alternative to
12+
the per-request `suppress_language_model_instrumentation` OTel context key.
13+
When set to a truthy value (`true`, `1`, `yes`, `on`), the openai-v2
14+
instrumentor skips creating spans entirely. Intended for zero-code deployments
15+
alongside `OTEL_PYTHON_DISABLED_INSTRUMENTATIONS=openai`.
16+
1017
### Fixed
1118

1219
- Fix `AttributeError: 'StreamWrapper' object has no attribute 'headers'` when

instrumentation-genai/opentelemetry-instrumentation-openai-v2/src/opentelemetry/instrumentation/openai_v2/patch.py

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
import asyncio
1717
import inspect
18+
import os
1819
import timeit
1920
from typing import Any, Iterable, Optional
2021

@@ -29,6 +30,7 @@
2930
)
3031
from opentelemetry.trace import Span
3132
from opentelemetry.util.genai.attributes import (
33+
SUPPRESS_LANGUAGE_MODEL_INSTRUMENTATION_ENV_VAR,
3234
SUPPRESS_LANGUAGE_MODEL_INSTRUMENTATION_KEY,
3335
)
3436
from opentelemetry.util.genai.handler import (
@@ -59,6 +61,23 @@
5961
)
6062

6163

64+
def _is_instrumentation_suppressed() -> bool:
65+
"""Return True when OpenAI spans should be skipped.
66+
67+
Checks two surfaces for the suppression signal:
68+
1. OTel context key — set per-request by the LangChain instrumentor's
69+
``_OpenAITracingWrapper`` to prevent duplicate LLM spans when both
70+
instrumentors are active simultaneously.
71+
2. Environment variable ``SUPPRESS_LANGUAGE_MODEL_INSTRUMENTATION`` —
72+
set globally (e.g. in zero-code deployments) together with
73+
``OTEL_PYTHON_DISABLED_INSTRUMENTATIONS=openai``.
74+
"""
75+
if context_api.get_value(SUPPRESS_LANGUAGE_MODEL_INSTRUMENTATION_KEY):
76+
return True
77+
raw = os.environ.get(SUPPRESS_LANGUAGE_MODEL_INSTRUMENTATION_ENV_VAR, "")
78+
return raw.strip().lower() in ("true", "1", "yes", "on")
79+
80+
6281
def _normalize_stop_sequences(stop_values: Any) -> list[str]:
6382
if stop_values is None:
6483
return []
@@ -394,7 +413,7 @@ def chat_completions_create(capture_content: bool, handler):
394413

395414
def traced_method(wrapped, instance, args, kwargs):
396415
# Check if instrumentation is suppressed (e.g., by LangChain)
397-
if context_api.get_value(SUPPRESS_LANGUAGE_MODEL_INSTRUMENTATION_KEY):
416+
if _is_instrumentation_suppressed():
398417
return wrapped(*args, **kwargs)
399418

400419
span_attributes = {**get_llm_request_attributes(kwargs, instance)}
@@ -449,7 +468,7 @@ def async_chat_completions_create(capture_content: bool, handler):
449468

450469
async def traced_method(wrapped, instance, args, kwargs):
451470
# Check if instrumentation is suppressed (e.g., by LangChain)
452-
if context_api.get_value(SUPPRESS_LANGUAGE_MODEL_INSTRUMENTATION_KEY):
471+
if _is_instrumentation_suppressed():
453472
return await wrapped(*args, **kwargs)
454473

455474
span_attributes = {**get_llm_request_attributes(kwargs, instance)}
@@ -504,7 +523,7 @@ def embeddings_create(capture_content: bool, handler):
504523

505524
def traced_method(wrapped, instance, args, kwargs):
506525
# Check if instrumentation is suppressed (e.g., by LangChain)
507-
if context_api.get_value(SUPPRESS_LANGUAGE_MODEL_INSTRUMENTATION_KEY):
526+
if _is_instrumentation_suppressed():
508527
return wrapped(*args, **kwargs)
509528

510529
span_attributes = get_llm_request_attributes(
@@ -553,7 +572,7 @@ def async_embeddings_create(capture_content: bool, handler):
553572

554573
async def traced_method(wrapped, instance, args, kwargs):
555574
# Check if instrumentation is suppressed (e.g., by LangChain)
556-
if context_api.get_value(SUPPRESS_LANGUAGE_MODEL_INSTRUMENTATION_KEY):
575+
if _is_instrumentation_suppressed():
557576
return await wrapped(*args, **kwargs)
558577

559578
span_attributes = get_llm_request_attributes(
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
interactions:
2+
- request:
3+
body: |-
4+
{
5+
"messages": [
6+
{
7+
"role": "user",
8+
"content": "Say this is a test"
9+
}
10+
],
11+
"model": "gpt-4o-mini",
12+
"stream": false
13+
}
14+
headers:
15+
accept:
16+
- application/json
17+
accept-encoding:
18+
- gzip, deflate
19+
authorization:
20+
- Bearer test_openai_api_key
21+
connection:
22+
- keep-alive
23+
content-length:
24+
- '106'
25+
content-type:
26+
- application/json
27+
host:
28+
- api.openai.com
29+
user-agent:
30+
- OpenAI/Python 1.54.3
31+
x-stainless-arch:
32+
- arm64
33+
x-stainless-async:
34+
- 'false'
35+
x-stainless-lang:
36+
- python
37+
x-stainless-os:
38+
- MacOS
39+
x-stainless-package-version:
40+
- 1.54.3
41+
x-stainless-retry-count:
42+
- '0'
43+
x-stainless-runtime:
44+
- CPython
45+
x-stainless-runtime-version:
46+
- 3.12.6
47+
method: POST
48+
uri: https://api.openai.com/v1/chat/completions
49+
response:
50+
body:
51+
string: |-
52+
{
53+
"id": "chatcmpl-ASYMQRl3A3DXL9FWCK9tnGRcKIO7q",
54+
"object": "chat.completion",
55+
"created": 1731368630,
56+
"model": "gpt-4o-mini-2024-07-18",
57+
"choices": [
58+
{
59+
"index": 0,
60+
"message": {
61+
"role": "assistant",
62+
"content": "This is a test.",
63+
"refusal": null
64+
},
65+
"logprobs": null,
66+
"finish_reason": "stop"
67+
}
68+
],
69+
"usage": {
70+
"prompt_tokens": 12,
71+
"completion_tokens": 5,
72+
"total_tokens": 17,
73+
"prompt_tokens_details": {
74+
"cached_tokens": 0,
75+
"audio_tokens": 0
76+
},
77+
"completion_tokens_details": {
78+
"reasoning_tokens": 0,
79+
"audio_tokens": 0,
80+
"accepted_prediction_tokens": 0,
81+
"rejected_prediction_tokens": 0
82+
}
83+
},
84+
"system_fingerprint": "fp_0ba0d124f1"
85+
}
86+
headers:
87+
CF-Cache-Status:
88+
- DYNAMIC
89+
CF-RAY:
90+
- 8e122593ff368bc8-SIN
91+
Connection:
92+
- keep-alive
93+
Content-Type:
94+
- application/json
95+
Date:
96+
- Mon, 11 Nov 2024 23:43:50 GMT
97+
Server:
98+
- cloudflare
99+
Set-Cookie: test_set_cookie
100+
Transfer-Encoding:
101+
- chunked
102+
X-Content-Type-Options:
103+
- nosniff
104+
access-control-expose-headers:
105+
- X-Request-ID
106+
alt-svc:
107+
- h3=":443"; ma=86400
108+
content-length:
109+
- '765'
110+
openai-organization: test_openai_org_id
111+
openai-processing-ms:
112+
- '287'
113+
openai-version:
114+
- '2020-10-01'
115+
strict-transport-security:
116+
- max-age=31536000; includeSubDomains; preload
117+
x-ratelimit-limit-requests:
118+
- '10000'
119+
x-ratelimit-limit-tokens:
120+
- '200000'
121+
x-ratelimit-remaining-requests:
122+
- '9999'
123+
x-ratelimit-remaining-tokens:
124+
- '199977'
125+
x-ratelimit-reset-requests:
126+
- 8.64s
127+
x-ratelimit-reset-tokens:
128+
- 6ms
129+
x-request-id:
130+
- req_58cff97afd0e7c0bba910ccf0b044a6f
131+
status:
132+
code: 200
133+
message: OK
134+
version: 1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
interactions:
2+
- request:
3+
body: |-
4+
{
5+
"messages": [
6+
{
7+
"role": "user",
8+
"content": "Say this is a test"
9+
}
10+
],
11+
"model": "gpt-4o-mini",
12+
"stream": false
13+
}
14+
headers:
15+
accept:
16+
- application/json
17+
accept-encoding:
18+
- gzip, deflate
19+
authorization:
20+
- Bearer test_openai_api_key
21+
connection:
22+
- keep-alive
23+
content-length:
24+
- '106'
25+
content-type:
26+
- application/json
27+
host:
28+
- api.openai.com
29+
user-agent:
30+
- OpenAI/Python 1.54.3
31+
x-stainless-arch:
32+
- arm64
33+
x-stainless-async:
34+
- 'false'
35+
x-stainless-lang:
36+
- python
37+
x-stainless-os:
38+
- MacOS
39+
x-stainless-package-version:
40+
- 1.54.3
41+
x-stainless-retry-count:
42+
- '0'
43+
x-stainless-runtime:
44+
- CPython
45+
x-stainless-runtime-version:
46+
- 3.12.6
47+
method: POST
48+
uri: https://api.openai.com/v1/chat/completions
49+
response:
50+
body:
51+
string: |-
52+
{
53+
"id": "chatcmpl-ASYMQRl3A3DXL9FWCK9tnGRcKIO7q",
54+
"object": "chat.completion",
55+
"created": 1731368630,
56+
"model": "gpt-4o-mini-2024-07-18",
57+
"choices": [
58+
{
59+
"index": 0,
60+
"message": {
61+
"role": "assistant",
62+
"content": "This is a test.",
63+
"refusal": null
64+
},
65+
"logprobs": null,
66+
"finish_reason": "stop"
67+
}
68+
],
69+
"usage": {
70+
"prompt_tokens": 12,
71+
"completion_tokens": 5,
72+
"total_tokens": 17,
73+
"prompt_tokens_details": {
74+
"cached_tokens": 0,
75+
"audio_tokens": 0
76+
},
77+
"completion_tokens_details": {
78+
"reasoning_tokens": 0,
79+
"audio_tokens": 0,
80+
"accepted_prediction_tokens": 0,
81+
"rejected_prediction_tokens": 0
82+
}
83+
},
84+
"system_fingerprint": "fp_0ba0d124f1"
85+
}
86+
headers:
87+
CF-Cache-Status:
88+
- DYNAMIC
89+
CF-RAY:
90+
- 8e122593ff368bc8-SIN
91+
Connection:
92+
- keep-alive
93+
Content-Type:
94+
- application/json
95+
Date:
96+
- Mon, 11 Nov 2024 23:43:50 GMT
97+
Server:
98+
- cloudflare
99+
Set-Cookie: test_set_cookie
100+
Transfer-Encoding:
101+
- chunked
102+
X-Content-Type-Options:
103+
- nosniff
104+
access-control-expose-headers:
105+
- X-Request-ID
106+
alt-svc:
107+
- h3=":443"; ma=86400
108+
content-length:
109+
- '765'
110+
openai-organization: test_openai_org_id
111+
openai-processing-ms:
112+
- '287'
113+
openai-version:
114+
- '2020-10-01'
115+
strict-transport-security:
116+
- max-age=31536000; includeSubDomains; preload
117+
x-ratelimit-limit-requests:
118+
- '10000'
119+
x-ratelimit-limit-tokens:
120+
- '200000'
121+
x-ratelimit-remaining-requests:
122+
- '9999'
123+
x-ratelimit-remaining-tokens:
124+
- '199977'
125+
x-ratelimit-reset-requests:
126+
- 8.64s
127+
x-ratelimit-reset-tokens:
128+
- 6ms
129+
x-request-id:
130+
- req_58cff97afd0e7c0bba910ccf0b044a6f
131+
status:
132+
code: 200
133+
message: OK
134+
version: 1

0 commit comments

Comments
 (0)