Skip to content

Fix high-frequency disabled realtime learning calls#195

Merged
EterUltimate merged 2 commits into
NickCharlie:mainfrom
EterUltimate:codex/fix-issue-192-llm-throttle
Jun 11, 2026
Merged

Fix high-frequency disabled realtime learning calls#195
EterUltimate merged 2 commits into
NickCharlie:mainfrom
EterUltimate:codex/fix-issue-192-llm-throttle

Conversation

@EterUltimate

@EterUltimate EterUltimate commented Jun 11, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Fixes [Bug] 筛选模型异常高频调用 #192 by keeping realtime expression learning disabled unless explicitly enabled, and adding a per-group expression-learning cooldown.
  • Disables LLM Hook context injection by default behind enable_llm_hooks to avoid high-frequency model calls when realtime LLM filtering is off.
  • Adds optional WebUI password auth in settings, default off; when enabled, the initial password is self_learning_pwd and the user is forced to change it.

Tests

  • python -m json.tool _conf_schema.json > $null
  • python -m py_compile config.py services\learning\message_pipeline.py services\learning\realtime_processor.py services\hooks\llm_hook_handler.py webui\services\auth_service.py webui\middleware\auth.py webui\blueprints\auth.py webui\services\config_service.py
  • python -m pytest -q tests\unit\test_learning_chain_regressions.py tests\unit\test_feature_delegation.py tests\unit\test_config.py tests\unit\test_config_service.py tests\unit\test_auth_service.py tests\integration\test_auth_blueprint.py tests\integration\test_webui_static_assets.py -o cache_dir=.tmp\pytest-cache --basetemp=.tmp\pytest-issue-192-final (142 passed, 2 warnings)
  • Real plugin directory C:\Users\zacza\Desktop\x\AstrBot\data\plugins\astrbot_plugin_self_learning: py_compile, json schema validation, import smoke, and WebUI/auth/config static subset (105 passed, 2 warnings). Two learning-chain tests are package-name bound to self_learning_EterU and do not collect from the real plugin package name astrbot_plugin_self_learning.

Summary by Sourcery

Tighten realtime learning and LLM hook behavior to avoid unintended high-frequency model calls, and add optional WebUI password authentication with supporting UI, routing, and configuration changes.

New Features:

  • Introduce an optional WebUI password login flow with configurable enable flag, default password, and forced initial password change.
  • Expose configuration toggles for WebUI password auth, realtime expression learning, and LLM hook context injection in the plugin config and WebUI schema.

Bug Fixes:

  • Ensure expression learning is skipped when realtime learning is disabled by default, unless explicitly re-enabled, and gate it with a per-group cooldown interval.
  • Disable LLM Hook context injection by default so that context-fetching calls are only made when explicitly enabled.

Enhancements:

  • Add rate limiting and lockout handling for WebUI login attempts and enforce password strength and migration on change.
  • Extend realtime expression learning with per-group minimum-interval throttling to reduce redundant learning runs.
  • Update WebUI auth middleware and blueprint to respect passwordless vs password modes while keeping backward compatibility.
  • Replace placeholder redirect-only HTML login/change-password pages with functional, styled forms that call the new auth APIs.

Tests:

  • Expand unit and integration tests for auth flows, config handling, LLM hook behavior, and realtime expression learning to cover the new flags and cooldown logic.

@sourcery-ai

sourcery-ai Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Implements safer defaults for realtime learning and LLM hooks, adds a per‑group cooldown for realtime expression learning, and introduces an optional WebUI password authentication flow (with default initial password and forced rotation) wired through config, middleware, blueprints, and tests.

Sequence diagram for WebUI optional password login and password change

sequenceDiagram
    actor User
    participant Browser
    participant AuthBlueprint as auth_blueprint
    participant AuthService as auth_service

    User->>Browser: Open /api/login
    Browser->>auth_blueprint: GET /login
    auth_blueprint->>AuthService: AuthService(get_container)
    alt [auth_service.is_password_enabled]
        alt [is_authenticated]
            auth_blueprint-->>Browser: 302 -> /api/index
        else [not authenticated]
            auth_blueprint-->>Browser: Render login.html
        end
    else [password not enabled]
        auth_blueprint-->>Browser: 302 -> /api/index
    end

    User->>Browser: Submit password
    Browser->>auth_blueprint: POST /login (password)
    auth_blueprint->>AuthService: login(password, client_ip)
    alt [passwordless mode]
        AuthService-->>auth_blueprint: success, redirect /api/index
        auth_blueprint-->>Browser: 200 {redirect:/api/index}
    else [password enabled]
        alt [valid password]
            AuthService-->>auth_blueprint: success, must_change flag
            auth_blueprint->>Browser: 200 {redirect, must_change}
            alt [must_change true]
                Browser->>auth_blueprint: GET /plugin_change_password
                auth_blueprint->>AuthService: is_password_enabled
                auth_blueprint-->>Browser: Render change_password.html

                User->>Browser: Submit old_password, new_password
                Browser->>auth_blueprint: POST /plugin_change_password
                auth_blueprint->>AuthService: change_password(old_password, new_password)
                alt [change success]
                    AuthService-->>auth_blueprint: (True, message)
                    auth_blueprint-->>Browser: 200 {redirect:/api/index}
                else [change failed]
                    AuthService-->>auth_blueprint: (False, error)
                    auth_blueprint-->>Browser: 400 {error}
                end
            else [must_change false]
                note over Browser,AuthService: User goes directly to dashboard
            end
        else [invalid password]
            AuthService-->>auth_blueprint: False, error, remaining_attempts
            auth_blueprint-->>Browser: 401/429 {error}
        end
    end
Loading

File-Level Changes

Change Details Files
Gate realtime expression learning behind an explicit flag and introduce a per‑group cooldown interval.
  • Change message pipeline to only dispatch realtime expression learning when the new enable_realtime_expression_learning flag is true rather than piggybacking on realtime mode alone.
  • Extend RealtimeProcessor to track last expression-learning trigger times per group and skip learning when called again within expression_learning_min_interval_seconds.
  • Add expression_learning_min_interval_seconds to PluginConfig defaults, validation, and mapping from grouped WebUI config, and ensure tests cover default and non-default values.
services/learning/message_pipeline.py
services/learning/realtime_processor.py
config.py
tests/unit/test_learning_chain_regressions.py
tests/unit/test_config.py
Disable LLM Hook context injection by default behind an enable_llm_hooks toggle to avoid high-frequency context fetches and model calls.
  • Add enable_llm_hooks to PluginConfig and WebUI schema, including default false and wiring through grouped Runtime_Internal_Settings.
  • Guard LLMHookHandler.handle with the enable_llm_hooks flag so it returns early without performing any context fetches when disabled.
  • Extend config and config_service tests to assert the new flag is present, defaulted, and synchronized between runtime and persisted config.
services/hooks/llm_hook_handler.py
config.py
webui/services/config_service.py
tests/unit/test_feature_delegation.py
tests/unit/test_config.py
tests/unit/test_config_service.py
Introduce optional WebUI password authentication with default initial password, forced change, and session-based auth while preserving passwordless compatibility by default.
  • Add enable_webui_password to PluginConfig and config schema, with defaults and mapping from Self_Learning_Basic group.
  • Implement AuthService to support: enabling/disabling password mode based on config, loading/saving password.json, default initial password self_learning_pwd with must_change, login attempt tracking/locking, password verification with migration, strength validation, and password change flow.
  • Replace passwordless-only auth blueprint with routes that render login/change-password pages, call AuthService for POST /login and /plugin_change_password, enforce must_change redirect logic, and implement logout that clears session only when password mode is enabled.
  • Extend auth middleware to actually enforce authentication when enable_webui_password is true (redirecting GET /api[/index] to /api/login and returning JSON 401 for other unauthenticated requests) while remaining a no-op in passwordless mode; expose is_authenticated accordingly.
  • Add new HTML templates for login and change_password pages with basic styling and JS that POST to the new JSON APIs and handle redirects.
  • Add focused unit and integration tests for AuthService and auth blueprint covering defaults, password-enabled mode, login, forced change, and password update flows, and modify fixtures to toggle enable_webui_password in tests.
config.py
webui/services/config_service.py
webui/services/auth_service.py
webui/blueprints/auth.py
webui/middleware/auth.py
web_res/static/html/login.html
web_res/static/html/change_password.html
tests/unit/test_auth_service.py
tests/integration/test_auth_blueprint.py

Assessment against linked issues

Issue Objective Addressed Explanation
#192 When realtime learning and realtime LLM filtering are disabled, prevent realtime expression learning from running by default so that the screening model is not called at high frequency in this mode.
#192 Introduce frequency control (e.g., a minimum per‑group interval) for realtime expression learning/persona updates so that, when enabled, they cannot trigger excessively often.
#192 Avoid extra high‑frequency screening/model calls caused by LLM Hook context injection when realtime LLM filtering is off, by gating this behavior behind an explicit configuration flag that defaults to disabled.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

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.

Hey - I've found 1 issue, and left some high level feedback:

  • The logic for determining whether WebUI password auth is enabled is duplicated between AuthService.is_password_enabled() and _webui_password_enabled() in the middleware; consider centralizing this into a single helper or source of truth to avoid future drift between behaviors.
  • In RealtimeProcessor._process_expression_style_learning, _last_expression_learning_times is keyed only by group_id; if you ever introduce per-plugin-instance or per-platform separation, you may want to include additional dimensions (e.g. platform or tenant) in the key to avoid unintended cross-group throttling collisions.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The logic for determining whether WebUI password auth is enabled is duplicated between `AuthService.is_password_enabled()` and `_webui_password_enabled()` in the middleware; consider centralizing this into a single helper or source of truth to avoid future drift between behaviors.
- In `RealtimeProcessor._process_expression_style_learning`, `_last_expression_learning_times` is keyed only by `group_id`; if you ever introduce per-plugin-instance or per-platform separation, you may want to include additional dimensions (e.g. platform or tenant) in the key to avoid unintended cross-group throttling collisions.

## Individual Comments

### Comment 1
<location path="webui/services/auth_service.py" line_range="29-30" />
<code_context>
 logger = get_astrbot_logger("self_learning.webui.auth")
-DEFAULT_PASSWORD_CONFIG = {"must_change": False}
+PASSWORDLESS_PASSWORD_CONFIG = {"must_change": False}
+DEFAULT_WEBUI_PASSWORD = "self_learning_pwd"
+DEFAULT_PASSWORD_CONFIG = {
+    "password": DEFAULT_WEBUI_PASSWORD,
+    "must_change": True,
</code_context>
<issue_to_address>
**🚨 suggestion (security):** Relying on a hardcoded default WebUI password has security implications and might warrant a safer initialization path.

With `enable_webui_password=True` and no stored config, this path uses the hardcoded `DEFAULT_WEBUI_PASSWORD`, so anyone who knows the default can log in until it’s changed, despite `must_change=True`.

Consider instead requiring an explicit initial password (env/config) when enabling the WebUI password, or at least emitting a prominent warning with clear instructions to change it immediately. Another option is to refuse enabling password mode without a persisted config so you never run with a known default secret.

Suggested implementation:

```python
import os

logger = get_astrbot_logger("self_learning.webui.auth")

PASSWORDLESS_PASSWORD_CONFIG = {"must_change": False}
INITIAL_WEBUI_PASSWORD_ENV_VAR = "ASTRBOT_WEBUI_INITIAL_PASSWORD"


def _build_default_password_config() -> Dict[str, Any]:
    """
    Build the default WebUI password configuration.

    This requires an explicit initial password to be provided via
    the environment variable defined in INITIAL_WEBUI_PASSWORD_ENV_VAR.

    If no password is configured, this function logs a prominent warning
    and returns a passwordless configuration instead of using a known
    default secret.
    """
    initial_password = os.getenv(INITIAL_WEBUI_PASSWORD_ENV_VAR)

    if not initial_password:
        logger.warning(
            (
                "WebUI password authentication is enabled but no initial password "
                "is configured. Refusing to use a built-in default password. "
                "Set %s to a strong secret or persist a password configuration "
                "before enabling WebUI password protection."
            ),
            INITIAL_WEBUI_PASSWORD_ENV_VAR,
        )
        # Fall back to a passwordless configuration so we never run with a
        # known default password.
        return PASSWORDLESS_PASSWORD_CONFIG.copy()

    return {
        "password": initial_password,
        "must_change": True,
    }


# NOTE: Do not call _build_default_password_config() eagerly here based solely on
# imports; it should only be invoked in the logic that enables WebUI password
# mode and detects a missing persisted configuration.
DEFAULT_PASSWORD_CONFIG: Dict[str, Any] = PASSWORDLESS_PASSWORD_CONFIG.copy()

```

To fully implement the safer initialization path and avoid ever running with a known default password, you should:

1. Locate the logic in `webui/services/auth_service.py` where:
   - `enable_webui_password` is checked (via `self.plugin_config.enable_webui_password` or similar), and
   - the password configuration is loaded or defaulted (where `DEFAULT_PASSWORD_CONFIG` was previously used when no stored config exists).
2. Replace any direct use of `DEFAULT_PASSWORD_CONFIG` as the "default when nothing is stored" with a call to `_build_default_password_config()` **only in the branch where**:
   - `enable_webui_password` is `True`, and
   - there is no persisted password configuration.
   For example (pseudo‑code):
   - `password_config = _build_default_password_config()` instead of `password_config = DEFAULT_PASSWORD_CONFIG`.
3. In that same branch, after calling `_build_default_password_config()`:
   - If the returned config is `PASSWORDLESS_PASSWORD_CONFIG` (i.e. `config.get("password") is None`), treat this as a configuration error:
     - Either:
       - disable password mode (log at `warning` or `error` level that password auth is being disabled because no explicit password is configured), **or**
       - raise an exception to refuse starting with `enable_webui_password=True` and no configured initial password.
   Pick the behavior that matches your application's startup/error‑handling conventions.
4. Ensure that `DEFAULT_PASSWORD_CONFIG` (now initialized to `PASSWORDLESS_PASSWORD_CONFIG.copy()`) is only used where a passwordless configuration is explicitly desired, and not as a fallback for password‑protected mode.

These additional adjustments will guarantee that:
- You never run the WebUI with a known hardcoded default password.
- Enabling password mode without an explicit initial password results in a clear warning and either disabled password mode or a hard failure, as per your chosen policy.
</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.

Comment thread webui/services/auth_service.py Outdated
@EterUltimate EterUltimate merged commit ade6666 into NickCharlie:main Jun 11, 2026
4 checks passed
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] 筛选模型异常高频调用

1 participant