From f6f934e6e84cba5704f57572ed76642a56847918 Mon Sep 17 00:00:00 2001 From: Liudmila Molkova Date: Sat, 15 Aug 2026 16:20:50 +0000 Subject: [PATCH 1/9] feat: support openai v3 Extend the supported version range for the openai library to include v3. openai v3 switched from httpx to httpx2 as its HTTP client library. Update the instrumentation to import URL and the HTTP response type from httpx with a fallback to httpx2, so both v1/v2 and v3 are supported. The httpx interop test is skipped when httpx is not installed (openai v3), since that test specifically exercises opentelemetry-instrumentation-httpx which only applies to the httpx transport used by openai v1/v2. Assisted-by: Claude Sonnet 4.6 --- .../.changelog/393.changed | 1 + .../pyproject.toml | 2 +- .../instrumentation/genai/openai/_raw_response.py | 7 +++++-- .../opentelemetry/instrumentation/genai/openai/utils.py | 6 +++++- .../tests/requirements.latest.txt | 5 ++--- .../tests/test_embedding_invocation_unit.py | 6 +++++- .../tests/test_raw_response_httpx_interop.py | 6 +++++- 7 files changed, 24 insertions(+), 9 deletions(-) create mode 100644 instrumentation/opentelemetry-instrumentation-genai-openai/.changelog/393.changed diff --git a/instrumentation/opentelemetry-instrumentation-genai-openai/.changelog/393.changed b/instrumentation/opentelemetry-instrumentation-genai-openai/.changelog/393.changed new file mode 100644 index 000000000..03ce326b0 --- /dev/null +++ b/instrumentation/opentelemetry-instrumentation-genai-openai/.changelog/393.changed @@ -0,0 +1 @@ +Extend supported version range for the openai library to include v3. diff --git a/instrumentation/opentelemetry-instrumentation-genai-openai/pyproject.toml b/instrumentation/opentelemetry-instrumentation-genai-openai/pyproject.toml index 117f596a6..239fd68b2 100644 --- a/instrumentation/opentelemetry-instrumentation-genai-openai/pyproject.toml +++ b/instrumentation/opentelemetry-instrumentation-genai-openai/pyproject.toml @@ -32,7 +32,7 @@ dependencies = [ ] [project.optional-dependencies] -instruments = ["openai >= 1.26.0"] +instruments = ["openai >= 1.26.0, < 4"] [project.entry-points.opentelemetry_instrumentor] openai = "opentelemetry.instrumentation.genai.openai:OpenAIInstrumentor" diff --git a/instrumentation/opentelemetry-instrumentation-genai-openai/src/opentelemetry/instrumentation/genai/openai/_raw_response.py b/instrumentation/opentelemetry-instrumentation-genai-openai/src/opentelemetry/instrumentation/genai/openai/_raw_response.py index 09c34f772..b1d39a549 100644 --- a/instrumentation/opentelemetry-instrumentation-genai-openai/src/opentelemetry/instrumentation/genai/openai/_raw_response.py +++ b/instrumentation/opentelemetry-instrumentation-genai-openai/src/opentelemetry/instrumentation/genai/openai/_raw_response.py @@ -16,7 +16,10 @@ runtime_checkable, ) -import httpx +try: + import httpx as _http_lib +except ImportError: + import httpx2 as _http_lib from openai import AsyncStream, Stream from wrapt import ObjectProxy @@ -51,7 +54,7 @@ class RawResponseLike(ParsableResponse, Protocol): finalize the span when the caller never calls ``parse()``. """ - http_response: httpx.Response + http_response: _http_lib.Response class RawResponseStreamProxy(ObjectProxy): diff --git a/instrumentation/opentelemetry-instrumentation-genai-openai/src/opentelemetry/instrumentation/genai/openai/utils.py b/instrumentation/opentelemetry-instrumentation-genai-openai/src/opentelemetry/instrumentation/genai/openai/utils.py index 14ceec1fb..0861131d2 100644 --- a/instrumentation/opentelemetry-instrumentation-genai-openai/src/opentelemetry/instrumentation/genai/openai/utils.py +++ b/instrumentation/opentelemetry-instrumentation-genai-openai/src/opentelemetry/instrumentation/genai/openai/utils.py @@ -9,9 +9,13 @@ from urllib.parse import urlparse import openai -from httpx import URL from openai import NotGiven +try: + from httpx import URL +except ImportError: + from httpx2 import URL + from opentelemetry.semconv._incubating.attributes import ( gen_ai_attributes as GenAIAttributes, ) diff --git a/instrumentation/opentelemetry-instrumentation-genai-openai/tests/requirements.latest.txt b/instrumentation/opentelemetry-instrumentation-genai-openai/tests/requirements.latest.txt index 19cde5079..55fd124b0 100644 --- a/instrumentation/opentelemetry-instrumentation-genai-openai/tests/requirements.latest.txt +++ b/instrumentation/opentelemetry-instrumentation-genai-openai/tests/requirements.latest.txt @@ -37,17 +37,16 @@ # This variant of the requirements aims to test the system using # the newest supported version of external dependencies. -openai==2.52.0 +openai==3.1.0 # older pydantic is required for PyPy < 3.11 pydantic==2.12.5 -httpx==0.28.1 +httpx2==2.7.0 # older jiter is required for PyPy < 3.11 jiter==0.11.1 Deprecated==1.3.1 importlib-metadata==9.0.0 packaging==26.2 wrapt==2.2.2 -opentelemetry-instrumentation-httpx==0.64b0 # test with the latest version of opentelemetry-api, sdk, semantic conventions, and instrumentation -e instrumentation/opentelemetry-instrumentation-genai-openai diff --git a/instrumentation/opentelemetry-instrumentation-genai-openai/tests/test_embedding_invocation_unit.py b/instrumentation/opentelemetry-instrumentation-genai-openai/tests/test_embedding_invocation_unit.py index aeb01cada..1ae5425a4 100644 --- a/instrumentation/opentelemetry-instrumentation-genai-openai/tests/test_embedding_invocation_unit.py +++ b/instrumentation/opentelemetry-instrumentation-genai-openai/tests/test_embedding_invocation_unit.py @@ -15,7 +15,11 @@ from typing import Any import pytest -from httpx import URL + +try: + from httpx import URL +except ImportError: + from httpx2 import URL from opentelemetry.instrumentation.genai.openai.patch import ( _create_embedding_invocation as create_embedding_invocation, diff --git a/instrumentation/opentelemetry-instrumentation-genai-openai/tests/test_raw_response_httpx_interop.py b/instrumentation/opentelemetry-instrumentation-genai-openai/tests/test_raw_response_httpx_interop.py index bfa0c61ba..aff9279dd 100644 --- a/instrumentation/opentelemetry-instrumentation-genai-openai/tests/test_raw_response_httpx_interop.py +++ b/instrumentation/opentelemetry-instrumentation-genai-openai/tests/test_raw_response_httpx_interop.py @@ -18,10 +18,14 @@ import json -import httpx import pytest from openai import AsyncOpenAI, OpenAI +# This module tests interop with opentelemetry-instrumentation-httpx, which +# only applies when openai uses httpx (v1/v2). Skip the entire module when +# httpx is not installed (e.g. openai v3+, which uses httpx2 instead). +httpx = pytest.importorskip("httpx") + # Only pinned in requirements.latest.txt; oldest envs skip this module. HTTPXClientInstrumentor = pytest.importorskip( "opentelemetry.instrumentation.httpx" From 186cf926812819463d6b27a3d1d7809912ca9cb2 Mon Sep 17 00:00:00 2001 From: Liudmila Molkova Date: Sat, 15 Aug 2026 16:35:08 +0000 Subject: [PATCH 2/9] refactor(openai): rely on existing importorskip for httpx in interop test Instead of a dedicated pytest.importorskip("httpx"), move import httpx after the existing pytest.importorskip("opentelemetry.instrumentation.httpx"). Since the httpx instrumentation depends on httpx, the existing importorskip already handles graceful skipping when httpx is absent (e.g. openai v3+). Assisted-by: Claude Sonnet 4.6 --- .../tests/test_raw_response_httpx_interop.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/instrumentation/opentelemetry-instrumentation-genai-openai/tests/test_raw_response_httpx_interop.py b/instrumentation/opentelemetry-instrumentation-genai-openai/tests/test_raw_response_httpx_interop.py index aff9279dd..52179f736 100644 --- a/instrumentation/opentelemetry-instrumentation-genai-openai/tests/test_raw_response_httpx_interop.py +++ b/instrumentation/opentelemetry-instrumentation-genai-openai/tests/test_raw_response_httpx_interop.py @@ -21,16 +21,14 @@ import pytest from openai import AsyncOpenAI, OpenAI -# This module tests interop with opentelemetry-instrumentation-httpx, which -# only applies when openai uses httpx (v1/v2). Skip the entire module when -# httpx is not installed (e.g. openai v3+, which uses httpx2 instead). -httpx = pytest.importorskip("httpx") - # Only pinned in requirements.latest.txt; oldest envs skip this module. +# When httpx is not installed (e.g. openai v3+), this import will also skip. HTTPXClientInstrumentor = pytest.importorskip( "opentelemetry.instrumentation.httpx" ).HTTPXClientInstrumentor +import httpx # noqa: E402 + _HTTPX_SPAN = "POST" _GENAI_SPAN = "chat gpt-4" From c02814f1bd46ff17c1b36d5309375b239e51d46a Mon Sep 17 00:00:00 2001 From: Liudmila Molkova Date: Sat, 15 Aug 2026 16:47:45 +0000 Subject: [PATCH 3/9] fix(openai): use structural URL access to fix server.address with openai v3 Use getattr-based host/port extraction instead of isinstance(URL) so server.address and server.port are recorded with any URL implementation (httpx.URL, httpx2.URL, or future types). This also removes the now-unused httpx/httpx2 URL import from utils.py. Adds a regression test verifying server.address is populated for a non-httpx URL (simulating httpx2.URL via SimpleNamespace). Fixes: server.address/server.port silently absent on openai v3+. Assisted-by: Claude Sonnet 4.6 --- .../.changelog/393.fixed | 1 + .../instrumentation/genai/openai/utils.py | 17 +++++------------ .../tests/test_embedding_invocation_unit.py | 12 ++++++++++++ 3 files changed, 18 insertions(+), 12 deletions(-) create mode 100644 instrumentation/opentelemetry-instrumentation-genai-openai/.changelog/393.fixed diff --git a/instrumentation/opentelemetry-instrumentation-genai-openai/.changelog/393.fixed b/instrumentation/opentelemetry-instrumentation-genai-openai/.changelog/393.fixed new file mode 100644 index 000000000..293c010b9 --- /dev/null +++ b/instrumentation/opentelemetry-instrumentation-genai-openai/.changelog/393.fixed @@ -0,0 +1 @@ +Fix ``server.address`` and ``server.port`` not recorded when using openai v3 (httpx2 URL type). diff --git a/instrumentation/opentelemetry-instrumentation-genai-openai/src/opentelemetry/instrumentation/genai/openai/utils.py b/instrumentation/opentelemetry-instrumentation-genai-openai/src/opentelemetry/instrumentation/genai/openai/utils.py index 0861131d2..d9c3821fc 100644 --- a/instrumentation/opentelemetry-instrumentation-genai-openai/src/opentelemetry/instrumentation/genai/openai/utils.py +++ b/instrumentation/opentelemetry-instrumentation-genai-openai/src/opentelemetry/instrumentation/genai/openai/utils.py @@ -11,11 +11,6 @@ import openai from openai import NotGiven -try: - from httpx import URL -except ImportError: - from httpx2 import URL - from opentelemetry.semconv._incubating.attributes import ( gen_ai_attributes as GenAIAttributes, ) @@ -72,13 +67,11 @@ def get_server_address_and_port( base_url = getattr(base_client, "base_url", None) if not base_url: return None, None - address = None - port = None - if isinstance(base_url, URL): - address = base_url.host - port = base_url.port - elif isinstance(base_url, str): - url = urlparse(base_url) + + address = getattr(base_url, "host", None) + port = getattr(base_url, "port", None) + if not address: + url = urlparse(str(base_url)) address = url.hostname port = url.port diff --git a/instrumentation/opentelemetry-instrumentation-genai-openai/tests/test_embedding_invocation_unit.py b/instrumentation/opentelemetry-instrumentation-genai-openai/tests/test_embedding_invocation_unit.py index 1ae5425a4..2dd98a344 100644 --- a/instrumentation/opentelemetry-instrumentation-genai-openai/tests/test_embedding_invocation_unit.py +++ b/instrumentation/opentelemetry-instrumentation-genai-openai/tests/test_embedding_invocation_unit.py @@ -102,6 +102,18 @@ def test_server_address_and_port_from_client(handler, span_exporter): assert span.attributes[ServerAttributes.SERVER_PORT] == 8080 +def test_server_address_and_port_from_non_httpx_url(handler, span_exporter): + """Works with any URL object exposing .host/.port (e.g. httpx2.URL).""" + fake_url = SimpleNamespace(host="openrouter.ai", port=None) + client = _make_client(fake_url) + invocation = create_embedding_invocation(handler, {"model": "m"}, client) + invocation.stop() + + span = span_exporter.get_finished_spans()[0] + assert span.attributes[ServerAttributes.SERVER_ADDRESS] == "openrouter.ai" + assert ServerAttributes.SERVER_PORT not in span.attributes + + # ─── create_embedding_invocation: dimensions / encoding_format ────────────── From 01bf8fc0cda322b7d6f0c0270ce6542eb9c6b253 Mon Sep 17 00:00:00 2001 From: Liudmila Molkova Date: Sat, 15 Aug 2026 16:56:20 +0000 Subject: [PATCH 4/9] fix(openai): add comment explaining structural URL access Assisted-by: Claude Sonnet 4.6 --- .../src/opentelemetry/instrumentation/genai/openai/utils.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/instrumentation/opentelemetry-instrumentation-genai-openai/src/opentelemetry/instrumentation/genai/openai/utils.py b/instrumentation/opentelemetry-instrumentation-genai-openai/src/opentelemetry/instrumentation/genai/openai/utils.py index d9c3821fc..76b3ae603 100644 --- a/instrumentation/opentelemetry-instrumentation-genai-openai/src/opentelemetry/instrumentation/genai/openai/utils.py +++ b/instrumentation/opentelemetry-instrumentation-genai-openai/src/opentelemetry/instrumentation/genai/openai/utils.py @@ -68,6 +68,8 @@ def get_server_address_and_port( if not base_url: return None, None + # Use getattr rather than isinstance(base_url, httpx.URL): openai v1/v2 + # uses httpx.URL while v3 uses httpx2.URL; both expose .host and .port. address = getattr(base_url, "host", None) port = getattr(base_url, "port", None) if not address: From 9030b62e33f0659146a3aa48d3ef062054e72a10 Mon Sep 17 00:00:00 2001 From: Liudmila Molkova Date: Sat, 15 Aug 2026 17:01:32 +0000 Subject: [PATCH 5/9] fix(openai): remove unused noqa directive and update uv.lock --- .../tests/test_raw_response_httpx_interop.py | 2 +- uv.lock | 34 +++++++++---------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/instrumentation/opentelemetry-instrumentation-genai-openai/tests/test_raw_response_httpx_interop.py b/instrumentation/opentelemetry-instrumentation-genai-openai/tests/test_raw_response_httpx_interop.py index 52179f736..a3951b8e0 100644 --- a/instrumentation/opentelemetry-instrumentation-genai-openai/tests/test_raw_response_httpx_interop.py +++ b/instrumentation/opentelemetry-instrumentation-genai-openai/tests/test_raw_response_httpx_interop.py @@ -27,7 +27,7 @@ "opentelemetry.instrumentation.httpx" ).HTTPXClientInstrumentor -import httpx # noqa: E402 +import httpx _HTTPX_SPAN = "POST" _GENAI_SPAN = "chat gpt-4" diff --git a/uv.lock b/uv.lock index 95d954521..b21248929 100644 --- a/uv.lock +++ b/uv.lock @@ -5,12 +5,12 @@ resolution-markers = [ "python_full_version >= '3.14' and sys_platform == 'win32'", "python_full_version >= '3.14' and sys_platform != 'win32'", "python_full_version == '3.13.*' and sys_platform == 'win32'", - "python_full_version == '3.12.*' and sys_platform == 'win32'", - "python_full_version == '3.11.*' and sys_platform == 'win32'", - "python_full_version < '3.11' and sys_platform == 'win32'", "python_full_version == '3.13.*' and sys_platform != 'win32'", + "python_full_version == '3.12.*' and sys_platform == 'win32'", "python_full_version == '3.12.*' and sys_platform != 'win32'", + "python_full_version == '3.11.*' and sys_platform == 'win32'", "python_full_version == '3.11.*' and sys_platform != 'win32'", + "python_full_version < '3.11' and sys_platform == 'win32'", "python_full_version < '3.11' and sys_platform != 'win32'", ] @@ -2820,10 +2820,10 @@ resolution-markers = [ "python_full_version >= '3.14' and sys_platform == 'win32'", "python_full_version >= '3.14' and sys_platform != 'win32'", "python_full_version == '3.13.*' and sys_platform == 'win32'", - "python_full_version == '3.12.*' and sys_platform == 'win32'", - "python_full_version == '3.11.*' and sys_platform == 'win32'", "python_full_version == '3.13.*' and sys_platform != 'win32'", + "python_full_version == '3.12.*' and sys_platform == 'win32'", "python_full_version == '3.12.*' and sys_platform != 'win32'", + "python_full_version == '3.11.*' and sys_platform == 'win32'", "python_full_version == '3.11.*' and sys_platform != 'win32'", ] sdist = { url = "https://files.pythonhosted.org/packages/6a/51/63fe664f3908c97be9d2e4f1158eb633317598cfa6e1fc14af5383f17512/networkx-3.6.1.tar.gz", hash = "sha256:26b7c357accc0c8cde558ad486283728b65b6a95d85ee1cd66bafab4c8168509", size = 2517025, upload-time = "2025-12-08T17:02:39.908Z" } @@ -3013,8 +3013,8 @@ resolution-markers = [ "python_full_version >= '3.14' and sys_platform == 'win32'", "python_full_version >= '3.14' and sys_platform != 'win32'", "python_full_version == '3.13.*' and sys_platform == 'win32'", - "python_full_version == '3.12.*' and sys_platform == 'win32'", "python_full_version == '3.13.*' and sys_platform != 'win32'", + "python_full_version == '3.12.*' and sys_platform == 'win32'", "python_full_version == '3.12.*' and sys_platform != 'win32'", ] sdist = { url = "https://files.pythonhosted.org/packages/22/fd/89965aa4ac08c74998539fcbf24fa3540f3e15237fbeb6bcf9c908f4aade/numpy-2.5.1.tar.gz", hash = "sha256:a48a113e6afea91f5608793bafa7ef2ad481fefbda87ec5069f483de61cb9fa3", size = 20755553, upload-time = "2026-07-04T17:08:00.933Z" } @@ -3123,10 +3123,10 @@ resolution-markers = [ "python_full_version >= '3.14' and sys_platform == 'win32'", "python_full_version >= '3.14' and sys_platform != 'win32'", "python_full_version == '3.13.*' and sys_platform == 'win32'", - "python_full_version == '3.12.*' and sys_platform == 'win32'", - "python_full_version == '3.11.*' and sys_platform == 'win32'", "python_full_version == '3.13.*' and sys_platform != 'win32'", + "python_full_version == '3.12.*' and sys_platform == 'win32'", "python_full_version == '3.12.*' and sys_platform != 'win32'", + "python_full_version == '3.11.*' and sys_platform == 'win32'", "python_full_version == '3.11.*' and sys_platform != 'win32'", ] dependencies = [ @@ -3450,7 +3450,7 @@ instruments = [ [package.metadata] requires-dist = [ - { name = "openai", marker = "extra == 'instruments'", specifier = ">=1.26.0" }, + { name = "openai", marker = "extra == 'instruments'", specifier = ">=1.26.0,<4" }, { name = "opentelemetry-api", specifier = "~=1.43" }, { name = "opentelemetry-instrumentation", specifier = ">=0.64b0,<1" }, { name = "opentelemetry-semantic-conventions", specifier = ">=0.64b0,<1" }, @@ -4802,10 +4802,10 @@ resolution-markers = [ "python_full_version >= '3.14' and sys_platform == 'win32'", "python_full_version >= '3.14' and sys_platform != 'win32'", "python_full_version == '3.13.*' and sys_platform == 'win32'", - "python_full_version == '3.12.*' and sys_platform == 'win32'", - "python_full_version == '3.11.*' and sys_platform == 'win32'", "python_full_version == '3.13.*' and sys_platform != 'win32'", + "python_full_version == '3.12.*' and sys_platform == 'win32'", "python_full_version == '3.12.*' and sys_platform != 'win32'", + "python_full_version == '3.11.*' and sys_platform == 'win32'", "python_full_version == '3.11.*' and sys_platform != 'win32'", ] dependencies = [ @@ -5320,10 +5320,10 @@ resolution-markers = [ "python_full_version >= '3.14' and sys_platform == 'win32'", "python_full_version >= '3.14' and sys_platform != 'win32'", "python_full_version == '3.13.*' and sys_platform == 'win32'", - "python_full_version == '3.12.*' and sys_platform == 'win32'", - "python_full_version == '3.11.*' and sys_platform == 'win32'", "python_full_version == '3.13.*' and sys_platform != 'win32'", + "python_full_version == '3.12.*' and sys_platform == 'win32'", "python_full_version == '3.12.*' and sys_platform != 'win32'", + "python_full_version == '3.11.*' and sys_platform == 'win32'", "python_full_version == '3.11.*' and sys_platform != 'win32'", ] sdist = { url = "https://files.pythonhosted.org/packages/aa/2a/9618a122aeb2a169a28b03889a2995fe297588964333d4a7d67bdf46e147/rpds_py-2026.6.3.tar.gz", hash = "sha256:1cebd1337c242e4ec2293e541f712b2da849b29f48f0c293684b71c0632625d4", size = 64051, upload-time = "2026-06-30T07:17:53.009Z" } @@ -5785,10 +5785,10 @@ resolution-markers = [ "python_full_version >= '3.14' and sys_platform == 'win32'", "python_full_version >= '3.14' and sys_platform != 'win32'", "python_full_version == '3.13.*' and sys_platform == 'win32'", - "python_full_version == '3.12.*' and sys_platform == 'win32'", - "python_full_version == '3.11.*' and sys_platform == 'win32'", "python_full_version == '3.13.*' and sys_platform != 'win32'", + "python_full_version == '3.12.*' and sys_platform == 'win32'", "python_full_version == '3.12.*' and sys_platform != 'win32'", + "python_full_version == '3.11.*' and sys_platform == 'win32'", "python_full_version == '3.11.*' and sys_platform != 'win32'", ] dependencies = [ @@ -5833,10 +5833,10 @@ resolution-markers = [ "python_full_version >= '3.14' and sys_platform == 'win32'", "python_full_version >= '3.14' and sys_platform != 'win32'", "python_full_version == '3.13.*' and sys_platform == 'win32'", - "python_full_version == '3.12.*' and sys_platform == 'win32'", - "python_full_version == '3.11.*' and sys_platform == 'win32'", "python_full_version == '3.13.*' and sys_platform != 'win32'", + "python_full_version == '3.12.*' and sys_platform == 'win32'", "python_full_version == '3.12.*' and sys_platform != 'win32'", + "python_full_version == '3.11.*' and sys_platform == 'win32'", "python_full_version == '3.11.*' and sys_platform != 'win32'", ] dependencies = [ From 32ce82ebb7babab5b54a5276ddc22681bec9306e Mon Sep 17 00:00:00 2001 From: Liudmila Molkova Date: Sat, 15 Aug 2026 17:04:37 +0000 Subject: [PATCH 6/9] feat(openai): add latest2 tox factor for openai v2 testing Adds a requirements.latest2.txt pinned to openai 2.54.0 + httpx 0.28.1 and a corresponding tox factor so the httpx interop tests continue to run under CI alongside the openai v3 (httpx2) latest factor. --- .../tests/requirements.latest2.txt | 55 +++++++++++++++++++ tox.ini | 6 +- 2 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 instrumentation/opentelemetry-instrumentation-genai-openai/tests/requirements.latest2.txt diff --git a/instrumentation/opentelemetry-instrumentation-genai-openai/tests/requirements.latest2.txt b/instrumentation/opentelemetry-instrumentation-genai-openai/tests/requirements.latest2.txt new file mode 100644 index 000000000..310c7cd92 --- /dev/null +++ b/instrumentation/opentelemetry-instrumentation-genai-openai/tests/requirements.latest2.txt @@ -0,0 +1,55 @@ +# 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. + + +# ******************************** +# WARNING: NOT HERMETIC !!!!!!!!!! +# ******************************** +# +# This "requirements.txt" is installed in conjunction +# with multiple other dependencies in the top-level "tox.ini" +# file. In particular, please see: +# +# openai-latest2: {[testenv]test_deps} +# openai-latest2: -r {toxinidir}/instrumentation/opentelemetry-instrumentation-genai-openai/tests/requirements.latest2.txt +# +# This provides additional dependencies, namely: +# +# opentelemetry-api +# opentelemetry-sdk +# opentelemetry-semantic-conventions +# opentelemetry-instrumentation +# +# ... with a "dev" version based on the latest distribution. + + +# This variant of the requirements aims to test the system using +# the latest openai v2.x release (httpx-based, as opposed to the httpx2-based v3.x). +# It also includes opentelemetry-instrumentation-httpx to exercise the httpx interop tests. + +openai==2.54.0 +# older pydantic is required for PyPy < 3.11 +pydantic==2.12.5 +httpx==0.28.1 +opentelemetry-instrumentation-httpx +# older jiter is required for PyPy < 3.11 +jiter==0.11.1 +Deprecated==1.2.14 +importlib-metadata==9.0.0 +packaging==26.2 +wrapt==2.2.2 +# test with the latest version of opentelemetry-api, sdk, semantic conventions, and instrumentation + +-e instrumentation/opentelemetry-instrumentation-genai-openai +-e util/opentelemetry-util-genai diff --git a/tox.ini b/tox.ini index 779dcddd8..9885d4485 100644 --- a/tox.ini +++ b/tox.ini @@ -12,6 +12,7 @@ envlist = ; oldest deps run only on the minimum supported Python: the lowest declared dependency versions ; predate newer interpreters and ship no wheels for them. latest covers the full interpreter range. py3{10,11,12,13,14}-test-instrumentation-genai-openai-latest + py3{10,11,12,13,14}-test-instrumentation-genai-openai-latest2 py310-test-instrumentation-genai-openai-oldest pypy3-test-instrumentation-genai-openai-latest py314-test-instrumentation-genai-openai-conformance @@ -123,6 +124,9 @@ deps = openai-latest: {[testenv]test_deps} openai-latest: {[testenv]pytest_deps} openai-latest: -r {toxinidir}/instrumentation/opentelemetry-instrumentation-genai-openai/tests/requirements.latest.txt + openai-latest2: {[testenv]test_deps} + openai-latest2: {[testenv]pytest_deps} + openai-latest2: -r {toxinidir}/instrumentation/opentelemetry-instrumentation-genai-openai/tests/requirements.latest2.txt openai-conformance: {[testenv]pytest_deps} openai-conformance: -r {toxinidir}/instrumentation/opentelemetry-instrumentation-genai-openai/tests/requirements.latest.txt @@ -245,7 +249,7 @@ commands_pre = coverage: python {toxinidir}/scripts/eachdist.py install --editable commands = - test-instrumentation-genai-openai-{oldest,latest}: pytest --ignore={toxinidir}/instrumentation/opentelemetry-instrumentation-genai-openai/tests/test_conformance.py {toxinidir}/instrumentation/opentelemetry-instrumentation-genai-openai/tests {posargs} + test-instrumentation-genai-openai-{oldest,latest,latest2}: pytest --ignore={toxinidir}/instrumentation/opentelemetry-instrumentation-genai-openai/tests/test_conformance.py {toxinidir}/instrumentation/opentelemetry-instrumentation-genai-openai/tests {posargs} test-instrumentation-genai-openai-conformance: pytest {toxinidir}/instrumentation/opentelemetry-instrumentation-genai-openai/tests/test_conformance.py --vcr-record=none {posargs} lint-instrumentation-genai-openai: sh -c "cd instrumentation && ruff check opentelemetry-instrumentation-genai-openai" test-instrumentation-genai-openai_agents-{oldest,latest}: pytest --ignore={toxinidir}/instrumentation/opentelemetry-instrumentation-genai-openai-agents/tests/test_conformance.py {toxinidir}/instrumentation/opentelemetry-instrumentation-genai-openai-agents/tests {posargs} From cd281f262dd5811719c6a43cc2dbe766160a3373 Mon Sep 17 00:00:00 2001 From: Liudmila Molkova Date: Sun, 16 Aug 2026 12:09:54 -0700 Subject: [PATCH 7/9] fix CI --- .github/renovate.json5 | 17 +++++++++++++++-- ...s.latest2.txt => requirements.openai-v2.txt} | 8 ++++---- .../tests/test_raw_response_httpx_interop.py | 7 +++---- tox.ini | 10 +++++----- 4 files changed, 27 insertions(+), 15 deletions(-) rename instrumentation/opentelemetry-instrumentation-genai-openai/tests/{requirements.latest2.txt => requirements.openai-v2.txt} (88%) diff --git a/.github/renovate.json5 b/.github/renovate.json5 index ff58313e7..5d0fce2c3 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -10,8 +10,13 @@ "pip_requirements": { // Default pip_requirements fileMatch only allows [\w-]* between // "requirements" and ".txt", so per-instrumentation - // requirements.latest.txt files are skipped without this. - "managerFilePatterns": ["/(^|/)requirements\\.txt$/", "/(^|/)requirements\\.latest\\.txt$/"] + // requirements.latest.txt and requirements.-v.txt files are + // skipped without this. + "managerFilePatterns": [ + "/(^|/)requirements\\.txt$/", + "/(^|/)requirements\\.latest\\.txt$/", + "/(^|/)requirements\\.[\\w-]+-v\\d+\\.txt$/" + ] }, // Manage WEAVER_VERSION and SEMCONV_GENAI_REF in versions.env via their // `# renovate:` annotations. @@ -65,6 +70,14 @@ "matchPackageNames": ["pydantic"], "allowedVersions": "<2.13" }, + { + // requirements.-v.txt tests the newest release of an older + // major of , so itself must stay on that major while the + // rest of the file keeps updating. Add one rule per such file. + "matchFileNames": ["**/requirements.openai-v2.txt"], + "matchPackageNames": ["openai"], + "allowedVersions": "<3" + }, { "matchUpdateTypes": ["minor", "major"], "schedule": ["before 8am on Monday"] diff --git a/instrumentation/opentelemetry-instrumentation-genai-openai/tests/requirements.latest2.txt b/instrumentation/opentelemetry-instrumentation-genai-openai/tests/requirements.openai-v2.txt similarity index 88% rename from instrumentation/opentelemetry-instrumentation-genai-openai/tests/requirements.latest2.txt rename to instrumentation/opentelemetry-instrumentation-genai-openai/tests/requirements.openai-v2.txt index 310c7cd92..8f522bcba 100644 --- a/instrumentation/opentelemetry-instrumentation-genai-openai/tests/requirements.latest2.txt +++ b/instrumentation/opentelemetry-instrumentation-genai-openai/tests/requirements.openai-v2.txt @@ -21,8 +21,8 @@ # with multiple other dependencies in the top-level "tox.ini" # file. In particular, please see: # -# openai-latest2: {[testenv]test_deps} -# openai-latest2: -r {toxinidir}/instrumentation/opentelemetry-instrumentation-genai-openai/tests/requirements.latest2.txt +# openai-v2: {[testenv]test_deps} +# openai-v2: -r {toxinidir}/instrumentation/opentelemetry-instrumentation-genai-openai/tests/requirements.openai-v2.txt # # This provides additional dependencies, namely: # @@ -42,10 +42,10 @@ openai==2.54.0 # older pydantic is required for PyPy < 3.11 pydantic==2.12.5 httpx==0.28.1 -opentelemetry-instrumentation-httpx +opentelemetry-instrumentation-httpx==0.64b0 # older jiter is required for PyPy < 3.11 jiter==0.11.1 -Deprecated==1.2.14 +Deprecated==1.3.1 importlib-metadata==9.0.0 packaging==26.2 wrapt==2.2.2 diff --git a/instrumentation/opentelemetry-instrumentation-genai-openai/tests/test_raw_response_httpx_interop.py b/instrumentation/opentelemetry-instrumentation-genai-openai/tests/test_raw_response_httpx_interop.py index a3951b8e0..b0a391a19 100644 --- a/instrumentation/opentelemetry-instrumentation-genai-openai/tests/test_raw_response_httpx_interop.py +++ b/instrumentation/opentelemetry-instrumentation-genai-openai/tests/test_raw_response_httpx_interop.py @@ -21,14 +21,13 @@ import pytest from openai import AsyncOpenAI, OpenAI -# Only pinned in requirements.latest.txt; oldest envs skip this module. -# When httpx is not installed (e.g. openai v3+), this import will also skip. +# Only pinned in requirements.openai-v2.txt; other envs (openai v3+ uses +# httpx2, oldest has no httpx instrumentation) skip this module. +httpx = pytest.importorskip("httpx") HTTPXClientInstrumentor = pytest.importorskip( "opentelemetry.instrumentation.httpx" ).HTTPXClientInstrumentor -import httpx - _HTTPX_SPAN = "POST" _GENAI_SPAN = "chat gpt-4" diff --git a/tox.ini b/tox.ini index 9885d4485..2586c80fb 100644 --- a/tox.ini +++ b/tox.ini @@ -12,7 +12,7 @@ envlist = ; oldest deps run only on the minimum supported Python: the lowest declared dependency versions ; predate newer interpreters and ship no wheels for them. latest covers the full interpreter range. py3{10,11,12,13,14}-test-instrumentation-genai-openai-latest - py3{10,11,12,13,14}-test-instrumentation-genai-openai-latest2 + py3{10,11,12,13,14}-test-instrumentation-genai-openai-v2 py310-test-instrumentation-genai-openai-oldest pypy3-test-instrumentation-genai-openai-latest py314-test-instrumentation-genai-openai-conformance @@ -124,9 +124,9 @@ deps = openai-latest: {[testenv]test_deps} openai-latest: {[testenv]pytest_deps} openai-latest: -r {toxinidir}/instrumentation/opentelemetry-instrumentation-genai-openai/tests/requirements.latest.txt - openai-latest2: {[testenv]test_deps} - openai-latest2: {[testenv]pytest_deps} - openai-latest2: -r {toxinidir}/instrumentation/opentelemetry-instrumentation-genai-openai/tests/requirements.latest2.txt + openai-v2: {[testenv]test_deps} + openai-v2: {[testenv]pytest_deps} + openai-v2: -r {toxinidir}/instrumentation/opentelemetry-instrumentation-genai-openai/tests/requirements.openai-v2.txt openai-conformance: {[testenv]pytest_deps} openai-conformance: -r {toxinidir}/instrumentation/opentelemetry-instrumentation-genai-openai/tests/requirements.latest.txt @@ -249,7 +249,7 @@ commands_pre = coverage: python {toxinidir}/scripts/eachdist.py install --editable commands = - test-instrumentation-genai-openai-{oldest,latest,latest2}: pytest --ignore={toxinidir}/instrumentation/opentelemetry-instrumentation-genai-openai/tests/test_conformance.py {toxinidir}/instrumentation/opentelemetry-instrumentation-genai-openai/tests {posargs} + test-instrumentation-genai-openai-{oldest,latest,v2}: pytest --ignore={toxinidir}/instrumentation/opentelemetry-instrumentation-genai-openai/tests/test_conformance.py {toxinidir}/instrumentation/opentelemetry-instrumentation-genai-openai/tests {posargs} test-instrumentation-genai-openai-conformance: pytest {toxinidir}/instrumentation/opentelemetry-instrumentation-genai-openai/tests/test_conformance.py --vcr-record=none {posargs} lint-instrumentation-genai-openai: sh -c "cd instrumentation && ruff check opentelemetry-instrumentation-genai-openai" test-instrumentation-genai-openai_agents-{oldest,latest}: pytest --ignore={toxinidir}/instrumentation/opentelemetry-instrumentation-genai-openai-agents/tests/test_conformance.py {toxinidir}/instrumentation/opentelemetry-instrumentation-genai-openai-agents/tests {posargs} From f3e022407bdf019a47b28cdf8a9220254a8bd10b Mon Sep 17 00:00:00 2001 From: Liudmila Molkova Date: Tue, 18 Aug 2026 05:50:35 -0700 Subject: [PATCH 8/9] fix(openai): update _instruments version constraint to < 4 --- .../src/opentelemetry/instrumentation/genai/openai/package.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/instrumentation/opentelemetry-instrumentation-genai-openai/src/opentelemetry/instrumentation/genai/openai/package.py b/instrumentation/opentelemetry-instrumentation-genai-openai/src/opentelemetry/instrumentation/genai/openai/package.py index 4d23be8ce..76e6b35ba 100644 --- a/instrumentation/opentelemetry-instrumentation-genai-openai/src/opentelemetry/instrumentation/genai/openai/package.py +++ b/instrumentation/opentelemetry-instrumentation-genai-openai/src/opentelemetry/instrumentation/genai/openai/package.py @@ -2,6 +2,6 @@ # SPDX-License-Identifier: Apache-2.0 -_instruments = ("openai >= 1.26.0",) +_instruments = ("openai >= 1.26.0, < 4",) _supports_metrics = True From 4e0791516f504ac853397fdcf4e46ff4e930202f Mon Sep 17 00:00:00 2001 From: Liudmila Molkova Date: Wed, 19 Aug 2026 06:21:06 -0700 Subject: [PATCH 9/9] docs: update generated README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1a8d80b24..a04df5bb4 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ All instrumentations use [opentelemetry-util-genai](./util/opentelemetry-util-ge | --------------- | ----------------- | ------- | | [opentelemetry-instrumentation-genai-anthropic](./instrumentation/opentelemetry-instrumentation-genai-anthropic) | anthropic >= 0.16.0 | [1.0b0](https://pypi.org/project/opentelemetry-instrumentation-genai-anthropic/) | | [opentelemetry-instrumentation-genai-langchain](./instrumentation/opentelemetry-instrumentation-genai-langchain) | langchain >= 0.3.21 | [1.0b0](https://pypi.org/project/opentelemetry-instrumentation-genai-langchain/) | -| [opentelemetry-instrumentation-genai-openai](./instrumentation/opentelemetry-instrumentation-genai-openai) | openai >= 1.26.0 | [1.0b0](https://pypi.org/project/opentelemetry-instrumentation-genai-openai/) | +| [opentelemetry-instrumentation-genai-openai](./instrumentation/opentelemetry-instrumentation-genai-openai) | openai >= 1.26.0, < 4 | [1.0b0](https://pypi.org/project/opentelemetry-instrumentation-genai-openai/) | | [opentelemetry-instrumentation-genai-openai-agents](./instrumentation/opentelemetry-instrumentation-genai-openai-agents) | openai-agents >= 0.3.3 | [1.0b0](https://pypi.org/project/opentelemetry-instrumentation-genai-openai-agents/) | | [opentelemetry-instrumentation-google-genai](./instrumentation/opentelemetry-instrumentation-google-genai) | google-genai >= 1.32.0, <3 | [1.0b1](https://pypi.org/project/opentelemetry-instrumentation-google-genai/) |