Skip to content

[WIP] Fix missing server.address and server.port for openai v3 - #395

Closed
lmolkova with Copilot wants to merge 1 commit into
copilot/support-openai-v3from
copilot/fix-openai-missing-server-address-port
Closed

lmolkova with Copilot wants to merge 1 commit into
copilot/support-openai-v3from
copilot/fix-openai-missing-server-address-port

Conversation

Copilot AI commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

Thanks for the feedback on #393. I've created this new PR, which merges into #393, to address your comment. I will work on the changes and keep this PR's description up to date as I make progress.

Original PR: #393
Triggering review: #393 (comment)

here's the body @copilot

I don't think we're fixing it- the issue is about us not populating server.address / port

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.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants