Skip to content
Merged
17 changes: 6 additions & 11 deletions python/packages/core/agent_framework/_workflows/_concurrent.py
Original file line number Diff line number Diff line change
Expand Up @@ -292,9 +292,7 @@ class MyCustomExecutor(Executor): ...
wf2 = ConcurrentBuilder().register_participants([create_researcher, MyCustomExecutor]).build()
"""
if self._participants:
raise ValueError(
"Cannot mix .participants([...]) and .register_participants() in the same builder instance."
)
raise ValueError("Cannot mix .participants() and .register_participants() in the same builder instance.")

if self._participant_factories:
raise ValueError("register_participants() has already been called on this builder instance.")
Expand Down Expand Up @@ -330,9 +328,7 @@ def participants(self, participants: Sequence[AgentProtocol | Executor]) -> "Con
wf2 = ConcurrentBuilder().participants([researcher_agent, my_custom_executor]).build()
"""
if self._participant_factories:
raise ValueError(
"Cannot mix .participants([...]) and .register_participants() in the same builder instance."
)
raise ValueError("Cannot mix .participants() and .register_participants() in the same builder instance.")

if self._participants:
raise ValueError("participants() has already been called on this builder instance.")
Expand Down Expand Up @@ -498,6 +494,10 @@ def with_request_info(

def _resolve_participants(self) -> list[Executor]:
"""Resolve participant instances into Executor objects."""
if not self._participants and not self._participant_factories:
raise ValueError("No participants provided. Call .participants() or .register_participants() first.")
# We don't need to check if both are set since that is handled in the respective methods

participants: list[Executor | AgentProtocol] = []
if self._participant_factories:
# Resolve the participant factories now. This doesn't break the factory pattern
Expand Down Expand Up @@ -549,11 +549,6 @@ def build(self) -> Workflow:

workflow = ConcurrentBuilder().participants([agent1, agent2]).build()
"""
if not self._participants and not self._participant_factories:
raise ValueError(
"No participants provided. Call .participants([...]) or .register_participants([...]) first."
)

# Internal nodes
dispatcher = _DispatchToAllParticipants(id="dispatcher")
aggregator = (
Expand Down
295 changes: 180 additions & 115 deletions python/packages/core/agent_framework/_workflows/_group_chat.py

Large diffs are not rendered by default.

41 changes: 15 additions & 26 deletions python/packages/core/agent_framework/_workflows/_handoff.py
Original file line number Diff line number Diff line change
Expand Up @@ -603,7 +603,7 @@ def __init__(
self._participant_factories: dict[str, Callable[[], AgentProtocol]] = {}
self._start_id: str | None = None
if participant_factories:
self.participant_factories(participant_factories)
self.register_participants(participant_factories)

if participants:
self.participants(participants)
Expand All @@ -623,7 +623,7 @@ def __init__(
# Termination related members
self._termination_condition: Callable[[list[ChatMessage]], bool | Awaitable[bool]] | None = None

def participant_factories(
def register_participants(
self, participant_factories: Mapping[str, Callable[[], AgentProtocol]]
) -> "HandoffBuilder":
"""Register factories that produce agents for the handoff workflow.
Expand All @@ -641,7 +641,7 @@ def participant_factories(
Self for method chaining.

Raises:
ValueError: If participant_factories is empty or `.participants(...)` or `.participant_factories(...)`
ValueError: If participant_factories is empty or `.participants(...)` or `.register_participants(...)`
has already been called.

Example:
Expand Down Expand Up @@ -670,17 +670,14 @@ def create_billing_agent() -> ChatAgent:

# Handoff will be created automatically unless specified otherwise
# The default creates a mesh topology where all agents can handoff to all others
builder = HandoffBuilder().participant_factories(factories)
builder = HandoffBuilder().register_participants(factories)
builder.with_start_agent("triage")
"""
if self._participants:
raise ValueError(
"Cannot mix .participants([...]) and .participant_factories() in the same builder instance."
)
raise ValueError("Cannot mix .participants() and .register_participants() in the same builder instance.")

if self._participant_factories:
raise ValueError("participant_factories() has already been called on this builder instance.")

raise ValueError("register_participants() has already been called on this builder instance.")
if not participant_factories:
raise ValueError("participant_factories cannot be empty")

Expand All @@ -698,8 +695,8 @@ def participants(self, participants: Sequence[AgentProtocol]) -> "HandoffBuilder
Self for method chaining.

Raises:
ValueError: If participants is empty, contains duplicates, or `.participants(...)` or
`.participant_factories(...)` has already been called.
ValueError: If participants is empty, contains duplicates, or `.participants()` or
`.register_participants()` has already been called.
TypeError: If participants are not AgentProtocol instances.

Example:
Expand All @@ -718,9 +715,7 @@ def participants(self, participants: Sequence[AgentProtocol]) -> "HandoffBuilder
builder.with_start_agent(triage)
"""
if self._participant_factories:
raise ValueError(
"Cannot mix .participants([...]) and .participant_factories() in the same builder instance."
)
raise ValueError("Cannot mix .participants() and .register_participants() in the same builder instance.")

if self._participants:
raise ValueError("participants have already been assigned")
Expand Down Expand Up @@ -900,7 +895,7 @@ def with_start_agent(self, agent: str | AgentProtocol) -> "HandoffBuilder":
if agent not in self._participant_factories:
raise ValueError(f"Start agent factory name '{agent}' is not in the participant_factories list")
else:
raise ValueError("Call participant_factories(...) before with_start_agent(...)")
raise ValueError("Call register_participants(...) before with_start_agent(...)")
self._start_id = agent
elif isinstance(agent, AgentProtocol):
resolved_id = self._resolve_to_id(agent)
Expand Down Expand Up @@ -1043,15 +1038,6 @@ def build(self) -> Workflow:
ValueError: If participants or coordinator were not configured, or if
required configuration is invalid.
"""
if not self._participants and not self._participant_factories:
raise ValueError(
"No participants or participant_factories have been configured. "
"Call participants(...) or participant_factories(...) first."
)

if self._start_id is None:
raise ValueError("Must call with_start_agent(...) before building the workflow.")

# Resolve agents (either from instances or factories)
# The returned map keys are either executor IDs or factory names, which is need to resolve handoff configs
resolved_agents = self._resolve_agents()
Expand All @@ -1062,6 +1048,8 @@ def build(self) -> Workflow:
executors = self._resolve_executors(resolved_agents, resolved_handoffs)

# Build the workflow graph
if self._start_id is None:
raise ValueError("Must call with_start_agent(...) before building the workflow.")
start_executor = executors[self._resolve_to_id(resolved_agents[self._start_id])]
builder = WorkflowBuilder(
name=self._name,
Expand Down Expand Up @@ -1100,8 +1088,9 @@ def _resolve_agents(self) -> dict[str, AgentProtocol]:
Returns:
Map of executor IDs or factory names to `AgentProtocol` instances
"""
if self._participants and self._participant_factories:
raise ValueError("Cannot have both executors and participant_factories configured")
if not self._participants and not self._participant_factories:
raise ValueError("No participants provided. Call .participants() or .register_participants() first.")
# We don't need to check if both are set since that is handled in the respective methods

if self._participants:
return self._participants
Expand Down
Loading
Loading