open-telemetry / open-telemetry/opentelemetry-python-contrib

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

Open Beginner friendly
#4,953 3 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Python
Stars
1.1k
Forks
1.1k
Avg merge
4d 15h
Merged PRs (30d)
16

Description

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.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in instrumentation/openai_v2/utils.py at get_server_address_and_port and reproduce the difference with openai 2.54.0 and 3.0.0 using the base_url shown in the issue. Verify that server.address and server.port are retained for both URL implementations, then add a regression test covering the non-default base URL and run the existing OpenAI instrumentation tests.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
observability-sre
Issue type
Bug
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
78/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.