Skip to content

Commit 5bd93d9

Browse files
authored
fix(openai): report request attributes in responses API instrumentation (#3471)
1 parent c11d45a commit 5bd93d9

File tree

3 files changed

+164
-4
lines changed

3 files changed

+164
-4
lines changed

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

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@
5151
from typing_extensions import NotRequired
5252

5353
from opentelemetry.instrumentation.openai.shared import (
54+
_extract_model_name_from_provider_format,
55+
_set_request_attributes,
5456
_set_span_attribute,
5557
model_as_dict,
5658
)
@@ -189,12 +191,26 @@ def process_content_block(
189191

190192

191193
@dont_throw
194+
def prepare_kwargs_for_shared_attributes(kwargs):
195+
"""
196+
Prepare kwargs for the shared _set_request_attributes function.
197+
Maps responses API specific parameters to the common format.
198+
"""
199+
prepared_kwargs = kwargs.copy()
200+
201+
# Map max_output_tokens to max_tokens for the shared function
202+
if "max_output_tokens" in kwargs:
203+
prepared_kwargs["max_tokens"] = kwargs["max_output_tokens"]
204+
205+
return prepared_kwargs
206+
207+
192208
def set_data_attributes(traced_response: TracedData, span: Span):
193-
_set_span_attribute(span, GenAIAttributes.GEN_AI_SYSTEM, "openai")
194-
_set_span_attribute(span, GenAIAttributes.GEN_AI_REQUEST_MODEL, traced_response.request_model)
195209
_set_span_attribute(span, GenAIAttributes.GEN_AI_RESPONSE_ID, traced_response.response_id)
196-
_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)
210+
211+
response_model = _extract_model_name_from_provider_format(traced_response.response_model)
212+
_set_span_attribute(span, GenAIAttributes.GEN_AI_RESPONSE_MODEL, response_model)
213+
198214
_set_span_attribute(span, OpenAIAttributes.OPENAI_RESPONSE_SERVICE_TIER, traced_response.response_service_tier)
199215
if usage := traced_response.usage:
200216
_set_span_attribute(span, GenAIAttributes.GEN_AI_USAGE_INPUT_TOKENS, usage.input_tokens)
@@ -445,6 +461,7 @@ def responses_get_or_create_wrapper(tracer: Tracer, wrapped, instance, args, kwa
445461
kind=SpanKind.CLIENT,
446462
start_time=start_time,
447463
)
464+
_set_request_attributes(span, prepare_kwargs_for_shared_attributes(kwargs), instance)
448465

449466
return ResponseStream(
450467
span=span,
@@ -503,6 +520,7 @@ def responses_get_or_create_wrapper(tracer: Tracer, wrapped, instance, args, kwa
503520
start_time if traced_data is None else int(traced_data.start_time)
504521
),
505522
)
523+
_set_request_attributes(span, prepare_kwargs_for_shared_attributes(kwargs), instance)
506524
span.set_attribute(ERROR_TYPE, e.__class__.__name__)
507525
span.record_exception(e)
508526
span.set_status(StatusCode.ERROR, str(e))
@@ -568,6 +586,7 @@ def responses_get_or_create_wrapper(tracer: Tracer, wrapped, instance, args, kwa
568586
kind=SpanKind.CLIENT,
569587
start_time=int(traced_data.start_time),
570588
)
589+
_set_request_attributes(span, prepare_kwargs_for_shared_attributes(kwargs), instance)
571590
set_data_attributes(traced_data, span)
572591
span.end()
573592

@@ -591,6 +610,7 @@ async def async_responses_get_or_create_wrapper(
591610
kind=SpanKind.CLIENT,
592611
start_time=start_time,
593612
)
613+
_set_request_attributes(span, prepare_kwargs_for_shared_attributes(kwargs), instance)
594614

595615
return ResponseStream(
596616
span=span,
@@ -645,6 +665,7 @@ async def async_responses_get_or_create_wrapper(
645665
start_time if traced_data is None else int(traced_data.start_time)
646666
),
647667
)
668+
_set_request_attributes(span, prepare_kwargs_for_shared_attributes(kwargs), instance)
648669
span.set_attribute(ERROR_TYPE, e.__class__.__name__)
649670
span.record_exception(e)
650671
span.set_status(StatusCode.ERROR, str(e))
@@ -711,6 +732,7 @@ async def async_responses_get_or_create_wrapper(
711732
kind=SpanKind.CLIENT,
712733
start_time=int(traced_data.start_time),
713734
)
735+
_set_request_attributes(span, prepare_kwargs_for_shared_attributes(kwargs), instance)
714736
set_data_attributes(traced_data, span)
715737
span.end()
716738

@@ -735,6 +757,7 @@ def responses_cancel_wrapper(tracer: Tracer, wrapped, instance, args, kwargs):
735757
start_time=existing_data.start_time,
736758
record_exception=True,
737759
)
760+
_set_request_attributes(span, prepare_kwargs_for_shared_attributes(kwargs), instance)
738761
span.record_exception(Exception("Response cancelled"))
739762
set_data_attributes(existing_data, span)
740763
span.end()
@@ -761,6 +784,7 @@ async def async_responses_cancel_wrapper(
761784
start_time=existing_data.start_time,
762785
record_exception=True,
763786
)
787+
_set_request_attributes(span, prepare_kwargs_for_shared_attributes(kwargs), instance)
764788
span.record_exception(Exception("Response cancelled"))
765789
set_data_attributes(existing_data, span)
766790
span.end()
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
interactions:
2+
- request:
3+
body: '{"input": "What is the capital of France?", "max_output_tokens": 100, "model":
4+
"gpt-4.1-nano", "temperature": 0.7, "top_p": 0.9}'
5+
headers:
6+
accept:
7+
- application/json
8+
accept-encoding:
9+
- gzip, deflate
10+
connection:
11+
- keep-alive
12+
content-length:
13+
- '128'
14+
content-type:
15+
- application/json
16+
host:
17+
- api.openai.com
18+
user-agent:
19+
- OpenAI/Python 1.99.7
20+
x-stainless-arch:
21+
- arm64
22+
x-stainless-async:
23+
- 'false'
24+
x-stainless-lang:
25+
- python
26+
x-stainless-os:
27+
- MacOS
28+
x-stainless-package-version:
29+
- 1.99.7
30+
x-stainless-read-timeout:
31+
- '600'
32+
x-stainless-retry-count:
33+
- '0'
34+
x-stainless-runtime:
35+
- CPython
36+
x-stainless-runtime-version:
37+
- 3.10.16
38+
method: POST
39+
uri: https://api.openai.com/v1/responses
40+
response:
41+
body:
42+
string: !!binary |
43+
H4sIAAAAAAAAA3RUwW7bMAy95ysEnZtCdp3YyQfsvMNuxWDQNp1qlUVBooIGRf59sBw78dbeLD7y
44+
mXyP0udGCKk7eRTSY3C12pVlh6qBBiqV56jU/pDvurLEw65VVXbYVf2havt90eUNFi/7Tj6NFNT8
45+
wZZnGrIBp3jrERi7GkYsK/eFqsp9WSUsMHAMY01LgzPIeCNroH0/eYp27KsHE3AKa2O0Pcmj+NwI
46+
IYR0cEE/1nd4RkMOvdwIcU3J6D2NmI3GpIC281/qDhm0CWs0sI8ta7Kr+AAfNUV2kWumd0xgptSC
47+
MZGpWzBrtoE6NGNjJ8fb4jnbWrC0zVW+26pimxU3zRKvPIrXNM401GLHEE7fulGpbJ9VyQ1V7opc
48+
ZX3evFSwqxJzYuGLw8SDIcAJ78B3siewJcto7009NrainUXBD16qUwJYSwyzkK+/V6Chk/PUfIEk
49+
oqOQv95QtOA0gxHUix8ebItCB/ETvA7Pcqm53r4WGunJpNYgBB0YLE/JY2JKkg48GINm7Rr7OO2X
50+
83jWFEM9r3CdnFhcdZ4Gx3UL7RvW73j5FvM4aqjJPmZ4hEB2tb/Y9+T5IWl0Jw4D+Jl7WecAPfKl
51+
1t1I3GtcrXZAf9Yt1qzn69BDNJMvMjB5fByTcXDogWMKq+fyFk363zrryQ9wPz/4nvImXW8dn9E3
52+
FDRfpm3rdBzu13BS+o10O1kTmeQC3NdAMrn6YTnUEnSpx8N09tG2cBNWdjpAY+Y3I6YlXwbQdn1l
53+
i6f/4w/vwDJmMrC7F6rVqP++BNVX8a9oF/O/Y2ZiMHcwzxcFY1ibPSBDBwwj/XVz/QsAAP//AwDZ
54+
CKVDwQUAAA==
55+
headers:
56+
CF-RAY:
57+
- 9a427a739fc97d95-TLV
58+
Connection:
59+
- keep-alive
60+
Content-Encoding:
61+
- gzip
62+
Content-Type:
63+
- application/json
64+
Date:
65+
- Tue, 25 Nov 2025 16:21:20 GMT
66+
Server:
67+
- cloudflare
68+
Set-Cookie:
69+
- __cf_bm=972AABf9T_oEadbDKJ76kKF9Af2tTgOaTdaX4cxL8kg-1764087680-1.0.1.1-3xsObn4FVX3Nmp7VNdniyOmOOdjB3JnvdcHUEpHKZqNX2Q6j1k9MbrU0lGYlUVLrVb2Ls0gopzNAe6FA5AiAIvLVTtFzLT2780nDD_KBqxM;
70+
path=/; expires=Tue, 25-Nov-25 16:51:20 GMT; domain=.api.openai.com; HttpOnly;
71+
Secure; SameSite=None
72+
- _cfuvid=w6zJyOCR9tAsFojGun.7hYM9_l7GfWdALH5Vd04TmeQ-1764087680812-0.0.1.1-604800000;
73+
path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None
74+
Strict-Transport-Security:
75+
- max-age=31536000; includeSubDomains; preload
76+
Transfer-Encoding:
77+
- chunked
78+
X-Content-Type-Options:
79+
- nosniff
80+
alt-svc:
81+
- h3=":443"; ma=86400
82+
cf-cache-status:
83+
- DYNAMIC
84+
openai-organization:
85+
- traceloop
86+
openai-processing-ms:
87+
- '2149'
88+
openai-project:
89+
- proj_tzz1TbPPOXaf6j9tEkVUBIAa
90+
openai-version:
91+
- '2020-10-01'
92+
x-envoy-upstream-service-time:
93+
- '2151'
94+
x-ratelimit-limit-requests:
95+
- '30000'
96+
x-ratelimit-limit-tokens:
97+
- '150000000'
98+
x-ratelimit-remaining-requests:
99+
- '29999'
100+
x-ratelimit-remaining-tokens:
101+
- '149999967'
102+
x-ratelimit-reset-requests:
103+
- 2ms
104+
x-ratelimit-reset-tokens:
105+
- 0s
106+
x-request-id:
107+
- req_500c9ae7e13b4845be386000962eb7fd
108+
status:
109+
code: 200
110+
message: OK
111+
version: 1

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

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

