Uh oh!
There was an error while loading. Please reload this page.
Python: Fix core observability unsafe serialization of function-call arguments containing dataclass/framework objects - #6026
Conversation
Apply make_json_safe() to content.arguments in _to_otel_part() before building the otel message dict, so that dataclass/framework payloads (e.g. workflow request_info events) do not cause a TypeError when _capture_messages() calls json.dumps(). Lift make_json_safe() into agent_framework._serialization (no new external deps — dataclasses/datetime only) so the core observability path can use it without a dependency on the ag-ui adapter. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…bility (microsoft#5733) - Add make_json_safe() helper to recursively convert non-serializable objects - Use make_json_safe() in _to_otel_part() for function_call arguments - Fix CustomPayload test class to use @DataClass (resolves B903 lint error) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR fixes a runtime failure in Python core observability where OpenTelemetry span attributes could not be serialized when function_call arguments contained dataclasses or framework objects (common in workflow request_info / handoff flows), by introducing a safe-serialization step and adding regression tests.
Changes:
- Add
make_json_safe()helper in core serialization utilities and apply it tofunction_callarguments in_to_otel_part. - Add tests ensuring dataclass and nested-object tool arguments are serializable and
_capture_messagesdoesn’t raise. - Minor formatting/import cleanups in Foundry hosting and A2A tests, plus a lockfile metadata normalization.
Reviewed changes
Copilot reviewed 6 out of 7 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| python/packages/core/agent_framework/observability.py | Serializes function_call arguments via make_json_safe before emitting OTel message parts. |
| python/packages/core/agent_framework/_serialization.py | Introduces make_json_safe() recursive conversion helper for JSON-serializable telemetry payloads. |
| python/packages/core/tests/core/test_observability.py | Adds regression tests for dataclass/nested objects in tool-call arguments and span capture. |
| python/packages/foundry_hosting/agent_framework_foundry_hosting/_responses.py | Consolidates imports (minor cleanup). |
| python/packages/foundry_hosting/tests/test_responses.py | Adds spacing to satisfy formatting/lint expectations around region boundaries. |
| python/packages/a2a/tests/test_a2a_agent.py | Formats a test function signature to a single line. |
| python/uv.lock | Normalizes dependency specifier ordering for github-copilot-sdk. |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Python Test Coverage Report •
Python Unit Test Overview
| |||||||||||||||||||||||||||||||||||||||||||||
Evan Mattson (moonbox3)
left a comment
There was a problem hiding this comment.
Automated Code Review
Reviewers: 2 | Confidence: 92% | Result: All clear
Reviewed: Security Reliability, Design Approach
Automated review by moonbox3's agents
…_json_safe (microsoft#5733) - Use callable(getattr(obj, method, None)) instead of hasattr() so that non-callable attributes named model_dump/to_dict/dict do not raise TypeError at runtime. - Wrap each call in try/except TypeError to handle callables with mandatory arguments gracefully. - Convert dict keys to str() so that non-string keys (e.g. datetime, int) cannot cause json.dumps to raise TypeError. - Add regression tests for both scenarios. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Evan Mattson (moonbox3)
left a comment
There was a problem hiding this comment.
Automated Code Review
Reviewers: 4 | Confidence: 86%
✓ Correctness
The PR correctly fixes two bugs: (1)
hasattrchecks replaced withcallable(getattr(..., None))to skip non-callable attributes namedmodel_dump/to_dict/dict, and (2) dict keys are now stringified sojson.dumpsnever raisesTypeErroron non-string keys. Thetry/except TypeErrorwrappers provide a safe fallback chain. The__dict__branch retains barekeyreferences butvars(obj)always yields string keys, so no issue there. No correctness problems found.
✓ Security Reliability
The PR correctly fixes two bugs: (1) using
callable(getattr(...))to guard against non-callable attributes namedmodel_dump/to_dict/dict, and (2) converting dict keys to strings sojson.dumpsnever raisesTypeErroron non-string keys. Both fixes are well-motivated and tested. One reliability concern: thestr(key)conversion at line 656 can silently clobber data when a dict contains both an integer key and its string equivalent (e.g.,{42: 'a', '42': 'b'}), since iteration order determines which value survives with no warning. This is a silent data-loss scenario not covered by the new tests. TheTypeErrorsuppression around serialization method calls is reasonable in scope sinceTypeErroron a zero-argument call is a narrow signal, but it could mask bugs inmodel_dump/to_dictimplementations that raiseTypeErrorinternally — the fallback to__dict__may then silently serialize incorrect/partial state.
✓ Test Coverage
The fix correctly guards against non-callable
model_dump/to_dict/dictattributes and converts dict keys to strings. Two test coverage gaps: (1) the assertion intest_make_json_safe_non_callable_method_attributeis trivially true (json.dumpsnever returnsNone), masking whether the correct fallback value is returned; (2) the newtry/except TypeErrorpath — where a callablemodel_dump()raisesTypeErrorand falls through — is never exercised by a test.
✓ Design Approach
I did not find a design-approach issue in this bounded pass. The change stays focused on making observability serialization tolerate the two concrete edge cases covered by the diff: non-callable serializer-like attributes and non-string dict keys that would otherwise break JSON encoding.
Automated review by moonbox3's agents
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
…icts # Conflicts: # python/packages/foundry_hosting/agent_framework_foundry_hosting/_responses.py
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Motivation and Context
Core observability in
agent_framework/observability.pypassed rawfunction_callarguments directly intojson.dumps, which raisesTypeErrorwhen those arguments contain dataclass or framework objects (e.g.HandoffAgentUserRequest) emitted by workflowrequest_info/ handoff flows. This affected any Foundry-hosted or handoff scenario with sensitive telemetry enabled.Fixes#5733
Description
The root cause was that
_to_otel_part'sfunction_callbranch keptcontent.argumentsas-is, with no safe-serialization step, mirroring the already-fixed AG-UI path. The fix lifts amake_json_safe()helper intoagent_framework/_serialization.py— which recursively converts dataclasses, Pydantic models, datetimes, and__dict__-bearing objects to JSON-serializable primitives — and calls it oncontent.argumentsbefore the value is included in the OTel span attribute. Tests covering dataclass payloads and nested non-primitive objects infunction_callarguments were added totest_observability.pyto prevent regression.Contribution Checklist