Skip to content

Commit 8fca71e

Browse files
authored
[BREAKING] Python: Add factory pattern to handoff orchestration builder (#2844)
* WIP: Factory pattern to handoff * Add factory pattern to concurrent orchestration builder; Next: tests and sample verification * Add tests and improve comments * Fix mypy * Simplify handoff_simple.py * Simplify handoff_autonoumous.py and bug fix * Update readme * Address Copilot comments
1 parent 2bde58f commit 8fca71e

File tree

11 files changed

+1431
-421
lines changed

11 files changed

+1431
-421
lines changed

python/packages/core/agent_framework/_workflows/_agent_executor.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,11 @@ def workflow_output_types(self) -> list[type[Any]]:
105105
return [AgentRunResponse]
106106
return []
107107

108+
@property
109+
def description(self) -> str | None:
110+
"""Get the description of the underlying agent."""
111+
return self._agent.description
112+
108113
@handler
109114
async def run(
110115
self, request: AgentExecutorRequest, ctx: WorkflowContext[AgentExecutorResponse, AgentRunResponse]

python/packages/core/agent_framework/_workflows/_handoff.py

Lines changed: 511 additions & 250 deletions
Large diffs are not rendered by default.

python/packages/core/agent_framework/_workflows/_participant_utils.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,15 +47,13 @@ def wrap_participant(participant: AgentProtocol | Executor, *, executor_id: str
4747
"""Represent `participant` as an `Executor`."""
4848
if isinstance(participant, Executor):
4949
return participant
50+
5051
if not isinstance(participant, AgentProtocol):
5152
raise TypeError(
5253
f"Participants must implement AgentProtocol or be Executor instances. Got {type(participant).__name__}."
5354
)
54-
name = getattr(participant, "name", None)
55-
if executor_id is None:
56-
if not name:
57-
raise ValueError("Agent participants must expose a stable 'name' attribute.")
58-
executor_id = str(name)
55+
56+
executor_id = executor_id or participant.display_name
5957
return AgentExecutor(participant, id=executor_id)
6058

6159

python/packages/core/agent_framework/_workflows/_sequential.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,9 @@ def register_participants(
154154
"Cannot mix .participants([...]) and .register_participants() in the same builder instance."
155155
)
156156

157+
if self._participant_factories:
158+
raise ValueError("register_participants() has already been called on this builder instance.")
159+
157160
if not participant_factories:
158161
raise ValueError("participant_factories cannot be empty")
159162

@@ -171,6 +174,9 @@ def participants(self, participants: Sequence[AgentProtocol | Executor]) -> "Seq
171174
"Cannot mix .participants([...]) and .register_participants() in the same builder instance."
172175
)
173176

177+
if self._participants:
178+
raise ValueError("participants() has already been called on this builder instance.")
179+
174180
if not participants:
175181
raise ValueError("participants cannot be empty")
176182

python/packages/core/agent_framework/openai/_chat_client.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ def _prepare_options(self, messages: MutableSequence[ChatMessage], chat_options:
162162
exclude={
163163
"type",
164164
"instructions", # included as system message
165+
"allow_multiple_tool_calls", # handled separately
165166
}
166167
)
167168

@@ -174,6 +175,8 @@ def _prepare_options(self, messages: MutableSequence[ChatMessage], chat_options:
174175
if web_search_options:
175176
options_dict["web_search_options"] = web_search_options
176177
options_dict["tools"] = self._chat_to_tool_spec(chat_options.tools)
178+
if chat_options.allow_multiple_tool_calls is not None:
179+
options_dict["parallel_tool_calls"] = chat_options.allow_multiple_tool_calls
177180
if not options_dict.get("tools", None):
178181
options_dict.pop("tools", None)
179182
options_dict.pop("parallel_tool_calls", None)

0 commit comments

Comments
 (0)