feat: add UMO config overrides#9147
Conversation
There was a problem hiding this comment.
Sorry @Soulter, your pull request is larger than the review limit of 150000 diff characters
| ) | ||
| ) | ||
| except SessionManagementServiceError as exc: | ||
| return _service_error(exc) |
| except SessionManagementServiceError as exc: | ||
| return _service_error(exc) | ||
| except Exception as exc: | ||
| return _unexpected_error("获取会话配置覆盖项列表失败", exc) |
| ) | ||
| ) | ||
| except SessionManagementServiceError as exc: | ||
| return _service_error(exc) |
| except SessionManagementServiceError as exc: | ||
| return _service_error(exc) | ||
| except Exception as exc: | ||
| return _unexpected_error("更新会话配置覆盖项失败", exc) |
| ) | ||
| ) | ||
| except SessionManagementServiceError as exc: | ||
| return _service_error(exc) |
| except SessionManagementServiceError as exc: | ||
| return _service_error(exc) | ||
| except Exception as exc: | ||
| return _unexpected_error("删除会话配置覆盖项失败", exc) |
| await service.update_session_alias(payload.model_dump(exclude_unset=True)) | ||
| ) | ||
| except SessionManagementServiceError as exc: | ||
| return _service_error(exc) |
| except SessionManagementServiceError as exc: | ||
| return _service_error(exc) | ||
| except Exception as exc: | ||
| return _unexpected_error("更新会话备注失败", exc) |
There was a problem hiding this comment.
Code Review
This pull request replaces the legacy session service and plugin managers with a unified configuration override system, allowing per-event and per-session configuration overrides for different Unified Message Origins (UMOs). It updates the pipeline stages to dynamically resolve the effective configuration and adds blacklist and plugin-disabling capabilities, along with corresponding database migrations and dashboard UI updates. The review feedback suggests several key improvements: raising AttributeError instead of returning None in EffectiveAstrBotConfig.__getattr__ to preserve standard Python behavior, removing a redundant call to get_current_effective_config(), refining payload normalization to prevent key collisions, filtering out None values in knowledge base retrieval to avoid downstream errors, and caching compiled regex patterns in the result decoration stage to improve performance.
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.
| def __getattr__(self, item: str) -> Any: | ||
| try: | ||
| return self[item] | ||
| except KeyError: | ||
| return None |
There was a problem hiding this comment.
Returning None instead of raising AttributeError in __getattr__ is problematic because it breaks standard Python behaviors like hasattr(obj, name) (which will always return True) and getattr(obj, name, default) (which will return None instead of the default value). It is highly recommended to raise AttributeError when a key is not found.
| def __getattr__(self, item: str) -> Any: | |
| try: | |
| return self[item] | |
| except KeyError: | |
| return None | |
| def __getattr__(self, item: str) -> Any: | |
| try: | |
| return self[item] | |
| except KeyError: | |
| raise AttributeError(f"'EffectiveAstrBotConfig' object has no attribute '{item}'") |
| if not umo: | ||
| effective_config = get_current_effective_config() | ||
| if effective_config: | ||
| return effective_config | ||
| return self.confs["default"] |
There was a problem hiding this comment.
The second call to get_current_effective_config() is redundant because if umo is None, get_current_effective_config(None) was already called on line 170 and would have returned the effective config if it existed. We can simplify this block to directly return the default configuration.
| if not umo: | |
| effective_config = get_current_effective_config() | |
| if effective_config: | |
| return effective_config | |
| return self.confs["default"] | |
| if not umo: | |
| return self.confs["default"] |
| if not isinstance(payload, dict): | ||
| return {} | ||
| paths = payload.get("paths", payload) | ||
| if not isinstance(paths, dict): | ||
| return {} |
There was a problem hiding this comment.
Using payload.get("paths", payload) can cause collisions if there is a legitimate configuration path named "paths". A safer way to distinguish between the versioned wrapper dictionary and the raw paths dictionary is to check for the presence of the "version" key.
| if not isinstance(payload, dict): | |
| return {} | |
| paths = payload.get("paths", payload) | |
| if not isinstance(paths, dict): | |
| return {} | |
| if not isinstance(payload, dict): | |
| return {} | |
| paths = payload.get("paths") if "version" in payload else payload | |
| if not isinstance(paths, dict): | |
| return {} |
| kb_helper = await kb_mgr.get_kb(kb_ref) | ||
| if not kb_helper: | ||
| kb_helper = await kb_mgr.get_kb_by_name(kb_ref) | ||
| all_kbs.append(kb_helper) |
There was a problem hiding this comment.
If kb_helper is None (e.g., the knowledge base is not found by ID or name), appending it to all_kbs can cause downstream AttributeErrors in functions like check_all_kb or other retrieval steps. We should only append non-None helpers.
| all_kbs.append(kb_helper) | |
| if kb_helper: | |
| all_kbs.append(kb_helper) |
| if split_words: | ||
| escaped_words = sorted( | ||
| [re.escape(word) for word in split_words], | ||
| key=len, | ||
| reverse=True, | ||
| ) | ||
| split_words_pattern = re.compile( | ||
| f"(.*?({'|'.join(escaped_words)})|.+$)", | ||
| re.DOTALL, | ||
| ) | ||
| else: | ||
| split_words_pattern = None |
There was a problem hiding this comment.
Compiling the split_words_pattern regex on every single message event process is inefficient because compiling regex patterns is CPU-intensive. We can cache the compiled pattern on the stage instance to avoid redundant compilation.
| if split_words: | |
| escaped_words = sorted( | |
| [re.escape(word) for word in split_words], | |
| key=len, | |
| reverse=True, | |
| ) | |
| split_words_pattern = re.compile( | |
| f"(.*?({'|'.join(escaped_words)})|.+$)", | |
| re.DOTALL, | |
| ) | |
| else: | |
| split_words_pattern = None | |
| if not hasattr(self, "_split_words_pattern_cache"): | |
| self._split_words_pattern_cache = {} | |
| split_words_key = tuple(split_words) if split_words else None | |
| if split_words_key not in self._split_words_pattern_cache: | |
| if split_words: | |
| escaped_words = sorted( | |
| [re.escape(word) for word in split_words], | |
| key=len, | |
| reverse=True, | |
| ) | |
| pattern = re.compile( | |
| f"(.*?({'|'.join(escaped_words)})|.+$)", | |
| re.DOTALL, | |
| ) | |
| else: | |
| pattern = None | |
| self._split_words_pattern_cache[split_words_key] = pattern | |
| split_words_pattern = self._split_words_pattern_cache[split_words_key] |
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
astrbot-docs | 31e7af8 | Commit Preview URL Branch Preview URL |
Jul 05 2026, 06:52 AM |
Summary
Validation
Note: dashboard build still reports existing warnings for mdi-platform/mdi-subset and vocechat.png runtime resolution.