Implementing Custom Group Chat Selectors for Agent Selection in AutoGen
You can override AutoGen's LLM-based speaker selection by passing a selector_func or candidate_func to SelectorGroupChat, enabling deterministic or domain-specific agent turn-taking logic.
The microsoft/autogen repository provides a flexible group-chat abstraction where multiple agents collaborate by taking turns speaking. While the default behavior uses an LLM to pick the next speaker, implementing custom group chat selectors for agent selection allows you to enforce business rules, deterministic workflows, or safety guardrails that pure probabilistic selection cannot guarantee.
Understanding the Group Chat Selection Architecture
The SelectorGroupChatManager
When you instantiate SelectorGroupChat, the system creates a SelectorGroupChatManager (a subclass of BaseGroupChatManager) to drive the turn-taking loop. According to the source code in python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_selector_group_chat.py, this manager accepts two optional callables that override LLM selection:
selector_func: Stored inself._selector_func, this callable receives the full conversation history and returns a single participant name (orNone) to force a specific speaker.candidate_func: Stored inself._candidate_func, this callable filters the candidate pool before the LLM is consulted, returning a list of allowed participant names.
If both functions return None or are omitted, the manager falls back to pure LLM-driven selection using the model_client parameter.
The Selection Flow
The speaker selection process follows this exact sequence as implemented in select_speaker (lines 152-196):
- Check
selector_func: If provided and returns a name, that agent speaks immediately (lines 152-179). - Apply
candidate_func: If provided, filter the available participants list (lines 179-196). - LLM selection: Build a prompt from
{roles},{participants}, and{history}templates and send toself._model_client(lines 32-45).
The manager detects whether your functions are synchronous or asynchronous using iscoroutinefunction (lines 94-98), allowing both def and async def implementations.
Implementing a Custom Selector Function
Use selector_func when you need deterministic control over the conversation flow. This function receives the message history and must return the name of the next speaker as a string, or None to defer to the default LLM selection.
from autogen_agentchat.agents import AssistantAgent
from autogen_agentchat.teams import SelectorGroupChat
from autogen_ext.models.openai import OpenAIChatCompletionClient
from autogen_agentchat.conditions import MaxMessageTermination
model_client = OpenAIChatCompletionClient(model="gpt-4o")
# Define three collaborating agents
agent_a = AssistantAgent("AgentA", model_client, description="asks questions")
agent_b = AssistantAgent("AgentB", model_client, description="answers questions")
agent_c = AssistantAgent("AgentC", model_client, description="provides summaries")
# Deterministic selector: forces A → B → C → A rotation
def selector(messages):
if not messages:
return "AgentA"
last = messages[-1].source
rotation = {"AgentA": "AgentB", "AgentB": "AgentC", "AgentC": "AgentA"}
return rotation.get(last, "AgentA")
team = SelectorGroupChat(
participants=[agent_a, agent_b, agent_c],
model_client=model_client,
selector_func=selector,
termination_condition=MaxMessageTermination(6),
)
# The conversation follows the exact order defined in selector()
await team.run(task="Plan a weekend trip.")
This pattern is validated in test_selector_group_chat_custom_selector (lines 61-88 of test_group_chat.py), which proves that custom logic fully governs speaker selection when provided.
Filtering Candidates with candidate_func
Use candidate_func when you want the LLM to make the final choice, but only from a restricted subset of agents. This is useful for safety guardrails or domain-specific routing where certain agents should never speak in specific contexts.
def candidate_filter(messages):
# Initial state: only user and calculator can start
if not messages:
return ["User", "Calculator"]
# After user input, force calculation
if messages[-1].source == "User":
return ["Calculator"]
# After calculation, allow reporting
return ["User", "Reporter"]
team = SelectorGroupChat(
participants=[user_agent, calculator_agent, reporter_agent],
model_client=model_client,
candidate_func=candidate_filter,
termination_condition=MaxMessageTermination(8),
)
As implemented in lines 179-196 of _selector_group_chat.py, the manager calls this function to obtain the participants list that is fed into the LLM prompt, effectively creating a dynamic "conversation graph."
Combining Both Approaches
You can implement both functions simultaneously for hierarchical control. When selector_func returns a specific name, it takes priority and candidate_func is bypassed. When selector_func returns None, the filtered candidate list is used for LLM selection.
def selector(messages):
# Emergency escalation: force moderator intervention on keyword
if any("escalate" in str(msg.content) for msg in messages):
return "Moderator"
return None # Defer to candidate-filtered LLM selection
def candidate_filter(messages):
# Normal operations: only assistant and helper compete
return ["Assistant", "Helper"]
team = SelectorGroupChat(
participants=[assistant, helper, moderator],
model_client=model_client,
selector_func=selector,
candidate_func=candidate_filter,
)
This pattern appears in test_selector_group_chat_custom_candidate_func (lines 101-126), which demonstrates how the candidate pool narrows dynamically on each turn.
Configuration Options and Edge Cases
Preventing Repeated Speakers
The allow_repeated_speaker flag (default False) automatically removes the previous speaker from the candidate list before the LLM runs. However, if your custom selector_func returns the same name consecutively, the manager logs a warning but allows the repetition, respecting your explicit instruction over the default deduplication logic.
Handling Selection Failures
The max_selector_attempts parameter limits how many times the LLM can be reprompted when it returns an invalid participant name. After exhausting attempts, the system falls back to the previous speaker or the first participant (lines 101-108 of _selector_group_chat.py). Custom selector functions bypass this retry logic entirely since they return valid names directly.
Summary
SelectorGroupChatacceptsselector_funcandcandidate_funcparameters to override LLM-based speaker selection.selector_funcreceives message history and returns a specific agent name to force deterministic turn-taking.candidate_funcfilters the available participant pool before LLM selection, enabling dynamic conversation graphs.- Both functions can be synchronous or asynchronous; the manager detects their type automatically.
- The selection priority is: custom selector > candidate filter > LLM selection.
- Refer to
python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_selector_group_chat.pyfor the core implementation andtest_group_chat.pyfor working examples.
Frequently Asked Questions
What is the difference between selector_func and candidate_func in AutoGen?
selector_func makes the final decision by returning a specific agent name, bypassing the LLM entirely. candidate_func only filters which agents are allowed to speak, leaving the final choice to the LLM from that restricted set. If selector_func returns a name, candidate_func is ignored for that turn.
Can I use async functions for custom group chat selectors?
Yes. The SelectorGroupChatManager automatically detects whether your selector_func or candidate_func is a coroutine using iscoroutinefunction (lines 94-98). You can define them with async def if they need to perform I/O operations like database lookups or API calls.
How do I prevent the same agent from speaking twice in a row?
Set allow_repeated_speaker=False when creating the SelectorGroupChat. This removes the previous speaker from the candidate list before LLM selection. Note that this flag does not block a custom selector_func from returning the same agent; it only affects the LLM selection path.
Where can I find working examples of custom selectors in the AutoGen source code?
The repository includes comprehensive tests in python/packages/autogen-agentchat/tests/test_group_chat.py. Specifically, test_selector_group_chat_custom_selector (lines 61-88) demonstrates deterministic rotation logic, while test_selector_group_chat_custom_candidate_func (lines 101-126) shows dynamic candidate filtering using a ReplayChatCompletionClient to isolate the custom logic from LLM calls.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →