Skip to content

Commit 589e104

Browse files
authored
fix(openai): record service_tier attribute (#3458)
1 parent f36e5a7 commit 589e104

File tree

6 files changed

+287
-0
lines changed

6 files changed

+287
-0
lines changed

packages/opentelemetry-instrumentation-openai/opentelemetry/instrumentation/openai/shared/__init__.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
)
1313
from opentelemetry.semconv._incubating.attributes import (
1414
gen_ai_attributes as GenAIAttributes,
15+
openai_attributes as OpenAIAttributes,
1516
)
1617
from opentelemetry.semconv_ai import SpanAttributes
1718
from opentelemetry.trace.propagation import set_span_in_context
@@ -141,6 +142,9 @@ def _set_request_attributes(span, kwargs, instance=None):
141142
_set_span_attribute(
142143
span, SpanAttributes.LLM_IS_STREAMING, kwargs.get("stream") or False
143144
)
145+
_set_span_attribute(
146+
span, OpenAIAttributes.OPENAI_REQUEST_SERVICE_TIER, kwargs.get("service_tier")
147+
)
144148
if response_format := kwargs.get("response_format"):
145149
# backward-compatible check for
146150
# openai.types.shared_params.response_format_json_schema.ResponseFormatJSONSchema
@@ -210,6 +214,11 @@ def _set_response_attributes(span, response):
210214
SpanAttributes.LLM_OPENAI_RESPONSE_SYSTEM_FINGERPRINT,
211215
response.get("system_fingerprint"),
212216
)
217+
_set_span_attribute(
218+
span,
219+
OpenAIAttributes.OPENAI_RESPONSE_SERVICE_TIER,
220+
response.get("service_tier"),
221+
)
213222
_log_prompt_filter(span, response)
214223
usage = response.get("usage")
215224
if not usage:

packages/opentelemetry-instrumentation-openai/opentelemetry/instrumentation/openai/v1/responses_wrappers.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
from opentelemetry.instrumentation.utils import _SUPPRESS_INSTRUMENTATION_KEY
4343
from opentelemetry.semconv._incubating.attributes import (
4444
gen_ai_attributes as GenAIAttributes,
45+
openai_attributes as OpenAIAttributes,
4546
)
4647
from opentelemetry.semconv_ai import SpanAttributes
4748
from opentelemetry.semconv.attributes.error_attributes import ERROR_TYPE
@@ -132,6 +133,10 @@ class TracedData(pydantic.BaseModel):
132133
request_reasoning_effort: Optional[str] = pydantic.Field(default=None)
133134
response_reasoning_effort: Optional[str] = pydantic.Field(default=None)
134135

136+
# OpenAI service tier
137+
request_service_tier: Optional[str] = pydantic.Field(default=None)
138+
response_service_tier: Optional[str] = pydantic.Field(default=None)
139+
135140

136141
responses: dict[str, TracedData] = {}
137142

@@ -189,6 +194,8 @@ def set_data_attributes(traced_response: TracedData, span: Span):
189194
_set_span_attribute(span, GenAIAttributes.GEN_AI_REQUEST_MODEL, traced_response.request_model)
190195
_set_span_attribute(span, GenAIAttributes.GEN_AI_RESPONSE_ID, traced_response.response_id)
191196
_set_span_attribute(span, GenAIAttributes.GEN_AI_RESPONSE_MODEL, traced_response.response_model)
197+
_set_span_attribute(span, OpenAIAttributes.OPENAI_REQUEST_SERVICE_TIER, traced_response.request_service_tier)
198+
_set_span_attribute(span, OpenAIAttributes.OPENAI_RESPONSE_SERVICE_TIER, traced_response.response_service_tier)
192199
if usage := traced_response.usage:
193200
_set_span_attribute(span, GenAIAttributes.GEN_AI_USAGE_INPUT_TOKENS, usage.input_tokens)
194201
_set_span_attribute(span, GenAIAttributes.GEN_AI_USAGE_OUTPUT_TOKENS, usage.output_tokens)
@@ -483,6 +490,8 @@ def responses_get_or_create_wrapper(tracer: Tracer, wrapped, instance, args, kwa
483490
)
484491
),
485492
response_reasoning_effort=kwargs.get("reasoning", {}).get("effort"),
493+
request_service_tier=kwargs.get("service_tier"),
494+
response_service_tier=existing_data.get("response_service_tier"),
486495
)
487496
except Exception:
488497
traced_data = None
@@ -546,6 +555,8 @@ def responses_get_or_create_wrapper(tracer: Tracer, wrapped, instance, args, kwa
546555
)
547556
),
548557
response_reasoning_effort=kwargs.get("reasoning", {}).get("effort"),
558+
request_service_tier=existing_data.get("request_service_tier", kwargs.get("service_tier")),
559+
response_service_tier=existing_data.get("response_service_tier", parsed_response.service_tier),
549560
)
550561
responses[parsed_response.id] = traced_data
551562
except Exception:
@@ -621,6 +632,8 @@ async def async_responses_get_or_create_wrapper(
621632
)
622633
),
623634
response_reasoning_effort=kwargs.get("reasoning", {}).get("effort"),
635+
request_service_tier=kwargs.get("service_tier"),
636+
response_service_tier=existing_data.get("response_service_tier"),
624637
)
625638
except Exception:
626639
traced_data = None
@@ -685,6 +698,8 @@ async def async_responses_get_or_create_wrapper(
685698
)
686699
),
687700
response_reasoning_effort=kwargs.get("reasoning", {}).get("effort"),
701+
request_service_tier=existing_data.get("request_service_tier", kwargs.get("service_tier")),
702+
response_service_tier=existing_data.get("response_service_tier", parsed_response.service_tier),
688703
)
689704
responses[parsed_response.id] = traced_data
690705
except Exception:
@@ -791,6 +806,8 @@ def __init__(
791806
),
792807
request_reasoning_effort=self._request_kwargs.get("reasoning", {}).get("effort"),
793808
response_reasoning_effort=None,
809+
request_service_tier=self._request_kwargs.get("service_tier"),
810+
response_service_tier=None,
794811
)
795812

