Python: Fix missing methods on the Content class in durable tasks#4738
Python: Fix missing methods on the Content class in durable tasks#4738eavanvalkenburg merged 7 commits intomicrosoft:mainfrom
Content class in durable tasks#4738Conversation
…ft#4719) DurableAgentStateUnknownContent.from_unknown_content() stored raw Content objects without converting them to dicts, causing json.dumps to fail in Azure Durable Functions' entity state serialization. This affected content types not explicitly handled (e.g., mcp_server_tool_call/result). The fix converts Content objects to dicts via to_dict() when storing in DurableAgentStateUnknownContent, and restores them via Content.from_dict() in to_ai_content(). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add to_json() and from_json() methods to the Content class to match the serialization interface provided by SerializationMixin on other model classes. Also fix pre-existing pyright type errors in durabletask's DurableAgentStateUnknownContent.to_ai_content(). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
eavanvalkenburg
left a comment
There was a problem hiding this comment.
Automated Code Review
Reviewers: 4 | Confidence: 86%
✓ Correctness
Clean, straightforward addition of JSON serialization/deserialization methods to the Content class, plus a type-narrowing fix in the durable agent state module. The logic is correct and well-tested. One minor robustness concern:
from_jsonpasses the result ofjson.loadsdirectly tofrom_dictwithout checking it's a dict, which could produce a confusingAttributeErrorinstead of a clearValueErrorif the JSON string represents a non-object type (e.g., a list or string). No blocking issues.
✓ Security Reliability
The diff adds
to_json/from_jsonserialization helpers toContentand refactors a type-narrowing cast in the durable agent state module. The changes are low-risk overall. The main reliability concern is thatfrom_jsonpasses the result ofjson.loadsdirectly tofrom_dictwithout verifying it is a dict, which means non-object JSON inputs (e.g., arrays, scalars) will produce confusing errors instead of a clear validation message.
✓ Test Coverage
The new
to_jsonandfrom_jsonmethods onContentare reasonably well tested: basic serialization/deserialization, theexclude_noneflag, roundtrip, and invalid input (missing 'type'). However, theexcludeparameter ofto_json()has zero test coverage despite being part of the public API, and the**kwargspass-through tojson.dumpsis also untested. The refactor in_durable_agent_state.pyis purely a type-narrowing change with no behavioral difference, so no new tests are needed there.
✓ Design Approach
The diff adds
to_json/from_jsonconvenience methods toContentand fixes a type-narrowing issue inDurableAgentStateUnknownContent.to_ai_content. The JSON serialization methods are thin, correct wrappers around the existingto_dict/from_dictpair and introduce no design problems. The durable-state fix properly casts todict[str, Any]after anisinstanceguard. No fundamentally misguided approaches or leaky abstractions are present. The test coverage is thorough and aligns with the existing roundtrip pattern. The only minor concern is thatfrom_jsonsilently delegates all error handling tojson.loadsandfrom_dict; callers will receive a rawjson.JSONDecodeErroron malformed JSON rather than a domain-levelValueError, which is inconsistent with theValueErrorraised byfrom_dictfor semantic errors — but this is a modest inconsistency rather than a design flaw requiring changes.
Suggestions
- In
from_json, validate thatjson.loads(value)returns adictbefore passing tofrom_dict, and consider wrappingjson.JSONDecodeErrorinto aValueErrorfor a consistent exception contract withfrom_dict. Currently, non-object JSON input (e.g.,"[1,2,3]") raises a confusingAttributeErroron.get("type"), and malformed JSON raisesjson.JSONDecodeErrorrather than theValueErrorthatfrom_dictuses for semantic errors. - Add a test for
Content.to_json(exclude=...)— it's an explicit public API parameter with no coverage. For example, serialize a Content with a known field excluded and assert it's absent from the JSON output. - Consider adding a test that
to_json(**kwargs)forwards options tojson.dumps(e.g.,to_json(indent=2)produces indented output), since the**kwargspass-through is part of the public signature.
Automated review by eavanvalkenburg's agents
eavanvalkenburg
left a comment
There was a problem hiding this comment.
Please move the json.dumps into the durable task
There was a problem hiding this comment.
Pull request overview
Adds first-class JSON string serialization/deserialization for the Python Content type, and updates Durable Task state handling/tests to ensure MCP-related content can be JSON-serialized (addressing the Azure Durable Functions to_json expectation reported in #4719).
Changes:
- Add
Content.to_json()/Content.from_json()implemented via existingto_dict()/from_dict()logic. - Ensure
DurableAgentStateUnknownContentstoresContentinstances as dicts (JSON-serializable) and can restoreContentfrom those dicts. - Add unit tests for
ContentJSON roundtrips and Durable Task JSON serialization scenarios involving MCP content.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| python/packages/core/agent_framework/_types.py | Adds to_json/from_json on Content. |
| python/packages/core/tests/core/test_types.py | Adds tests for Content JSON serialization/deserialization. |
| python/packages/durabletask/agent_framework_durabletask/_durable_agent_state.py | Makes unknown durable content JSON-safe by dict-serializing Content and restoring via Content.from_dict. |
| python/packages/durabletask/tests/test_durable_agent_state.py | Adds Durable Task tests reproducing JSON serialization scenarios with MCP content. |
You can also share your feedback on Copilot code review. Take the survey.
python/packages/durabletask/agent_framework_durabletask/_durable_agent_state.py
Outdated
Show resolved
Hide resolved
…tests - Remove Content.to_json() per reviewer request (comment 3) - Add type guard in Content.from_json() for non-dict JSON (comments 1, 4) - Wrap json.JSONDecodeError as ValueError for consistent exception contract - Add try/except fallback in to_ai_content() for invalid Content dicts (comment 5) - Add test_content_to_dict_exclude_none and test_content_to_dict_exclude_fields (comment 2) - Add test_unknown_content_to_ai_content_fallback_on_invalid_type_dict (comment 5) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Remove the from_json convenience method from Content class per review feedback. This is the same trivial json.loads + from_dict wrapper as to_json which was already removed. Consumers should call json.loads and Content.from_dict directly. Update tests to use Content.from_dict(json.loads(...)) pattern and remove from_json-specific error handling tests (those errors are already covered by json.loads and Content.from_dict). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
giles17
left a comment
There was a problem hiding this comment.
PR title and description still reference to_json and from_json which were removed
to_json and from_json methods to the Content classContent class in durable tasks
Motivation and Context
The
Contentclass exposesto_dict/from_dictfor dictionary serialization but lacks correspondingto_json/from_jsonmethods for JSON string serialization, forcing users to manually calljson.dumps/json.loadsas a workaround.Fixes #4719
Description
The fix adds a
to_jsoninstance method that serializesContentto a JSON string viato_dict, and afrom_jsonclassmethod that deserializes a JSON string back into aContentinstance viafrom_dict. Both methods delegate to the existing dict-based serialization logic to stay consistent. A minor type-narrowing fix inDurableAgentStateUnknownContent.to_ai_contentwas also included to resolve a related type-checking issue withContent.from_dict.Contribution Checklist