Skip to content

fix(sdk): warn when both exporter and processor are passed to Traceloop.init()#4132

Open
dvirski wants to merge 2 commits into
mainfrom
dr/-fix(sdk)-warn-when-both-exporter-and-processor-are-passed-to-Traceloop.init()
Open

fix(sdk): warn when both exporter and processor are passed to Traceloop.init()#4132
dvirski wants to merge 2 commits into
mainfrom
dr/-fix(sdk)-warn-when-both-exporter-and-processor-are-passed-to-Traceloop.init()

Conversation

@dvirski
Copy link
Copy Markdown

@dvirski dvirski commented May 12, 2026

Fixes #3046
Problem: passing both exporter and processor is a mistake — the processor already wraps the exporter
internally. The exporter was silently ignored with no feedback to the user.

Fix: emit a UserWarning pointing at the caller's line. Also removes the redundant
exporter= from the exporter_with_custom_span_processor test fixture which had the same bug.

Summary by CodeRabbit

  • New Features

    • Response instructions are now automatically included in trace content for OpenAI agent spans when content tracing is enabled.
  • Improvements

    • SDK now warns users when both exporter and processor are configured, clarifying that the exporter configuration will be ignored.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 12, 2026

📝 Walkthrough

Walkthrough

The PR adds response instruction prepending to traced OpenAI agent input messages when trace_content is enabled, and introduces a warning in Traceloop.init() when both exporter and processor are provided simultaneously. The first feature enriches traced input context; the second prevents silent exporter ignoration by alerting users to the conflict.

Changes

Response Instructions Prepending in Traced Input

Layer / File(s) Summary
Instruction prepending in end_generation_span
packages/opentelemetry-instrumentation-openai-agents/opentelemetry/instrumentation/openai_agents/_hooks.py
When trace_content is enabled and response.instructions exists, a synthesized system message is prepended to input_data before prompt attribute extraction.
Tests for response instructions handling
packages/opentelemetry-instrumentation-openai-agents/tests/test_openai_agents.py, packages/opentelemetry-instrumentation-openai-agents/tests/test_tracing_processor.py
Integration test validates system message appears first and user message uses parts schema; processor tests verify instructions are prepended when trace_content=True and omitted when trace_content=False.

Exporter and Processor Conflict Warning

Layer / File(s) Summary
Warning in Traceloop.init() for dual configuration
packages/traceloop-sdk/traceloop/sdk/__init__.py
Added warnings import and conditional UserWarning when both exporter and processor are supplied to Traceloop.init(), notifying users that exporter will be ignored.
Tests for exporter/processor conflict handling
packages/traceloop-sdk/tests/test_sdk_initialization.py, packages/traceloop-sdk/tests/conftest.py
New imports for TracerWrapper and span infrastructure; test_both_exporter_and_processor_warns verifies warning is raised with proper TracerWrapper state cleanup; fixture updated to wire processor to Traceloop.init.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Poem

🐰 Hops skip with grace, instructions now trace,
System messages lead the way with style,
When dual processors dance in one place,
A warning's whisper makes users smile! 🐾

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning Changes to openai_agents instrumentation files appear unrelated to issue #3046 about Traceloop.init() warning logic and may be out of scope. Verify whether openai_agents changes are part of issue #3046 or should be separated into a different PR.
✅ 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 accurately summarizes the main change: adding a warning when both exporter and processor are passed to Traceloop.init().
Linked Issues check ✅ Passed The PR fulfills the linked issue #3046 by implementing option (b): emitting a UserWarning when both exporter and processor are provided, preventing silent misconfiguration.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch dr/-fix(sdk)-warn-when-both-exporter-and-processor-are-passed-to-Traceloop.init()

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.

@CLAassistant
Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.


Dvir Rezenman seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account.
You have signed the CLA already but the status is still pending? Let us recheck it.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/traceloop-sdk/tests/test_sdk_initialization.py (1)

242-246: ⚡ Quick win

Assert warning source location to cover the stacklevel=2 behavior.

The test currently checks only message content; validating filename/lineno would lock in the caller-line contract from Traceloop.init.

Suggested assertion enhancement
+import inspect
@@
-    with pytest.warns(UserWarning, match="exporter.*ignored"):
-        Traceloop.init(
-            exporter=exporter,
-            processor=processor,
-        )
+    with pytest.warns(UserWarning, match="exporter.*ignored") as caught:
+        call_line = inspect.currentframe().f_lineno + 1
+        Traceloop.init(
+            exporter=exporter,
+            processor=processor,
+        )
+    warning = caught.list[0]
+    assert warning.filename == __file__
+    assert warning.lineno == call_line
🤖 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/traceloop-sdk/tests/test_sdk_initialization.py` around lines 242 -
246, The test should capture the warnings recorder from the pytest.warns context
and assert the warning's source file and line to verify stacklevel=2 behavior:
wrap the Traceloop.init call with "with pytest.warns(UserWarning,
match='exporter.*ignored') as rec:" then inspect rec.list[0] (or rec.records[0])
and assert record.filename endswith "test_sdk_initialization.py" and that
record.lineno equals the line where Traceloop.init was invoked; update the
assertion locations around the Traceloop.init call to use these checks so the
test enforces the stacklevel caller location contract.
🤖 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/traceloop-sdk/tests/test_sdk_initialization.py`:
- Around line 235-249: The test mutates TracerWrapper.instance and only restores
it after the pytest.warns block, which can leak state if the assertion fails;
wrap the call to Traceloop.init (the pytest.warns(...) context and its body) in
a try/finally and move the restoration of TracerWrapper.instance (using the
saved _instance) into the finally so TracerWrapper.instance is always restored
even if the warning assertion fails; locate the code around
TracerWrapper.instance, _instance, and the Traceloop.init(...) call and
implement the try/finally around that block.

---

Nitpick comments:
In `@packages/traceloop-sdk/tests/test_sdk_initialization.py`:
- Around line 242-246: The test should capture the warnings recorder from the
pytest.warns context and assert the warning's source file and line to verify
stacklevel=2 behavior: wrap the Traceloop.init call with "with
pytest.warns(UserWarning, match='exporter.*ignored') as rec:" then inspect
rec.list[0] (or rec.records[0]) and assert record.filename endswith
"test_sdk_initialization.py" and that record.lineno equals the line where
Traceloop.init was invoked; update the assertion locations around the
Traceloop.init call to use these checks so the test enforces the stacklevel
caller location contract.
🪄 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: d5ba66d7-e16c-4114-8fa4-5c8d2011e920

📥 Commits

Reviewing files that changed from the base of the PR and between 6d3e696 and fdcce3b.

⛔ Files ignored due to path filters (3)
  • packages/opentelemetry-instrumentation-openai-agents/uv.lock is excluded by !**/*.lock
  • packages/sample-app/uv.lock is excluded by !**/*.lock
  • packages/traceloop-sdk/uv.lock is excluded by !**/*.lock
📒 Files selected for processing (6)
  • packages/opentelemetry-instrumentation-openai-agents/opentelemetry/instrumentation/openai_agents/_hooks.py
  • packages/opentelemetry-instrumentation-openai-agents/tests/test_openai_agents.py
  • packages/opentelemetry-instrumentation-openai-agents/tests/test_tracing_processor.py
  • packages/traceloop-sdk/tests/conftest.py
  • packages/traceloop-sdk/tests/test_sdk_initialization.py
  • packages/traceloop-sdk/traceloop/sdk/__init__.py
💤 Files with no reviewable changes (1)
  • packages/traceloop-sdk/tests/conftest.py

Comment on lines +235 to +249
if hasattr(TracerWrapper, "instance"):
_instance = TracerWrapper.instance
del TracerWrapper.instance

exporter = InMemorySpanExporter()
processor = SimpleSpanProcessor(exporter)

with pytest.warns(UserWarning, match="exporter.*ignored"):
Traceloop.init(
exporter=exporter,
processor=processor,
)

if "_instance" in dir():
TracerWrapper.instance = _instance
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Restore TracerWrapper.instance in a finally block to prevent cross-test state leakage.

If the warning assertion fails before cleanup, this test can leave mutated global state and cause downstream flaky failures.

Proposed fix
 def test_both_exporter_and_processor_warns():
@@
-    if hasattr(TracerWrapper, "instance"):
-        _instance = TracerWrapper.instance
-        del TracerWrapper.instance
+    had_instance = hasattr(TracerWrapper, "instance")
+    previous_instance = getattr(TracerWrapper, "instance", None)
+    if had_instance:
+        del TracerWrapper.instance
@@
-    with pytest.warns(UserWarning, match="exporter.*ignored"):
-        Traceloop.init(
-            exporter=exporter,
-            processor=processor,
-        )
-
-    if "_instance" in dir():
-        TracerWrapper.instance = _instance
+    try:
+        with pytest.warns(UserWarning, match="exporter.*ignored"):
+            Traceloop.init(
+                exporter=exporter,
+                processor=processor,
+            )
+    finally:
+        if had_instance:
+            TracerWrapper.instance = previous_instance
🤖 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/traceloop-sdk/tests/test_sdk_initialization.py` around lines 235 -
249, The test mutates TracerWrapper.instance and only restores it after the
pytest.warns block, which can leak state if the assertion fails; wrap the call
to Traceloop.init (the pytest.warns(...) context and its body) in a try/finally
and move the restoration of TracerWrapper.instance (using the saved _instance)
into the finally so TracerWrapper.instance is always restored even if the
warning assertion fails; locate the code around TracerWrapper.instance,
_instance, and the Traceloop.init(...) call and implement the try/finally around
that block.

Comment on lines +944 to +946
response = getattr(span_data, "response", None)
if trace_content and response and getattr(response, "instructions", None):
input_data = [{"role": "system", "content": response.instructions}] + (input_data if input_data else [])
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

you got some branches mess

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

: O

I'll reopen it

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: exporter is ignored when processor is also defined in Traceloop.init()

3 participants