796813
self._complete_response_data = None
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
interactions:
2+
- request:
3+
body: '{"messages": [{"role": "user", "content": "Say hello"}], "model": "gpt-5",
4+
"service_tier": "priority"}'
5+
headers:
6+
accept:
7+
- application/json
8+
accept-encoding:
9+
- gzip, deflate
10+
connection:
11+
- keep-alive
12+
content-length:
13+
- '102'
14+
content-type:
15+
- application/json
16+
host:
17+
- api.openai.com
18+
traceparent:
19+
- 00-9abdb187b56105d7f694cb1bcd7d6ff7-ff31170765953060-01
20+
user-agent:
21+
- OpenAI/Python 1.99.7
22+
x-stainless-arch:
23+
- arm64
24+
x-stainless-async:
25+
- 'false'
26+
x-stainless-lang:
27+
- python
28+
x-stainless-os:
29+
- MacOS
30+
x-stainless-package-version:
31+
- 1.99.7
32+
x-stainless-read-timeout:
33+
- '600'
34+
x-stainless-retry-count:
35+
- '0'
36+
x-stainless-runtime:
37+
- CPython
38+
x-stainless-runtime-version:
39+
- 3.9.6
40+
method: POST
41+
uri: https://api.openai.com/v1/chat/completions
42+
response:
43+
body:
44+
string: !!binary |
45+
H4sIAAAAAAAAA3RSQW7bMBC86xVbnq1Cduok9iVIU6AuCgQucmsQCCy5tthSXJZcNVUC/70g7Vgy
46+
0lx02NkZzQz3uQAQRoslCNVIVq235c1m/vh5vVp//B2fbtSnVf9tzYu72+u7p+9fb8UkMejHT1T8
47+
wnqvqPUW2ZDbwyqgZEyq04vzs8W0OpvOMtCSRptoW8/lvJxVs3lZXZbVxYHXkFEYxRLuCwCA5/xN
48+
Dp3Gv2IJ1eRl0mKMcotieVwCEIFsmggZo4ksHYvJACpyjC6bXqG19A5W9AhKOvgCDVoPPXXApGV/
49+
NaYF3HRRJtOus3YESOeIZQqdDT8ckN3R4sY4E5s6oIzk0m8jkxcZ3RUADzlyd5JC+ECt55rpF2bZ
50+
y72aGBoeYbMDyMTSDvPFoaVTsVojS2PjqDGhpGpQD8yhXtlpQyOgGEV7beZ/2vvYxm0HlfMPb+oP
51+
gFLoGXXtA2qjThMPawHTAb61duw4OxYRwx+jsGaDIb2DD4aC4X5/dLGPjG29MW6LwQeTTyS9dbEr
52+
/gEAAP//AwB9nhCJHwMAAA==
53+
headers:
54+
CF-RAY:
55+
- 9a319038ca178687-ARN
56+
Connection:
57+
- keep-alive
58+
Content-Encoding:
59+
- gzip
60+
Content-Type:
61+
- application/json
62+
Date:
63+
- Sun, 23 Nov 2025 15:05:13 GMT
64+
Server:
65+
- cloudflare
66+
Set-Cookie:
67+
- __cf_bm=BzRM2cFojUtXKFo1i0gAAj1_aXPTRwMVXEDci5ux0eY-1763910313-1.0.1.1-Cq0yyUFvMCbE6GRA747glZtqTPRyXEkAE44E2L5S_TwRwNKZ.r26uFYeXvwQbhTSqDZ2KeyvsS693q_z9Dd9aWBj.j0eUJcBE5nNJ.pIhOg;
68+
path=/; expires=Sun, 23-Nov-25 15:35:13 GMT; domain=.api.openai.com; HttpOnly;
69+
Secure; SameSite=None
70+
- _cfuvid=BoDvu6D4M8n.b5OoTqVk4rwbKfyeViFSKx_hJR4S4W0-1763910313444-0.0.1.1-604800000;
71+
path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None
72+
Strict-Transport-Security:
73+
- max-age=31536000; includeSubDomains; preload
74+
Transfer-Encoding:
75+
- chunked
76+
X-Content-Type-Options:
77+
- nosniff
78+
access-control-expose-headers:
79+
- X-Request-ID
80+
alt-svc:
81+
- h3=":443"; ma=86400
82+
cf-cache-status:
83+
- DYNAMIC
84+
openai-organization:
85+
- agentpaid
86+
openai-processing-ms:
87+
- '968'
88+
openai-project:
89+
- proj_g54CtVh9wETqz1duiA1cw9Kw
90+
openai-version:
91+
- '2020-10-01'
92+
x-envoy-upstream-service-time:
93+
- '1163'
94+
x-openai-proxy-wasm:
95+
- v0.1
96+
x-ratelimit-limit-requests:
97+
- '500'
98+
x-ratelimit-limit-tokens:
99+
- '500000'
100+
x-ratelimit-remaining-requests:
101+
- '499'
102+
x-ratelimit-remaining-tokens:
103+
- '499995'
104+
x-ratelimit-reset-requests:
105+
- 120ms
106+
x-ratelimit-reset-tokens:
107+
- 0s
108+
x-request-id:
109+
- req_b623980c393049c6b6d99da2cc70576a
110+
status:
111+
code: 200
112+
message: OK
113+
version: 1
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
interactions:
2+
- request:
3+
body: '{"input": "Say hello", "model": "gpt-5", "service_tier": "priority"}'
4+
headers:
5+
accept:
6+
- application/json
7+
accept-encoding:
8+
- gzip, deflate
9+
connection:
10+
- keep-alive
11+
content-length:
12+
- '68'
13+
content-type:
14+
- application/json
15+
host:
16+
- api.openai.com
17+
user-agent:
18+
- OpenAI/Python 1.99.7
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+
- 1.99.7
29+
x-stainless-read-timeout:
30+
- '600'
31+
x-stainless-retry-count:
32+
- '0'
33+
x-stainless-runtime:
34+
- CPython
35+
x-stainless-runtime-version:
36+
- 3.9.6
37+
method: POST
38+
uri: https://api.openai.com/v1/responses
39+
response:
40+
body:
41+
string: !!binary |
42+
H4sIAAAAAAAAAwAAAP//fFTLctswDLznK1ie444efki+9Jp+QyajgSjIYUMJHBJyo+n43zuibD0a
43+
uzcJCywXWJB/noSQupJHIR16W0TbAyRJtIV9lMT17hBF+zxJk6RMsnqnsjjf51msMpWmZRKXCtOd
44+
fB4oqPyFim801Hoc48ohMFYFDFh82Kd5HKVJEjDPwJ0fahQ11iBjNRaVoD5Ojrp20FWD8TiGtTG6
45+
Pcmj+PMkhBDSQo9uqK/wjIYsOvkkxCUko3M0YG1nTAjo9nZKUSGDNn6NenadYk3tKt7AZ0Ed244L
46+
pg/8CjKRKRSYNV1DFZpB2cnyZrdJomS3ibJNdLhOKzDKo3gNjYztzEb4xzakW0i2wYa6yjDKs+qA
47+
e8gO47gDCfcWRyPAUzsMbIJ81zTg+uHgtxC7PN8T0PjTfxRkOYRFgBTzapdXaR1vY5UlXxU06D2c
48+
FtIeOR5ARS1jO09lKWxFe/MDP3mqDgnQtsRw8/D1bQUaOllH5R0kEB2FfEFj6Jt4od9CQSt+inc0
49+
VvTUCaYK+h9yKrpcvyYe6cgEbeC99gwtj8lDYkiSFhwYg2a9MOy6cbetw7Omzhe361MEK6aFso4a
50+
y4UC9Y7FB/YPMYfDEDW1y4x5Faa7g3VNjkeXKt0110kuVmSonq6Thxq5L3Q1kNcaV1fLoztrhQWP
51+
cWmdJqe5l9drTg6XvTI2Fh1wF8Lx9+gaDS5c5dXkGpj/F+6HvOXyyjO6kvxw3tzNJHwc9ztpNfrT
52+
MckJmJdBMtlisSLRFLRLja5rFVynKyvtoTS3R6sLqz41oNvVm5E9fw0v3qGpy2BiNddFq07/fYni
53+
3T3gHu+0AY+omRjMDCbpNMHOr91ukKEChoH+8nT5CwAA//8DAD1YGBRCBgAA
54+
headers:
55+
CF-RAY:
56+
- 9a3190799a8282d6-ARN
57+
Connection:
58+
- keep-alive
59+
Content-Encoding:
60+
- gzip
61+
Content-Type:
62+
- application/json
63+
Date:
64+
- Sun, 23 Nov 2025 15:05:24 GMT
65+
Server:
66+
- cloudflare
67+
Set-Cookie:
68+
- __cf_bm=5epdftKChRBshn5d9QnvOUIH6jdbbC2Z49fpF_kViyo-1763910324-1.0.1.1-cHXuCjdspu_Vvx.3Ax1PpY9WHMivIEjCSU1BsYYaHZ_cEDxkd62QXyB39dzmzPGQ9zjTm8MVBplGzsr60dhv0gBrBaj9EQkh58nrbutTgVo;
69+
path=/; expires=Sun, 23-Nov-25 15:35:24 GMT; domain=.api.openai.com; HttpOnly;
70+
Secure; SameSite=None
71+
- _cfuvid=gp4oUQF19kcdJq_p9DCnU9cTap7yKDGhmqIT54evW5M-1763910324011-0.0.1.1-604800000;
72+
path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None
73+
Strict-Transport-Security:
74+
- max-age=31536000; includeSubDomains; preload
75+
Transfer-Encoding:
76+
- chunked
77+
X-Content-Type-Options:
78+
- nosniff
79+
alt-svc:
80+
- h3=":443"; ma=86400
81+
cf-cache-status:
82+
- DYNAMIC
83+
openai-organization:
84+
- agentpaid
85+
openai-processing-ms:
86+
- '1395'
87+
openai-project:
88+
- proj_g54CtVh9wETqz1duiA1cw9Kw
89+
openai-version:
90+
- '2020-10-01'
91+
x-envoy-upstream-service-time:
92+
- '1398'
93+
x-ratelimit-limit-requests:
94+
- '500'
95+
x-ratelimit-limit-tokens:
96+
- '500000'
97+
x-ratelimit-remaining-requests:
98+
- '499'
99+
x-ratelimit-remaining-tokens:
100+
- '500000'
101+
x-ratelimit-reset-requests:
102+
- 120ms
103+
x-ratelimit-reset-tokens:
104+
- 0s
105+
x-request-id:
106+
- req_c7a7a91635ad4051892623b7a91f919f
107+
status:
108+
code: 200
109+
message: OK
110+
version: 1

