# Implementing Custom Group Chat Selectors for Agent Selection in AutoGen

> Implement custom group chat selectors in AutoGen by overriding LLM speaker selection with selector_func or candidate_func for deterministic agent turn taking

- Repository: [Microsoft/autogen](https://github.com/microsoft/autogen)
- Tags: how-to-guide
- Published: 2026-03-07

---

**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`](https://github.com/microsoft/autogen/blob/main/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 in `self._selector_func`, this callable receives the full conversation history and returns a single participant name (or `None`) to force a specific speaker.
- **`candidate_func`**: Stored in `self._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):

1. **Check `selector_func`**: If provided and returns a name, that agent speaks immediately (lines 152-179).
2. **Apply `candidate_func`**: If provided, filter the available participants list (lines 179-196).
3. **LLM selection**: Build a prompt from `{roles}`, `{participants}`, and `{history}` templates and send to `self._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.

```python
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`](https://github.com/microsoft/autogen/blob/main/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.

```python
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`](https://github.com/microsoft/autogen/blob/main/_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.

```python
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`](https://github.com/microsoft/autogen/blob/main/_selector_group_chat.py)). Custom selector functions bypass this retry logic entirely since they return valid names directly.

## Summary

- **`SelectorGroupChat`** accepts `selector_func` and `candidate_func` parameters to override LLM-based speaker selection.
- **`selector_func`** receives message history and returns a specific agent name to force deterministic turn-taking.
- **`candidate_func`** filters 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.py`](https://github.com/microsoft/autogen/blob/main/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_selector_group_chat.py) for the core implementation and [`test_group_chat.py`](https://github.com/microsoft/autogen/blob/main/test_group_chat.py) for 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`](https://github.com/microsoft/autogen/blob/main/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.