Skip to content

feat: support persona custom error reply message with fallback#5547

Merged
zouyonghe merged 2 commits intoAstrBotDevs:masterfrom
zouyonghe:master
Feb 28, 2026
Merged

feat: support persona custom error reply message with fallback#5547
zouyonghe merged 2 commits intoAstrBotDevs:masterfrom
zouyonghe:master

Conversation

@zouyonghe
Copy link
Member

@zouyonghe zouyonghe commented Feb 28, 2026

Summary

  • add persona-level custom_error_message persistence and API support
  • add WebUI persona setting for custom error reply message with i18n updates
  • prefer persona custom error reply across internal agent, third-party runner, and tool-loop LLM error paths
  • keep default error output when custom message is not configured

Validation

  • uv run ruff format .
  • uv run ruff check .
  • uv run pytest tests/test_dashboard.py -q

由 Sourcery 提供的摘要

添加“按人格配置”的可定制错误回退消息,并贯通后端、持久化层和仪表盘 UI,使其在可用时在代理的各类错误路径中生效。

新功能:

  • 允许人格在数据库中存储一个可选的自定义错误回复消息,并通过 API 暴露出来。
  • 在仪表盘中新增表单字段和详情视图,用于创建、编辑和展示人格的自定义错误消息。

增强内容:

  • 在代理事件和运行器中传递来源于人格的自定义错误消息,使 LLM 和第三方错误在可能的情况下优先采用人格特定的措辞,而不是通用错误信息。
  • 扩展人格更新逻辑,以支持对工具、技能和自定义错误消息的部分更新,同时保留未指定的字段。
Original summary in English

Summary by Sourcery

添加可在 persona 级别配置的错误回退消息,这些消息会被持久化存储,通过 API 暴露,并在各类智能体错误路径中复用。

New Features:

  • 允许 persona 在数据库中存储一个可选的自定义错误回复消息,并通过 persona API 和 v3 persona 数据对外暴露。
  • 在控制台(dashboard)中新增 UI 字段和详情视图,用于创建、编辑和展示 persona 自定义错误消息。

Enhancements:

  • 在内部智能体、tool-loop runner、第三方 runner 以及主智能体错误处理流程中传播并优先使用 persona 级自定义错误消息;当未配置时,则回退到现有的通用错误消息。
  • 支持对工具(tools)、技能(skills)以及自定义错误消息进行 persona 部分更新,而不会覆盖未指定的字段。
  • 引入共享的 helper 和一个哨兵常量,用于在各组件间规范化、存储和解析 persona 自定义错误消息。
Original summary in English

Summary by Sourcery

Add persona-level configurable error fallback messages that are persisted, exposed via APIs, and used across agent error paths.

New Features:

  • Allow personas to store an optional custom error reply message in the database and expose it through persona APIs and v3 persona data.
  • Add dashboard UI fields and details views to create, edit, and display persona custom error messages.

Enhancements:

  • Propagate and prefer persona-specific custom error messages across internal agents, tool-loop runners, third-party runners, and main agent error handling while falling back to existing generic messages when not configured.
  • Support partial persona updates for tools, skills, and custom error message without overwriting unspecified fields.
  • Introduce shared helpers and a sentinel constant for normalizing, storing, and resolving persona custom error messages across components.

@dosubot dosubot bot added the size:L This PR changes 100-499 lines, ignoring generated files. label Feb 28, 2026
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request enhances the system's error handling capabilities by allowing users to define custom error messages for each persona. This feature provides a more user-friendly experience by replacing generic system errors with specific, pre-configured messages when an agent's LLM request encounters an issue. The changes span across the core agent logic, database persistence, API, and the WebUI, ensuring a complete and configurable solution.

Highlights

  • Persona-level Custom Error Messages: Introduced the ability to define and persist custom error reply messages for individual personas, allowing for more tailored error handling.
  • WebUI Integration: Added a new input field in the persona settings within the WebUI for configuring custom error messages, complete with internationalization support.
  • Prioritized Error Handling: Implemented logic to prefer persona-specific custom error messages across internal agent, third-party runner, and tool-loop LLM error paths when an LLM request fails.
  • Database and API Support: Extended the database schema and API endpoints to store, retrieve, and update the new custom_error_message field for personas.
  • Fallback Mechanism: Ensured that if a custom error message is not configured for a persona, the system will gracefully fall back to displaying the default error message.
