Skip to content

fix(agentchat): SelectorGroupChat fallback no longer returns excluded previous speaker#7610

Open
alvinttang wants to merge 1 commit intomicrosoft:mainfrom
alvinttang:fix/selector-fallback-excludes-previous
Open

fix(agentchat): SelectorGroupChat fallback no longer returns excluded previous speaker#7610
alvinttang wants to merge 1 commit intomicrosoft:mainfrom
alvinttang:fix/selector-fallback-excludes-previous

Conversation

@alvinttang
Copy link
Copy Markdown

Summary

Fixes a livelock in SelectorGroupChat when allow_repeated_speaker=False: after the model exhausts max_selector_attempts, _select_speaker unconditionally returned self._previous_speaker — the very speaker that is supposed to be excluded — so if the model kept picking that speaker, the group chat could loop forever on the same agent.

Fixes #7471

Root cause

_select_speaker in _selector_group_chat.py has two fallback arms:

  1. When all max_selector_attempts fail, reuse the previous speaker.
  2. Or, if there is no previous speaker, use the first participant.

Arm (1) ran regardless of allow_repeated_speaker. When that flag is False, the caller has already filtered the previous speaker out of participants, so reusing them breaks the user-visible contract and livelocks if the model keeps selecting them.

Fix

  • When allow_repeated_speaker=True, behaviour is unchanged.
  • When allow_repeated_speaker=False, pick the first candidate in participants that is not self._previous_speaker, with participants[0] as a defensive default.
  • The no-candidate_func path already pre-filters participants; the candidate_func path does not, so the same explicit skip protects both paths.
  • Updated the max_selector_attempts docstring to reflect the new fallback behaviour.

Tests

python/packages/autogen-agentchat/tests/test_group_chat.py:

  • test_selector_group_chat_fall_back_excludes_previous_when_disallowed — default allow_repeated_speaker=False path with three agents; verifies the fallback picks agent1 or agent3, never the repeated agent2.
  • test_selector_group_chat_fall_back_excludes_previous_with_candidate_func — same scenario but driven via candidate_func, which does NOT pre-filter. Places agent2 first in the candidate list to exercise the worst case.
  • test_selector_group_chat_fall_back_to_previous_after_3_attempts — the existing test was adjusted to set allow_repeated_speaker=True, because it was previously codifying the buggy default behaviour.

Locally on darwin/arm64 with uv:

  • pytest tests/test_group_chat.py -k "fall_back_excludes_previous" → 4 passed, 87 deselected.
  • pytest tests/test_group_chat.py -k "selector" → 32 passed.
  • ruff check clean, pyright 0 errors.

Risk notes

Behavioural change is observable only when the selector model exhausts max_selector_attempts; users who relied on the prior "always fall back to previous speaker even when disallowed" behaviour need to opt into it with allow_repeated_speaker=True.

… previous speaker

When `allow_repeated_speaker=False`, exhausting `max_selector_attempts`
caused `_select_speaker` to fall back to `self._previous_speaker` -- the
exact participant that was supposed to be excluded. If the LLM kept
picking the previous speaker, this produced a livelock where the same
agent was chosen forever, silently violating `allow_repeated_speaker=False`.

Fix: only fall back to the previous speaker when repeated speakers are
explicitly allowed. Otherwise fall back to the first participant of the
already-filtered candidate list (which upstream guarantees excludes the
previous speaker). Added a regression test and updated the existing
fallback test to opt into `allow_repeated_speaker=True`, which was its
original intent.

Fixes microsoft#7471
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] SelectorGroupChat livelock: fallback returns excluded previous speaker when allow_repeated_speaker=False

1 participant