packages/opentelemetry-instrumentation-openai/tests/traces/test_chat.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1497,6 +1497,27 @@ def test_chat_reasoning(instrument_legacy, span_exporter,
14971497
assert span.attributes["gen_ai.usage.reasoning_tokens"] > 0
14981498

14991499

1500+
@pytest.mark.vcr
1501+
def test_chat_with_service_tier(instrument_legacy, span_exporter, openai_client):
1502+
openai_client.chat.completions.create(
1503+
model="gpt-5",
1504+
messages=[
1505+
{
1506+
"role": "user",
1507+
"content": "Say hello"
1508+
}
1509+
],
1510+
service_tier="priority",
1511+
)
1512+
1513+
spans = span_exporter.get_finished_spans()
1514+
assert len(spans) >= 1
1515+
span = spans[-1]
1516+
1517+
assert span.attributes["openai.request.service_tier"] == "priority"
1518+
assert span.attributes["openai.response.service_tier"] == "priority"
1519+
1520+
15001521
def test_chat_exception(instrument_legacy, span_exporter, openai_client):
15011522
openai_client.api_key = "invalid"
15021523
with pytest.raises(Exception):

packages/opentelemetry-instrumentation-openai/tests/traces/test_responses.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,23 @@ def test_responses(
2929
# assert span.attributes["gen_ai.prompt.0.role"] == "user"
3030

3131

32+
@pytest.mark.vcr
33+
def test_responses_with_service_tier(
34+
instrument_legacy, span_exporter: InMemorySpanExporter, openai_client: OpenAI
35+
):
36+
_ = openai_client.responses.create(
37+
model="gpt-5",
38+
input="Say hello",
39+
service_tier="priority",
40+
)
41+
spans = span_exporter.get_finished_spans()
42+
assert len(spans) == 1
43+
span = spans[0]
44+
assert span.name == "openai.response"
45+
assert span.attributes["openai.request.service_tier"] == "priority"
46+
assert span.attributes["openai.response.service_tier"] == "priority"
47+
48+
3249
@pytest.mark.vcr
3350
def test_responses_with_input_history(
3451
instrument_legacy, span_exporter: InMemorySpanExporter, openai_client: OpenAI

0 commit comments

Comments
 (0)