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
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"]:
|
||||
|
||||
@@ -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."""
|
||||
...
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
NOT_GIVEN = object()
|
||||
@@ -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:
|
||||
|
||||
@@ -21,6 +21,17 @@
|
||||
|
||||
<v-textarea v-model="personaForm.system_prompt" :label="tm('form.systemPrompt')"
|
||||
:rules="systemPromptRules" variant="outlined" rows="16" class="mb-4" />
|
||||
|
||||
<v-textarea
|
||||
v-model="personaForm.custom_error_message"
|
||||
:label="tm('form.customErrorMessage')"
|
||||
:hint="tm('form.customErrorMessageHelp')"
|
||||
variant="outlined"
|
||||
rows="4"
|
||||
persistent-hint
|
||||
clearable
|
||||
class="mb-4"
|
||||
/>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" md="6" class="persona-panels-col">
|
||||
@@ -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 || [])],
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
"form": {
|
||||
"personaId": "人格 ID",
|
||||
"systemPrompt": "系统提示词",
|
||||
"customErrorMessage": "自定义报错回复信息(可选)",
|
||||
"customErrorMessageHelp": "当该人格的 LLM 请求失败(例如连接失败)时,优先发送这条报错回复;留空则发送默认报错信息。",
|
||||
"presetDialogs": "预设对话",
|
||||
"presetDialogsHelp": "添加一些预设的对话来帮助机器人更好地理解角色设定。",
|
||||
"userMessage": "用户消息",
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -137,6 +137,11 @@
|
||||
<pre class="system-prompt-content">{{ viewingPersona.system_prompt }}</pre>
|
||||
</div>
|
||||
|
||||
<div v-if="viewingPersona.custom_error_message" class="mb-4">
|
||||
<h4 class="text-h6 mb-2">{{ tm('form.customErrorMessage') }}</h4>
|
||||
<pre class="system-prompt-content">{{ viewingPersona.custom_error_message }}</pre>
|
||||
</div>
|
||||
|
||||
<div v-if="viewingPersona.begin_dialogs && viewingPersona.begin_dialogs.length > 0" class="mb-4">
|
||||
<h4 class="text-h6 mb-2">{{ tm('form.presetDialogs') }}</h4>
|
||||
<div v-for="(dialog, index) in viewingPersona.begin_dialogs" :key="index" class="mb-2">
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user