Skip to content
Open
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 @@ -28,6 +28,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Add strongly typed Responses API extractors with validation and content
extraction improvements
([#4337](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/4337))
- Add instrumentation for OpenAI Responses API `create`
([#4474](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/4474))

## Version 2.3b0 (2025-12-24)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
---
"""

from importlib import import_module
from typing import Collection

from wrapt import wrap_function_wrapper
Expand Down Expand Up @@ -70,6 +71,9 @@
chat_completions_create_v_old,
embeddings_create,
)
from .patch_responses import (
responses_create,
)


class OpenAIInstrumentor(BaseInstrumentor):
Expand Down Expand Up @@ -159,10 +163,33 @@ def _instrument(self, **kwargs):
),
)

responses_module = _get_responses_module()
# Responses instrumentation is intentionally limited to the latest
# experimental semconv path. Unlike chat completions, we do not carry
# a second legacy wrapper here; the current implementation is built on
# the inference handler lifecycle and would need a separate old-path
# implementation to support legacy semconv mode.
if responses_module is not None and latest_experimental_enabled:
wrap_function_wrapper(
"openai.resources.responses.responses",
"Responses.create",
responses_create(handler, content_mode),
)

def _uninstrument(self, **kwargs):
import openai # pylint: disable=import-outside-toplevel # noqa: PLC0415

unwrap(openai.resources.chat.completions.Completions, "create")
unwrap(openai.resources.chat.completions.AsyncCompletions, "create")
unwrap(openai.resources.embeddings.Embeddings, "create")
unwrap(openai.resources.embeddings.AsyncEmbeddings, "create")
responses_module = _get_responses_module()
if responses_module is not None:
unwrap(responses_module.Responses, "create")


def _get_responses_module():
try:
return import_module("openai.resources.responses.responses")
except ImportError:
return None
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from __future__ import annotations

from opentelemetry.util.genai.handler import TelemetryHandler
from opentelemetry.util.genai.types import ContentCapturingMode, Error

from .response_extractors import (
_apply_request_attributes,
_get_inference_creation_kwargs,
_set_invocation_response_attributes,
)
from .response_wrappers import ResponseStreamWrapper
from .utils import is_streaming


def responses_create(
handler: TelemetryHandler,
content_capturing_mode: ContentCapturingMode,
):
"""Wrap the `create` method of the `Responses` class to trace it."""

capture_content = content_capturing_mode != ContentCapturingMode.NO_CONTENT

def traced_method(wrapped, instance, args, kwargs):
invocation = handler.start_inference(
**_get_inference_creation_kwargs(kwargs, instance)
)
_apply_request_attributes(invocation, kwargs, capture_content)

try:
result = wrapped(*args, **kwargs)
parsed_result = _get_response_stream_result(result)

if is_streaming(kwargs):
return ResponseStreamWrapper(
parsed_result,
invocation,
capture_content,
)

_set_invocation_response_attributes(
invocation, parsed_result, capture_content
)
invocation.stop()
return result
except Exception as error:
invocation.fail(Error(type=type(error), message=str(error)))
raise

return traced_method


def _get_response_stream_result(result):
if hasattr(result, "parse"):
return result.parse()
return result
Loading
Loading