Skip to content

fix(agentchat): MessageFilterAgent count=0 returns no messages instead of all#7915

Open
nolanchic wants to merge 2 commits into
microsoft:mainfrom
nolanchic:fix/message-filter-agent-count-zero
Open

fix(agentchat): MessageFilterAgent count=0 returns no messages instead of all#7915
nolanchic wants to merge 2 commits into
microsoft:mainfrom
nolanchic:fix/message-filter-agent-count-zero

Conversation

@nolanchic

Copy link
Copy Markdown

Why are these changes needed?

In MessageFilterAgent._apply_filter, the per-source slice guards used a truthiness check on count:

if source_filter.position == "first" and source_filter.count:   # <- 0 is falsy
    msgs = msgs[: source_filter.count]
elif source_filter.position == "last" and source_filter.count:  # <- 0 is falsy
    msgs = msgs[-source_filter.count :]

Since 0 is falsy, count=0 skipped slicing entirely and returned all messages from that source — the opposite of the intended behavior.

The class docstring states:

If position is None, all messages from that source are included.

So the intended way to keep all messages is position=None; a count=0 with position="first"/"last" should mean zero messages. (PerSourceFilter.count is Optional[int], so 0 is a valid, explicit user input.)

There is a second, subtler issue: even after switching to is not None, the last branch would still return all messages for count=0, because msgs[-0:] is equivalent to msgs[0:] (the whole list) in Python.

Fix

Handle count <= 0 explicitly by returning an empty list, and switch the remaining guards from truthiness to is not None:

if source_filter.count is not None and source_filter.count <= 0:
    # count == 0 means "no messages" (and negative values are not meaningful).
    # Note: msgs[-0:] would return the whole list, so handle this explicitly.
    msgs = []
elif source_filter.position == "first" and source_filter.count is not None:
    msgs = msgs[: source_filter.count]
elif source_filter.position == "last" and source_filter.count is not None:
    msgs = msgs[-source_filter.count :]

Behavior summary:

count position before after
0 first / last all messages no messages
None any unchanged (all messages) unchanged (all messages)
>0 first / last unchanged unchanged

Related issue number

N/A — no existing issue. Edge-case correctness fix; happy to open a tracking issue if maintainers prefer.

Checks

  • I've included any doc changes needed for https://microsoft.github.io/autogen/. (None — internal logic fix.)
  • I've added tests (if relevant) corresponding to the changes introduced in this PR. (New tests/test_message_filter_agent.py covering count=0 for both first/last, count=None preserving all messages, and positive-count sanity. The count=0 last-position case fails on main and passes with the fix.)
  • I've made sure all auto checks have passed. (Local verification below.)

Local verification

cd python
uv run pytest packages/autogen-agentchat/tests/test_message_filter_agent.py -q                  # 4 passed
uv run pytest packages/autogen-agentchat/tests/test_group_chat_graph.py -q                       # 76 passed (no regressions)
uv run ruff format --check packages/autogen-agentchat/src packages/autogen-agentchat/tests       # already formatted
uv run ruff check packages/autogen-agentchat/src packages/autogen-agentchat/tests                # all checks passed
uv run pyright packages/autogen-agentchat/src packages/autogen-agentchat/tests                   # 0 errors
uv run mypy --config-file pyproject.toml packages/autogen-agentchat/src packages/autogen-agentchat/tests  # no issues

nolanchic added 2 commits July 4, 2026 22:47
…d of all

In MessageFilterAgent._apply_filter, the per-source slice guards used a
truthiness check on count ('and source_filter.count'). Since 0 is falsy,
count=0 skipped slicing entirely and returned ALL messages from that
source — the opposite of the intended behavior. The class docstring
states that 'If position is None, all messages from that source are
included', so count=0 (with position set) should mean zero messages.

Additionally, even after switching to 'is not None', the 'last' branch
would still return all messages for count=0 because msgs[-0:] is
equivalent to msgs[0:] (the whole list). Handle count <= 0 explicitly by
returning an empty list.

Add a focused test module covering count=0 (first/last), count=None
(all messages preserved), and positive counts.
Following review feedback, constrain PerSourceFilter.count with
Field(default=None, ge=0) so negative values are rejected at the model
boundary (fail-fast) rather than silently coerced inside _apply_filter.
Since negative values can no longer reach _apply_filter, the defensive
'count <= 0' branch is tightened to 'count == 0' (which still needs
explicit handling because msgs[-0:] returns the whole list).

Also add a docstring to PerSourceFilter clarifying the count semantics:
0 means 'no messages', None means 'do not slice'. Add a test asserting
negative count raises ValidationError at construction.
@nolanchic

Copy link
Copy Markdown
Author

Added a follow-up commit to harden the boundary validation:

  • Constrain PerSourceFilter.count with Field(default=None, ge=0) so negative values are rejected at construction (fail-fast at the model boundary) instead of being silently coerced inside _apply_filter.
  • Since negatives can no longer reach _apply_filter, tightened the defensive count <= 0 branch to count == 0 (still needed because msgs[-0:] returns the whole list).
  • Added a docstring to PerSourceFilter clarifying the count semantics: 0 → no messages, None → do not slice.
  • Added a test asserting count=-1 raises ValidationError at construction.

All checks still green: 81 passed (5 message-filter + 76 graph), ruff/pyright/mypy clean.

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.

1 participant