feat(suppress): expose SUPPRESS_LANGUAGE_MODEL_INSTRUMENTATION as env var#336
Draft
adityamehra wants to merge 13 commits into
Draft
feat(suppress): expose SUPPRESS_LANGUAGE_MODEL_INSTRUMENTATION as env var#336adityamehra wants to merge 13 commits into
adityamehra wants to merge 13 commits into
Conversation
…ributes StreamWrapper was missing __getattr__, causing AttributeError when LiteLLM (and other clients) access raw_response.headers after calling with_raw_response.create(stream=True). Ports the fix from upstream opentelemetry-python-contrib PR #4184 (fixes issue #4113). Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
…or with_raw_response streaming When LiteLLM calls with_raw_response.create(stream=True), the OpenAI SDK returns a LegacyAPIResponse (with .headers). SDOT's _parse_response was calling .parse() on it to get the AsyncStream before wrapping in StreamWrapper, discarding the headers. LiteLLM then accesses raw_response.headers, which failed with AttributeError since AsyncStream has no .headers attribute. Fix: - Capture LegacyAPIResponse.headers before _parse_response discards it - Store captured headers as StreamWrapper.headers (direct attribute, not proxied) - Add parse() returning self so callers can treat StreamWrapper as a raw response - Keep __getattr__ for any other unknown attribute proxying to the underlying stream Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
…aming headers bug Reproduces the production error reported in lab0: AttributeError: 'StreamWrapper' object has no attribute 'headers' Mirrors the pattern from upstream issues: #4032 - StreamWrapper missing .parse() #4113 - StreamWrapper missing .headers Co-authored-by: Cursor <cursoragent@cursor.com>
…im in reproducer Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
3b66c84 to
399915b
Compare
b8aab3a to
d0eff63
Compare
… var
Add SUPPRESS_LANGUAGE_MODEL_INSTRUMENTATION_ENV_VAR to util-genai attributes
as the environment variable counterpart of the existing
suppress_language_model_instrumentation OTel context key.
The openai-v2 instrumentor now checks both surfaces via a single
_is_instrumentation_suppressed() helper:
1. OTel context key (existing) — set per-request by the LangChain
instrumentor to prevent duplicate LLM spans.
2. New env var — set globally for zero-code deployments together with
OTEL_PYTHON_DISABLED_INSTRUMENTATIONS=openai.
One concept, two surfaces. No new flags introduced in the LangChain package.
Co-authored-by: Cursor <cursoragent@cursor.com>
d0eff63 to
8f324d4
Compare
…anguage-model-key Co-authored-by: Cursor <cursoragent@cursor.com>
shuningc
reviewed
May 19, 2026
| the per-request `suppress_language_model_instrumentation` OTel context key | ||
| (the env var is the uppercase form of the same string). When set to a truthy | ||
| value (`true`, `1`, `yes`, `on`), the openai-v2 instrumentor skips creating | ||
| spans entirely. Intended for zero-code deployments alongside |
Contributor
There was a problem hiding this comment.
Is this an alternative instead of "alongside"?
shuningc
reviewed
May 19, 2026
| by the LangChain instrumentor to prevent duplicate LLM spans when both | ||
| instrumentors are active simultaneously. | ||
| 2. SUPPRESS_LANGUAGE_MODEL_INSTRUMENTATION environment variable — set globally | ||
| for zero-code deployments alongside OTEL_PYTHON_DISABLED_INSTRUMENTATIONS=openai. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Exposes the existing
suppress_language_model_instrumentationOTel context key as a runtime environment variable, giving zero-code deployments a global suppression knob without introducing any new concept or constant.util-genai/attributes.py— Updated docstring onSUPPRESS_LANGUAGE_MODEL_INSTRUMENTATION_KEYto document that its uppercase form (SUPPRESS_LANGUAGE_MODEL_INSTRUMENTATION) is also honoured as an environment variable. No new constant added.openai-v2/patch.py— New_is_instrumentation_suppressed()helper checks both surfaces (OTel context key or env var). All four traced entry-points (chat,async chat,embeddings,async embeddings) use it. No change to the LangChain instrumentor.openai-v2/tests/test_suppression.py— 15 new tests covering env var truthy/falsey parsing and two VCR integration tests.Motivation
The LangChain instrumentor's
_OpenAITracingWrappersets thesuppress_language_model_instrumentationcontext key per-request to prevent duplicate LLM spans when both instrumentors are active. For zero-code deployments the preferred pattern going forward isOTEL_PYTHON_DISABLED_INSTRUMENTATIONS=openai(excludes openai-v2 at startup). The env var provides a runtime fallback when startup-time exclusion is not possible — same concept, environment variable form — without introducing any new LangChain-specific flag.Changes
util-genai/attributes.pySUPPRESS_LANGUAGE_MODEL_INSTRUMENTATION_KEYto describe env var usageopenai-v2/patch.py_is_instrumentation_suppressed(): checks context key first, then env var; four call-sites updatedopenai-v2/tests/test_suppression.pyopenai-v2/tests/cassettes/test_chat_completion_suppressed_via_env_var.yamlopenai-v2/tests/cassettes/test_chat_completion_not_suppressed_when_env_var_false.yamlopenai-v2/CHANGELOG.mdAddedentryutil-genai/CHANGELOG.mdAddedentryUsage
Preferred zero-code path — exclude openai-v2 at startup:
export OTEL_PYTHON_DISABLED_INSTRUMENTATIONS=openai opentelemetry-instrument python myapp.pyRuntime suppression via env var — openai-v2 is loaded but skips span creation:
export SUPPRESS_LANGUAGE_MODEL_INSTRUMENTATION=trueExplicit opt-in to spans (override any env var default):
export SUPPRESS_LANGUAGE_MODEL_INSTRUMENTATION=falsePer-request suppression via context key (LangChain, unchanged):
The
_OpenAITracingWrapperin the LangChain instrumentor continues to set the context key on every OpenAI call — no code changes there.Suppression precedence
_is_instrumentation_suppressed()checks in order:suppress_language_model_instrumentation) — set per-request by LangChainSUPPRESS_LANGUAGE_MODEL_INSTRUMENTATION— global, checked at call timeEither surface alone is sufficient to suppress. Truthy values:
true,1,yes,on(case-insensitive). Unset or any other value → not suppressed.Testing notes
Validated with the SRE Incident Copilot demo (LangChain + openai-v2, manual and zero-code):
chat gpt-4o-mini/embeddingspans carry scopeopentelemetry.util.genai.handler(LangChain) — zero direct openai-v2 spans in bothSUPPRESS=falseandSUPPRESS=trueruns, confirming no duplicate telemetry regardless of env var value.SUPPRESS_LANGUAGE_MODEL_INSTRUMENTATION=falsewithOTEL_PYTHON_DISABLED_INSTRUMENTATIONS=""(openai-v2 loaded): copilot runs successfully; LangChain context-key suppression prevents duplicates as before.Test plan
pytest instrumentation-genai/opentelemetry-instrumentation-openai-v2/tests/test_suppression.py -v— 19/19 passmake lintpasses