From 7bf44bd8d29f84328e09b6d043050d7dae549b97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=82=A8=E3=82=A4=E3=82=AB=E3=82=AF?= <62183434+zouyonghe@users.noreply.github.com> Date: Sat, 28 Feb 2026 13:34:12 +0900 Subject: [PATCH] feat: support persona custom error reply message with fallback (#5547) * feat: support persona custom error reply message with fallback * refactor: centralize persona custom error message helpers --- .../agent/runners/tool_loop_agent_runner.py | 16 +++- astrbot/core/astr_agent_run_util.py | 14 ++- astrbot/core/astr_main_agent.py | 8 ++ astrbot/core/db/__init__.py | 3 + astrbot/core/db/po.py | 4 + astrbot/core/db/sqlite.py | 18 +++- astrbot/core/persona_error_reply.py | 86 +++++++++++++++++++ astrbot/core/persona_mgr.py | 22 ++++- .../method/agent_sub_stages/internal.py | 13 ++- .../method/agent_sub_stages/third_party.py | 39 ++++++++- astrbot/core/sentinels.py | 1 + astrbot/dashboard/routes/persona.py | 42 +++++++-- .../src/components/shared/PersonaForm.vue | 14 +++ .../src/components/shared/PersonaSelector.vue | 1 + .../i18n/locales/en-US/features/persona.json | 2 + .../i18n/locales/zh-CN/features/persona.json | 2 + dashboard/src/stores/personaStore.ts | 1 + dashboard/src/views/persona/PersonaCard.vue | 1 + .../src/views/persona/PersonaManager.vue | 6 ++ 19 files changed, 269 insertions(+), 24 deletions(-) create mode 100644 astrbot/core/persona_error_reply.py create mode 100644 astrbot/core/sentinels.py diff --git a/astrbot/core/agent/runners/tool_loop_agent_runner.py b/astrbot/core/agent/runners/tool_loop_agent_runner.py index 94069089d..743b28007 100644 --- a/astrbot/core/agent/runners/tool_loop_agent_runner.py +++ b/astrbot/core/agent/runners/tool_loop_agent_runner.py @@ -23,6 +23,9 @@ from astrbot.core.message.components import Json from astrbot.core.message.message_event_result import ( MessageChain, ) +from astrbot.core.persona_error_reply import ( + extract_persona_custom_error_message_from_event, +) from astrbot.core.provider.entities import ( LLMResponse, ProviderRequest, @@ -78,6 +81,11 @@ class FollowUpTicket: class ToolLoopAgentRunner(BaseAgentRunner[TContext]): + 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) + @override async def reset( self, @@ -463,12 +471,14 @@ class ToolLoopAgentRunner(BaseAgentRunner[TContext]): 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 '未知错误'}" + ) yield AgentResponse( type="err", data=AgentResponseData( - chain=MessageChain().message( - f"LLM 响应错误: {llm_resp.completion_text or '未知错误'}", - ), + chain=MessageChain().message(error_text), ), ) return diff --git a/astrbot/core/astr_agent_run_util.py b/astrbot/core/astr_agent_run_util.py index 017f2cea2..c6f05b867 100644 --- a/astrbot/core/astr_agent_run_util.py +++ b/astrbot/core/astr_agent_run_util.py @@ -14,6 +14,9 @@ from astrbot.core.message.message_event_result import ( MessageEventResult, ResultContentType, ) +from astrbot.core.persona_error_reply import ( + extract_persona_custom_error_message_from_event, +) from astrbot.core.provider.entities import LLMResponse from astrbot.core.provider.provider import TTSProvider @@ -235,7 +238,16 @@ async def run_agent( pass logger.error(traceback.format_exc()) - err_msg = f"\n\nAstrBot 请求失败。\n错误类型: {type(e).__name__}\n错误信息: {e!s}\n\n请在平台日志查看和分享错误详情。\n" + 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"\n\nAstrBot 请求失败。\n错误类型: {type(e).__name__}\n错误信息: " + f"{e!s}\n\n请在平台日志查看和分享错误详情。\n" + ) error_llm_response = LLMResponse( role="err", diff --git a/astrbot/core/astr_main_agent.py b/astrbot/core/astr_main_agent.py index 6c1242f61..22e4ffad1 100644 --- a/astrbot/core/astr_main_agent.py +++ b/astrbot/core/astr_main_agent.py @@ -37,6 +37,10 @@ from astrbot.core.astr_main_agent_resources import ( ) from astrbot.core.conversation_mgr import Conversation from astrbot.core.message.components import File, Image, Reply +from astrbot.core.persona_error_reply import ( + extract_persona_custom_error_message_from_persona, + set_persona_custom_error_message_on_event, +) from astrbot.core.platform.astr_message_event import AstrMessageEvent from astrbot.core.provider import Provider from astrbot.core.provider.entities import ProviderRequest @@ -285,6 +289,10 @@ async def _ensure_persona_and_skills( provider_settings=cfg, ) + set_persona_custom_error_message_on_event( + event, extract_persona_custom_error_message_from_persona(persona) + ) + if persona: # Inject persona system prompt if prompt := persona["prompt"]: diff --git a/astrbot/core/db/__init__.py b/astrbot/core/db/__init__.py index 11f408e70..166f770a5 100644 --- a/astrbot/core/db/__init__.py +++ b/astrbot/core/db/__init__.py @@ -306,6 +306,7 @@ class BaseDatabase(abc.ABC): begin_dialogs: list[str] | None = None, tools: list[str] | None = None, skills: list[str] | None = None, + custom_error_message: str | None = None, folder_id: str | None = None, sort_order: int = 0, ) -> Persona: @@ -317,6 +318,7 @@ class BaseDatabase(abc.ABC): begin_dialogs: Optional list of initial dialog strings tools: Optional list of tool names (None means all tools, [] means no tools) skills: Optional list of skill names (None means all skills, [] means no skills) + custom_error_message: Optional persona-level fallback error message folder_id: Optional folder ID to place the persona in (None means root) sort_order: Sort order within the folder (default 0) """ @@ -340,6 +342,7 @@ class BaseDatabase(abc.ABC): begin_dialogs: list[str] | None = None, tools: list[str] | None = None, skills: list[str] | None = None, + custom_error_message: str | None = None, ) -> Persona | None: """Update a persona's system prompt or begin dialogs.""" ... diff --git a/astrbot/core/db/po.py b/astrbot/core/db/po.py index bf0a94547..451f054f6 100644 --- a/astrbot/core/db/po.py +++ b/astrbot/core/db/po.py @@ -126,6 +126,8 @@ class Persona(TimestampMixin, SQLModel, table=True): """None means use ALL tools for default, empty list means no tools, otherwise a list of tool names.""" skills: list | None = Field(default=None, sa_type=JSON) """None means use ALL skills for default, empty list means no skills, otherwise a list of skill names.""" + custom_error_message: str | None = Field(default=None, sa_type=Text) + """Optional custom error message sent to end users when the agent request fails.""" folder_id: str | None = Field(default=None, max_length=36) """所属文件夹ID,NULL 表示在根目录""" sort_order: int = Field(default=0) @@ -472,6 +474,8 @@ class Personality(TypedDict): """工具列表。None 表示使用所有工具,空列表表示不使用任何工具""" skills: list[str] | None """Skills 列表。None 表示使用所有 Skills,空列表表示不使用任何 Skills""" + custom_error_message: str | None + """可选的人格自定义报错回复信息。配置后将优先发送给最终用户。""" # cache _begin_dialogs_processed: list[dict] diff --git a/astrbot/core/db/sqlite.py b/astrbot/core/db/sqlite.py index 661c4a9f7..f496e19d5 100644 --- a/astrbot/core/db/sqlite.py +++ b/astrbot/core/db/sqlite.py @@ -32,8 +32,8 @@ from astrbot.core.db.po import ( from astrbot.core.db.po import ( Stats as DeprecatedStats, ) +from astrbot.core.sentinels import NOT_GIVEN -NOT_GIVEN = T.TypeVar("NOT_GIVEN") TxResult = T.TypeVar("TxResult") CRON_FIELD_NOT_SET = object() @@ -58,6 +58,7 @@ class SQLiteDatabase(BaseDatabase): # 确保 personas 表有 folder_id、sort_order、skills 列(前向兼容) await self._ensure_persona_folder_columns(conn) await self._ensure_persona_skills_column(conn) + await self._ensure_persona_custom_error_message_column(conn) await conn.commit() async def _ensure_persona_folder_columns(self, conn) -> None: @@ -92,6 +93,16 @@ class SQLiteDatabase(BaseDatabase): if "skills" not in columns: await conn.execute(text("ALTER TABLE personas ADD COLUMN skills JSON")) + async def _ensure_persona_custom_error_message_column(self, conn) -> None: + """确保 personas 表有 custom_error_message 列。""" + result = await conn.execute(text("PRAGMA table_info(personas)")) + columns = {row[1] for row in result.fetchall()} + + if "custom_error_message" not in columns: + await conn.execute( + text("ALTER TABLE personas ADD COLUMN custom_error_message TEXT") + ) + # ==== # Platform Statistics # ==== @@ -675,6 +686,7 @@ class SQLiteDatabase(BaseDatabase): begin_dialogs=None, tools=None, skills=None, + custom_error_message=None, folder_id=None, sort_order=0, ): @@ -688,6 +700,7 @@ class SQLiteDatabase(BaseDatabase): begin_dialogs=begin_dialogs or [], tools=tools, skills=skills, + custom_error_message=custom_error_message, folder_id=folder_id, sort_order=sort_order, ) @@ -719,6 +732,7 @@ class SQLiteDatabase(BaseDatabase): begin_dialogs=None, tools=NOT_GIVEN, skills=NOT_GIVEN, + custom_error_message=NOT_GIVEN, ): """Update a persona's system prompt or begin dialogs.""" async with self.get_db() as session: @@ -734,6 +748,8 @@ class SQLiteDatabase(BaseDatabase): values["tools"] = tools if skills is not NOT_GIVEN: values["skills"] = skills + if custom_error_message is not NOT_GIVEN: + values["custom_error_message"] = custom_error_message if not values: return None query = query.values(**values) diff --git a/astrbot/core/persona_error_reply.py b/astrbot/core/persona_error_reply.py new file mode 100644 index 000000000..5a99e0918 --- /dev/null +++ b/astrbot/core/persona_error_reply.py @@ -0,0 +1,86 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import Any + +PERSONA_CUSTOM_ERROR_MESSAGE_EXTRA_KEY = "persona_custom_error_message" + + +def normalize_persona_custom_error_message(value: object) -> str | None: + """Normalize persona custom error reply text.""" + if not isinstance(value, str): + return None + message = value.strip() + return message or None + + +def extract_persona_custom_error_message_from_persona( + persona: Mapping[str, Any] | None, +) -> str | None: + """Extract normalized custom error reply text from persona mapping.""" + if persona is None: + return None + return normalize_persona_custom_error_message(persona.get("custom_error_message")) + + +def extract_persona_custom_error_message_from_event(event: Any) -> str | None: + """Extract normalized custom error reply text from event extras.""" + try: + if event is None or not hasattr(event, "get_extra"): + return None + raw_message = event.get_extra(PERSONA_CUSTOM_ERROR_MESSAGE_EXTRA_KEY) + return normalize_persona_custom_error_message(raw_message) + except Exception: + return None + + +def set_persona_custom_error_message_on_event( + event: Any, message: object +) -> str | None: + """Normalize and store persona custom error reply text into event extras.""" + normalized = normalize_persona_custom_error_message(message) + try: + if event is not None and hasattr(event, "set_extra"): + event.set_extra(PERSONA_CUSTOM_ERROR_MESSAGE_EXTRA_KEY, normalized) + except Exception: + pass + return normalized + + +async def resolve_persona_custom_error_message( + *, + event: Any, + persona_manager: Any, + provider_settings: dict | None = None, + conversation_persona_id: str | None = None, +) -> str | None: + """Resolve normalized custom error reply text for the selected persona.""" + ( + _persona_id, + persona, + _force_applied_persona_id, + _use_webchat_special_default, + ) = await persona_manager.resolve_selected_persona( + umo=event.unified_msg_origin, + conversation_persona_id=conversation_persona_id, + platform_name=event.get_platform_name(), + provider_settings=provider_settings, + ) + return extract_persona_custom_error_message_from_persona(persona) + + +async def resolve_event_conversation_persona_id( + event: Any, conversation_manager: Any +) -> str | None: + """Resolve current conversation persona_id from event and conversation manager.""" + curr_cid = await conversation_manager.get_curr_conversation_id( + event.unified_msg_origin + ) + if not curr_cid: + return None + conversation = await conversation_manager.get_conversation( + event.unified_msg_origin, curr_cid + ) + if not conversation: + return None + return conversation.persona_id diff --git a/astrbot/core/persona_mgr.py b/astrbot/core/persona_mgr.py index 66002b2bd..d141f40e4 100644 --- a/astrbot/core/persona_mgr.py +++ b/astrbot/core/persona_mgr.py @@ -4,6 +4,7 @@ from astrbot.core.astrbot_config_mgr import AstrBotConfigManager from astrbot.core.db import BaseDatabase from astrbot.core.db.po import Persona, PersonaFolder, Personality from astrbot.core.platform.message_session import MessageSession +from astrbot.core.sentinels import NOT_GIVEN DEFAULT_PERSONALITY = Personality( prompt="You are a helpful and friendly assistant.", @@ -12,6 +13,7 @@ DEFAULT_PERSONALITY = Personality( mood_imitation_dialogs=[], tools=None, skills=None, + custom_error_message=None, _begin_dialogs_processed=[], _mood_imitation_dialogs_processed="", ) @@ -126,19 +128,27 @@ class PersonaManager: persona_id: str, system_prompt: str | None = None, begin_dialogs: list[str] | None = None, - tools: list[str] | None = None, - skills: list[str] | None = None, + tools: list[str] | None | object = NOT_GIVEN, + skills: list[str] | None | object = NOT_GIVEN, + custom_error_message: str | None | object = NOT_GIVEN, ): """更新指定 persona 的信息。tools 参数为 None 时表示使用所有工具,空列表表示不使用任何工具""" existing_persona = await self.db.get_persona_by_id(persona_id) if not existing_persona: raise ValueError(f"Persona with ID {persona_id} does not exist.") + update_kwargs = {} + if tools is not NOT_GIVEN: + update_kwargs["tools"] = tools + if skills is not NOT_GIVEN: + update_kwargs["skills"] = skills + if custom_error_message is not NOT_GIVEN: + update_kwargs["custom_error_message"] = custom_error_message + persona = await self.db.update_persona( persona_id, system_prompt, begin_dialogs, - tools=tools, - skills=skills, + **update_kwargs, ) if persona: for i, p in enumerate(self.personas): @@ -298,6 +308,7 @@ class PersonaManager: begin_dialogs: list[str] | None = None, tools: list[str] | None = None, skills: list[str] | None = None, + custom_error_message: str | None = None, folder_id: str | None = None, sort_order: int = 0, ) -> Persona: @@ -320,6 +331,7 @@ class PersonaManager: begin_dialogs, tools=tools, skills=skills, + custom_error_message=custom_error_message, folder_id=folder_id, sort_order=sort_order, ) @@ -346,6 +358,7 @@ class PersonaManager: "mood_imitation_dialogs": [], # deprecated "tools": persona.tools, "skills": persona.skills, + "custom_error_message": persona.custom_error_message, } for persona in self.personas ] @@ -402,6 +415,7 @@ class PersonaManager: begin_dialogs=selected_default_persona["begin_dialogs"], tools=selected_default_persona["tools"] or None, skills=selected_default_persona["skills"] or None, + custom_error_message=selected_default_persona["custom_error_message"], ) return v3_persona_config, personas_v3, selected_default_persona 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 d95f7f86c..523d758a0 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 @@ -19,6 +19,9 @@ from astrbot.core.message.message_event_result import ( MessageEventResult, ResultContentType, ) +from astrbot.core.persona_error_reply import ( + extract_persona_custom_error_message_from_event, +) from astrbot.core.pipeline.stage import Stage from astrbot.core.platform.astr_message_event import AstrMessageEvent from astrbot.core.provider.entities import ( @@ -366,11 +369,13 @@ class InternalAgentSubStage(Stage): except Exception as e: logger.error(f"Error occurred while processing agent: {e}") - await event.send( - MessageChain().message( - f"Error occurred while processing agent request: {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}" + ) + await event.send(MessageChain().message(error_text)) finally: if follow_up_capture: await finalize_follow_up_capture( 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 7fb5cee82..0b7ee1094 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 @@ -15,6 +15,11 @@ from astrbot.core.message.message_event_result import ( MessageEventResult, ResultContentType, ) +from astrbot.core.persona_error_reply import ( + resolve_event_conversation_persona_id, + resolve_persona_custom_error_message, + set_persona_custom_error_message_on_event, +) if TYPE_CHECKING: from astrbot.core.agent.runners.base import BaseAgentRunner @@ -39,6 +44,7 @@ AGENT_RUNNER_TYPE_KEY = { async def run_third_party_agent( runner: "BaseAgentRunner", stream_to_general: bool = False, + custom_error_message: str | None = None, ) -> AsyncGenerator[MessageChain | None, None]: """ 运行第三方 agent runner 并转换响应格式 @@ -55,10 +61,12 @@ async def run_third_party_agent( yield resp.data["chain"] except Exception as e: logger.error(f"Third party agent runner error: {e}") - err_msg = ( - f"\nAstrBot 请求失败。\n错误类型: {type(e).__name__}\n" - f"错误信息: {e!s}\n\n请在平台日志查看和分享错误详情。\n" - ) + err_msg = custom_error_message + if not err_msg: + err_msg = ( + f"\nAstrBot 请求失败。\n错误类型: {type(e).__name__}\n" + f"错误信息: {e!s}\n\n请在平台日志查看和分享错误详情。\n" + ) yield MessageChain().message(err_msg) @@ -77,6 +85,24 @@ class ThirdPartyAgentSubStage(Stage): "unsupported_streaming_strategy" ] + async def _resolve_persona_custom_error_message( + self, event: AstrMessageEvent + ) -> str | None: + try: + conversation_persona_id = await resolve_event_conversation_persona_id( + event, + self.ctx.plugin_manager.context.conversation_manager, + ) + return await resolve_persona_custom_error_message( + event=event, + persona_manager=self.ctx.plugin_manager.context.persona_manager, + provider_settings=self.conf["provider_settings"], + conversation_persona_id=conversation_persona_id, + ) + except Exception as e: + logger.debug("Failed to resolve persona custom error message: %s", e) + return None + async def process( self, event: AstrMessageEvent, provider_wake_prefix: str ) -> AsyncGenerator[None, None]: @@ -112,6 +138,9 @@ class ThirdPartyAgentSubStage(Stage): if not req.prompt and not req.image_urls: return + custom_error_message = await self._resolve_persona_custom_error_message(event) + set_persona_custom_error_message_on_event(event, custom_error_message) + # call event hook if await call_event_hook(event, EventType.OnLLMRequestEvent, req): return @@ -161,6 +190,7 @@ class ThirdPartyAgentSubStage(Stage): run_third_party_agent( runner, stream_to_general=False, + custom_error_message=custom_error_message, ), ), ) @@ -179,6 +209,7 @@ class ThirdPartyAgentSubStage(Stage): async for _ in run_third_party_agent( runner, stream_to_general=stream_to_general, + custom_error_message=custom_error_message, ): yield diff --git a/astrbot/core/sentinels.py b/astrbot/core/sentinels.py new file mode 100644 index 000000000..8d0b82ca3 --- /dev/null +++ b/astrbot/core/sentinels.py @@ -0,0 +1 @@ +NOT_GIVEN = object() diff --git a/astrbot/dashboard/routes/persona.py b/astrbot/dashboard/routes/persona.py index 804d514a3..56c14fe61 100644 --- a/astrbot/dashboard/routes/persona.py +++ b/astrbot/dashboard/routes/persona.py @@ -58,6 +58,7 @@ class PersonaRoute(Route): "begin_dialogs": persona.begin_dialogs or [], "tools": persona.tools, "skills": persona.skills, + "custom_error_message": persona.custom_error_message, "folder_id": persona.folder_id, "sort_order": persona.sort_order, "created_at": persona.created_at.isoformat() @@ -98,6 +99,7 @@ class PersonaRoute(Route): "begin_dialogs": persona.begin_dialogs or [], "tools": persona.tools, "skills": persona.skills, + "custom_error_message": persona.custom_error_message, "folder_id": persona.folder_id, "sort_order": persona.sort_order, "created_at": persona.created_at.isoformat() @@ -123,6 +125,7 @@ class PersonaRoute(Route): begin_dialogs = data.get("begin_dialogs", []) tools = data.get("tools") skills = data.get("skills") + custom_error_message = data.get("custom_error_message") folder_id = data.get("folder_id") # None 表示根目录 sort_order = data.get("sort_order", 0) @@ -132,6 +135,11 @@ class PersonaRoute(Route): if not system_prompt: return Response().error("系统提示词不能为空").__dict__ + if custom_error_message is not None: + if not isinstance(custom_error_message, str): + return Response().error("自定义报错回复信息必须是字符串").__dict__ + custom_error_message = custom_error_message.strip() or None + # 验证 begin_dialogs 格式 if begin_dialogs and len(begin_dialogs) % 2 != 0: return ( @@ -146,6 +154,7 @@ class PersonaRoute(Route): begin_dialogs=begin_dialogs if begin_dialogs else None, tools=tools if tools else None, skills=skills if skills else None, + custom_error_message=custom_error_message, folder_id=folder_id, sort_order=sort_order, ) @@ -161,6 +170,7 @@ class PersonaRoute(Route): "begin_dialogs": persona.begin_dialogs or [], "tools": persona.tools or [], "skills": persona.skills or [], + "custom_error_message": persona.custom_error_message, "folder_id": persona.folder_id, "sort_order": persona.sort_order, "created_at": persona.created_at.isoformat() @@ -187,12 +197,24 @@ class PersonaRoute(Route): persona_id = data.get("persona_id") system_prompt = data.get("system_prompt") begin_dialogs = data.get("begin_dialogs") + has_tools = "tools" in data tools = data.get("tools") + has_skills = "skills" in data skills = data.get("skills") + has_custom_error_message = "custom_error_message" in data + custom_error_message = data.get("custom_error_message") if not persona_id: return Response().error("缺少必要参数: persona_id").__dict__ + if has_custom_error_message: + if custom_error_message is not None and not isinstance( + custom_error_message, str + ): + return Response().error("自定义报错回复信息必须是字符串").__dict__ + if isinstance(custom_error_message, str): + custom_error_message = custom_error_message.strip() or None + # 验证 begin_dialogs 格式 if begin_dialogs is not None and len(begin_dialogs) % 2 != 0: return ( @@ -201,13 +223,19 @@ class PersonaRoute(Route): .__dict__ ) - await self.persona_mgr.update_persona( - persona_id=persona_id, - system_prompt=system_prompt, - begin_dialogs=begin_dialogs, - tools=tools, - skills=skills, - ) + update_kwargs = { + "persona_id": persona_id, + "system_prompt": system_prompt, + "begin_dialogs": begin_dialogs, + } + if has_tools: + update_kwargs["tools"] = tools + if has_skills: + update_kwargs["skills"] = skills + if has_custom_error_message: + update_kwargs["custom_error_message"] = custom_error_message + + await self.persona_mgr.update_persona(**update_kwargs) return Response().ok({"message": "人格更新成功"}).__dict__ except ValueError as e: diff --git a/dashboard/src/components/shared/PersonaForm.vue b/dashboard/src/components/shared/PersonaForm.vue index de1c508dd..19865feb6 100644 --- a/dashboard/src/components/shared/PersonaForm.vue +++ b/dashboard/src/components/shared/PersonaForm.vue @@ -21,6 +21,17 @@ + + @@ -360,6 +371,7 @@ export default { personaForm: { persona_id: '', system_prompt: '', + custom_error_message: '', begin_dialogs: [], tools: [], skills: [], @@ -480,6 +492,7 @@ export default { this.personaForm = { persona_id: '', system_prompt: '', + custom_error_message: '', begin_dialogs: [], tools: [], skills: [], @@ -494,6 +507,7 @@ export default { this.personaForm = { persona_id: persona.persona_id, system_prompt: persona.system_prompt, + custom_error_message: persona.custom_error_message || '', begin_dialogs: [...(persona.begin_dialogs || [])], tools: persona.tools === null ? null : [...(persona.tools || [])], skills: persona.skills === null ? null : [...(persona.skills || [])], diff --git a/dashboard/src/components/shared/PersonaSelector.vue b/dashboard/src/components/shared/PersonaSelector.vue index 07a5358d3..53eaf3bfd 100644 --- a/dashboard/src/components/shared/PersonaSelector.vue +++ b/dashboard/src/components/shared/PersonaSelector.vue @@ -40,6 +40,7 @@ import type { FolderTreeNode, SelectableItem } from '@/components/folder/types' interface Persona { persona_id: string system_prompt: string + custom_error_message?: string | null folder_id?: string | null [key: string]: any } diff --git a/dashboard/src/i18n/locales/en-US/features/persona.json b/dashboard/src/i18n/locales/en-US/features/persona.json index 9a091e80a..84aaef52c 100644 --- a/dashboard/src/i18n/locales/en-US/features/persona.json +++ b/dashboard/src/i18n/locales/en-US/features/persona.json @@ -20,6 +20,8 @@ "form": { "personaId": "Persona ID", "systemPrompt": "System Prompt", + "customErrorMessage": "Custom Error Reply Message (Optional)", + "customErrorMessageHelp": "When this persona's LLM request fails (for example, connection failures), this error reply is sent first. Leave empty to use the default error message.", "presetDialogs": "Preset Dialogs", "presetDialogsHelp": "Add some preset dialogs to help the bot better understand the role settings. The number of dialogs must be even (users and assistants take turns).", "userMessage": "User Message", diff --git a/dashboard/src/i18n/locales/zh-CN/features/persona.json b/dashboard/src/i18n/locales/zh-CN/features/persona.json index b2484844e..d3eec49a5 100644 --- a/dashboard/src/i18n/locales/zh-CN/features/persona.json +++ b/dashboard/src/i18n/locales/zh-CN/features/persona.json @@ -20,6 +20,8 @@ "form": { "personaId": "人格 ID", "systemPrompt": "系统提示词", + "customErrorMessage": "自定义报错回复信息(可选)", + "customErrorMessageHelp": "当该人格的 LLM 请求失败(例如连接失败)时,优先发送这条报错回复;留空则发送默认报错信息。", "presetDialogs": "预设对话", "presetDialogsHelp": "添加一些预设的对话来帮助机器人更好地理解角色设定。", "userMessage": "用户消息", diff --git a/dashboard/src/stores/personaStore.ts b/dashboard/src/stores/personaStore.ts index aa60339f8..e27354f1d 100644 --- a/dashboard/src/stores/personaStore.ts +++ b/dashboard/src/stores/personaStore.ts @@ -18,6 +18,7 @@ export interface PersonaFolder { export interface Persona { persona_id: string; system_prompt: string; + custom_error_message: string | null; begin_dialogs: string[]; tools: string[] | null; skills: string[] | null; diff --git a/dashboard/src/views/persona/PersonaCard.vue b/dashboard/src/views/persona/PersonaCard.vue index 135514bbf..37f523c67 100644 --- a/dashboard/src/views/persona/PersonaCard.vue +++ b/dashboard/src/views/persona/PersonaCard.vue @@ -79,6 +79,7 @@ import { useModuleI18n } from '@/i18n/composables'; interface Persona { persona_id: string; system_prompt: string; + custom_error_message?: string | null; begin_dialogs?: string[] | null; tools?: string[] | null; skills?: string[] | null; diff --git a/dashboard/src/views/persona/PersonaManager.vue b/dashboard/src/views/persona/PersonaManager.vue index b29f57715..8ad581779 100644 --- a/dashboard/src/views/persona/PersonaManager.vue +++ b/dashboard/src/views/persona/PersonaManager.vue @@ -137,6 +137,11 @@ {{ viewingPersona.system_prompt }} + + {{ tm('form.customErrorMessage') }} + {{ viewingPersona.custom_error_message }} + + {{ tm('form.presetDialogs') }} @@ -281,6 +286,7 @@ import type { Folder, FolderTreeNode } from '@/components/folder/types'; interface Persona { persona_id: string; system_prompt: string; + custom_error_message?: string | null; begin_dialogs?: string[] | null; tools?: string[] | null; skills?: string[] | null;
{{ viewingPersona.system_prompt }}
{{ viewingPersona.custom_error_message }}