-
Notifications
You must be signed in to change notification settings - Fork 498
Refactor _to_dict for OTLP-compatible JSON serialization
#1506
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Refactor _to_dict for OTLP-compatible JSON serialization
#1506
Conversation
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>
WalkthroughThe Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes 🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
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. Comment |
There was a problem hiding this 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 blindExceptioncatch or explicitly justify it.Ruff BLE001 flags this. If the catch-all is intentional for best‑effort serialization, add a
# noqa: BLE001with 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)
|
/ok to test 3c89d8d |
bbednarski9
left a comment
There was a problem hiding this 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) |
There was a problem hiding this comment.
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:
-
silent failure str(data) could return non serializable json
-
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): |
There was a problem hiding this comment.
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?
Summary
_to_dictinSpanExporterpreviously returned raw Python objects (LangChain messages, dicts with nestedNonevalues) asinput.value_obj/output.value_objspan attributes, which OTLP's protobuf encoder cannot serialize_to_dictto always return a JSON string viajson.dumps(result, default=str), guaranteeing OTLP compatibility for all input typesNonevalues, arbitrary objects, and the always-returns-string invariantTest plan
tests/nat/observability/exporter/test_span_exporter.py(24 existing + 15 new)By Submitting this PR I confirm:
Summary by CodeRabbit
Bug Fixes
Tests
✏️ Tip: You can customize this high-level summary in your review settings.