Skip to content

openai 3.x: server.address / server.port are never recorded (isinstance check misses httpx2.URL) #4953

Open

Description

@ari-abb

Describe your environment

  • OS: macOS 15 (arm64)
  • Python: 3.13.13
  • Package version: opentelemetry-instrumentation-openai-v2 2.4b0
  • Related: openai 3.0.0, httpx 0.28.1, httpx2 2.10.0

What happened?

Spans produced for OpenAI SDK calls no longer carry the server.address (or server.port) attribute
when the installed openai is 3.x. The same code against openai 2.54.0 records them normally.

The cause is in instrumentation/openai_v2/utils.py:

from httpx import URL          # line 23
...
def get_server_address_and_port(client_instance):     # line 98
    base_client = getattr(client_instance, "_client", None)
    base_url = getattr(base_client, "base_url", None)
    if not base_url:
        return None, None
    address = None
    port = None
    if isinstance(base_url, URL):        # httpx.URL
        address = base_url.host
        port = base_url.port
    elif isinstance(base_url, str):
        url = urlparse(base_url)
        address = url.hostname
        port = url.port
    ...

openai 3.x builds on httpx2, so client._client.base_url is an httpx2.URL. It is neither an
httpx.URL nor a str, so both branches are skipped and the function returns (None, None). Because the
values are only set when truthy, the attributes are dropped silently, with no warning and no error.

Steps to reproduce

from openai import OpenAI
from opentelemetry.instrumentation.openai_v2.utils import get_server_address_and_port

client = OpenAI(base_url="https://openrouter.ai/api/v1", api_key="x")
print(type(client._client.base_url))                      # <class 'httpx2.URL'>
print(get_server_address_and_port(client.chat.completions))
# openai 2.54.0 -> ('openrouter.ai', None)
# openai 3.0.0  -> (None, None)

End to end, with a real request through the instrumentor, the emitted span attributes are:

openai 2.54.0: gen_ai.operation.name, gen_ai.provider.name, gen_ai.request.model,
               gen_ai.response.finish_reasons, gen_ai.response.id, gen_ai.response.model,
               gen_ai.usage.input_tokens, gen_ai.usage.output_tokens, server.address
openai 3.0.0 : (identical, minus server.address)

Expected result

server.address and server.port are recorded regardless of which URL type the SDK uses internally, as
required by the semantic conventions for client spans.

Actual result

Both attributes are absent on openai 3.x.

Why it matters beyond a missing attribute

gen_ai.provider.name names the SDK, not the operator of the endpoint. For anything reached through
the OpenAI-compatible protocol, such as a router or gateway, an Azure OpenAI deployment, or a self-hosted
vLLM or Ollama server, server.address is the only attribute distinguishing them. Without it, all of these are
indistinguishable from a direct api.openai.com call, so consumers that attribute traffic per provider or
per region will silently misattribute it. It fails quietly, which makes it easy to miss in a version bump.

Suggested fix

Avoid the type check and read the URL structurally, which also covers any future URL implementation:

def get_server_address_and_port(client_instance):
    base_client = getattr(client_instance, "_client", None)
    base_url = getattr(base_client, "base_url", None)
    if not base_url:
        return None, None

    address = getattr(base_url, "host", None)
    port = getattr(base_url, "port", None)
    if not address:                       # plain string base_url
        parsed = urlparse(str(base_url))
        address, port = parsed.hostname, parsed.port

    if port == 443:
        port = None
    return address, port

Both httpx.URL and httpx2.URL expose .host / .port, so this keeps existing behaviour and fixes
3.x. A regression test asserting server.address is present for a non-default base_url would catch the
next recurrence. The current tests pass with the attribute missing.

I'm happy to open a PR with the fix and the test if that's useful.

Activity

  1. ari-abb commented on Aug 12, 2026

    @ari-abb
    Author

    Two additions after further investigation, one of which makes this more severe than the report above.

    The package is unimportable on a clean openai 3.x install, not just missing an attribute

    opentelemetry-instrumentation-openai-v2 declares no dependency on httpx (Requires-Dist lists only
    the opentelemetry-* packages, plus openai>=1.26.0 under the instruments extra), and openai 3.x
    requires httpx2<3,>=2.7.0 rather than httpx. So on a clean install of openai==3.0.0 with
    opentelemetry-instrumentation-openai-v2==2.4b0, httpx is simply not present, and the module-scope
    from httpx import URL at utils.py:23 fails outright:

    httpx  present: False
    httpx2 present: True
    
    >>> from opentelemetry.instrumentation.openai_v2 import OpenAIInstrumentor
    ModuleNotFoundError: No module named 'httpx'
    

    The environment in the original report only got as far as a missing server.address because httpx
    happened to be installed transitively by unrelated packages. Without that coincidence the instrumentation
    does not load at all, so nothing is recorded. Same root cause, more severe symptom, and it fails quietly
    from the operator's point of view: auto-instrumentation that never attaches looks identical to an
    application that made no calls.

    The suggested fix in the report resolves both symptoms, since removing the isinstance check also
    removes the only use of the import. URL is referenced in exactly one place, so the import can be
    dropped rather than made conditional.

    Related httpx2 work

    This looks like a spot missed by the recent httpx2 migration rather than a separate problem:

    Given that, openai_v2 may not be the only instrumentation affected. Anywhere an isinstance check
    against an httpx type guards attribute extraction will behave the same way once the underlying SDK
    moves to httpx2: the check silently stops matching, and the attributes quietly disappear. Reading the
    URL structurally with getattr(base_url, "host", None) avoids the coupling entirely.

    I am happy to open a PR with the fix, a regression test asserting server.address is present for a
    non-default base_url, and a test that the package imports without httpx installed.

  2. aabmass commented on Aug 13, 2026

    @aabmass
    Member

    Hi @ari-abb can thanks for the PR, but development on this has moved to https://github.com/open-telemetry/opentelemetry-python-genai (see #4924). The version in this repo will be deleted from this repo once we make a final release with the deprecation notice.

    I'm happy to transfer the issue over if you can reopen the PR?

  3. ari-abb commented on Aug 14, 2026

    @ari-abb
    Author

    Hey @aabmass thanks for the heads up. Feel free to move the issue and I can reopen the PR 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions