Skip to content

fix(ltm): prevent wake commands from being recorded as group chat context#8536

Merged
RC-CHN merged 3 commits into
AstrBotDevs:masterfrom
RC-CHN:exclude-slash-commands-from-group-chat-context
Jun 3, 2026
Merged

fix(ltm): prevent wake commands from being recorded as group chat context#8536
RC-CHN merged 3 commits into
AstrBotDevs:masterfrom
RC-CHN:exclude-slash-commands-from-group-chat-context

Conversation

@RC-CHN
Copy link
Copy Markdown
Member

@RC-CHN RC-CHN commented Jun 3, 2026

Motivation

Slash commands (e.g. /reset, /new, /help) were incorrectly recorded into the group chat context buffer (raw_records) by the on_message handler. For /reset specifically, this caused a timing issue where the command message was re-added after after_message_sent had already cleared the buffer via _clean_group_context_session, leaving stale context that would be injected into future LLM requests.

Changes

  • astrbot/builtin_stars/astrbot/main.py: In the on_message handler, check event.get_extra("handlers_parsed_params", {}) before calling handle_message. This dict is populated by WakingCheckStage only when a CommandFilter successfully matches — i.e. a slash command is triggered. If non-empty, skip recording because the message is a bot instruction, not group chat context. Regular @bot mentions (which also set is_at_or_wake_command but do not match any CommandFilter) continue to be recorded as before.

Impact

  • All built-in slash commands (/reset, /new, /help, /sid, /stop, /stats, /set, /unset, /provider, /dashboard_update) are no longer stored in the group context buffer.
  • @bot mentions and unrecognised /prefix messages (which fall through to the LLM agent) are unaffected.
  • This is NOT a breaking change. / 这不是一个破坏性变更。

Screenshots or Test Results / 运行截图或测试结果

image

Checklist / 检查清单

  • 😊 If there are new features added in the PR, I have discussed it with the authors through issues/emails, etc.
    / 如果 PR 中有新加入的功能,已经通过 Issue / 邮件等方式和作者讨论过。

  • 👀 My changes have been well-tested, and "Verification Steps" and "Screenshots" have been provided above.
    / 我的更改经过了良好的测试,并已在上方提供了“验证步骤”和“运行截图”

  • 🤓 I have ensured that no new dependencies are introduced, OR if new dependencies are introduced, they have been added to the appropriate locations in requirements.txt and pyproject.toml.
    / 我确保没有引入新依赖库,或者引入了新依赖库的同时将其添加到 requirements.txtpyproject.toml 文件相应位置。

  • 😮 My changes do not introduce malicious code.
    / 我的更改没有引入恶意代码。

Summary by Sourcery

Prevent command-like wake messages from being stored in group chat long-term context to avoid stale slash command instructions being injected into future LLM requests.

Bug Fixes:

  • Skip recording messages into group chat context when a command handler has matched, preventing slash commands like /reset or /help from polluting long-term context.

Tests:

  • Adjust group chat context wiring test fixtures to simulate regular messages with no matched command handler.

@dosubot dosubot Bot added size:S This PR changes 10-29 lines, ignoring generated files. area:core The bug / feature is about astrbot's core, backend labels Jun 3, 2026
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request prevents command handler messages (like slash commands) from being recorded into the group chat context. It also updates the unit tests to mock this behavior. The reviewer recommends refactoring the mock for event.get_extra in the tests to use a stateful dictionary with side_effect instead of a static return value, which avoids fragile tests when other keys or default values are requested.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +24 to +25
# Simulate a regular message: no command handler matched.
event.get_extra.return_value = {}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Mocking event.get_extra with a static return value of {} can lead to fragile tests and unexpected behavior. For example, other code paths calling event.get_extra with different keys and default values (such as _clean_group_context_session defaulting to False, or _group_context_raw_idx defaulting to -1) will incorrectly receive {} (which is truthy in some contexts or of the wrong type).

Instead, simulate a stateful get_extra and set_extra using a local dictionary and side_effect to ensure correct default values and state retention.

Suggested change
# Simulate a regular message: no command handler matched.
event.get_extra.return_value = {}
extra = {}
event.get_extra.side_effect = lambda key, default=None: extra.get(key, default)
event.set_extra.side_effect = extra.__setitem__

Copy link
Copy Markdown
Contributor

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 2 issues, and left some high level feedback:

  • Using the truthiness of event.get_extra("handlers_parsed_params", {}) couples behavior to the structure of that payload; consider instead checking for the presence of the key or a dedicated flag (e.g. is_command) so that a valid-but-empty param dict doesn’t accidentally re-enable recording.
  • In make_event, event.get_extra.return_value = {} will return {} for any key, which can hide regressions for other extras; it would be clearer to configure get_extra to only return {} for handlers_parsed_params and behave normally (e.g. raise or return a sentinel) for other keys.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Using the truthiness of `event.get_extra("handlers_parsed_params", {})` couples behavior to the structure of that payload; consider instead checking for the presence of the key or a dedicated flag (e.g. `is_command`) so that a valid-but-empty param dict doesn’t accidentally re-enable recording.
- In `make_event`, `event.get_extra.return_value = {}` will return `{}` for any key, which can hide regressions for other extras; it would be clearer to configure `get_extra` to only return `{}` for `handlers_parsed_params` and behave normally (e.g. raise or return a sentinel) for other keys.

## Individual Comments

### Comment 1
<location path="astrbot/builtin_stars/astrbot/main.py" line_range="175" />
<code_context>
+                # Skip recording if a command handler matched (e.g. /reset,
+                # /help, /new). Slash commands are bot instructions, not group
+                # chat context that should be injected into future LLM requests.
+                if not event.get_extra("handlers_parsed_params", {}):
+                    try:
+                        await self.group_chat_context.handle_message(event)
</code_context>
<issue_to_address>
**suggestion:** The truthiness check on `handlers_parsed_params` may be too implicit and could behave unexpectedly if the value is set but empty.

This relies on the truthiness of `handlers_parsed_params`. If a handler intentionally sets it to an empty dict or another falsy value, this will still treat the message as non-command and add it to group context. If you only need to know whether any handler matched, consider using `None` as the default and checking `is None`, or track a separate boolean like `handler_matched` so behavior isn’t tied to the parameter contents.

Suggested implementation:

```python
            if group_icl_enable:
                # Skip recording if a command handler matched (e.g. /reset,
                # /help, /new). Slash commands are bot instructions, not group
                # chat context that should be injected into future LLM requests.
                handlers_parsed_params = event.get_extra("handlers_parsed_params", None)
                if handlers_parsed_params is None:
                    try:
                        await self.group_chat_context.handle_message(event)
                    except BaseException as e:
                        logger.error(e)

```

This change assumes that elsewhere in the code, command handlers either:
1. Set `handlers_parsed_params` to a non-`None` value when they match (even if empty dict/list/etc.), and
2. Leave it unset (or explicitly `None`) when no handler matches.

If current handlers rely on different behavior (e.g. they sometimes set `handlers_parsed_params` to `None` even when they matched), you should normalize that behavior so that "no handler matched" is consistently represented by an absent key or `None`, and "handler matched" is always a non-`None` value.
</issue_to_address>

### Comment 2
<location path="tests/unit/test_group_chat_context_wiring.py" line_range="24-25" />
<code_context>
     event.message_obj = SimpleNamespace(message=[Plain("hello")])
     event.message_str = "hello"
     event.session_id = "session-1"
+    # Simulate a regular message: no command handler matched.
+    event.get_extra.return_value = {}
     return event

</code_context>
<issue_to_address>
**issue (testing):** Add a dedicated test for slash-command events where `handlers_parsed_params` is non-empty to prove that `group_chat_context.handle_message` is not called.

The helper now covers the regular-message path (no command handler match). Please also add a test for the other branch: when `event.get_extra("handlers_parsed_params", {})` returns a non-empty dict (e.g., a slash command), assert that `group_chat_context.handle_message` is not called. This will guard against slash commands polluting `raw_records` again.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

# Skip recording if a command handler matched (e.g. /reset,
# /help, /new). Slash commands are bot instructions, not group
# chat context that should be injected into future LLM requests.
if not event.get_extra("handlers_parsed_params", {}):
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: The truthiness check on handlers_parsed_params may be too implicit and could behave unexpectedly if the value is set but empty.

This relies on the truthiness of handlers_parsed_params. If a handler intentionally sets it to an empty dict or another falsy value, this will still treat the message as non-command and add it to group context. If you only need to know whether any handler matched, consider using None as the default and checking is None, or track a separate boolean like handler_matched so behavior isn’t tied to the parameter contents.

Suggested implementation:

            if group_icl_enable:
                # Skip recording if a command handler matched (e.g. /reset,
                # /help, /new). Slash commands are bot instructions, not group
                # chat context that should be injected into future LLM requests.
                handlers_parsed_params = event.get_extra("handlers_parsed_params", None)
                if handlers_parsed_params is None:
                    try:
                        await self.group_chat_context.handle_message(event)
                    except BaseException as e:
                        logger.error(e)

This change assumes that elsewhere in the code, command handlers either:

  1. Set handlers_parsed_params to a non-None value when they match (even if empty dict/list/etc.), and
  2. Leave it unset (or explicitly None) when no handler matches.

If current handlers rely on different behavior (e.g. they sometimes set handlers_parsed_params to None even when they matched), you should normalize that behavior so that "no handler matched" is consistently represented by an absent key or None, and "handler matched" is always a non-None value.

Comment on lines +24 to +25
# Simulate a regular message: no command handler matched.
event.get_extra.return_value = {}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (testing): Add a dedicated test for slash-command events where handlers_parsed_params is non-empty to prove that group_chat_context.handle_message is not called.

The helper now covers the regular-message path (no command handler match). Please also add a test for the other branch: when event.get_extra("handlers_parsed_params", {}) returns a non-empty dict (e.g., a slash command), assert that group_chat_context.handle_message is not called. This will guard against slash commands polluting raw_records again.

@RC-CHN RC-CHN merged commit d2f5551 into AstrBotDevs:master Jun 3, 2026
21 checks passed
@RC-CHN RC-CHN deleted the exclude-slash-commands-from-group-chat-context branch June 3, 2026 01:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:core The bug / feature is about astrbot's core, backend size:S This PR changes 10-29 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant