Skip to content

Conversation

@dnandakumar-nv
Copy link
Contributor

@dnandakumar-nv dnandakumar-nv commented Jan 28, 2026

Summary

  • Fix OTLP span attribute encoding failures when exporting telemetry (e.g. to LangSmith)
  • _to_dict in SpanExporter previously returned raw Python objects (LangChain messages, dicts with nested None values) as input.value_obj/output.value_obj span attributes, which OTLP's protobuf encoder cannot serialize
  • Changed _to_dict to always return a JSON string via json.dumps(result, default=str), guaranteeing OTLP compatibility for all input types
  • Added 15 new tests covering Pydantic models, lists of messages, nested None values, arbitrary objects, and the always-returns-string invariant

Test plan

  • All 39 tests pass in tests/nat/observability/exporter/test_span_exporter.py (24 existing + 15 new)

By Submitting this PR I confirm:

  • I am familiar with the Contributing Guidelines.
  • We require that all contributors "sign-off" on their commits. This certifies that the contribution is your original work, or you have rights to submit it under the same license, or a compatible license.
    • Any contribution which contains commits that are not Signed-Off will not be accepted.
  • When the PR is ready for review, new or existing tests cover these changes.
  • When the PR is ready for review, the documentation is up to date with these changes.

Summary by CodeRabbit

  • Bug Fixes

    • Improved span export serialization reliability with consistent JSON format handling and enhanced fallback mechanisms.
  • Tests

    • Added comprehensive test coverage for span serialization across various data types and edge cases.

✏️ Tip: You can customize this high-level summary in your review settings.

Ensure `_to_dict` consistently returns a JSON string, handling complex, nested, or mixed data types safely. Added comprehensive tests to validate the serialization logic and edge cases.

Signed-off-by: dnandakumar-nv <dnandakumar@nvidia.com>
@dnandakumar-nv dnandakumar-nv requested a review from a team as a code owner January 28, 2026 21:18
@copy-pr-bot
Copy link

copy-pr-bot bot commented Jan 28, 2026

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@coderabbitai
Copy link

coderabbitai bot commented Jan 28, 2026

Walkthrough

The _to_dict method in SpanExporter was refactored to consistently return JSON string serialization instead of dictionary or raw values. The method now serializes data structures to JSON with fallback to string representation for non-serializable objects. Comprehensive unit tests verify all serialization paths produce string outputs.

Changes

Cohort / File(s) Summary
Production serialization logic
src/nat/observability/exporter/span_exporter.py
Changed _to_dict method return type from dict[str, typing.Any] | typing.Any to str. Refactored logic to serialize output to JSON using json.dumps, with fallback to str(data) for non-serializable objects. Added json import.
Serialization test suite
tests/nat/observability/exporter/test_span_exporter.py
Added TestToDictSerialization test class with comprehensive test cases covering string inputs, dict serialization, Pydantic model handling, list serialization, nested dictionaries, non-serializable objects, and exception fallback behavior. Verifies result is always a string. Added imports for json and BaseModel from pydantic.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: refactoring the _to_dict method to ensure OTLP-compatible JSON serialization, which is the core purpose of this PR.
Docstring Coverage ✅ Passed Docstring coverage is 82.61% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing touches
  • 📝 Generate docstrings

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.

Copy link

@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

🤖 Fix all issues with AI agents
In `@tests/nat/observability/exporter/test_span_exporter.py`:
- Around line 710-715: The test's BrokenModel.model_dump currently accepts
unused kwargs and raises an exception with a message that triggers Ruff TRY003;
rename the parameter from kwargs to _kwargs in BrokenModel.model_dump to mark it
intentionally unused and either remove the exception message or append a `#
noqa: TRY003` to the raise line—update the BrokenModel class (model_dump)
accordingly so Ruff warnings are silenced.
🧹 Nitpick comments (1)
src/nat/observability/exporter/span_exporter.py (1)

326-350: Avoid blind Exception catch or explicitly justify it.

Ruff BLE001 flags this. If the catch-all is intentional for best‑effort serialization, add a # noqa: BLE001 with a short rationale; otherwise, narrow to expected serialization exceptions to avoid masking bugs.

♻️ Minimal fix to silence Ruff while preserving intent
-        except Exception:
-            return str(data)
+        except Exception:  # noqa: BLE001 - best-effort serialization must never fail span export
+            return str(data)

@dnandakumar-nv dnandakumar-nv added bug Something isn't working non-breaking Non-breaking change labels Jan 28, 2026
@dnandakumar-nv
Copy link
Contributor Author

/ok to test 3c89d8d

Copy link
Contributor

@bbednarski9 bbednarski9 left a comment

Choose a reason for hiding this comment

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

an improvement but left some questions above to see if we can harden the serialization to be more robust

elif isinstance(data, list):
result = [item.model_dump(exclude_none=True) if hasattr(item, 'model_dump') else item for item in data]
else:
return str(data)
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm worries that the current implementation could be unreliable in the following ways:

  1. silent failure str(data) could return non serializable json

  2. inconsistent normalization for different inputs like lists and nested dicts.

Something like this could harden it a bit:

'''
def _to_json_string(self, typing.Any) -> str:

def _normalize(obj: typing.Any) -> typing.Any:
    # Pydantic models
    if hasattr(obj, "model_dump"):
        dumped = obj.model_dump(exclude_none=True)
        return _normalize(dumped)

    # Dicts: drop None values, normalize values
    if isinstance(obj, dict):
        return {
            k: _normalize(v)
            for k, v in obj.items()
            if v is not None
        }

    # Lists / tuples: normalize each element
    if isinstance(obj, (list, tuple)):
        return [_normalize(item) for item in obj]

    # Anything else: leave as-is; json.dumps(default=str) will sanitize it
    return obj

try:
    normalized = _normalize(data)
    return json.dumps(normalized, default=str)
except Exception:
    # Last-resort: always valid JSON string, but clearly a fallback
    return str(data)

'''

assert "top_none" not in parsed
assert parsed["level1"]["level2"][0]["key"] == "value"

def test_arbitrary_object_falls_back_to_str(self, exporter):
Copy link
Contributor

Choose a reason for hiding this comment

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

Would a fallback to a json.dumps be more reliable?

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

Labels

bug Something isn't working non-breaking Non-breaking change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants