Skip to content

fix(openai): suppress duplicate httpx spans during instrumented calls#4270

Open
leorivastech wants to merge 1 commit into
traceloop:mainfrom
leorivastech:fix/openai-suppress-duplicate-httpx-spans
Open

fix(openai): suppress duplicate httpx spans during instrumented calls#4270
leorivastech wants to merge 1 commit into
traceloop:mainfrom
leorivastech:fix/openai-suppress-duplicate-httpx-spans

Conversation

@leorivastech

@leorivastech leorivastech commented Jun 16, 2026

Copy link
Copy Markdown

What

When the underlying HTTP client is instrumented in addition to OpenAI — e.g.
HTTPXClientInstrumentor().instrument() alongside OpenAIInstrumentor().instrument(),
a very common setup — every OpenAI call is traced twice:

  • openai.chat (this instrumentation, with prompts/model/tokens), and
  • a raw POST span from the httpx instrumentation for the same request.

This wraps the underlying SDK call in suppress_http_instrumentation() so the
HTTP-client span is suppressed for the duration of the request, while the OpenAI
span is still recorded as usual.

This is the approach suggested by @nirga in the issue thread ("set the suppress
flag within the OpenAI instrumentation"). It uses the HTTP-specific suppression
key (_SUPPRESS_HTTP_INSTRUMENTATION_KEY), not the global one, so only HTTP-client
instrumentation is suppressed.

Applied to the request-firing wrappers: chat, completion, embeddings, responses
and image generation (sync, async and streaming). Assistants (polling) and realtime
(websocket, not HTTP) are out of scope.

Fixes #2845

How it was verified

Tested against a local HTTP server over a real socket, so the httpx instrumentation
actually runs (VCR replay would bypass the transport it wraps):

before after
chat (non-streaming) openai.chat + POST openai.chat
chat (streaming) openai.chat + POST openai.chat

Added tests/traces/test_suppress_http_instrumentation.py covering both the
streaming and non-streaming paths; the tests fail without the fix
(['POST', 'openai.chat'] != ['openai.chat']) and pass with it. Full package
suite stays green (270 passed, 1 skipped) and ruff is clean.

Checklist

  • I have added tests that cover my changes.
  • If adding a new instrumentation or changing an existing one, I've added screenshots from some observability platform showing the change. (bug fix verified by span assertions; no behavioral change to the OpenAI span itself.)
  • PR name follows conventional commits format: fix(openai): ...
  • (If applicable) I have updated the documentation accordingly.

Summary by CodeRabbit

  • Bug Fixes

    • Prevented duplicate HTTP client spans when both OpenAI and HTTP client instrumentation are enabled by suppressing underlying HTTP instrumentation during OpenAI requests (sync and async) across chat, completions, embeddings, image generation, and responses.
  • Tests

    • Added regression coverage for both standard and streaming chat completion calls to ensure HTTPX spans are not emitted.
  • Chores

    • Updated the test dependency set to include the required HTTPX instrumentation package.

@CLAassistant

CLAassistant commented Jun 16, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@coderabbitai

coderabbitai Bot commented Jun 16, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

All five OpenAI instrumentation wrapper modules (chat_wrappers, completion_wrappers, embeddings_wrappers, image_gen_wrappers, responses_wrappers) now import suppress_http_instrumentation from opentelemetry.instrumentation.utils and wrap each wrapped(*args, **kwargs) call site with it. A new regression test file verifies that only the openai.chat span is emitted when HTTPX instrumentation is active concurrently.

Changes

Suppress HTTPX double-tracing in OpenAI wrappers

Layer / File(s) Summary
suppress_http_instrumentation applied to all wrapper call sites
opentelemetry/instrumentation/openai/shared/chat_wrappers.py, opentelemetry/instrumentation/openai/shared/completion_wrappers.py, opentelemetry/instrumentation/openai/shared/embeddings_wrappers.py, opentelemetry/instrumentation/openai/shared/image_gen_wrappers.py, opentelemetry/instrumentation/openai/v1/responses_wrappers.py
Imports suppress_http_instrumentation and wraps every wrapped(*args, **kwargs) / await wrapped(*args, **kwargs) call in a with suppress_http_instrumentation(): block across all synchronous and asynchronous OpenAI wrapper functions.
Regression tests and test dependency
pyproject.toml, tests/traces/test_suppress_http_instrumentation.py
Adds opentelemetry-instrumentation-httpx>=0.63b1 to test dependencies. Adds a new test module with a local stub HTTP server, fixtures for enabling HTTPX instrumentation, and two regression tests (standard and streaming) asserting no HTTP client spans are emitted alongside openai.chat.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

  • traceloop/openllmetry#4198: Adds instrumentation for Responses.parse/AsyncResponses.parse, which are routed through the same responses_get_or_create_wrapper now wrapped with suppress_http_instrumentation().

Suggested reviewers

  • doronkopit5

Poem

🐇 Hoppin' through the trace maze, what did I find?
Two HTTP spans where only one's designed!
With suppress_http_instrumentation in paw,
I wrapped each call — no duplicates, hurraw!
One openai.chat span, clean and true,
No httpx ghost span sneaking through! 🎉

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and clearly summarizes the main change: suppressing duplicate httpx spans during OpenAI instrumented calls, which aligns with the core objective and changeset.
Linked Issues check ✅ Passed The PR fully addresses issue #2845 by implementing suppress_http_instrumentation in all relevant request-firing wrappers (chat, completion, embeddings, responses, image generation) for both sync and async paths, with comprehensive test coverage added.
Out of Scope Changes check ✅ Passed All changes are directly scoped to fixing duplicate httpx spans: wrapping OpenAI calls with suppress_http_instrumentation, adding test dependencies, and new regression tests validating the fix.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@packages/opentelemetry-instrumentation-openai/tests/traces/test_suppress_http_instrumentation.py`:
- Line 112: Replace the hardcoded API key literal "test-key" passed to the
OpenAI client constructor with a reference to an environment variable. In the
OpenAI client instantiation, change the api_key parameter from a hardcoded
string to use os.getenv() or similar environment variable lookup (such as
os.environ.get()) to retrieve the API key from the test environment instead of
committing it as a literal in the code. This change is needed in multiple
locations where the OpenAI client is instantiated with a hardcoded api_key
value, following the guideline that API keys should never be committed as code
literals.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 933f137d-01e1-415b-aa81-2baedf561f94

📥 Commits

Reviewing files that changed from the base of the PR and between aa4a469 and 2c2a453.

⛔ Files ignored due to path filters (1)
  • packages/opentelemetry-instrumentation-openai/uv.lock is excluded by !**/*.lock
📒 Files selected for processing (7)
  • packages/opentelemetry-instrumentation-openai/opentelemetry/instrumentation/openai/shared/chat_wrappers.py
  • packages/opentelemetry-instrumentation-openai/opentelemetry/instrumentation/openai/shared/completion_wrappers.py
  • packages/opentelemetry-instrumentation-openai/opentelemetry/instrumentation/openai/shared/embeddings_wrappers.py
  • packages/opentelemetry-instrumentation-openai/opentelemetry/instrumentation/openai/shared/image_gen_wrappers.py
  • packages/opentelemetry-instrumentation-openai/opentelemetry/instrumentation/openai/v1/responses_wrappers.py
  • packages/opentelemetry-instrumentation-openai/pyproject.toml
  • packages/opentelemetry-instrumentation-openai/tests/traces/test_suppress_http_instrumentation.py

@leorivastech leorivastech force-pushed the fix/openai-suppress-duplicate-httpx-spans branch from 2c2a453 to f9568f5 Compare June 16, 2026 20:28

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@packages/opentelemetry-instrumentation-openai/tests/traces/test_suppress_http_instrumentation.py`:
- Around line 88-90: The HTTPServer socket is not being properly released in the
finally block of the test fixture teardown. After the existing server.shutdown()
and thread.join() calls, add server.server_close() to explicitly release the
bound socket and prevent accumulated open descriptors that can cause test
flakiness on repeated runs.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: dfe5bd3a-76ea-412e-be01-76fe117826cb

📥 Commits

Reviewing files that changed from the base of the PR and between 2c2a453 and f9568f5.

⛔ Files ignored due to path filters (1)
  • packages/opentelemetry-instrumentation-openai/uv.lock is excluded by !**/*.lock
📒 Files selected for processing (7)
  • packages/opentelemetry-instrumentation-openai/opentelemetry/instrumentation/openai/shared/chat_wrappers.py
  • packages/opentelemetry-instrumentation-openai/opentelemetry/instrumentation/openai/shared/completion_wrappers.py
  • packages/opentelemetry-instrumentation-openai/opentelemetry/instrumentation/openai/shared/embeddings_wrappers.py
  • packages/opentelemetry-instrumentation-openai/opentelemetry/instrumentation/openai/shared/image_gen_wrappers.py
  • packages/opentelemetry-instrumentation-openai/opentelemetry/instrumentation/openai/v1/responses_wrappers.py
  • packages/opentelemetry-instrumentation-openai/pyproject.toml
  • packages/opentelemetry-instrumentation-openai/tests/traces/test_suppress_http_instrumentation.py
🚧 Files skipped from review as they are similar to previous changes (6)
  • packages/opentelemetry-instrumentation-openai/pyproject.toml
  • packages/opentelemetry-instrumentation-openai/opentelemetry/instrumentation/openai/shared/image_gen_wrappers.py
  • packages/opentelemetry-instrumentation-openai/opentelemetry/instrumentation/openai/v1/responses_wrappers.py
  • packages/opentelemetry-instrumentation-openai/opentelemetry/instrumentation/openai/shared/completion_wrappers.py
  • packages/opentelemetry-instrumentation-openai/opentelemetry/instrumentation/openai/shared/embeddings_wrappers.py
  • packages/opentelemetry-instrumentation-openai/opentelemetry/instrumentation/openai/shared/chat_wrappers.py

When the underlying HTTP client is also instrumented (e.g.
opentelemetry-instrumentation-httpx), every OpenAI call was traced
twice: once as the openai.* span and once as a raw HTTP POST span from
the HTTP-client instrumentation.

Wrap the underlying SDK call in suppress_http_instrumentation() so the
HTTP-client span is suppressed while the OpenAI span is still recorded.
Applies to the chat, completion, embeddings, responses and image
generation wrappers (sync, async and streaming).

Fixes traceloop#2845
@leorivastech leorivastech force-pushed the fix/openai-suppress-duplicate-httpx-spans branch from f9568f5 to fadcdca Compare June 16, 2026 20:41

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
packages/opentelemetry-instrumentation-openai/tests/traces/test_suppress_http_instrumentation.py (1)

122-124: Consider adding an optional ConsoleSpanExporter for span-hierarchy debugging.

This regression test validates span hierarchy behavior but lacks a console debug path for local diagnosis if the test fails. Adding an opt-in debug fixture (e.g., env-gated) that wires ConsoleSpanExporter alongside the existing InMemorySpanExporter would aid future troubleshooting without affecting normal test runs.

Per coding guidelines: "Use ConsoleSpanExporter from opentelemetry.sdk.trace.export for debugging OpenTelemetry spans and hierarchy issues."

Also applies to: 142-144

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/opentelemetry-instrumentation-openai/tests/traces/test_suppress_http_instrumentation.py`
around lines 122 - 124, Add an optional environment-gated ConsoleSpanExporter to
aid debugging of span hierarchy in this regression test. Import
ConsoleSpanExporter from opentelemetry.sdk.trace.export and modify the test
fixture (likely the setup/initialization code that creates the span exporter
used by the test assertions at lines 122-124) to conditionally wire both the
existing InMemorySpanExporter and the ConsoleSpanExporter when a debug
environment variable (e.g., DEBUG_SPANS) is set. This enables developers to see
console output of span hierarchies during local test failures without affecting
normal test runs. Apply the same pattern to the other affected location at lines
142-144.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In
`@packages/opentelemetry-instrumentation-openai/tests/traces/test_suppress_http_instrumentation.py`:
- Around line 122-124: Add an optional environment-gated ConsoleSpanExporter to
aid debugging of span hierarchy in this regression test. Import
ConsoleSpanExporter from opentelemetry.sdk.trace.export and modify the test
fixture (likely the setup/initialization code that creates the span exporter
used by the test assertions at lines 122-124) to conditionally wire both the
existing InMemorySpanExporter and the ConsoleSpanExporter when a debug
environment variable (e.g., DEBUG_SPANS) is set. This enables developers to see
console output of span hierarchies during local test failures without affecting
normal test runs. Apply the same pattern to the other affected location at lines
142-144.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4530dc29-84cd-408d-899d-75b7aab19472

📥 Commits

Reviewing files that changed from the base of the PR and between f9568f5 and fadcdca.

⛔ Files ignored due to path filters (1)
  • packages/opentelemetry-instrumentation-openai/uv.lock is excluded by !**/*.lock
📒 Files selected for processing (7)
  • packages/opentelemetry-instrumentation-openai/opentelemetry/instrumentation/openai/shared/chat_wrappers.py
  • packages/opentelemetry-instrumentation-openai/opentelemetry/instrumentation/openai/shared/completion_wrappers.py
  • packages/opentelemetry-instrumentation-openai/opentelemetry/instrumentation/openai/shared/embeddings_wrappers.py
  • packages/opentelemetry-instrumentation-openai/opentelemetry/instrumentation/openai/shared/image_gen_wrappers.py
  • packages/opentelemetry-instrumentation-openai/opentelemetry/instrumentation/openai/v1/responses_wrappers.py
  • packages/opentelemetry-instrumentation-openai/pyproject.toml
  • packages/opentelemetry-instrumentation-openai/tests/traces/test_suppress_http_instrumentation.py
🚧 Files skipped from review as they are similar to previous changes (6)
  • packages/opentelemetry-instrumentation-openai/pyproject.toml
  • packages/opentelemetry-instrumentation-openai/opentelemetry/instrumentation/openai/shared/completion_wrappers.py
  • packages/opentelemetry-instrumentation-openai/opentelemetry/instrumentation/openai/shared/embeddings_wrappers.py
  • packages/opentelemetry-instrumentation-openai/opentelemetry/instrumentation/openai/shared/chat_wrappers.py
  • packages/opentelemetry-instrumentation-openai/opentelemetry/instrumentation/openai/v1/responses_wrappers.py
  • packages/opentelemetry-instrumentation-openai/opentelemetry/instrumentation/openai/shared/image_gen_wrappers.py

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.

🐛 Bug Report: Duplicate OpenAI chat completion traces when using httpx instrumentor

2 participants