Python: Fix conversation-id propagation when chat_options is a dict#4340
Python: Fix conversation-id propagation when chat_options is a dict#4340moonbox3 wants to merge 1 commit intomicrosoft:mainfrom
Conversation
_update_conversation_id assumed chat_options had attribute access, but ChatOptions is a TypedDict (dict). When a dict was passed, setting .conversation_id raised AttributeError. Now checks isinstance(dict) and uses key access for dicts, falling back to attribute access for objects. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
moonbox3
left a comment
There was a problem hiding this comment.
Automated Code Review
Reviewers: 3 | Confidence: 92%
✓ Correctness
The change correctly extends
_update_conversation_idto handlechat_optionspassed as a plain dict (or TypedDict) in addition to an object with attribute access. Theisinstance(chat_opts, dict)check is correct since TypedDict instances are dicts at runtime, and the fallback to attribute-based access for non-dict objects preserves existing behavior. Tests cover the key scenarios adequately. No correctness issues found.
✓ Security Reliability
This diff adds a type check so that
_update_conversation_idhandleschat_optionsas either a dict (key access) or an object (attribute access), along with thorough unit tests. The change is straightforward and low-risk from a security and reliability perspective. No injection risks, secrets, resource leaks, or unsafe deserialization are introduced. The isinstance check is a reasonable way to distinguish the two cases.
✓ Test Coverage
The new tests in TestUpdateConversationId provide solid coverage of the dict-vs-object branching logic added to _update_conversation_id. All key code paths are exercised: dict chat_options, TypedDict chat_options, object chat_options, absent chat_options, None conversation_id early return, and the optional options parameter. Assertions are meaningful and verify correct values rather than just absence of exceptions. No blocking issues found.
Suggestions
- Minor: consider whether a Mapping ABC check (
isinstance(chat_opts, collections.abc.Mapping)) would be more robust thanisinstance(chat_opts, dict), in case a non-dict mapping type is ever passed. Not blocking since current callers only use dict/TypedDict/objects. - Consider whether a
TypedDictsubclass could bypass theisinstance(chat_opts, dict)check in edge cases (it shouldn't, sinceTypedDictinstances are plain dicts at runtime, but worth a mental note). - Consider adding a test that passes an object-style chat_options together with the optional
optionsdict to verify both are updated correctly in the attribute-access branch (currentlytest_options_dict_also_updatedonly exercises the dict branch). - Consider a test where the dict already contains a previous conversation_id to verify it gets overwritten, confirming idempotent behavior.
Automated review by moonbox3's agents
There was a problem hiding this comment.
Pull request overview
Fixes conversation-id propagation during function invocation when chat_options is provided via kwargs as a plain dict/TypedDict, avoiding an AttributeError and ensuring the conversation id is correctly carried forward across tool iterations.
Changes:
- Update
_update_conversation_idto use key assignment whenkwargs["chat_options"]is adict. - Add unit tests covering dict,
TypedDict, object-style options, kwargs fallback behavior, no-op onNone, and updating the optionaloptionsdict.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
python/packages/core/agent_framework/_tools.py |
Fixes _update_conversation_id to support dict-based chat_options by using key assignment. |
python/packages/core/tests/core/test_function_invocation_logic.py |
Adds regression tests for _update_conversation_id across dict/object/fallback/no-op cases. |
Python Test Coverage Report •
Python Unit Test Overview
|
||||||||||||||||||||||||||||||
| if "chat_options" in kwargs: | ||
| kwargs["chat_options"].conversation_id = conversation_id | ||
| chat_opts = kwargs["chat_options"] | ||
| if isinstance(chat_opts, dict): |
There was a problem hiding this comment.
Chat options should always be a dict, afaik
Motivation and Context
When
chat_optionswas passed as a plaindict(orTypedDict) in kwargs,_update_conversation_idcrashed with anAttributeErrorbecause it unconditionally used attribute assignment (chat_opts.conversation_id = ...) instead of key-based access.Fixes #4305
Description
The root cause was that
_update_conversation_idassumedchat_optionswas always an object supporting attribute assignment, but callers could pass it as adictorTypedDict. The fix adds anisinstance(chat_opts, dict)check and uses dictionary key assignment for dict-like values while preserving attribute assignment for object-style options. Unit tests covering dict, TypedDict, object, fallback, and no-op cases were added to prevent regression.Contribution Checklist