diff --git a/astrbot/core/agent/runners/tool_loop_agent_runner.py b/astrbot/core/agent/runners/tool_loop_agent_runner.py index 3fb487cbe6..3ec3523809 100644 --- a/astrbot/core/agent/runners/tool_loop_agent_runner.py +++ b/astrbot/core/agent/runners/tool_loop_agent_runner.py @@ -33,7 +33,7 @@ MessageChain, ) from astrbot.core.persona_error_reply import ( - extract_persona_custom_error_message_from_event, + get_user_facing_error_message, ) from astrbot.core.provider.entities import ( LLMResponse, @@ -106,11 +106,6 @@ class ToolLoopAgentRunner(BaseAgentRunner[TContext]): EMPTY_OUTPUT_RETRY_WAIT_MIN_S = 1 EMPTY_OUTPUT_RETRY_WAIT_MAX_S = 4 - 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_from_event(event) - async def _complete_with_assistant_response(self, llm_resp: LLMResponse) -> None: """Finalize the current step as a plain assistant response with no tool calls.""" self.final_llm_resp = llm_resp @@ -517,10 +512,12 @@ async def step(self): self.stats.end_time = time.time() self._transition_state(AgentState.ERROR) self._resolve_unconsumed_follow_ups() - custom_error_message = self._get_persona_custom_error_message() - error_text = custom_error_message or ( - f"LLM 响应错误: {llm_resp.completion_text or '未知错误'}" + logger.error( + "LLM responded with error role: %s", + llm_resp.completion_text or "unknown", ) + event = getattr(self.run_context.context, "event", None) + error_text = get_user_facing_error_message(event) yield AgentResponse( type="err", data=AgentResponseData( diff --git a/astrbot/core/astr_agent_run_util.py b/astrbot/core/astr_agent_run_util.py index eca24699ae..0ee6af9dc9 100644 --- a/astrbot/core/astr_agent_run_util.py +++ b/astrbot/core/astr_agent_run_util.py @@ -1,7 +1,6 @@ import asyncio import re import time -import traceback from collections.abc import AsyncGenerator from astrbot.core import logger @@ -15,7 +14,7 @@ ResultContentType, ) from astrbot.core.persona_error_reply import ( - extract_persona_custom_error_message_from_event, + get_user_facing_error_message, ) from astrbot.core.provider.entities import LLMResponse from astrbot.core.provider.provider import TTSProvider @@ -234,26 +233,15 @@ async def run_agent( break - except Exception as e: + except Exception: if "stop_watcher" in locals() and not stop_watcher.done(): stop_watcher.cancel() try: await stop_watcher except asyncio.CancelledError: pass - logger.error(traceback.format_exc()) - - custom_error_message = extract_persona_custom_error_message_from_event( - astr_event - ) - if custom_error_message: - err_msg = custom_error_message - else: - err_msg = ( - f"Error occurred during AI execution.\n" - f"Error Type: {type(e).__name__}\n" - f"Error Message: {str(e)}" - ) + logger.error("Error occurred during AI execution", exc_info=True) + err_msg = get_user_facing_error_message(astr_event) error_llm_response = LLMResponse( role="err", @@ -270,6 +258,7 @@ async def run_agent( yield MessageChain().message(err_msg) else: astr_event.set_result(MessageEventResult().message(err_msg)) + yield return diff --git a/astrbot/core/persona_error_reply.py b/astrbot/core/persona_error_reply.py index 5a99e0918e..52daa3067c 100644 --- a/astrbot/core/persona_error_reply.py +++ b/astrbot/core/persona_error_reply.py @@ -4,6 +4,7 @@ from typing import Any PERSONA_CUSTOM_ERROR_MESSAGE_EXTRA_KEY = "persona_custom_error_message" +DEFAULT_USER_FACING_ERROR_MESSAGE = "处理请求时出现异常,请稍后再试。" def normalize_persona_custom_error_message(value: object) -> str | None: @@ -34,6 +35,22 @@ def extract_persona_custom_error_message_from_event(event: Any) -> str | None: return None +def get_user_facing_error_message( + event: Any, + fallback_message: object = DEFAULT_USER_FACING_ERROR_MESSAGE, +) -> str: + """Resolve the user-facing error message with persona override support.""" + custom_error_message = extract_persona_custom_error_message_from_event(event) + if custom_error_message: + return custom_error_message + + normalized_fallback = normalize_persona_custom_error_message(fallback_message) + if normalized_fallback: + return normalized_fallback + + return DEFAULT_USER_FACING_ERROR_MESSAGE + + def set_persona_custom_error_message_on_event( event: Any, message: object ) -> str | None: diff --git a/astrbot/core/pipeline/process_stage/method/agent_sub_stages/internal.py b/astrbot/core/pipeline/process_stage/method/agent_sub_stages/internal.py index 1a04e3a48e..ce829e3655 100644 --- a/astrbot/core/pipeline/process_stage/method/agent_sub_stages/internal.py +++ b/astrbot/core/pipeline/process_stage/method/agent_sub_stages/internal.py @@ -21,6 +21,10 @@ ) from astrbot.core.persona_error_reply import ( extract_persona_custom_error_message_from_event, + get_user_facing_error_message, + resolve_event_conversation_persona_id, + resolve_persona_custom_error_message, + set_persona_custom_error_message_on_event, ) from astrbot.core.pipeline.stage import Stage from astrbot.core.platform.astr_message_event import AstrMessageEvent @@ -138,6 +142,39 @@ async def initialize(self, ctx: PipelineContext) -> None: max_quoted_fallback_images=settings.get("max_quoted_fallback_images", 20), ) + async def _prime_persona_custom_error_message( + self, event: AstrMessageEvent + ) -> None: + if extract_persona_custom_error_message_from_event(event) is not None: + return + + try: + conversation_persona_id = None + provider_request = event.get_extra("provider_request") + if ( + isinstance(provider_request, ProviderRequest) + and provider_request.conversation is not None + ): + conversation_persona_id = provider_request.conversation.persona_id + + if conversation_persona_id is None: + conversation_persona_id = await resolve_event_conversation_persona_id( + event, + self.conv_manager, + ) + + custom_error_message = await resolve_persona_custom_error_message( + event=event, + persona_manager=self.ctx.plugin_manager.context.persona_manager, + provider_settings=self.main_agent_cfg.provider_settings, + conversation_persona_id=conversation_persona_id, + ) + except Exception as exc: + logger.debug("Failed to prime persona custom error message: %s", exc) + return + + set_persona_custom_error_message_on_event(event, custom_error_message) + async def process( self, event: AstrMessageEvent, provider_wake_prefix: str ) -> AsyncGenerator[None, None]: @@ -164,6 +201,7 @@ async def process( logger.debug("skip llm request: empty message and no provider_request") return + await self._prime_persona_custom_error_message(event) logger.debug("ready to request llm provider") follow_up_capture = try_capture_follow_up(event) if follow_up_capture: @@ -381,14 +419,9 @@ async def process( if runner_registered and agent_runner is not None: unregister_active_runner(event.unified_msg_origin, agent_runner) - except Exception as e: - logger.error(f"Error occurred while processing agent: {e}") - custom_error_message = extract_persona_custom_error_message_from_event( - event - ) - error_text = custom_error_message or ( - f"Error occurred while processing agent request: {e}" - ) + except Exception: + logger.error("Error occurred while processing agent", exc_info=True) + error_text = get_user_facing_error_message(event) await event.send(MessageChain().message(error_text)) finally: if typing_requested: diff --git a/astrbot/core/pipeline/process_stage/method/agent_sub_stages/third_party.py b/astrbot/core/pipeline/process_stage/method/agent_sub_stages/third_party.py index 070ad7bdee..e1a0a774ee 100644 --- a/astrbot/core/pipeline/process_stage/method/agent_sub_stages/third_party.py +++ b/astrbot/core/pipeline/process_stage/method/agent_sub_stages/third_party.py @@ -24,6 +24,7 @@ ResultContentType, ) from astrbot.core.persona_error_reply import ( + get_user_facing_error_message, resolve_event_conversation_persona_id, resolve_persona_custom_error_message, set_persona_custom_error_message_on_event, @@ -79,15 +80,9 @@ async def run_third_party_agent( yield resp.data["chain"], False elif resp.type == "err": yield resp.data["chain"], True - except Exception as e: - logger.error(f"Third party agent runner error: {e}") - err_msg = custom_error_message - if not err_msg: - err_msg = ( - f"Error occurred during AI execution.\n" - f"Error Type: {type(e).__name__} (3rd party)\n" - f"Error Message: {str(e)}" - ) + except Exception: + logger.error("Third party agent runner error", exc_info=True) + err_msg = get_user_facing_error_message(None, custom_error_message) yield MessageChain().message(err_msg), True