From 4749159bb9d1c5f89f18df555908c2b082e5c9b2 Mon Sep 17 00:00:00 2001 From: Raven95676 Date: Wed, 25 Feb 2026 14:48:46 +0800 Subject: [PATCH 1/2] fix(conversation): retain existing persona_id when updating conversation --- astrbot/dashboard/routes/conversation.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/astrbot/dashboard/routes/conversation.py b/astrbot/dashboard/routes/conversation.py index 513d3603f3..68eed7ef16 100644 --- a/astrbot/dashboard/routes/conversation.py +++ b/astrbot/dashboard/routes/conversation.py @@ -148,7 +148,6 @@ async def upd_conv(self): user_id = data.get("user_id") cid = data.get("cid") title = data.get("title") - persona_id = data.get("persona_id", "") if not user_id or not cid: return Response().error("缺少必要参数: user_id 和 cid").__dict__ @@ -158,6 +157,9 @@ async def upd_conv(self): ) if not conversation: return Response().error("对话不存在").__dict__ + + persona_id = data.get("persona_id", conversation.persona_id) + if title is not None or persona_id is not None: await self.conv_mgr.update_conversation( unified_msg_origin=user_id, From d80598b9c3277384ea6c0efc8c27a00457b94255 Mon Sep 17 00:00:00 2001 From: Raven95676 Date: Wed, 25 Feb 2026 15:14:46 +0800 Subject: [PATCH 2/2] fix(persona): enhance persona resolution logic for conversations and sessions --- .../builtin_commands/commands/conversation.py | 31 ++++++++--- .../builtin_commands/commands/persona.py | 34 ++++++++---- astrbot/core/astr_main_agent.py | 49 +++++------------ astrbot/core/persona_mgr.py | 55 +++++++++++++++++++ 4 files changed, 115 insertions(+), 54 deletions(-) diff --git a/astrbot/builtin_stars/builtin_commands/commands/conversation.py b/astrbot/builtin_stars/builtin_commands/commands/conversation.py index 63561f64ed..55b75cb1bd 100644 --- a/astrbot/builtin_stars/builtin_commands/commands/conversation.py +++ b/astrbot/builtin_stars/builtin_commands/commands/conversation.py @@ -206,16 +206,33 @@ async def convs(self, message: AstrMessageEvent, page: int = 1) -> None: _titles[conv.cid] = title """遍历分页后的对话生成列表显示""" + provider_settings = cfg.get("provider_settings", {}) + platform_name = message.get_platform_name() for conv in conversations_paged: - persona_id = conv.persona_id - if not persona_id or persona_id == "[%None]": - persona = await self.context.persona_manager.get_default_persona_v3( - umo=message.unified_msg_origin, - ) - persona_id = persona["name"] + ( + persona_id, + _, + force_applied_persona_id, + _, + ) = await self.context.persona_manager.resolve_selected_persona( + umo=message.unified_msg_origin, + conversation_persona_id=conv.persona_id, + platform_name=platform_name, + provider_settings=provider_settings, + ) + if persona_id == "[%None]": + persona_name = "无" + elif persona_id: + persona_name = persona_id + else: + persona_name = "无" + + if force_applied_persona_id: + persona_name = f"{persona_name} (自定义规则)" + title = _titles.get(conv.cid, "新对话") parts.append( - f"{global_index}. {title}({conv.cid[:4]})\n 人格情景: {persona_id}\n 上次更新: {datetime.datetime.fromtimestamp(conv.updated_at).strftime('%m-%d %H:%M')}\n" + f"{global_index}. {title}({conv.cid[:4]})\n 人格情景: {persona_name}\n 上次更新: {datetime.datetime.fromtimestamp(conv.updated_at).strftime('%m-%d %H:%M')}\n" ) global_index += 1 diff --git a/astrbot/builtin_stars/builtin_commands/commands/persona.py b/astrbot/builtin_stars/builtin_commands/commands/persona.py index cf99988a2e..7a7416bbaf 100644 --- a/astrbot/builtin_stars/builtin_commands/commands/persona.py +++ b/astrbot/builtin_stars/builtin_commands/commands/persona.py @@ -1,7 +1,7 @@ import builtins from typing import TYPE_CHECKING -from astrbot.api import sp, star +from astrbot.api import star from astrbot.api.event import AstrMessageEvent, MessageEventResult if TYPE_CHECKING: @@ -59,12 +59,7 @@ async def persona(self, message: AstrMessageEvent) -> None: default_persona = await self.context.persona_manager.get_default_persona_v3( umo=umo, ) - - force_applied_persona_id = ( - await sp.get_async( - scope="umo", scope_id=umo, key="session_service_config", default={} - ) - ).get("persona_id") + force_applied_persona_id = None curr_cid_title = "无" if cid: @@ -80,10 +75,27 @@ async def persona(self, message: AstrMessageEvent) -> None: ), ) return - if not conv.persona_id and conv.persona_id != "[%None]": - curr_persona_name = default_persona["name"] - else: - curr_persona_name = conv.persona_id + + provider_settings = self.context.get_config(umo=umo).get( + "provider_settings", + {}, + ) + ( + persona_id, + _, + force_applied_persona_id, + _, + ) = await self.context.persona_manager.resolve_selected_persona( + umo=umo, + conversation_persona_id=conv.persona_id, + platform_name=message.get_platform_name(), + provider_settings=provider_settings, + ) + + if persona_id == "[%None]": + curr_persona_name = "无" + elif persona_id: + curr_persona_name = persona_id if force_applied_persona_id: curr_persona_name = f"{curr_persona_name} (自定义规则)" diff --git a/astrbot/core/astr_main_agent.py b/astrbot/core/astr_main_agent.py index 7883dca8fd..6c1242f61b 100644 --- a/astrbot/core/astr_main_agent.py +++ b/astrbot/core/astr_main_agent.py @@ -1,7 +1,6 @@ from __future__ import annotations import asyncio -import builtins import copy import datetime import json @@ -10,7 +9,6 @@ from collections.abc import Coroutine from dataclasses import dataclass, field -from astrbot.api import sp from astrbot.core import logger from astrbot.core.agent.handoff import HandoffTool from astrbot.core.agent.mcp_client import MCPTool @@ -275,47 +273,26 @@ async def _ensure_persona_and_skills( if not req.conversation: return - # get persona ID - - # 1. from session service config - highest priority - persona_id = ( - await sp.get_async( - scope="umo", - scope_id=event.unified_msg_origin, - key="session_service_config", - default={}, - ) - ).get("persona_id") - - if not persona_id: - # 2. from conversation setting - second priority - persona_id = req.conversation.persona_id - - if persona_id == "[%None]": - # explicitly set to no persona - pass - elif persona_id is None: - # 3. from config default persona setting - last priority - persona_id = cfg.get("default_personality") - - persona = next( - builtins.filter( - lambda persona: persona["name"] == persona_id, - plugin_context.persona_manager.personas_v3, - ), - None, + ( + persona_id, + persona, + _, + use_webchat_special_default, + ) = await plugin_context.persona_manager.resolve_selected_persona( + umo=event.unified_msg_origin, + conversation_persona_id=req.conversation.persona_id, + platform_name=event.get_platform_name(), + provider_settings=cfg, ) + if persona: # Inject persona system prompt if prompt := persona["prompt"]: req.system_prompt += f"\n# Persona Instructions\n\n{prompt}\n" if begin_dialogs := copy.deepcopy(persona.get("_begin_dialogs_processed")): req.contexts[:0] = begin_dialogs - else: - # special handling for webchat persona - if event.get_platform_name() == "webchat" and persona_id != "[%None]": - persona_id = "_chatui_default_" - req.system_prompt += CHATUI_SPECIAL_DEFAULT_PERSONA_PROMPT + elif use_webchat_special_default: + req.system_prompt += CHATUI_SPECIAL_DEFAULT_PERSONA_PROMPT # Inject skills prompt runtime = cfg.get("computer_use_runtime", "local") diff --git a/astrbot/core/persona_mgr.py b/astrbot/core/persona_mgr.py index f3633f20fa..66002b2bd5 100644 --- a/astrbot/core/persona_mgr.py +++ b/astrbot/core/persona_mgr.py @@ -1,4 +1,5 @@ from astrbot import logger +from astrbot.api import sp from astrbot.core.astrbot_config_mgr import AstrBotConfigManager from astrbot.core.db import BaseDatabase from astrbot.core.db.po import Persona, PersonaFolder, Personality @@ -58,6 +59,60 @@ async def get_default_persona_v3( except Exception: return DEFAULT_PERSONALITY + async def resolve_selected_persona( + self, + *, + umo: str | MessageSession, + conversation_persona_id: str | None, + platform_name: str, + provider_settings: dict | None = None, + ) -> tuple[str | None, Personality | None, str | None, bool]: + """解析当前会话最终生效的人格。 + + Returns: + tuple: + - selected persona_id + - selected persona object + - force applied persona_id from session rule + - whether use webchat special default persona + """ + session_service_config = ( + await sp.get_async( + scope="umo", + scope_id=str(umo), + key="session_service_config", + default={}, + ) + or {} + ) + + force_applied_persona_id = session_service_config.get("persona_id") + persona_id = force_applied_persona_id + + if not persona_id: + persona_id = conversation_persona_id + if persona_id == "[%None]": + pass + elif persona_id is None: + persona_id = (provider_settings or {}).get("default_personality") + + persona = next( + (item for item in self.personas_v3 if item["name"] == persona_id), + None, + ) + + use_webchat_special_default = False + if not persona and platform_name == "webchat" and persona_id != "[%None]": + persona_id = "_chatui_default_" + use_webchat_special_default = True + + return ( + persona_id, + persona, + force_applied_persona_id, + use_webchat_special_default, + ) + async def delete_persona(self, persona_id: str) -> None: """删除指定 persona""" if not await self.db.get_persona_by_id(persona_id):