-
Notifications
You must be signed in to change notification settings - Fork 1k
[BREAKING] Python: Add factory pattern to GroupChat and Magentic #3224
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: main
Are you sure you want to change the base?
[BREAKING] Python: Add factory pattern to GroupChat and Magentic #3224
Conversation
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.
Pull request overview
This PR adds factory pattern support to GroupChat and Magentic workflow builders, enabling reusable builder instances for creating multiple workflows with fresh agent instances. The PR also renames with_standard_manager to with_manager to better support both standard and custom manager implementations.
Changes:
- Added
register_participants()and factory support for participant creation in GroupChatBuilder and MagenticBuilder - Added
manager_factoryandagent_factoryoptions to MagenticBuilder'swith_manager()method - Added
orchestrator_factoryoption to GroupChatBuilder'swith_orchestrator()method - Renamed
with_standard_manager()towith_manager()across all samples and tests - Consolidated orchestrator configuration methods in GroupChatBuilder into a single
with_orchestrator()method
Reviewed changes
Copilot reviewed 23 out of 23 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| python/packages/core/agent_framework/_workflows/_magentic.py | Added participant and manager factory support to MagenticBuilder; renamed with_standard_manager to with_manager |
| python/packages/core/agent_framework/_workflows/_group_chat.py | Added participant and orchestrator factory support; consolidated with_agent_orchestrator and with_select_speaker_func into with_orchestrator |
| python/packages/core/agent_framework/_workflows/_handoff.py | Renamed participant_factories() to register_participants() for API consistency |
| python/packages/core/agent_framework/_workflows/_sequential.py | Updated error messages for consistency |
| python/packages/core/agent_framework/_workflows/_concurrent.py | Updated error messages for consistency |
| python/packages/core/tests/workflow/test_magentic.py | Added comprehensive factory tests and updated all method calls |
| python/packages/core/tests/workflow/test_group_chat.py | Added comprehensive factory tests and updated all method calls |
| python/packages/core/tests/workflow/test_handoff.py | Updated method calls to use register_participants() |
| python/packages/core/tests/workflow/test_workflow_kwargs.py | Updated method calls to new API |
| python/samples/* | Updated all sample code to use the new API |
| self._termination_condition: Callable[[list[ChatMessage]], bool | Awaitable[bool]] | None = None | ||
|
|
||
| def participant_factories( | ||
| def register_participants( |
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.
Should we align the HandoffBuilder.register_participants() signature with GroupChatBuilder and MagenticBuilder?
The HandoffBuilder uses Mapping[str, Callable[[], AgentProtocol]] (dict with explicit names) while the other two use Sequence[Callable[[], AgentProtocol | Executor]] (list)
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.
This is by designed.
Agents in Handoff could potentially be configured to handoff only to a subset of other agents, thus requiring identifiers.
| if self._orchestrator: | ||
| return self._orchestrator | ||
|
|
||
| if self._orchestrator_factory: |
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.
Should we inject ParticipantRegistry when orchestrator_factory returns a BaseGroupChatOrchestrator?
Currently, if the factory returns a ChatAgent, the builder wraps it with the resolved participants. But if it returns a BaseGroupChatOrchestrator, it's used as-is, meaning the builder's .participants() / .register_participants() are ignored for orchestration. Is this intentional, or should we align the behavior (or at least document the caveat)?
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.
In my thinking, BaseGroupChatOrchestrator are for very advanced users and they really need to know what they are doing, including providing the right ParticipantRegistry. We can document the caveat.
This also keeps things simple. If we were to inject a ParticipantRegistry, what do we do if the custom orchestrator already configures a ParticipantRegistry? We cannot make the ParticipantRegistry an optional member (by adding a None type) because it's required for the orchestrator to function.
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 think documenting it is fine, then, so we can point devs to it when there are questions. We can't assume that even "very advanced users" may know this caveat unless explicitly stated.
| selection_func: GroupChatSelectionFunction | None = None, | ||
| orchestrator: BaseGroupChatOrchestrator | None = None, | ||
| orchestrator_factory: Callable[[], ChatAgent | BaseGroupChatOrchestrator] | None = None, | ||
| orchestrator_name: str | None = None, |
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.
Have we considered whether the orchestrator_name parameter in with_orchestrator() should apply to all orchestrator types, not just selection_func?
Currently orchestrator_name only affects the selection_func path. Should we allow naming the orchestrator for other configuration methods as well?
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.
It's not necessary. Agent based orchestrator will use the agent name. Custom orchestrator has its own name. It's documented in the comment that this param will only apply when selection_func is set.
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.
why would a selection_func need a name? could you not just take the functionname?
| sure all participants are synced and picking the next speaker according to the defined logic | ||
| until the termination conditions are met. | ||
| There are a few ways to configure the orchestrator: |
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 think we should simplify this, why not do:
agent: ChatAgent | Callable[[], ChatAgent] | None = None,
selection_func: GroupChatSelectionFunction | None = None,
orchestrator: BaseGroupChatOrchestrator | Callable[[], BaseGroupChatOrchestrator] | None = None,
orchestrator_name: str | None = None,This leaves a lot less explaining to do. Maybe also have overloads of this method for the agent route, selection func, or orchestrator route.
| self, | ||
| manager: MagenticManagerBase | None = None, | ||
| *, | ||
| manager_factory: Callable[[], MagenticManagerBase] | None = None, |
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.
same thing, I think taking a single param and providing the option for a concrete instance or a callable makes things simpler
| workflow = ( | ||
| GroupChatBuilder() | ||
| .with_agent_orchestrator(moderator) | ||
| .with_orchestrator(agent=moderator) |
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.
could we not make the verbs used in GroupChat and magentic aligned, so that either both use orchestrator or manager, for me Manager would be more understandable.
| selection_func: GroupChatSelectionFunction | None = None, | ||
| orchestrator: BaseGroupChatOrchestrator | None = None, | ||
| orchestrator_factory: Callable[[], ChatAgent | BaseGroupChatOrchestrator] | None = None, | ||
| orchestrator_name: str | None = None, |
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.
why would a selection_func need a name? could you not just take the functionname?
| sure all participants are synced and picking the next speaker according to the defined logic | ||
| until the termination conditions are met. | ||
| There are a few ways to configure the orchestrator: |
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 think we should simplify this, why not do:
agent: ChatAgent | Callable[[], ChatAgent] | None = None,
selection_func: GroupChatSelectionFunction | None = None,
orchestrator: BaseGroupChatOrchestrator | Callable[[], BaseGroupChatOrchestrator] | None = None,
orchestrator_name: str | None = None,This leaves a lot less explaining to do. Maybe also have overloads of this method for the agent route, selection func, or orchestrator route.
Alternatively you could look into single dispatch for this.
Motivation and Context
This completes #429
Description
with_standard_managertowith_managersince the API supports non-standard magentic manager implementations.participant_factoriesAPI toregister_participantto align with the rest of the orchestrations.Contribution Checklist