AstrBot 4.7.4__py3-none-any.whl → 4.9.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- astrbot/cli/__init__.py +1 -1
- astrbot/core/agent/runners/tool_loop_agent_runner.py +0 -1
- astrbot/core/agent/tool.py +7 -2
- astrbot/core/astr_agent_run_util.py +15 -1
- astrbot/core/astr_agent_tool_exec.py +5 -1
- astrbot/core/config/astrbot_config.py +4 -0
- astrbot/core/config/default.py +116 -1
- astrbot/core/core_lifecycle.py +1 -1
- astrbot/core/db/__init__.py +32 -4
- astrbot/core/db/migration/migra_3_to_4.py +2 -0
- astrbot/core/db/migration/sqlite_v3.py +6 -4
- astrbot/core/db/po.py +16 -15
- astrbot/core/db/sqlite.py +56 -1
- astrbot/core/db/vec_db/faiss_impl/embedding_storage.py +2 -0
- astrbot/core/event_bus.py +6 -1
- astrbot/core/knowledge_base/retrieval/manager.py +5 -1
- astrbot/core/log.py +2 -1
- astrbot/core/message/components.py +9 -3
- astrbot/core/persona_mgr.py +2 -2
- astrbot/core/pipeline/content_safety_check/stage.py +1 -1
- astrbot/core/pipeline/context_utils.py +2 -1
- astrbot/core/pipeline/process_stage/method/agent_sub_stages/third_party.py +1 -1
- astrbot/core/pipeline/process_stage/method/star_request.py +1 -2
- astrbot/core/pipeline/process_stage/stage.py +1 -1
- astrbot/core/pipeline/respond/stage.py +4 -2
- astrbot/core/pipeline/result_decorate/stage.py +68 -21
- astrbot/core/pipeline/scheduler.py +5 -1
- astrbot/core/pipeline/waking_check/stage.py +10 -0
- astrbot/core/platform/astr_message_event.py +5 -3
- astrbot/core/platform/astrbot_message.py +2 -2
- astrbot/core/platform/manager.py +71 -9
- astrbot/core/platform/platform.py +109 -4
- astrbot/core/platform/platform_metadata.py +1 -1
- astrbot/core/platform/register.py +1 -0
- astrbot/core/platform/sources/aiocqhttp/aiocqhttp_message_event.py +8 -6
- astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py +13 -8
- astrbot/core/platform/sources/dingtalk/dingtalk_adapter.py +28 -22
- astrbot/core/platform/sources/dingtalk/dingtalk_event.py +5 -2
- astrbot/core/platform/sources/discord/client.py +16 -4
- astrbot/core/platform/sources/discord/components.py +2 -2
- astrbot/core/platform/sources/discord/discord_platform_adapter.py +53 -26
- astrbot/core/platform/sources/discord/discord_platform_event.py +29 -8
- astrbot/core/platform/sources/lark/lark_adapter.py +178 -22
- astrbot/core/platform/sources/lark/lark_event.py +39 -4
- astrbot/core/platform/sources/lark/server.py +206 -0
- astrbot/core/platform/sources/misskey/misskey_adapter.py +3 -5
- astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py +64 -18
- astrbot/core/platform/sources/qqofficial/qqofficial_platform_adapter.py +14 -10
- astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_adapter.py +36 -11
- astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_server.py +15 -2
- astrbot/core/platform/sources/satori/satori_adapter.py +1 -2
- astrbot/core/platform/sources/slack/client.py +58 -40
- astrbot/core/platform/sources/slack/slack_adapter.py +36 -16
- astrbot/core/platform/sources/slack/slack_event.py +11 -10
- astrbot/core/platform/sources/telegram/tg_adapter.py +2 -3
- astrbot/core/platform/sources/telegram/tg_event.py +23 -27
- astrbot/core/platform/sources/webchat/webchat_adapter.py +97 -31
- astrbot/core/platform/sources/webchat/webchat_event.py +35 -35
- astrbot/core/platform/sources/wechatpadpro/wechatpadpro_adapter.py +27 -11
- astrbot/core/platform/sources/wecom/wecom_adapter.py +75 -36
- astrbot/core/platform/sources/wecom/wecom_event.py +3 -3
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_adapter.py +26 -9
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_event.py +3 -3
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_server.py +27 -5
- astrbot/core/platform/sources/weixin_official_account/weixin_offacc_adapter.py +81 -35
- astrbot/core/platform/sources/weixin_official_account/weixin_offacc_event.py +11 -8
- astrbot/core/platform_message_history_mgr.py +3 -3
- astrbot/core/provider/func_tool_manager.py +3 -3
- astrbot/core/provider/manager.py +130 -74
- astrbot/core/provider/provider.py +12 -1
- astrbot/core/provider/sources/azure_tts_source.py +31 -9
- astrbot/core/provider/sources/bailian_rerank_source.py +4 -0
- astrbot/core/provider/sources/dashscope_tts.py +3 -2
- astrbot/core/provider/sources/edge_tts_source.py +1 -1
- astrbot/core/provider/sources/fishaudio_tts_api_source.py +5 -4
- astrbot/core/provider/sources/gemini_embedding_source.py +15 -5
- astrbot/core/provider/sources/gemini_source.py +12 -10
- astrbot/core/provider/sources/minimax_tts_api_source.py +4 -2
- astrbot/core/provider/sources/openai_embedding_source.py +2 -2
- astrbot/core/provider/sources/openai_source.py +4 -0
- astrbot/core/provider/sources/sensevoice_selfhosted_source.py +5 -2
- astrbot/core/provider/sources/vllm_rerank_source.py +1 -0
- astrbot/core/provider/sources/whisper_api_source.py +44 -12
- astrbot/core/provider/sources/whisper_selfhosted_source.py +6 -2
- astrbot/core/provider/sources/xinference_rerank_source.py +10 -2
- astrbot/core/star/context.py +2 -2
- astrbot/core/star/register/star_handler.py +22 -5
- astrbot/core/star/star_handler.py +85 -4
- astrbot/core/updator.py +3 -3
- astrbot/core/utils/io.py +1 -1
- astrbot/core/utils/session_waiter.py +17 -10
- astrbot/core/utils/shared_preferences.py +32 -0
- astrbot/core/utils/t2i/__init__.py +2 -2
- astrbot/core/utils/t2i/local_strategy.py +25 -31
- astrbot/core/utils/tencent_record_helper.py +2 -2
- astrbot/core/utils/version_comparator.py +6 -3
- astrbot/core/utils/webhook_utils.py +66 -0
- astrbot/dashboard/routes/__init__.py +2 -0
- astrbot/dashboard/routes/chat.py +311 -76
- astrbot/dashboard/routes/config.py +14 -5
- astrbot/dashboard/routes/knowledge_base.py +254 -79
- astrbot/dashboard/routes/log.py +13 -8
- astrbot/dashboard/routes/platform.py +100 -0
- astrbot/dashboard/routes/plugin.py +108 -51
- astrbot/dashboard/routes/route.py +2 -0
- astrbot/dashboard/server.py +9 -4
- {astrbot-4.7.4.dist-info → astrbot-4.9.0.dist-info}/METADATA +50 -37
- {astrbot-4.7.4.dist-info → astrbot-4.9.0.dist-info}/RECORD +111 -108
- {astrbot-4.7.4.dist-info → astrbot-4.9.0.dist-info}/WHEEL +0 -0
- {astrbot-4.7.4.dist-info → astrbot-4.9.0.dist-info}/entry_points.txt +0 -0
- {astrbot-4.7.4.dist-info → astrbot-4.9.0.dist-info}/licenses/LICENSE +0 -0
astrbot/core/log.py
CHANGED
|
@@ -24,6 +24,7 @@ import asyncio
|
|
|
24
24
|
import logging
|
|
25
25
|
import os
|
|
26
26
|
import sys
|
|
27
|
+
import time
|
|
27
28
|
from asyncio import Queue
|
|
28
29
|
from collections import deque
|
|
29
30
|
|
|
@@ -148,7 +149,7 @@ class LogQueueHandler(logging.Handler):
|
|
|
148
149
|
self.log_broker.publish(
|
|
149
150
|
{
|
|
150
151
|
"level": record.levelname,
|
|
151
|
-
"time":
|
|
152
|
+
"time": time.time(),
|
|
152
153
|
"data": log_entry,
|
|
153
154
|
},
|
|
154
155
|
)
|
|
@@ -66,6 +66,9 @@ class ComponentType(str, Enum):
|
|
|
66
66
|
class BaseMessageComponent(BaseModel):
|
|
67
67
|
type: ComponentType
|
|
68
68
|
|
|
69
|
+
def __init__(self, **kwargs):
|
|
70
|
+
super().__init__(**kwargs)
|
|
71
|
+
|
|
69
72
|
def toDict(self):
|
|
70
73
|
data = {}
|
|
71
74
|
for k, v in self.__dict__.items():
|
|
@@ -551,7 +554,7 @@ class Node(BaseMessageComponent):
|
|
|
551
554
|
id: int | None = 0 # 忽略
|
|
552
555
|
name: str | None = "" # qq昵称
|
|
553
556
|
uin: str | None = "0" # qq号
|
|
554
|
-
content: list[BaseMessageComponent]
|
|
557
|
+
content: list[BaseMessageComponent] = []
|
|
555
558
|
seq: str | list | None = "" # 忽略
|
|
556
559
|
time: int | None = 0 # 忽略
|
|
557
560
|
|
|
@@ -615,7 +618,7 @@ class Nodes(BaseMessageComponent):
|
|
|
615
618
|
ret["messages"].append(d)
|
|
616
619
|
return ret
|
|
617
620
|
|
|
618
|
-
async def to_dict(self):
|
|
621
|
+
async def to_dict(self) -> dict:
|
|
619
622
|
"""将 Nodes 转换为字典格式,适用于 OneBot JSON 格式"""
|
|
620
623
|
ret = {"messages": []}
|
|
621
624
|
for node in self.nodes:
|
|
@@ -714,12 +717,15 @@ class File(BaseMessageComponent):
|
|
|
714
717
|
|
|
715
718
|
if self.url:
|
|
716
719
|
await self._download_file()
|
|
717
|
-
|
|
720
|
+
if self.file_:
|
|
721
|
+
return os.path.abspath(self.file_)
|
|
718
722
|
|
|
719
723
|
return ""
|
|
720
724
|
|
|
721
725
|
async def _download_file(self):
|
|
722
726
|
"""下载文件"""
|
|
727
|
+
if not self.url:
|
|
728
|
+
raise ValueError("Download failed: No URL provided in File component.")
|
|
723
729
|
download_dir = os.path.join(get_astrbot_data_path(), "temp")
|
|
724
730
|
os.makedirs(download_dir, exist_ok=True)
|
|
725
731
|
if self.name:
|
astrbot/core/persona_mgr.py
CHANGED
|
@@ -98,8 +98,8 @@ class PersonaManager:
|
|
|
98
98
|
self,
|
|
99
99
|
persona_id: str,
|
|
100
100
|
system_prompt: str,
|
|
101
|
-
begin_dialogs: list[str] = None,
|
|
102
|
-
tools: list[str] = None,
|
|
101
|
+
begin_dialogs: list[str] | None = None,
|
|
102
|
+
tools: list[str] | None = None,
|
|
103
103
|
) -> Persona:
|
|
104
104
|
"""创建新的 persona。tools 参数为 None 时表示使用所有工具,空列表表示不使用任何工具"""
|
|
105
105
|
if await self.db.get_persona_by_id(persona_id):
|
|
@@ -24,7 +24,7 @@ class ContentSafetyCheckStage(Stage):
|
|
|
24
24
|
self,
|
|
25
25
|
event: AstrMessageEvent,
|
|
26
26
|
check_text: str | None = None,
|
|
27
|
-
) ->
|
|
27
|
+
) -> AsyncGenerator[None, None]:
|
|
28
28
|
"""检查内容安全"""
|
|
29
29
|
text = check_text if check_text else event.get_message_str()
|
|
30
30
|
ok, info = self.strategy_selector.check(text)
|
|
@@ -11,7 +11,7 @@ from astrbot.core.star.star_handler import EventType, star_handlers_registry
|
|
|
11
11
|
|
|
12
12
|
async def call_handler(
|
|
13
13
|
event: AstrMessageEvent,
|
|
14
|
-
handler: T.Callable[..., T.Awaitable[T.Any]],
|
|
14
|
+
handler: T.Callable[..., T.Awaitable[T.Any] | T.AsyncGenerator[T.Any, None]],
|
|
15
15
|
*args,
|
|
16
16
|
**kwargs,
|
|
17
17
|
) -> T.AsyncGenerator[T.Any, None]:
|
|
@@ -91,6 +91,7 @@ async def call_event_hook(
|
|
|
91
91
|
)
|
|
92
92
|
for handler in handlers:
|
|
93
93
|
try:
|
|
94
|
+
assert inspect.iscoroutinefunction(handler.handler)
|
|
94
95
|
logger.debug(
|
|
95
96
|
f"hook({hook_type.name}) -> {star_map[handler.handler_module_path].name} - {handler.handler_name}",
|
|
96
97
|
)
|
|
@@ -57,7 +57,7 @@ async def run_third_party_agent(
|
|
|
57
57
|
logger.error(f"Third party agent runner error: {e}")
|
|
58
58
|
err_msg = (
|
|
59
59
|
f"\nAstrBot 请求失败。\n错误类型: {type(e).__name__}\n"
|
|
60
|
-
f"错误信息: {e!s}\n\n
|
|
60
|
+
f"错误信息: {e!s}\n\n请在平台日志查看和分享错误详情。\n"
|
|
61
61
|
)
|
|
62
62
|
yield MessageChain().message(err_msg)
|
|
63
63
|
|
|
@@ -16,7 +16,6 @@ from ..stage import Stage
|
|
|
16
16
|
|
|
17
17
|
class StarRequestSubStage(Stage):
|
|
18
18
|
async def initialize(self, ctx: PipelineContext) -> None:
|
|
19
|
-
self.curr_provider = ctx.plugin_manager.context.get_using_provider()
|
|
20
19
|
self.prompt_prefix = ctx.astrbot_config["provider_settings"]["prompt_prefix"]
|
|
21
20
|
self.identifier = ctx.astrbot_config["provider_settings"]["identifier"]
|
|
22
21
|
self.ctx = ctx
|
|
@@ -24,7 +23,7 @@ class StarRequestSubStage(Stage):
|
|
|
24
23
|
async def process(
|
|
25
24
|
self,
|
|
26
25
|
event: AstrMessageEvent,
|
|
27
|
-
) -> AsyncGenerator[
|
|
26
|
+
) -> AsyncGenerator[Any, None]:
|
|
28
27
|
activated_handlers: list[StarHandlerMetadata] = event.get_extra(
|
|
29
28
|
"activated_handlers",
|
|
30
29
|
)
|
|
@@ -60,7 +60,7 @@ class ProcessStage(Stage):
|
|
|
60
60
|
):
|
|
61
61
|
# 是否有过发送操作 and 是否是被 @ 或者通过唤醒前缀
|
|
62
62
|
if (
|
|
63
|
-
event.get_result() and not event.
|
|
63
|
+
event.get_result() and not event.is_stopped()
|
|
64
64
|
) or not event.get_result():
|
|
65
65
|
async for _ in self.agent_sub_stage.process(event):
|
|
66
66
|
yield
|
|
@@ -117,7 +117,9 @@ class RespondStage(Stage):
|
|
|
117
117
|
if not self.enable_seg:
|
|
118
118
|
return False
|
|
119
119
|
|
|
120
|
-
if
|
|
120
|
+
if (result := event.get_result()) is None:
|
|
121
|
+
return False
|
|
122
|
+
if self.only_llm_result and result.is_llm_result():
|
|
121
123
|
return False
|
|
122
124
|
|
|
123
125
|
if event.get_platform_name() in [
|
|
@@ -185,7 +187,7 @@ class RespondStage(Stage):
|
|
|
185
187
|
if isinstance(component, Comp.File) and component.file:
|
|
186
188
|
# 支持 File 消息段的路径映射。
|
|
187
189
|
component.file = path_Mapping(mappings, component.file)
|
|
188
|
-
|
|
190
|
+
result.chain[idx] = component
|
|
189
191
|
|
|
190
192
|
# 检查消息链是否为空
|
|
191
193
|
try:
|
|
@@ -6,6 +6,7 @@ from collections.abc import AsyncGenerator
|
|
|
6
6
|
from astrbot.core import file_token_service, html_renderer, logger
|
|
7
7
|
from astrbot.core.message.components import At, File, Image, Node, Plain, Record, Reply
|
|
8
8
|
from astrbot.core.message.message_event_result import ResultContentType
|
|
9
|
+
from astrbot.core.pipeline.content_safety_check.stage import ContentSafetyCheckStage
|
|
9
10
|
from astrbot.core.platform.astr_message_event import AstrMessageEvent
|
|
10
11
|
from astrbot.core.platform.message_type import MessageType
|
|
11
12
|
from astrbot.core.star.session_llm_manager import SessionServiceManager
|
|
@@ -53,7 +54,22 @@ class ResultDecorateStage(Stage):
|
|
|
53
54
|
self.only_llm_result = ctx.astrbot_config["platform_settings"][
|
|
54
55
|
"segmented_reply"
|
|
55
56
|
]["only_llm_result"]
|
|
57
|
+
self.split_mode = ctx.astrbot_config["platform_settings"][
|
|
58
|
+
"segmented_reply"
|
|
59
|
+
].get("split_mode", "regex")
|
|
56
60
|
self.regex = ctx.astrbot_config["platform_settings"]["segmented_reply"]["regex"]
|
|
61
|
+
self.split_words = ctx.astrbot_config["platform_settings"][
|
|
62
|
+
"segmented_reply"
|
|
63
|
+
].get("split_words", ["。", "?", "!", "~", "…"])
|
|
64
|
+
if self.split_words:
|
|
65
|
+
escaped_words = sorted(
|
|
66
|
+
[re.escape(word) for word in self.split_words], key=len, reverse=True
|
|
67
|
+
)
|
|
68
|
+
self.split_words_pattern = re.compile(
|
|
69
|
+
f"(.*?({'|'.join(escaped_words)})|.+$)", re.DOTALL
|
|
70
|
+
)
|
|
71
|
+
else:
|
|
72
|
+
self.split_words_pattern = None
|
|
57
73
|
self.content_cleanup_rule = ctx.astrbot_config["platform_settings"][
|
|
58
74
|
"segmented_reply"
|
|
59
75
|
]["content_cleanup_rule"]
|
|
@@ -69,6 +85,28 @@ class ResultDecorateStage(Stage):
|
|
|
69
85
|
self.content_safe_check_stage = stage_cls()
|
|
70
86
|
await self.content_safe_check_stage.initialize(ctx)
|
|
71
87
|
|
|
88
|
+
def _split_text_by_words(self, text: str) -> list[str]:
|
|
89
|
+
"""使用分段词列表分段文本"""
|
|
90
|
+
if not self.split_words_pattern:
|
|
91
|
+
return [text]
|
|
92
|
+
|
|
93
|
+
segments = self.split_words_pattern.findall(text)
|
|
94
|
+
result = []
|
|
95
|
+
for seg in segments:
|
|
96
|
+
if isinstance(seg, tuple):
|
|
97
|
+
content = seg[0]
|
|
98
|
+
if not isinstance(content, str):
|
|
99
|
+
continue
|
|
100
|
+
for word in self.split_words:
|
|
101
|
+
if content.endswith(word):
|
|
102
|
+
content = content[: -len(word)]
|
|
103
|
+
break
|
|
104
|
+
if content.strip():
|
|
105
|
+
result.append(content)
|
|
106
|
+
elif seg and seg.strip():
|
|
107
|
+
result.append(seg)
|
|
108
|
+
return result if result else [text]
|
|
109
|
+
|
|
72
110
|
async def process(
|
|
73
111
|
self,
|
|
74
112
|
event: AstrMessageEvent,
|
|
@@ -93,11 +131,13 @@ class ResultDecorateStage(Stage):
|
|
|
93
131
|
for comp in result.chain:
|
|
94
132
|
if isinstance(comp, Plain):
|
|
95
133
|
text += comp.text
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
134
|
+
|
|
135
|
+
if isinstance(self.content_safe_check_stage, ContentSafetyCheckStage):
|
|
136
|
+
async for _ in self.content_safe_check_stage.process(
|
|
137
|
+
event,
|
|
138
|
+
check_text=text,
|
|
139
|
+
):
|
|
140
|
+
yield
|
|
101
141
|
|
|
102
142
|
# 发送消息前事件钩子
|
|
103
143
|
handlers = star_handlers_registry.get_handlers_by_event_type(
|
|
@@ -114,7 +154,8 @@ class ResultDecorateStage(Stage):
|
|
|
114
154
|
"启用流式输出时,依赖发送消息前事件钩子的插件可能无法正常工作",
|
|
115
155
|
)
|
|
116
156
|
await handler.handler(event)
|
|
117
|
-
|
|
157
|
+
|
|
158
|
+
if (result := event.get_result()) is None or not result.chain:
|
|
118
159
|
logger.debug(
|
|
119
160
|
f"hook(on_decorating_result) -> {star_map[handler.handler_module_path].name} - {handler.handler_name} 将消息结果清空。",
|
|
120
161
|
)
|
|
@@ -161,21 +202,27 @@ class ResultDecorateStage(Stage):
|
|
|
161
202
|
# 不分段回复
|
|
162
203
|
new_chain.append(comp)
|
|
163
204
|
continue
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
205
|
+
|
|
206
|
+
# 根据 split_mode 选择分段方式
|
|
207
|
+
if self.split_mode == "words":
|
|
208
|
+
split_response = self._split_text_by_words(comp.text)
|
|
209
|
+
else: # regex 模式
|
|
210
|
+
try:
|
|
211
|
+
split_response = re.findall(
|
|
212
|
+
self.regex,
|
|
213
|
+
comp.text,
|
|
214
|
+
re.DOTALL | re.MULTILINE,
|
|
215
|
+
)
|
|
216
|
+
except re.error:
|
|
217
|
+
logger.error(
|
|
218
|
+
f"分段回复正则表达式错误,使用默认分段方式: {traceback.format_exc()}",
|
|
219
|
+
)
|
|
220
|
+
split_response = re.findall(
|
|
221
|
+
r".*?[。?!~…]+|.+$",
|
|
222
|
+
comp.text,
|
|
223
|
+
re.DOTALL | re.MULTILINE,
|
|
224
|
+
)
|
|
225
|
+
|
|
179
226
|
if not split_response:
|
|
180
227
|
new_chain.append(comp)
|
|
181
228
|
continue
|
|
@@ -2,6 +2,10 @@ from collections.abc import AsyncGenerator
|
|
|
2
2
|
|
|
3
3
|
from astrbot.core import logger
|
|
4
4
|
from astrbot.core.platform import AstrMessageEvent
|
|
5
|
+
from astrbot.core.platform.sources.webchat.webchat_event import WebChatMessageEvent
|
|
6
|
+
from astrbot.core.platform.sources.wecom_ai_bot.wecomai_event import (
|
|
7
|
+
WecomAIBotMessageEvent,
|
|
8
|
+
)
|
|
5
9
|
|
|
6
10
|
from . import STAGES_ORDER
|
|
7
11
|
from .context import PipelineContext
|
|
@@ -78,7 +82,7 @@ class PipelineScheduler:
|
|
|
78
82
|
await self._process_stages(event)
|
|
79
83
|
|
|
80
84
|
# 如果没有发送操作, 则发送一个空消息, 以便于后续的处理
|
|
81
|
-
if event
|
|
85
|
+
if isinstance(event, (WebChatMessageEvent, WecomAIBotMessageEvent)):
|
|
82
86
|
await event.send(None)
|
|
83
87
|
|
|
84
88
|
logger.debug("pipeline 执行完毕。")
|
|
@@ -50,6 +50,9 @@ class WakingCheckStage(Stage):
|
|
|
50
50
|
"ignore_at_all",
|
|
51
51
|
False,
|
|
52
52
|
)
|
|
53
|
+
self.disable_builtin_commands = self.ctx.astrbot_config.get(
|
|
54
|
+
"disable_builtin_commands", False
|
|
55
|
+
)
|
|
53
56
|
|
|
54
57
|
async def process(
|
|
55
58
|
self,
|
|
@@ -131,6 +134,13 @@ class WakingCheckStage(Stage):
|
|
|
131
134
|
EventType.AdapterMessageEvent,
|
|
132
135
|
plugins_name=event.plugins_name,
|
|
133
136
|
):
|
|
137
|
+
if (
|
|
138
|
+
self.disable_builtin_commands
|
|
139
|
+
and handler.handler_module_path == "packages.builtin_commands.main"
|
|
140
|
+
):
|
|
141
|
+
logger.debug("skipping builtin command")
|
|
142
|
+
continue
|
|
143
|
+
|
|
134
144
|
# filter 需满足 AND 逻辑关系
|
|
135
145
|
passed = True
|
|
136
146
|
permission_not_pass = False
|
|
@@ -153,7 +153,9 @@ class AstrMessageEvent(abc.ABC):
|
|
|
153
153
|
|
|
154
154
|
def get_sender_name(self) -> str:
|
|
155
155
|
"""获取消息发送者的名称。(可能会返回空字符串)"""
|
|
156
|
-
|
|
156
|
+
if isinstance(self.message_obj.sender.nickname, str):
|
|
157
|
+
return self.message_obj.sender.nickname
|
|
158
|
+
return ""
|
|
157
159
|
|
|
158
160
|
def set_extra(self, key, value):
|
|
159
161
|
"""设置额外的信息。"""
|
|
@@ -270,7 +272,7 @@ class AstrMessageEvent(abc.ABC):
|
|
|
270
272
|
"""
|
|
271
273
|
self.call_llm = call_llm
|
|
272
274
|
|
|
273
|
-
def get_result(self) -> MessageEventResult:
|
|
275
|
+
def get_result(self) -> MessageEventResult | None:
|
|
274
276
|
"""获取消息事件的结果。"""
|
|
275
277
|
return self._result
|
|
276
278
|
|
|
@@ -320,7 +322,7 @@ class AstrMessageEvent(abc.ABC):
|
|
|
320
322
|
self,
|
|
321
323
|
prompt: str,
|
|
322
324
|
func_tool_manager=None,
|
|
323
|
-
session_id: str =
|
|
325
|
+
session_id: str = "",
|
|
324
326
|
image_urls: list[str] | None = None,
|
|
325
327
|
contexts: list | None = None,
|
|
326
328
|
system_prompt: str = "",
|
|
@@ -54,7 +54,7 @@ class AstrBotMessage:
|
|
|
54
54
|
self_id: str # 机器人的识别id
|
|
55
55
|
session_id: str # 会话id。取决于 unique_session 的设置。
|
|
56
56
|
message_id: str # 消息id
|
|
57
|
-
group: Group # 群组
|
|
57
|
+
group: Group | None # 群组
|
|
58
58
|
sender: MessageMember # 发送者
|
|
59
59
|
message: list[BaseMessageComponent] # 消息链使用 Nakuru 的消息链格式
|
|
60
60
|
message_str: str # 最直观的纯文本消息字符串
|
|
@@ -78,7 +78,7 @@ class AstrBotMessage:
|
|
|
78
78
|
return ""
|
|
79
79
|
|
|
80
80
|
@group_id.setter
|
|
81
|
-
def group_id(self, value: str):
|
|
81
|
+
def group_id(self, value: str | None):
|
|
82
82
|
"""设置 group_id"""
|
|
83
83
|
if value:
|
|
84
84
|
if self.group:
|
astrbot/core/platform/manager.py
CHANGED
|
@@ -5,8 +5,9 @@ from asyncio import Queue
|
|
|
5
5
|
from astrbot.core import logger
|
|
6
6
|
from astrbot.core.config.astrbot_config import AstrBotConfig
|
|
7
7
|
from astrbot.core.star.star_handler import EventType, star_handlers_registry, star_map
|
|
8
|
+
from astrbot.core.utils.webhook_utils import ensure_platform_webhook_config
|
|
8
9
|
|
|
9
|
-
from .platform import Platform
|
|
10
|
+
from .platform import Platform, PlatformStatus
|
|
10
11
|
from .register import platform_cls_map
|
|
11
12
|
from .sources.webchat.webchat_adapter import WebChatAdapter
|
|
12
13
|
|
|
@@ -16,8 +17,9 @@ class PlatformManager:
|
|
|
16
17
|
self.platform_insts: list[Platform] = []
|
|
17
18
|
"""加载的 Platform 的实例"""
|
|
18
19
|
|
|
19
|
-
self._inst_map = {}
|
|
20
|
+
self._inst_map: dict[str, dict] = {}
|
|
20
21
|
|
|
22
|
+
self.astrbot_config = config
|
|
21
23
|
self.platforms_config = config["platform"]
|
|
22
24
|
self.settings = config["platform_settings"]
|
|
23
25
|
"""NOTE: 这里是 default 的配置文件,以保证最大的兼容性;
|
|
@@ -29,6 +31,8 @@ class PlatformManager:
|
|
|
29
31
|
"""初始化所有平台适配器"""
|
|
30
32
|
for platform in self.platforms_config:
|
|
31
33
|
try:
|
|
34
|
+
if ensure_platform_webhook_config(platform):
|
|
35
|
+
self.astrbot_config.save_config()
|
|
32
36
|
await self.load_platform(platform)
|
|
33
37
|
except Exception as e:
|
|
34
38
|
logger.error(f"初始化 {platform} 平台适配器失败: {e}")
|
|
@@ -37,7 +41,10 @@ class PlatformManager:
|
|
|
37
41
|
webchat_inst = WebChatAdapter({}, self.settings, self.event_queue)
|
|
38
42
|
self.platform_insts.append(webchat_inst)
|
|
39
43
|
asyncio.create_task(
|
|
40
|
-
self._task_wrapper(
|
|
44
|
+
self._task_wrapper(
|
|
45
|
+
asyncio.create_task(webchat_inst.run(), name="webchat"),
|
|
46
|
+
platform=webchat_inst,
|
|
47
|
+
),
|
|
41
48
|
)
|
|
42
49
|
|
|
43
50
|
async def load_platform(self, platform_config: dict):
|
|
@@ -107,7 +114,7 @@ class PlatformManager:
|
|
|
107
114
|
)
|
|
108
115
|
except (ImportError, ModuleNotFoundError) as e:
|
|
109
116
|
logger.error(
|
|
110
|
-
f"加载平台适配器 {platform_config['type']} 失败,原因:{e}。请检查依赖库是否安装。提示:可以在
|
|
117
|
+
f"加载平台适配器 {platform_config['type']} 失败,原因:{e}。请检查依赖库是否安装。提示:可以在 管理面板->平台日志->安装Pip库 中安装依赖库。",
|
|
111
118
|
)
|
|
112
119
|
except Exception as e:
|
|
113
120
|
logger.error(f"加载平台适配器 {platform_config['type']} 失败,原因:{e}。")
|
|
@@ -131,6 +138,7 @@ class PlatformManager:
|
|
|
131
138
|
inst.run(),
|
|
132
139
|
name=f"platform_{platform_config['type']}_{platform_config['id']}",
|
|
133
140
|
),
|
|
141
|
+
platform=inst,
|
|
134
142
|
),
|
|
135
143
|
)
|
|
136
144
|
handlers = star_handlers_registry.get_handlers_by_event_type(
|
|
@@ -145,17 +153,28 @@ class PlatformManager:
|
|
|
145
153
|
except Exception:
|
|
146
154
|
logger.error(traceback.format_exc())
|
|
147
155
|
|
|
148
|
-
async def _task_wrapper(self, task: asyncio.Task):
|
|
156
|
+
async def _task_wrapper(self, task: asyncio.Task, platform: Platform | None = None):
|
|
157
|
+
# 设置平台状态为运行中
|
|
158
|
+
if platform:
|
|
159
|
+
platform.status = PlatformStatus.RUNNING
|
|
160
|
+
|
|
149
161
|
try:
|
|
150
162
|
await task
|
|
151
163
|
except asyncio.CancelledError:
|
|
152
|
-
|
|
164
|
+
if platform:
|
|
165
|
+
platform.status = PlatformStatus.STOPPED
|
|
153
166
|
except Exception as e:
|
|
167
|
+
error_msg = str(e)
|
|
168
|
+
tb_str = traceback.format_exc()
|
|
154
169
|
logger.error(f"------- 任务 {task.get_name()} 发生错误: {e}")
|
|
155
|
-
for line in
|
|
170
|
+
for line in tb_str.split("\n"):
|
|
156
171
|
logger.error(f"| {line}")
|
|
157
172
|
logger.error("-------")
|
|
158
173
|
|
|
174
|
+
# 记录错误到平台实例
|
|
175
|
+
if platform:
|
|
176
|
+
platform.record_error(error_msg, tb_str)
|
|
177
|
+
|
|
159
178
|
async def reload(self, platform_config: dict):
|
|
160
179
|
await self.terminate_platform(platform_config["id"])
|
|
161
180
|
if platform_config["enable"]:
|
|
@@ -172,9 +191,9 @@ class PlatformManager:
|
|
|
172
191
|
logger.info(f"正在尝试终止 {platform_id} 平台适配器 ...")
|
|
173
192
|
|
|
174
193
|
# client_id = self._inst_map.pop(platform_id, None)
|
|
175
|
-
info = self._inst_map.pop(platform_id
|
|
194
|
+
info = self._inst_map.pop(platform_id)
|
|
176
195
|
client_id = info["client_id"]
|
|
177
|
-
inst = info["inst"]
|
|
196
|
+
inst: Platform = info["inst"]
|
|
178
197
|
try:
|
|
179
198
|
self.platform_insts.remove(
|
|
180
199
|
next(
|
|
@@ -196,3 +215,46 @@ class PlatformManager:
|
|
|
196
215
|
|
|
197
216
|
def get_insts(self):
|
|
198
217
|
return self.platform_insts
|
|
218
|
+
|
|
219
|
+
def get_all_stats(self) -> dict:
|
|
220
|
+
"""获取所有平台的统计信息
|
|
221
|
+
|
|
222
|
+
Returns:
|
|
223
|
+
包含所有平台统计信息的字典
|
|
224
|
+
"""
|
|
225
|
+
stats_list = []
|
|
226
|
+
total_errors = 0
|
|
227
|
+
running_count = 0
|
|
228
|
+
error_count = 0
|
|
229
|
+
|
|
230
|
+
for inst in self.platform_insts:
|
|
231
|
+
try:
|
|
232
|
+
stat = inst.get_stats()
|
|
233
|
+
stats_list.append(stat)
|
|
234
|
+
total_errors += stat.get("error_count", 0)
|
|
235
|
+
if stat.get("status") == PlatformStatus.RUNNING.value:
|
|
236
|
+
running_count += 1
|
|
237
|
+
elif stat.get("status") == PlatformStatus.ERROR.value:
|
|
238
|
+
error_count += 1
|
|
239
|
+
except Exception as e:
|
|
240
|
+
# 如果获取统计信息失败,记录基本信息
|
|
241
|
+
logger.warning(f"获取平台统计信息失败: {e}")
|
|
242
|
+
stats_list.append(
|
|
243
|
+
{
|
|
244
|
+
"id": getattr(inst, "config", {}).get("id", "unknown"),
|
|
245
|
+
"type": "unknown",
|
|
246
|
+
"status": "unknown",
|
|
247
|
+
"error_count": 0,
|
|
248
|
+
"last_error": None,
|
|
249
|
+
}
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
return {
|
|
253
|
+
"platforms": stats_list,
|
|
254
|
+
"summary": {
|
|
255
|
+
"total": len(stats_list),
|
|
256
|
+
"running": running_count,
|
|
257
|
+
"error": error_count,
|
|
258
|
+
"total_errors": total_errors,
|
|
259
|
+
},
|
|
260
|
+
}
|