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
22 changes: 21 additions & 1 deletion astrbot/core/astr_agent_run_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
import re
import time
import traceback
from collections.abc import AsyncGenerator
from collections.abc import AsyncGenerator, Callable
from typing import Any

from astrbot.core import logger
from astrbot.core.agent.message import Message
Expand Down Expand Up @@ -94,6 +95,8 @@ async def run_agent(
show_tool_call_result: bool = False,
stream_to_general: bool = False,
show_reasoning: bool = False,
# 回调函数:每步完成后调用,参数为 (step_idx, resp_type, resp_data)
step_callback: Callable[[int, str, Any], None] | None = None,
) -> AsyncGenerator[MessageChain | None, None]:
step_idx = 0
astr_event = agent_runner.run_context.context.event
Expand Down Expand Up @@ -149,6 +152,10 @@ async def run_agent(
),
)

# 回调通知
if step_callback:
step_callback(step_idx, "tool_call_result", resp.data)

if msg_chain.type == "tool_direct_result":
# tool_direct_result 用于标记 llm tool 需要直接发送给用户的内容
await astr_event.send(msg_chain)
Expand Down Expand Up @@ -181,6 +188,10 @@ async def run_agent(
)
_record_tool_call_name(tool_info, tool_name_by_call_id)

# 回调通知
if step_callback:
step_callback(step_idx, "tool_call", resp.data)

if astr_event.get_platform_name() == "webchat":
await astr_event.send(resp.data["chain"])
elif show_tool_use:
Expand All @@ -202,6 +213,11 @@ async def run_agent(
if resp.type == "llm_result"
else ResultContentType.GENERAL_RESULT
)

# 回调通知 llm_result
if step_callback and resp.type == "llm_result":
step_callback(step_idx, "llm_result", resp.data)