Changelog
  • astrbot/core/agent/runners/tool_loop_agent_runner.py
    • Added a private method _get_persona_custom_error_message to retrieve custom error messages from event extras.
    • Modified the step method to use the persona's custom error message if available, otherwise falling back to a default LLM error message.
  • astrbot/core/astr_agent_run_util.py
    • Introduced a new utility function _get_persona_custom_error_message to extract custom error messages from astr_event.
    • Updated the run_agent function to check for and use a persona's custom error message when an exception occurs.
  • astrbot/core/astr_main_agent.py
    • Added logic to extract the custom_error_message from the persona and store it in the event's extras.
  • astrbot/core/db/init.py
    • Updated the insert_persona and update_persona function signatures to include custom_error_message as an optional parameter.
  • astrbot/core/db/po.py
    • Added a custom_error_message field to the Persona SQLModel with Text type and a description.
    • Included custom_error_message in the Personality TypedDict definition.
  • astrbot/core/db/sqlite.py
    • Added a call to _ensure_persona_custom_error_message_column during database initialization to ensure schema compatibility.
    • Implemented _ensure_persona_custom_error_message_column to add the custom_error_message column to the personas table if it doesn't exist.
    • Updated insert_persona and update_persona methods to pass and handle the custom_error_message parameter.
  • astrbot/core/persona_mgr.py
    • Updated DEFAULT_PERSONA_CONFIG to include custom_error_message.
    • Defined NOT_GIVEN object for optional parameter handling in update_persona.
    • Modified update_persona to accept custom_error_message and pass it to the database update method.
    • Updated create_persona to accept and pass custom_error_message.
    • Included custom_error_message when retrieving persona data for get_v3_persona_data.
  • astrbot/core/pipeline/process_stage/method/agent_sub_stages/internal.py
    • Modified the exception handling block to retrieve and use the persona's custom error message, if available, before sending an error reply.
  • astrbot/core/pipeline/process_stage/method/agent_sub_stages/third_party.py
    • Added custom_error_message as an optional parameter to run_third_party_agent and used it for error replies.
    • Implemented logic in the process method to resolve the persona's custom error message and pass it to run_third_party_agent.
  • astrbot/dashboard/routes/persona.py
    • Included custom_error_message in the JSON response for list_personas and get_persona_detail.
    • Added custom_error_message to the create_persona and update_persona request data handling, including validation.
    • Modified update_persona to conditionally include custom_error_message in the update arguments.
  • dashboard/src/components/shared/PersonaForm.vue
    • Added a new v-textarea component for custom_error_message with label, hint, and clearable functionality.
    • Initialized custom_error_message in personaForm data.
    • Populated custom_error_message when loading an existing persona.
  • dashboard/src/components/shared/PersonaSelector.vue
    • Updated the Persona interface to include an optional custom_error_message field.
  • dashboard/src/i18n/locales/en-US/features/persona.json
    • Added new translation keys for customErrorMessage and customErrorMessageHelp.
  • dashboard/src/i18n/locales/zh-CN/features/persona.json
    • Added new Chinese translation keys for customErrorMessage and customErrorMessageHelp.
  • dashboard/src/stores/personaStore.ts
    • Updated the Persona interface to include custom_error_message as a string or null.
  • dashboard/src/views/persona/PersonaCard.vue
    • Updated the Persona interface to include an optional custom_error_message field.
  • dashboard/src/views/persona/PersonaManager.vue
    • Added a conditional display block to show the custom_error_message in the persona detail view if it exists.
    • Updated the Persona interface to include an optional custom_error_message field.
Activity
  • The author added persona-level custom error message persistence and API support.
  • The author added WebUI persona settings for custom error reply messages with i18n updates.
  • The author ensured persona custom error replies are preferred across internal agent, third-party runner, and tool-loop LLM error paths.
  • The author confirmed that default error output is kept when a custom message is not configured.
  • The author validated the changes by running ruff format ., ruff check ., and pytest tests/test_dashboard.py -q.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@dosubot dosubot bot added the feature:persona The bug / feature is about astrbot AI persona system (system prompt) label Feb 28, 2026
@dosubot
Copy link

dosubot bot commented Feb 28, 2026

Related Documentation

Checked 1 published document(s) in 1 knowledge base(s). No updates required.

How did I do? Any feedback?  Join Discord

Copy link
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 - 我发现了 2 个问题,并给出了一些整体性的反馈:

  • 从当前会话 / persona 中解析 persona.custom_error_message 的逻辑在 astr_main_agent._ensure_persona_and_skillsagent_sub_stages/third_party.process 中被重复实现;建议抽取到一个共享的 helper(例如放到 persona_manager 上或一个小的工具模块中),这样 persona 解析和裁剪的行为可以保持一致,并且在每个 event 上只执行一次。
  • event 的 extras 中读取和规范化 persona_custom_error_message 目前在多个地方实现(ToolLoopAgentRunner._get_persona_custom_error_messageastr_agent_run_util 中的 _get_persona_custom_error_message,以及 internal.py 中的内联逻辑);将其统一收敛到一个通用的 helper,可以减少重复代码,并更容易在不同错误路径上保持一致的回退行为。
  • 用于部分 persona 更新的 NOT_GIVEN 哨兵值,现在同时存在于 DB 层(sqlite.update_persona)和 PersonaManager.update_persona 中;将这个哨兵集中到一个共享模块/类型中会更清晰,避免调用方和实现层不小心混用不同的哨兵对象或语义。
给 AI Agents 的提示词
Please address the comments from this code review:

## Overall Comments
- 从当前会话 / persona 中解析 `persona.custom_error_message` 的逻辑在 `astr_main_agent._ensure_persona_and_skills``agent_sub_stages/third_party.process` 中被重复实现;建议抽取到一个共享的 helper(例如放到 `persona_manager` 上或一个小的工具模块中),这样 persona 解析和裁剪的行为可以保持一致,并且在每个 event 上只执行一次。
-`event` 的 extras 中读取和规范化 `persona_custom_error_message` 目前在多个地方实现(`ToolLoopAgentRunner._get_persona_custom_error_message``astr_agent_run_util` 中的 `_get_persona_custom_error_message`,以及 `internal.py` 中的内联逻辑);将其统一收敛到一个通用的 helper,可以减少重复代码,并更容易在不同错误路径上保持一致的回退行为。
- 用于部分 persona 更新的 `NOT_GIVEN` 哨兵值,现在同时存在于 DB 层(`sqlite.update_persona`)和 `PersonaManager.update_persona` 中;将这个哨兵集中到一个共享模块/类型中会更清晰,避免调用方和实现层不小心混用不同的哨兵对象或语义。

## Individual Comments

### Comment 1
<location path="astrbot/core/agent/runners/tool_loop_agent_runner.py" line_range="81-90" />
<code_context>


 class ToolLoopAgentRunner(BaseAgentRunner[TContext]):
+    def _get_persona_custom_error_message(self) -> str | None:
+        """Read persona-level custom error message from event extras when available."""
+        try:
+            event = getattr(self.run_context.context, "event", None)
+            if event is None or not hasattr(event, "get_extra"):
+                return None
+            raw_message = event.get_extra("persona_custom_error_message")
+            if not isinstance(raw_message, str):
+                return None
+            return raw_message.strip() or None
+        except Exception:
+            return None
+
</code_context>
<issue_to_address>
**suggestion:** 考虑将 persona 自定义错误消息的提取逻辑进行集中管理,以避免重复和行为偏移。

用于读取/规范化 `persona_custom_error_message` 的逻辑现在在这里、`astr_agent_run_util._get_persona_custom_error_message``third_party.process``internal.process` 以及 `_ensure_persona_and_skills` 中都有实现,并且在错误处理和类型检查上存在细微差异。为了保持行为一致(裁剪、`None` 与空字符串的处理、异常处理行为)并便于后续演进,建议抽取一个共享的 helper 或小型模块级工具函数,并在这些调用点统一复用。

建议实现如下:

```python
def _extract_persona_custom_error_message(event) -> str | None:
    """Shared helper to extract a normalized persona custom error message from an event.

    Normalization rules:
    - If the event is None or does not expose `get_extra`, return None.
    - If the extra is not a string, return None.
    - Trim whitespace; if the result is empty, return None.
    - Swallow all exceptions to avoid breaking caller flows on bad extras.
    """
    try:
        if event is None or not hasattr(event, "get_extra"):
            return None

        raw_message = event.get_extra("persona_custom_error_message")

        if not isinstance(raw_message, str):
            return None

        message = raw_message.strip()
        return message or None
    except Exception:
        return None


class ToolLoopAgentRunner(BaseAgentRunner[TContext]):

```

```python
    def _get_persona_custom_error_message(self) -> str | None:
        """Read persona-level custom error message from event extras when available."""
        event = getattr(self.run_context.context, "event", None)
        return _extract_persona_custom_error_message(event)

```

为了在整个项目中真正实现“单一共享 helper”的目标并避免行为偏移:

1.`_extract_persona_custom_error_message` 移动到一个共享模块(例如 `astrbot/core/agent/persona_utils.py` 或类似位置),避免与该 runner 强绑定。
2. 用该共享 helper 替换以下位置中现有的自定义错误消息解析逻辑:
   - `astr_agent_run_util._get_persona_custom_error_message`
   - `third_party.process`
   - `internal.process`
   - `_ensure_persona_and_skills`
   确保它们在裁剪、`None` 与空字符串处理,以及异常处理语义上保持一致。
3. 调整这些模块中的 import,改为引用共享 helper,而不是在本地重新实现这段逻辑。
</issue_to_address>

### Comment 2
<location path="astrbot/core/pipeline/process_stage/method/agent_sub_stages/third_party.py" line_range="118" />
<code_context>
         provider_settings=cfg,
     )

+    custom_error_message = None
+    if persona:
+        raw_custom_error_message = persona.get("custom_error_message")
</code_context>
<issue_to_address>
**issue (complexity):** 建议将 persona 相关的自定义错误消息解析提取到一个单独的异步 helper 方法中,以让 `process` 函数更专注于编排逻辑。

你可以保持当前的新行为不变,但通过将 persona / 错误消息解析抽取到一个小的 helper 中来降低复杂度。这样可以让 `process` 专注于流程编排,同时将分支逻辑、await 调用和错误处理封装起来。

例如,在 `ThirdPartyAgentSubStage` 内:

```python
class ThirdPartyAgentSubStage(Stage):
    ...

    async def _resolve_persona_custom_error_message(
        self, event: MessageEvent
    ) -> str | None:
        try:
            conversation_persona_id = None
            conv_mgr = self.ctx.plugin_manager.context.conversation_manager

            curr_cid = await conv_mgr.get_curr_conversation_id(event.unified_msg_origin)
            if curr_cid:
                conv = await conv_mgr.get_conversation(event.unified_msg_origin, curr_cid)
                if conv:
                    conversation_persona_id = conv.persona_id

            (
                _,
                persona,
                _,
                _,
            ) = await self.ctx.plugin_manager.context.persona_manager.resolve_selected_persona(
                umo=event.unified_msg_origin,
                conversation_persona_id=conversation_persona_id,
                platform_name=event.get_platform_name(),
                provider_settings=self.conf["provider_settings"],
            )

            if not persona:
                return None

            raw_msg = persona.get("custom_error_message")
            if isinstance(raw_msg, str):
                return raw_msg.strip() or None
            return None
        except Exception as e:
            logger.debug("Failed to resolve persona custom error message: %s", e)
            return None
```

然后可以简化 `process`(或主要处理方法):

```python
# before your hook call
custom_error_message = await self._resolve_persona_custom_error_message(event)
event.set_extra("persona_custom_error_message", custom_error_message)

# later when creating the stream
event.set_result(
    MessageEventResult()
    .set_result_content_type(ResultContentType.STREAMING_RESULT)
    .set_async_stream(
        run_third_party_agent(
            runner,
            stream_to_general=False,
            custom_error_message=custom_error_message,
        ),
    ),
)
```

这样既保留了全部功能,也保留了仅用于调试的日志输出,同时把紧耦合的解析逻辑从主控制流中抽离出来。另外,它也为你提供了一个集中位置,可以在需要时与任何 dashboard 端的规范化逻辑进行复用/对齐。
</issue_to_address>

