Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@
from typing_extensions import NotRequired

from opentelemetry.instrumentation.openai.shared import (
_extract_model_name_from_provider_format,
_set_request_attributes,
_set_span_attribute,
model_as_dict,
)
Expand Down Expand Up @@ -189,12 +191,26 @@ def process_content_block(


@dont_throw
def prepare_kwargs_for_shared_attributes(kwargs):
"""
Prepare kwargs for the shared _set_request_attributes function.
Maps responses API specific parameters to the common format.
"""
prepared_kwargs = kwargs.copy()

# Map max_output_tokens to max_tokens for the shared function
if "max_output_tokens" in kwargs:
prepared_kwargs["max_tokens"] = kwargs["max_output_tokens"]

return prepared_kwargs


def set_data_attributes(traced_response: TracedData, span: Span):
_set_span_attribute(span, GenAIAttributes.GEN_AI_SYSTEM, "openai")
_set_span_attribute(span, GenAIAttributes.GEN_AI_REQUEST_MODEL, traced_response.request_model)
_set_span_attribute(span, GenAIAttributes.GEN_AI_RESPONSE_ID, traced_response.response_id)
_set_span_attribute(span, GenAIAttributes.GEN_AI_RESPONSE_MODEL, traced_response.response_model)
_set_span_attribute(span, OpenAIAttributes.OPENAI_REQUEST_SERVICE_TIER, traced_response.request_service_tier)

response_model = _extract_model_name_from_provider_format(traced_response.response_model)
_set_span_attribute(span, GenAIAttributes.GEN_AI_RESPONSE_MODEL, response_model)

_set_span_attribute(span, OpenAIAttributes.OPENAI_RESPONSE_SERVICE_TIER, traced_response.response_service_tier)
if usage := traced_response.usage:
_set_span_attribute(span, GenAIAttributes.GEN_AI_USAGE_INPUT_TOKENS, usage.input_tokens)
Expand Down Expand Up @@ -445,6 +461,7 @@ def responses_get_or_create_wrapper(tracer: Tracer, wrapped, instance, args, kwa
kind=SpanKind.CLIENT,
start_time=start_time,
)
_set_request_attributes(span, prepare_kwargs_for_shared_attributes(kwargs), instance)

return ResponseStream(
span=span,
Expand Down Expand Up @@ -503,6 +520,7 @@ def responses_get_or_create_wrapper(tracer: Tracer, wrapped, instance, args, kwa
start_time if traced_data is None else int(traced_data.start_time)
),
)
_set_request_attributes(span, prepare_kwargs_for_shared_attributes(kwargs), instance)
span.set_attribute(ERROR_TYPE, e.__class__.__name__)
span.record_exception(e)
span.set_status(StatusCode.ERROR, str(e))
Expand Down Expand Up @@ -568,6 +586,7 @@ def responses_get_or_create_wrapper(tracer: Tracer, wrapped, instance, args, kwa
kind=SpanKind.CLIENT,
start_time=int(traced_data.start_time),
)
_set_request_attributes(span, prepare_kwargs_for_shared_attributes(kwargs), instance)
set_data_attributes(traced_data, span)
span.end()

Expand All @@ -591,6 +610,7 @@ async def async_responses_get_or_create_wrapper(
kind=SpanKind.CLIENT,
start_time=start_time,
)
_set_request_attributes(span, prepare_kwargs_for_shared_attributes(kwargs), instance)

return ResponseStream(
span=span,
Expand Down Expand Up @@ -645,6 +665,7 @@ async def async_responses_get_or_create_wrapper(
start_time if traced_data is None else int(traced_data.start_time)
),
)
_set_request_attributes(span, prepare_kwargs_for_shared_attributes(kwargs), instance)
span.set_attribute(ERROR_TYPE, e.__class__.__name__)
span.record_exception(e)
span.set_status(StatusCode.ERROR, str(e))
Expand Down Expand Up @@ -711,6 +732,7 @@ async def async_responses_get_or_create_wrapper(
kind=SpanKind.CLIENT,
start_time=int(traced_data.start_time),
)
_set_request_attributes(span, prepare_kwargs_for_shared_attributes(kwargs), instance)
set_data_attributes(traced_data, span)
span.end()

Expand All @@ -735,6 +757,7 @@ def responses_cancel_wrapper(tracer: Tracer, wrapped, instance, args, kwargs):
start_time=existing_data.start_time,
record_exception=True,
)
_set_request_attributes(span, prepare_kwargs_for_shared_attributes(kwargs), instance)
span.record_exception(Exception("Response cancelled"))
set_data_attributes(existing_data, span)
span.end()
Expand All @@ -761,6 +784,7 @@ async def async_responses_cancel_wrapper(
start_time=existing_data.start_time,
record_exception=True,
)
_set_request_attributes(span, prepare_kwargs_for_shared_attributes(kwargs), instance)
span.record_exception(Exception("Response cancelled"))
set_data_attributes(existing_data, span)
span.end()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
interactions:
- request:
body: '{"input": "What is the capital of France?", "max_output_tokens": 100, "model":
"gpt-4.1-nano", "temperature": 0.7, "top_p": 0.9}'
headers:
accept:
- application/json
accept-encoding:
- gzip, deflate
connection:
- keep-alive
content-length:
- '128'
content-type:
- application/json
host:
- api.openai.com
user-agent:
- OpenAI/Python 1.99.7
x-stainless-arch:
- arm64
x-stainless-async:
- 'false'
x-stainless-lang:
- python
x-stainless-os:
- MacOS
x-stainless-package-version:
- 1.99.7
x-stainless-read-timeout:
- '600'
x-stainless-retry-count:
- '0'
x-stainless-runtime:
- CPython
x-stainless-runtime-version:
- 3.10.16
method: POST
uri: https://api.openai.com/v1/responses
response:
body:
string: !!binary |
H4sIAAAAAAAAA3RUwW7bMAy95ysEnZtCdp3YyQfsvMNuxWDQNp1qlUVBooIGRf59sBw78dbeLD7y
mXyP0udGCKk7eRTSY3C12pVlh6qBBiqV56jU/pDvurLEw65VVXbYVf2havt90eUNFi/7Tj6NFNT8
wZZnGrIBp3jrERi7GkYsK/eFqsp9WSUsMHAMY01LgzPIeCNroH0/eYp27KsHE3AKa2O0Pcmj+NwI
IYR0cEE/1nd4RkMOvdwIcU3J6D2NmI3GpIC281/qDhm0CWs0sI8ta7Kr+AAfNUV2kWumd0xgptSC
MZGpWzBrtoE6NGNjJ8fb4jnbWrC0zVW+26pimxU3zRKvPIrXNM401GLHEE7fulGpbJ9VyQ1V7opc
ZX3evFSwqxJzYuGLw8SDIcAJ78B3siewJcto7009NrainUXBD16qUwJYSwyzkK+/V6Chk/PUfIEk
oqOQv95QtOA0gxHUix8ebItCB/ETvA7Pcqm53r4WGunJpNYgBB0YLE/JY2JKkg48GINm7Rr7OO2X
83jWFEM9r3CdnFhcdZ4Gx3UL7RvW73j5FvM4aqjJPmZ4hEB2tb/Y9+T5IWl0Jw4D+Jl7WecAPfKl
1t1I3GtcrXZAf9Yt1qzn69BDNJMvMjB5fByTcXDogWMKq+fyFk363zrryQ9wPz/4nvImXW8dn9E3
FDRfpm3rdBzu13BS+o10O1kTmeQC3NdAMrn6YTnUEnSpx8N09tG2cBNWdjpAY+Y3I6YlXwbQdn1l
i6f/4w/vwDJmMrC7F6rVqP++BNVX8a9oF/O/Y2ZiMHcwzxcFY1ibPSBDBwwj/XVz/QsAAP//AwDZ
CKVDwQUAAA==
headers:
CF-RAY:
- 9a427a739fc97d95-TLV
Connection:
- keep-alive
Content-Encoding:
- gzip
Content-Type:
- application/json
Date:
- Tue, 25 Nov 2025 16:21:20 GMT
Server:
- cloudflare
Set-Cookie:
- __cf_bm=972AABf9T_oEadbDKJ76kKF9Af2tTgOaTdaX4cxL8kg-1764087680-1.0.1.1-3xsObn4FVX3Nmp7VNdniyOmOOdjB3JnvdcHUEpHKZqNX2Q6j1k9MbrU0lGYlUVLrVb2Ls0gopzNAe6FA5AiAIvLVTtFzLT2780nDD_KBqxM;
path=/; expires=Tue, 25-Nov-25 16:51:20 GMT; domain=.api.openai.com; HttpOnly;
Secure; SameSite=None
- _cfuvid=w6zJyOCR9tAsFojGun.7hYM9_l7GfWdALH5Vd04TmeQ-1764087680812-0.0.1.1-604800000;
path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None
Strict-Transport-Security:
- max-age=31536000; includeSubDomains; preload
Transfer-Encoding:
- chunked
X-Content-Type-Options:
- nosniff
alt-svc:
- h3=":443"; ma=86400
cf-cache-status:
- DYNAMIC
openai-organization:
- traceloop
openai-processing-ms:
- '2149'
openai-project:
- proj_tzz1TbPPOXaf6j9tEkVUBIAa
openai-version:
- '2020-10-01'
x-envoy-upstream-service-time:
- '2151'
x-ratelimit-limit-requests:
- '30000'
x-ratelimit-limit-tokens:
- '150000000'
x-ratelimit-remaining-requests:
- '29999'
x-ratelimit-remaining-tokens:
- '149999967'
x-ratelimit-reset-requests:
- 2ms
x-ratelimit-reset-tokens:
- 0s
x-request-id:
- req_500c9ae7e13b4845be386000962eb7fd
status:
code: 200
message: OK
version: 1
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,31 @@ def test_responses(
# assert span.attributes["gen_ai.prompt.0.role"] == "user"


@pytest.mark.vcr
def test_responses_with_request_params(
instrument_legacy, span_exporter: InMemorySpanExporter, openai_client: OpenAI
):
"""Test that request parameters like temperature, max_tokens, top_p are captured"""
_ = openai_client.responses.create(
model="gpt-4.1-nano",
input="What is the capital of France?",
temperature=0.7,
max_output_tokens=100,
top_p=0.9,
)
spans = span_exporter.get_finished_spans()
assert len(spans) == 1
span = spans[0]
assert span.name == "openai.response"
assert span.attributes["gen_ai.system"] == "openai"
assert span.attributes["gen_ai.request.model"] == "gpt-4.1-nano"

# Check that request parameters are captured
assert span.attributes["gen_ai.request.temperature"] == 0.7
assert span.attributes["gen_ai.request.max_tokens"] == 100
assert span.attributes["gen_ai.request.top_p"] == 0.9


@pytest.mark.vcr
def test_responses_with_service_tier(
instrument_legacy, span_exporter: InMemorySpanExporter, openai_client: OpenAI
Expand Down