3131

32+
@pytest.mark.vcr
33+
def test_responses_with_request_params(
34+
instrument_legacy, span_exporter: InMemorySpanExporter, openai_client: OpenAI
35+
):
36+
"""Test that request parameters like temperature, max_tokens, top_p are captured"""
37+
_ = openai_client.responses.create(
38+
model="gpt-4.1-nano",
39+
input="What is the capital of France?",
40+
temperature=0.7,
41+
max_output_tokens=100,
42+
top_p=0.9,
43+
)
44+
spans = span_exporter.get_finished_spans()
45+
assert len(spans) == 1
46+
span = spans[0]
47+
assert span.name == "openai.response"
48+
assert span.attributes["gen_ai.system"] == "openai"
49+
assert span.attributes["gen_ai.request.model"] == "gpt-4.1-nano"
50+
51+
# Check that request parameters are captured
52+
assert span.attributes["gen_ai.request.temperature"] == 0.7
53+
assert span.attributes["gen_ai.request.max_tokens"] == 100
54+
assert span.attributes["gen_ai.request.top_p"] == 0.9
55+
56+
3257
@pytest.mark.vcr
3358
def test_responses_with_service_tier(
3459
instrument_legacy, span_exporter: InMemorySpanExporter, openai_client: OpenAI

0 commit comments

Comments
 (0)