Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 31 additions & 2 deletions astrbot/core/astr_main_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,23 @@ async def _ensure_persona_and_skills(

assigned_tools: set[str] = set()
agents = orch_cfg.get("agents", [])

# 1. 提取白名单(归一化 subagents 名称)
sub_agents_cfg = (persona or {}).get("subagents")
normalized_subagents = (
{str(name).strip() for name in sub_agents_cfg if str(name).strip()}
if sub_agents_cfg is not None
else None
)

# 2. 过滤 agents(使用归一化后的名称)
if normalized_subagents is not None:
agents = [
agent
for agent in agents
if isinstance(agent, dict)
and str(agent.get("name", "")).strip() in normalized_subagents
]
if isinstance(agents, list):
for a in agents:
if not isinstance(a, dict):
Expand Down Expand Up @@ -421,8 +438,20 @@ async def _ensure_persona_and_skills(
req.func_tool = ToolSet()

# add subagent handoff tools
for tool in so.handoffs:
req.func_tool.add_tool(tool)
# 如果 normalized_subagents 为 None 则默认放行所有 handoffs,空集合禁用所有handoffs
if normalized_subagents is None:
# 不配置 subagents 时,默认放行所有 handoffs
for tool in so.handoffs:
req.func_tool.add_tool(tool)
else:
# 只允许指向归一化白名单中的 subagents 的 handoff
for tool in so.handoffs:
agent = getattr(tool, "agent", None)
agent_name = getattr(agent, "name", None) if agent else None
if agent_name is not None:
name_norm = str(agent_name).strip()
if name_norm and name_norm in normalized_subagents:
req.func_tool.add_tool(tool)

# check duplicates
if remove_dup:
Expand Down
3 changes: 3 additions & 0 deletions astrbot/core/db/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,7 @@ async def insert_persona(
begin_dialogs: list[str] | None = None,
tools: list[str] | None = None,
skills: list[str] | None = None,
subagents: list[str] | None = None,
custom_error_message: str | None = None,
folder_id: str | None = None,
sort_order: int = 0,
Expand All @@ -326,6 +327,7 @@ async def insert_persona(
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)
subagents: Optional list of subagent names (None means all subagents, [] means no subagents)
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)
Expand All @@ -350,6 +352,7 @@ async def update_persona(
begin_dialogs: list[str] | None = None,
tools: list[str] | None = None,
skills: list[str] | None = None,
subagents: list[str] | None = None,
custom_error_message: str | None = None,
) -> Persona | None:
"""Update a persona's system prompt or begin dialogs."""
Expand Down
4 changes: 4 additions & 0 deletions astrbot/core/db/po.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""
subagents: list | None = Field(default=None, sa_type=JSON)
"""None means use ALL subagents for default, empty list means no subagents, otherwise a list of subagents 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)
Expand Down Expand Up @@ -474,6 +476,8 @@ class Personality(TypedDict):
"""工具列表。None 表示使用所有工具,空列表表示不使用任何工具"""
skills: list[str] | None
"""Skills 列表。None 表示使用所有 Skills,空列表表示不使用任何 Skills"""
subagents: list[str] | None
"""Subagents 列表。None 表示使用所有 Subagents,空列表表示不使用任何 Subagents"""
custom_error_message: str | None
"""可选的人格自定义报错回复信息。配置后将优先发送给最终用户。"""

Expand Down
20 changes: 19 additions & 1 deletion astrbot/core/db/sqlite.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,10 @@ async def initialize(self) -> None:
await conn.execute(text("PRAGMA temp_store=MEMORY"))
await conn.execute(text("PRAGMA mmap_size=134217728"))
await conn.execute(text("PRAGMA optimize"))
# 确保 personas 表有 folder_id、sort_order、skills 列(前向兼容)
# 确保 personas 表有 folder_id、sort_order、skills、subagents 列(前向兼容)
await self._ensure_persona_folder_columns(conn)
await self._ensure_persona_skills_column(conn)
await self._ensure_persona_subagents_column(conn)
await self._ensure_persona_custom_error_message_column(conn)
await conn.commit()

Expand Down Expand Up @@ -93,6 +94,18 @@ async def _ensure_persona_skills_column(self, conn) -> None:
if "skills" not in columns:
await conn.execute(text("ALTER TABLE personas ADD COLUMN skills JSON"))

async def _ensure_persona_subagents_column(self, conn) -> None:
"""确保 personas 表有 subagents 列。

这是为了支持旧版数据库的平滑升级。新版数据库通过 SQLModel
的 metadata.create_all 自动创建这些列。
"""
result = await conn.execute(text("PRAGMA table_info(personas)"))
columns = {row[1] for row in result.fetchall()}

if "subagents" not in columns:
await conn.execute(text("ALTER TABLE personas ADD COLUMN subagents 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)"))
Expand Down Expand Up @@ -686,6 +699,7 @@ async def insert_persona(
begin_dialogs=None,
tools=None,
skills=None,
subagents=None,
custom_error_message=None,
folder_id=None,
sort_order=0,
Expand All @@ -700,6 +714,7 @@ async def insert_persona(
begin_dialogs=begin_dialogs or [],
tools=tools,
skills=skills,
subagents=subagents,
custom_error_message=custom_error_message,
folder_id=folder_id,
sort_order=sort_order,
Expand Down Expand Up @@ -732,6 +747,7 @@ async def update_persona(
begin_dialogs=None,
tools=NOT_GIVEN,
skills=NOT_GIVEN,
subagents=NOT_GIVEN,
custom_error_message=NOT_GIVEN,
):
"""Update a persona's system prompt or begin dialogs."""
Expand All @@ -748,6 +764,8 @@ async def update_persona(
values["tools"] = tools
if skills is not NOT_GIVEN:
values["skills"] = skills
if subagents is not NOT_GIVEN:
values["subagents"] = subagents
if custom_error_message is not NOT_GIVEN:
values["custom_error_message"] = custom_error_message
if not values:
Expand Down
9 changes: 9 additions & 0 deletions astrbot/core/persona_mgr.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
mood_imitation_dialogs=[],
tools=None,
skills=None,
subagents=None,
custom_error_message=None,
_begin_dialogs_processed=[],
_mood_imitation_dialogs_processed="",
Expand Down Expand Up @@ -141,6 +142,7 @@ async def update_persona(
begin_dialogs: list[str] | None = None,
tools: list[str] | None | object = NOT_GIVEN,
skills: list[str] | None | object = NOT_GIVEN,
subagents: list[str] | None | object = NOT_GIVEN,
custom_error_message: str | None | object = NOT_GIVEN,
):
"""更新指定 persona 的信息。tools 参数为 None 时表示使用所有工具,空列表表示不使用任何工具"""
Expand All @@ -152,6 +154,8 @@ async def update_persona(
update_kwargs["tools"] = tools
if skills is not NOT_GIVEN:
update_kwargs["skills"] = skills
if subagents is not NOT_GIVEN:
update_kwargs["subagents"] = subagents
if custom_error_message is not NOT_GIVEN:
update_kwargs["custom_error_message"] = custom_error_message

Expand Down Expand Up @@ -319,6 +323,7 @@ async def create_persona(
begin_dialogs: list[str] | None = None,
tools: list[str] | None = None,
skills: list[str] | None = None,
subagents: list[str] | None = None,
custom_error_message: str | None = None,
folder_id: str | None = None,
sort_order: int = 0,
Expand All @@ -331,6 +336,7 @@ async def create_persona(
begin_dialogs: 预设对话列表
tools: 工具列表,None 表示使用所有工具,空列表表示不使用任何工具
skills: Skills 列表,None 表示使用所有 Skills,空列表表示不使用任何 Skills
subagents: Subagents 列表,None 表示使用所有 Subagents,空列表表示不使用任何 Subagents
folder_id: 所属文件夹 ID,None 表示根目录
sort_order: 排序顺序
"""
Expand All @@ -342,6 +348,7 @@ async def create_persona(
begin_dialogs,
tools=tools,
skills=skills,
subagents=subagents,
custom_error_message=custom_error_message,
folder_id=folder_id,
sort_order=sort_order,
Expand Down Expand Up @@ -369,6 +376,7 @@ def get_v3_persona_data(
"mood_imitation_dialogs": [], # deprecated
"tools": persona.tools,
"skills": persona.skills,
"subagents": persona.subagents,
"custom_error_message": persona.custom_error_message,
}
for persona in self.personas
Expand Down Expand Up @@ -426,6 +434,7 @@ def get_v3_persona_data(
begin_dialogs=selected_default_persona["begin_dialogs"],
tools=selected_default_persona["tools"] or None,
skills=selected_default_persona["skills"] or None,
subagents=selected_default_persona["subagents"] or None,
custom_error_message=selected_default_persona["custom_error_message"],
)

Expand Down
9 changes: 9 additions & 0 deletions astrbot/dashboard/routes/persona.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ async def list_personas(self):
"begin_dialogs": persona.begin_dialogs or [],
"tools": persona.tools,
"skills": persona.skills,
"subagents": persona.subagents,
"custom_error_message": persona.custom_error_message,
"folder_id": persona.folder_id,
"sort_order": persona.sort_order,
Expand Down Expand Up @@ -99,6 +100,7 @@ async def get_persona_detail(self):
"begin_dialogs": persona.begin_dialogs or [],
"tools": persona.tools,
"skills": persona.skills,
"subagents": persona.subagents,
"custom_error_message": persona.custom_error_message,
"folder_id": persona.folder_id,
"sort_order": persona.sort_order,
Expand All @@ -125,6 +127,7 @@ async def create_persona(self):
begin_dialogs = data.get("begin_dialogs", [])
tools = data.get("tools")
skills = data.get("skills")
subagents = data.get("subagents")
custom_error_message = data.get("custom_error_message")
folder_id = data.get("folder_id") # None 表示根目录
sort_order = data.get("sort_order", 0)
Expand Down Expand Up @@ -154,6 +157,7 @@ async def create_persona(self):
begin_dialogs=begin_dialogs if begin_dialogs else None,
tools=tools if tools else None,
skills=skills if skills else None,
subagents=subagents if subagents else None,
custom_error_message=custom_error_message,
folder_id=folder_id,
sort_order=sort_order,
Expand All @@ -170,6 +174,7 @@ async def create_persona(self):
"begin_dialogs": persona.begin_dialogs or [],
"tools": persona.tools or [],
"skills": persona.skills or [],
"subagents": persona.subagents or [],
"custom_error_message": persona.custom_error_message,
"folder_id": persona.folder_id,
"sort_order": persona.sort_order,
Expand Down Expand Up @@ -201,6 +206,8 @@ async def update_persona(self):
tools = data.get("tools")
has_skills = "skills" in data
skills = data.get("skills")
has_subagents = "subagents" in data
subagents = data.get("subagents")
has_custom_error_message = "custom_error_message" in data
custom_error_message = data.get("custom_error_message")

Expand Down Expand Up @@ -232,6 +239,8 @@ async def update_persona(self):
update_kwargs["tools"] = tools
if has_skills:
update_kwargs["skills"] = skills
if has_subagents:
update_kwargs["subagents"] = subagents
if has_custom_error_message:
update_kwargs["custom_error_message"] = custom_error_message

Expand Down
Loading