Sourcery 对开源项目是免费的——如果你觉得这些评审有帮助,欢迎分享 ✨
请帮我变得更有用!请在每条评论上点 👍 或 👎,我会根据你的反馈改进评审质量。
Original comment in English

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

  • The logic for resolving persona.custom_error_message from the current conversation/persona is duplicated in astr_main_agent._ensure_persona_and_skills and agent_sub_stages/third_party.process; consider extracting a shared helper (e.g. on persona_manager or a small utility) so persona resolution and trimming behavior stay consistent and only run once per event.
  • Reading and normalizing the persona_custom_error_message from event extras is implemented in multiple places (ToolLoopAgentRunner._get_persona_custom_error_message, _get_persona_custom_error_message in astr_agent_run_util, and inline in internal.py); consolidating this into a single shared helper would reduce duplication and make it easier to keep the fallback behavior uniform across error paths.
  • The use of a NOT_GIVEN sentinel for partial persona updates now exists in both the DB layer (sqlite.update_persona) and PersonaManager.update_persona; it might be clearer to centralize this sentinel in a shared module/type so that callers and implementations cannot accidentally mix different sentinel objects or semantics.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The logic for resolving `persona.custom_error_message` from the current conversation/persona is duplicated in `astr_main_agent._ensure_persona_and_skills` and `agent_sub_stages/third_party.process`; consider extracting a shared helper (e.g. on `persona_manager` or a small utility) so persona resolution and trimming behavior stay consistent and only run once per event.
- Reading and normalizing the `persona_custom_error_message` from `event` extras is implemented in multiple places (`ToolLoopAgentRunner._get_persona_custom_error_message`, `_get_persona_custom_error_message` in `astr_agent_run_util`, and inline in `internal.py`); consolidating this into a single shared helper would reduce duplication and make it easier to keep the fallback behavior uniform across error paths.
- The use of a `NOT_GIVEN` sentinel for partial persona updates now exists in both the DB layer (`sqlite.update_persona`) and `PersonaManager.update_persona`; it might be clearer to centralize this sentinel in a shared module/type so that callers and implementations cannot accidentally mix different sentinel objects or semantics.

## Individual Comments

### Comment 1
<location path="astrbot/core/agent/runners/tool_loop_agent_runner.py" line_range="81-90" />
<code_context>


 class ToolLoopAgentRunner(BaseAgentRunner[TContext]):
+    def _get_persona_custom_error_message(self) -> str | None:
+        """Read persona-level custom error message from event extras when available."""
+        try:
+            event = getattr(self.run_context.context, "event", None)
+            if event is None or not hasattr(event, "get_extra"):
+                return None
+            raw_message = event.get_extra("persona_custom_error_message")
+            if not isinstance(raw_message, str):
+                return None
+            return raw_message.strip() or None
+        except Exception:
+            return None
+
</code_context>
<issue_to_address>
**suggestion:** Consider centralizing persona custom error message extraction to avoid duplication and drift.

This logic for reading/normalizing `persona_custom_error_message` is now duplicated here, in `astr_agent_run_util._get_persona_custom_error_message`, `third_party.process`, `internal.process`, and `_ensure_persona_and_skills`, with subtle differences in error handling and type checks. To keep behavior consistent (trimming, `None` vs empty string, exception behavior) and easier to evolve, please extract a single shared helper or small module-level utility and reuse it across these call sites.

Suggested implementation:

```python
def _extract_persona_custom_error_message(event) -> str | None:
    """Shared helper to extract a normalized persona custom error message from an event.

    Normalization rules:
    - If the event is None or does not expose `get_extra`, return None.
    - If the extra is not a string, return None.
    - Trim whitespace; if the result is empty, return None.
    - Swallow all exceptions to avoid breaking caller flows on bad extras.
    """
    try:
        if event is None or not hasattr(event, "get_extra"):
            return None

        raw_message = event.get_extra("persona_custom_error_message")

        if not isinstance(raw_message, str):
            return None

        message = raw_message.strip()
        return message or None
    except Exception:
        return None


class ToolLoopAgentRunner(BaseAgentRunner[TContext]):

```

```python
    def _get_persona_custom_error_message(self) -> str | None:
        """Read persona-level custom error message from event extras when available."""
        event = getattr(self.run_context.context, "event", None)
        return _extract_persona_custom_error_message(event)

```

To fully realize the “single shared helper” goal project-wide and avoid behavior drift:

1. Move `_extract_persona_custom_error_message` into a shared module (e.g. `astrbot/core/agent/persona_utils.py` or similar) so it’s not tied to this runner.
2. Replace the existing custom-error-message parsing logic in:
   - `astr_agent_run_util._get_persona_custom_error_message`
   - `third_party.process`
   - `internal.process`
   - `_ensure_persona_and_skills`
   with calls to this shared helper, ensuring they all follow the same trimming, `None` vs empty-string, and exception-handling semantics.
