traceloop / traceloop/openllmetry

๐Ÿ› Bug Report: Duplicate OpenAI chat completion traces when using httpx instrumentor

Open
#2,845 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Python
Stars
7.4k
Forks
1.1k
Avg merge
8d 14h
Merged PRs (30d)
2

Description

Which component is this bug for?

OpenAI Instrumentation

๐Ÿ“œ Description

If you use the httpx instrumentor and the openai instrumentor you get duplicated traces recorded.

๐Ÿ‘Ÿ Reproduction steps

Here is some minimal code to recreate the issue

# install 
# "openai>=1.76.0",
# "opentelemetry-api>=1.32.1",
# "opentelemetry-exporter-otlp>=1.32.1",
# "opentelemetry-instrumentation-httpx>=0.53b1",
# "opentelemetry-instrumentation-openai>=0.39.3",

from opentelemetry import trace
from opentelemetry.sdk.resources import Resource
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter

from opentelemetry.instrumentation.httpx import HTTPXClientInstrumentor
from opentelemetry.instrumentation.openai import OpenAIInstrumentor

resource = Resource(attributes={"service.name": "test-service"})

provider = TracerProvider(resource=resource)
trace.set_tracer_provider(provider)

OTEL_ENDPOINT = "http://localhost:4318/v1/traces"  # Replace with your OpenTelemetry Collector endpoint

otlp_exporter = OTLPSpanExporter(endpoint=OTEL_ENDPOINT)
span_processor = BatchSpanProcessor(otlp_exporter)
provider.add_span_processor(span_processor)

HTTPXClientInstrumentor().instrument()
OpenAIInstrumentor().instrument()


from openai import AsyncOpenAI
import asyncio

OPENAI_API_KEY = "key"

async def run():
    client = AsyncOpenAI(api_key=OPENAI_API_KEY)

    tracer = trace.get_tracer(__name__)

    with tracer.start_as_current_span("test-span"):
        resp = await client.chat.completions.create(
            model="gpt-4o-mini",
            messages=[
                {
                    "role": "user",
                    "content": "say hello only",
                }
            ],
        )
        print(resp.choices[0].message.content)

def main():
    asyncio.run(run())

if __name__ == "__main__":
    main()

๐Ÿ‘ Expected behavior

Ideally the openai instrumentor should suppress all traces from httpx.

๐Ÿ‘Ž Actual Behavior with Screenshots

Image

Image

as you can see in the jaeger screenshot there are duplicated spans. These do not follow the typical parent child relationship you would expect from nested spans (which would also mitigate the issue)

๐Ÿค– Python Version

3.13

๐Ÿ“ƒ Provide any additional context for the Bug.

No response

๐Ÿ‘€ Have you spent some time to check if this bug has been raised before?
  • I checked and didn't find similar issue
Are you willing to submit PR?

Yes I am willing to submit a PR!

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 by running the provided reproduction with both HTTPXClientInstrumentor and OpenAIInstrumentor enabled, then inspect the OpenAI and HTTPX instrumentation entry points involved in creating spans. Done means the OpenAI request produces one expected trace without duplicate HTTPX spans while the two instrumentors remain enabled.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
observability
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.