astr_event.set_result(
MessageEventResult(
chain=resp.data["chain"].chain,
Expand All @@ -223,6 +239,10 @@ async def run_agent(
except asyncio.CancelledError:
pass
if agent_runner.done():
# 回调通知完成
if step_callback:
step_callback(step_idx, "done", None)

# send agent stats to webchat
if astr_event.get_platform_name() == "webchat":
await astr_event.send(
Expand Down
20 changes: 20 additions & 0 deletions astrbot/core/db/po.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,16 @@ class Persona(TimestampMixin, SQLModel, table=True):
"""所属文件夹ID,NULL 表示在根目录"""
sort_order: int = Field(default=0)
"""排序顺序"""
personality_config: dict | None = Field(default=None, sa_type=JSON)
"""高级人格配置:人格特质、表达风格、识别规则、心情标签等"""
chat_config: dict | None = Field(default=None, sa_type=JSON)
"""高级人格配置:聊天频率、动态频率、消息长度等"""
robot_config: dict | None = Field(default=None, sa_type=JSON)
"""高级人格配置:昵称、别名、平台等"""
llm_model_config: dict | None = Field(default=None, sa_type=JSON)
"""高级人格配置:模型配置(功能模型、回复模型、思考模型)"""
is_advanced: bool | None = Field(default=False)
"""是否为高级人格"""

__table_args__ = (
UniqueConstraint(
Expand Down Expand Up @@ -476,6 +486,16 @@ class Personality(TypedDict):
"""Skills 列表。None 表示使用所有 Skills,空列表表示不使用任何 Skills"""
custom_error_message: str | None
"""可选的人格自定义报错回复信息。配置后将优先发送给最终用户。"""
personality_config: dict | None
"""高级人格配置:人格特质、表达风格、识别规则、心情标签等"""
chat_config: dict | None
"""高级人格配置:聊天频率、动态频率、消息长度等"""
robot_config: dict | None
"""高级人格配置:昵称、别名、平台等"""
llm_model_config: dict | None
"""高级人格配置:模型配置(功能模型、回复模型、思考模型)"""
is_advanced: bool
"""是否为高级人格"""

# cache
_begin_dialogs_processed: list[dict]
Expand Down
70 changes: 70 additions & 0 deletions astrbot/core/db/sqlite.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,12 @@ def __init__(self, db_path: str) -> None:

async def initialize(self) -> None:
"""Initialize the database by creating tables if they do not exist."""
# 延迟导入 MindSim 记忆模型,避免循环导入
from astrbot.core.mind_sim.memory.models import ( # noqa: F401
MindSimChatMemory,
MindSimPersonMemory,
)

async with self.engine.begin() as conn:
await conn.run_sync(SQLModel.metadata.create_all)
await conn.execute(text("PRAGMA journal_mode=WAL"))
Expand All @@ -59,6 +65,7 @@ async def initialize(self) -> None:
await self._ensure_persona_folder_columns(conn)
await self._ensure_persona_skills_column(conn)
await self._ensure_persona_custom_error_message_column(conn)
await self._ensure_persona_advanced_columns(conn)
await conn.commit()

async def _ensure_persona_folder_columns(self, conn) -> None:
Expand Down Expand Up @@ -103,6 +110,44 @@ async def _ensure_persona_custom_error_message_column(self, conn) -> None:
text("ALTER TABLE personas ADD COLUMN custom_error_message TEXT")
)

async def _ensure_persona_advanced_columns(self, conn) -> None:
"""确保 personas 表有高级人格配置列(前向兼容)。

新增列:
- personality_config: JSON - 人格特质、表达风格、识别规则、心情标签等
- chat_config: JSON - 聊天频率、动态频率、消息长度等
- robot_config: JSON - 昵称、别名、平台等
- llm_model_config: JSON - 模型配置(功能模型、回复模型、思考模型)
- is_advanced: INTEGER - 是否为高级人格
"""
result = await conn.execute(text("PRAGMA table_info(personas)"))
columns = {row[1] for row in result.fetchall()}

if "personality_config" not in columns:
await conn.execute(
text(
"ALTER TABLE personas ADD COLUMN personality_config JSON DEFAULT NULL"
)
)
if "chat_config" not in columns:
await conn.execute(
text("ALTER TABLE personas ADD COLUMN chat_config JSON DEFAULT NULL")
)
if "robot_config" not in columns:
await conn.execute(
text("ALTER TABLE personas ADD COLUMN robot_config JSON DEFAULT NULL")
)
if "llm_model_config" not in columns:
await conn.execute(
text(
"ALTER TABLE personas ADD COLUMN llm_model_config JSON DEFAULT NULL"
)
)
if "is_advanced" not in columns:
await conn.execute(
text("ALTER TABLE personas ADD COLUMN is_advanced INTEGER DEFAULT 0")
)
Comment on lines +113 to +149
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

这个函数中对每个新列的检查和添加逻辑有些重复。为了提高代码的可维护性并减少重复,可以考虑将列定义信息存放在一个列表中,然后通过循环来执行 ALTER TABLE 操作。这样未来再增加新列时,只需修改列表即可。

    async def _ensure_persona_advanced_columns(self, conn) -> None:
        """确保 personas 表有高级人格配置列(前向兼容)。"""
        result = await conn.execute(text("PRAGMA table_info(personas)"))
        columns = {row[1] for row in result.fetchall()}

        columns_to_add = {
            "personality_config": "JSON DEFAULT NULL",
            "chat_config": "JSON DEFAULT NULL",
            "robot_config": "JSON DEFAULT NULL",
            "llm_model_config": "JSON DEFAULT NULL",
            "is_advanced": "INTEGER DEFAULT 0",
        }

        for col_name, col_def in columns_to_add.items():
            if col_name not in columns:
                await conn.execute(
                    text(f"ALTER TABLE personas ADD COLUMN {col_name} {col_def}")
                )


# ====
# Platform Statistics
# ====
Expand Down Expand Up @@ -689,6 +734,11 @@ async def insert_persona(
custom_error_message=None,
folder_id=None,
sort_order=0,
personality_config=None,
chat_config=None,
robot_config=None,
llm_model_config=None,
is_advanced=False,
):
"""Insert a new persona record."""
async with self.get_db() as session:
Expand All @@ -703,6 +753,11 @@ async def insert_persona(
custom_error_message=custom_error_message,
folder_id=folder_id,
sort_order=sort_order,
personality_config=personality_config,
chat_config=chat_config,
robot_config=robot_config,
llm_model_config=llm_model_config,
is_advanced=is_advanced,
)
session.add(new_persona)
await session.flush()
Expand Down Expand Up @@ -733,6 +788,11 @@ async def update_persona(
tools=NOT_GIVEN,
skills=NOT_GIVEN,
custom_error_message=NOT_GIVEN,
personality_config=NOT_GIVEN,
chat_config=NOT_GIVEN,
robot_config=NOT_GIVEN,
llm_model_config=NOT_GIVEN,
is_advanced=NOT_GIVEN,
):
"""Update a persona's system prompt or begin dialogs."""
async with self.get_db() as session:
Expand All @@ -750,6 +810,16 @@ async def update_persona(
values["skills"] = skills
if custom_error_message is not NOT_GIVEN:
values["custom_error_message"] = custom_error_message
if personality_config is not NOT_GIVEN:
values["personality_config"] = personality_config
if chat_config is not NOT_GIVEN:
values["chat_config"] = chat_config
if robot_config is not NOT_GIVEN:
values["robot_config"] = robot_config
if llm_model_config is not NOT_GIVEN:
values["llm_model_config"] = llm_model_config
if is_advanced is not NOT_GIVEN:
values["is_advanced"] = is_advanced
if not values:
return None
query = query.values(**values)
Expand Down
Loading