3. Adjust imports in those modules to pull in the shared helper instead of re-implementing the logic locally.
</issue_to_address>

### Comment 2
<location path="astrbot/core/pipeline/process_stage/method/agent_sub_stages/third_party.py" line_range="118" />
<code_context>
         provider_settings=cfg,
     )

+    custom_error_message = None
+    if persona:
+        raw_custom_error_message = persona.get("custom_error_message")
</code_context>
<issue_to_address>
**issue (complexity):** Consider extracting the persona-specific custom error message resolution into a dedicated async helper method to keep the `process` function focused on orchestration logic.

You can keep the new behavior but reduce complexity by extracting the persona/error-message resolution into a small helper. That keeps `process` focused on orchestration while encapsulating the branching, awaits, and error handling.

For example, inside `ThirdPartyAgentSubStage`:

```python
class ThirdPartyAgentSubStage(Stage):
    ...

    async def _resolve_persona_custom_error_message(
        self, event: MessageEvent
    ) -> str | None:
        try:
            conversation_persona_id = None
            conv_mgr = self.ctx.plugin_manager.context.conversation_manager

            curr_cid = await conv_mgr.get_curr_conversation_id(event.unified_msg_origin)
            if curr_cid:
                conv = await conv_mgr.get_conversation(event.unified_msg_origin, curr_cid)
                if conv:
                    conversation_persona_id = conv.persona_id

            (
                _,
                persona,
                _,
                _,
            ) = await self.ctx.plugin_manager.context.persona_manager.resolve_selected_persona(
                umo=event.unified_msg_origin,
                conversation_persona_id=conversation_persona_id,
                platform_name=event.get_platform_name(),
                provider_settings=self.conf["provider_settings"],
            )

            if not persona:
                return None

            raw_msg = persona.get("custom_error_message")
            if isinstance(raw_msg, str):
                return raw_msg.strip() or None
            return None
        except Exception as e:
            logger.debug("Failed to resolve persona custom error message: %s", e)
            return None
```

Then `process` (or the main handler method) can be simplified:

```python
# before your hook call
custom_error_message = await self._resolve_persona_custom_error_message(event)
event.set_extra("persona_custom_error_message", custom_error_message)

# later when creating the stream
event.set_result(
    MessageEventResult()
    .set_result_content_type(ResultContentType.STREAMING_RESULT)
    .set_async_stream(
        run_third_party_agent(
            runner,
            stream_to_general=False,
            custom_error_message=custom_error_message,
        ),
    ),
)
```

This keeps all functionality, preserves the debug-only logging, but removes the tightly-coupled resolution block from the main control flow. It also gives you a single place to reuse/align with any dashboard-side normalization logic if needed.
</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.

Copy link
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

此拉取请求为 persona 添加了有用的自定义错误消息功能。这些更改是全面的,涉及数据库、后端逻辑和前端 UI。实现大部分是可靠的。我发现了一些代码重复和可以简化的区域,特别是在代理管道的不同部分如何检索和处理自定义错误消息方面。将这些重复的代码块重构为共享的实用函数将提高代码的可维护性。

Comment on lines +118 to +146
custom_error_message = None
try:
conversation_persona_id = None
conv_mgr = self.ctx.plugin_manager.context.conversation_manager
curr_cid = await conv_mgr.get_curr_conversation_id(event.unified_msg_origin)
if curr_cid:
conv = await conv_mgr.get_conversation(
event.unified_msg_origin, curr_cid
)
if conv:
conversation_persona_id = conv.persona_id
(
_,
persona,
_,
_,
) = await self.ctx.plugin_manager.context.persona_manager.resolve_selected_persona(
umo=event.unified_msg_origin,
conversation_persona_id=conversation_persona_id,
platform_name=event.get_platform_name(),
provider_settings=self.conf["provider_settings"],
)
if persona:
raw_custom_error_message = persona.get("custom_error_message")
if isinstance(raw_custom_error_message, str):
custom_error_message = raw_custom_error_message.strip() or None
except Exception as e:
logger.debug("Failed to resolve persona custom error message: %s", e)
event.set_extra("persona_custom_error_message", custom_error_message)
Copy link
Contributor

Choose a reason for hiding this comment

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

high

这段用于解析人格并检索自定义错误消息的代码块是重复的。在 astrbot/core/astr_main_agent.py_ensure_persona_and_skills 中存在非常相似的逻辑。这段重复的代码应该被重构为一个共享的辅助函数,以提高可维护性并减少冗余。

此外,except Exception as e: 过于宽泛,并且只在 debug 级别记录日志,这可能会在解析人格期间隐藏重要的错误。最好捕获更具体的异常或在更高的级别(如 warning)记录日志。

Comment on lines +46 to +51
def _get_persona_custom_error_message(astr_event) -> str | None:
raw_message = astr_event.get_extra("persona_custom_error_message")
if not isinstance(raw_message, str):
return None
return raw_message.strip() or None

Copy link
Contributor

Choose a reason for hiding this comment

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

medium

这个函数的健壮性不足。如果 astr_event 没有 get_extra 方法,或者 get_extra 抛出异常,它可能会失败。在 astrbot/core/agent/runners/tool_loop_agent_runner.py 中有一个更健壮的类似实现。这里也存在代码重复。建议通过添加 try...except 块来使此函数更安全。理想情况下,此逻辑应位于单个共享的工具函数中以避免重复,但这可能需要进一步重构以避免循环依赖。

def _get_persona_custom_error_message(astr_event) -> str | None:
    try:
        raw_message = astr_event.get_extra("persona_custom_error_message")
        if not isinstance(raw_message, str):
            return None
        return raw_message.strip() or None
    except Exception:
        return None

Comment on lines +245 to +252
custom_error_message = _get_persona_custom_error_message(astr_event)
if custom_error_message:
err_msg = custom_error_message
else:
err_msg = (
f"\n\nAstrBot 请求失败。\n错误类型: {type(e).__name__}\n错误信息: "
f"{e!s}\n\n请在平台日志查看和分享错误详情。\n"
)
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

这个 if/else 块可以使用 or 操作符来简化,这是一种更简洁、更符合 Python 风格的提供回退值的方式。

            custom_error_message = _get_persona_custom_error_message(astr_event)
            err_msg = custom_error_message or (
                f"\n\nAstrBot 请求失败。\n错误类型: {type(e).__name__}\n错误信息: "
                f"{e!s}\n\n请在平台日志查看和分享错误详情。\n"
            )

Comment on lines +369 to +373
custom_error_message = event.get_extra("persona_custom_error_message")
if isinstance(custom_error_message, str):
custom_error_message = custom_error_message.strip() or None
else:
custom_error_message = None
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

此处清理 custom_error_message 的逻辑是多余的。存储在 event.get_extra("persona_custom_error_message") 中的值在设置之前已经被清理(剥离空格并验证为字符串或 None)。您可以通过在使用前仅确保类型正确来简化此部分。

Suggested change
custom_error_message = event.get_extra("persona_custom_error_message")
if isinstance(custom_error_message, str):
custom_error_message = custom_error_message.strip() or None
else:
custom_error_message = None
custom_error_message = event.get_extra("persona_custom_error_message")
if not isinstance(custom_error_message, str):
custom_error_message = None

Comment on lines +59 to +64
err_msg = custom_error_message
if not err_msg:
err_msg = (
f"\nAstrBot 请求失败。\n错误类型: {type(e).__name__}\n"
f"错误信息: {e!s}\n\n请在平台日志查看和分享错误详情。\n"
)
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

这个 if 块可以使用 or 操作符来简化,这是一种更简洁、更符合 Python 风格的提供回退值的方式。

        err_msg = custom_error_message or (
            f"\nAstrBot 请求失败。\n错误类型: {type(e).__name__}\n"
            f"错误信息: {e!s}\n\n请在平台日志查看和分享错误详情。\n"
        )

@zouyonghe
Copy link
Member Author

@sourcery-ai review

Copy link
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 - 我已经审查了你的更改,看起来非常不错!


Sourcery 对开源项目是免费的——如果你喜欢我们的审查,请考虑分享它们 ✨
帮我变得更有用!请在每条评论上点 👍 或 👎,我会根据你的反馈改进后续的审查。
Original comment in English

Hey - I've reviewed your changes and they look great!


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.

@zouyonghe zouyonghe merged commit 7bf44bd into AstrBotDevs:master Feb 28, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature:persona The bug / feature is about astrbot AI persona system (system prompt) size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants