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/cli/__init__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "4.
|
|
1
|
+
__version__ = "4.9.0"
|
|
@@ -97,7 +97,6 @@ class ToolLoopAgentRunner(BaseAgentRunner[TContext]):
|
|
|
97
97
|
llm_resp_result = None
|
|
98
98
|
|
|
99
99
|
async for llm_response in self._iter_llm_responses():
|
|
100
|
-
assert isinstance(llm_response, LLMResponse)
|
|
101
100
|
if llm_response.is_chunk:
|
|
102
101
|
if llm_response.result_chain:
|
|
103
102
|
yield AgentResponse(
|
astrbot/core/agent/tool.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from collections.abc import Awaitable, Callable
|
|
1
|
+
from collections.abc import AsyncGenerator, Awaitable, Callable
|
|
2
2
|
from typing import Any, Generic
|
|
3
3
|
|
|
4
4
|
import jsonschema
|
|
@@ -7,6 +7,8 @@ from deprecated import deprecated
|
|
|
7
7
|
from pydantic import Field, model_validator
|
|
8
8
|
from pydantic.dataclasses import dataclass
|
|
9
9
|
|
|
10
|
+
from astrbot.core.message.message_event_result import MessageEventResult
|
|
11
|
+
|
|
10
12
|
from .run_context import ContextWrapper, TContext
|
|
11
13
|
|
|
12
14
|
ParametersType = dict[str, Any]
|
|
@@ -38,7 +40,10 @@ class ToolSchema:
|
|
|
38
40
|
class FunctionTool(ToolSchema, Generic[TContext]):
|
|
39
41
|
"""A callable tool, for function calling."""
|
|
40
42
|
|
|
41
|
-
handler:
|
|
43
|
+
handler: (
|
|
44
|
+
Callable[..., Awaitable[str | None] | AsyncGenerator[MessageEventResult, None]]
|
|
45
|
+
| None
|
|
46
|
+
) = None
|
|
42
47
|
"""a callable that implements the tool's functionality. It should be an async function."""
|
|
43
48
|
|
|
44
49
|
handler_module_path: str | None = None
|
|
@@ -9,6 +9,7 @@ from astrbot.core.message.message_event_result import (
|
|
|
9
9
|
MessageEventResult,
|
|
10
10
|
ResultContentType,
|
|
11
11
|
)
|
|
12
|
+
from astrbot.core.provider.entities import LLMResponse
|
|
12
13
|
|
|
13
14
|
AgentRunner = ToolLoopAgentRunner[AstrAgentContext]
|
|
14
15
|
|
|
@@ -72,7 +73,20 @@ async def run_agent(
|
|
|
72
73
|
|
|
73
74
|
except Exception as e:
|
|
74
75
|
logger.error(traceback.format_exc())
|
|
75
|
-
|
|
76
|
+
|
|
77
|
+
err_msg = f"\n\nAstrBot 请求失败。\n错误类型: {type(e).__name__}\n错误信息: {e!s}\n\n请在平台日志查看和分享错误详情。\n"
|
|
78
|
+
|
|
79
|
+
error_llm_response = LLMResponse(
|
|
80
|
+
role="err",
|
|
81
|
+
completion_text=err_msg,
|
|
82
|
+
)
|
|
83
|
+
try:
|
|
84
|
+
await agent_runner.agent_hooks.on_agent_done(
|
|
85
|
+
agent_runner.run_context, error_llm_response
|
|
86
|
+
)
|
|
87
|
+
except Exception:
|
|
88
|
+
logger.exception("Error in on_agent_done hook")
|
|
89
|
+
|
|
76
90
|
if agent_runner.streaming:
|
|
77
91
|
yield MessageChain().message(err_msg)
|
|
78
92
|
else:
|
|
@@ -185,7 +185,11 @@ class FunctionToolExecutor(BaseFunctionToolExecutor[AstrAgentContext]):
|
|
|
185
185
|
|
|
186
186
|
async def call_local_llm_tool(
|
|
187
187
|
context: ContextWrapper[AstrAgentContext],
|
|
188
|
-
handler: T.Callable[
|
|
188
|
+
handler: T.Callable[
|
|
189
|
+
...,
|
|
190
|
+
T.Awaitable[MessageEventResult | mcp.types.CallToolResult | str | None]
|
|
191
|
+
| T.AsyncGenerator[MessageEventResult | CommandResult | str | None, None],
|
|
192
|
+
],
|
|
189
193
|
method_name: str,
|
|
190
194
|
*args,
|
|
191
195
|
**kwargs,
|
astrbot/core/config/default.py
CHANGED
|
@@ -4,9 +4,18 @@ import os
|
|
|
4
4
|
|
|
5
5
|
from astrbot.core.utils.astrbot_path import get_astrbot_data_path
|
|
6
6
|
|
|
7
|
-
VERSION = "4.
|
|
7
|
+
VERSION = "4.9.0"
|
|
8
8
|
DB_PATH = os.path.join(get_astrbot_data_path(), "data_v4.db")
|
|
9
9
|
|
|
10
|
+
WEBHOOK_SUPPORTED_PLATFORMS = [
|
|
11
|
+
"qq_official_webhook",
|
|
12
|
+
"weixin_official_account",
|
|
13
|
+
"wecom",
|
|
14
|
+
"wecom_ai_bot",
|
|
15
|
+
"slack",
|
|
16
|
+
"lark",
|
|
17
|
+
]
|
|
18
|
+
|
|
10
19
|
# 默认配置
|
|
11
20
|
DEFAULT_CONFIG = {
|
|
12
21
|
"config_version": 2,
|
|
@@ -34,7 +43,15 @@ DEFAULT_CONFIG = {
|
|
|
34
43
|
"interval": "1.5,3.5",
|
|
35
44
|
"log_base": 2.6,
|
|
36
45
|
"words_count_threshold": 150,
|
|
46
|
+
"split_mode": "regex", # regex 或 words
|
|
37
47
|
"regex": ".*?[。?!~…]+|.+$",
|
|
48
|
+
"split_words": [
|
|
49
|
+
"。",
|
|
50
|
+
"?",
|
|
51
|
+
"!",
|
|
52
|
+
"~",
|
|
53
|
+
"…",
|
|
54
|
+
], # 当 split_mode 为 words 时使用
|
|
38
55
|
"content_cleanup_rule": "",
|
|
39
56
|
},
|
|
40
57
|
"no_permission_reply": True,
|
|
@@ -149,6 +166,7 @@ DEFAULT_CONFIG = {
|
|
|
149
166
|
"kb_fusion_top_k": 20, # 知识库检索融合阶段返回结果数量
|
|
150
167
|
"kb_final_top_k": 5, # 知识库检索最终返回结果数量
|
|
151
168
|
"kb_agentic_mode": False,
|
|
169
|
+
"disable_builtin_commands": False,
|
|
152
170
|
}
|
|
153
171
|
|
|
154
172
|
|
|
@@ -185,6 +203,8 @@ CONFIG_METADATA_2 = {
|
|
|
185
203
|
"appid": "",
|
|
186
204
|
"secret": "",
|
|
187
205
|
"is_sandbox": False,
|
|
206
|
+
"unified_webhook_mode": True,
|
|
207
|
+
"webhook_uuid": "",
|
|
188
208
|
"callback_server_host": "0.0.0.0",
|
|
189
209
|
"port": 6196,
|
|
190
210
|
},
|
|
@@ -215,6 +235,8 @@ CONFIG_METADATA_2 = {
|
|
|
215
235
|
"token": "",
|
|
216
236
|
"encoding_aes_key": "",
|
|
217
237
|
"api_base_url": "https://api.weixin.qq.com/cgi-bin/",
|
|
238
|
+
"unified_webhook_mode": True,
|
|
239
|
+
"webhook_uuid": "",
|
|
218
240
|
"callback_server_host": "0.0.0.0",
|
|
219
241
|
"port": 6194,
|
|
220
242
|
"active_send_mode": False,
|
|
@@ -229,6 +251,8 @@ CONFIG_METADATA_2 = {
|
|
|
229
251
|
"encoding_aes_key": "",
|
|
230
252
|
"kf_name": "",
|
|
231
253
|
"api_base_url": "https://qyapi.weixin.qq.com/cgi-bin/",
|
|
254
|
+
"unified_webhook_mode": True,
|
|
255
|
+
"webhook_uuid": "",
|
|
232
256
|
"callback_server_host": "0.0.0.0",
|
|
233
257
|
"port": 6195,
|
|
234
258
|
},
|
|
@@ -241,6 +265,8 @@ CONFIG_METADATA_2 = {
|
|
|
241
265
|
"wecom_ai_bot_name": "",
|
|
242
266
|
"token": "",
|
|
243
267
|
"encoding_aes_key": "",
|
|
268
|
+
"unified_webhook_mode": True,
|
|
269
|
+
"webhook_uuid": "",
|
|
244
270
|
"callback_server_host": "0.0.0.0",
|
|
245
271
|
"port": 6198,
|
|
246
272
|
},
|
|
@@ -252,6 +278,10 @@ CONFIG_METADATA_2 = {
|
|
|
252
278
|
"app_id": "",
|
|
253
279
|
"app_secret": "",
|
|
254
280
|
"domain": "https://open.feishu.cn",
|
|
281
|
+
"lark_connection_mode": "socket", # webhook, socket
|
|
282
|
+
"webhook_uuid": "",
|
|
283
|
+
"lark_encrypt_key": "",
|
|
284
|
+
"lark_verification_token": "",
|
|
255
285
|
},
|
|
256
286
|
"钉钉(DingTalk)": {
|
|
257
287
|
"id": "dingtalk",
|
|
@@ -308,6 +338,8 @@ CONFIG_METADATA_2 = {
|
|
|
308
338
|
"app_token": "",
|
|
309
339
|
"signing_secret": "",
|
|
310
340
|
"slack_connection_mode": "socket", # webhook, socket
|
|
341
|
+
"unified_webhook_mode": True,
|
|
342
|
+
"webhook_uuid": "",
|
|
311
343
|
"slack_webhook_host": "0.0.0.0",
|
|
312
344
|
"slack_webhook_port": 6197,
|
|
313
345
|
"slack_webhook_path": "/astrbot-slack-webhook/callback",
|
|
@@ -343,6 +375,28 @@ CONFIG_METADATA_2 = {
|
|
|
343
375
|
# "type": "string",
|
|
344
376
|
# "options": ["fullscreen", "embedded"],
|
|
345
377
|
# },
|
|
378
|
+
"lark_connection_mode": {
|
|
379
|
+
"description": "订阅方式",
|
|
380
|
+
"type": "string",
|
|
381
|
+
"options": ["socket", "webhook"],
|
|
382
|
+
"labels": ["长连接模式", "推送至服务器模式"],
|
|
383
|
+
},
|
|
384
|
+
"lark_encrypt_key": {
|
|
385
|
+
"description": "Encrypt Key",
|
|
386
|
+
"type": "string",
|
|
387
|
+
"hint": "用于解密飞书回调数据的加密密钥",
|
|
388
|
+
"condition": {
|
|
389
|
+
"lark_connection_mode": "webhook",
|
|
390
|
+
},
|
|
391
|
+
},
|
|
392
|
+
"lark_verification_token": {
|
|
393
|
+
"description": "Verification Token",
|
|
394
|
+
"type": "string",
|
|
395
|
+
"hint": "用于验证飞书回调请求的令牌",
|
|
396
|
+
"condition": {
|
|
397
|
+
"lark_connection_mode": "webhook",
|
|
398
|
+
},
|
|
399
|
+
},
|
|
346
400
|
"is_sandbox": {
|
|
347
401
|
"description": "沙箱模式",
|
|
348
402
|
"type": "bool",
|
|
@@ -387,16 +441,28 @@ CONFIG_METADATA_2 = {
|
|
|
387
441
|
"description": "Slack Webhook Host",
|
|
388
442
|
"type": "string",
|
|
389
443
|
"hint": "Only valid when Slack connection mode is `webhook`.",
|
|
444
|
+
"condition": {
|
|
445
|
+
"slack_connection_mode": "webhook",
|
|
446
|
+
"unified_webhook_mode": False,
|
|
447
|
+
},
|
|
390
448
|
},
|
|
391
449
|
"slack_webhook_port": {
|
|
392
450
|
"description": "Slack Webhook Port",
|
|
393
451
|
"type": "int",
|
|
394
452
|
"hint": "Only valid when Slack connection mode is `webhook`.",
|
|
453
|
+
"condition": {
|
|
454
|
+
"slack_connection_mode": "webhook",
|
|
455
|
+
"unified_webhook_mode": False,
|
|
456
|
+
},
|
|
395
457
|
},
|
|
396
458
|
"slack_webhook_path": {
|
|
397
459
|
"description": "Slack Webhook Path",
|
|
398
460
|
"type": "string",
|
|
399
461
|
"hint": "Only valid when Slack connection mode is `webhook`.",
|
|
462
|
+
"condition": {
|
|
463
|
+
"slack_connection_mode": "webhook",
|
|
464
|
+
"unified_webhook_mode": False,
|
|
465
|
+
},
|
|
400
466
|
},
|
|
401
467
|
"active_send_mode": {
|
|
402
468
|
"description": "是否换用主动发送接口",
|
|
@@ -587,6 +653,33 @@ CONFIG_METADATA_2 = {
|
|
|
587
653
|
"type": "string",
|
|
588
654
|
"hint": "可选的 Discord 活动名称。留空则不设置活动。",
|
|
589
655
|
},
|
|
656
|
+
"port": {
|
|
657
|
+
"description": "回调服务器端口",
|
|
658
|
+
"type": "int",
|
|
659
|
+
"hint": "回调服务器端口。留空则不启用回调服务器。",
|
|
660
|
+
"condition": {
|
|
661
|
+
"unified_webhook_mode": False,
|
|
662
|
+
},
|
|
663
|
+
},
|
|
664
|
+
"callback_server_host": {
|
|
665
|
+
"description": "回调服务器主机",
|
|
666
|
+
"type": "string",
|
|
667
|
+
"hint": "回调服务器主机。留空则不启用回调服务器。",
|
|
668
|
+
"condition": {
|
|
669
|
+
"unified_webhook_mode": False,
|
|
670
|
+
},
|
|
671
|
+
},
|
|
672
|
+
"unified_webhook_mode": {
|
|
673
|
+
"description": "统一 Webhook 模式",
|
|
674
|
+
"type": "bool",
|
|
675
|
+
"hint": "启用后,将使用 AstrBot 统一 Webhook 入口,无需单独开启端口。回调地址为 /api/platform/webhook/{webhook_uuid}。",
|
|
676
|
+
},
|
|
677
|
+
"webhook_uuid": {
|
|
678
|
+
"invisible": True,
|
|
679
|
+
"description": "Webhook UUID",
|
|
680
|
+
"type": "string",
|
|
681
|
+
"hint": "统一 Webhook 模式下的唯一标识符,创建平台时自动生成。",
|
|
682
|
+
},
|
|
590
683
|
},
|
|
591
684
|
},
|
|
592
685
|
"platform_settings": {
|
|
@@ -2604,6 +2697,11 @@ CONFIG_METADATA_3 = {
|
|
|
2604
2697
|
"description": "只 @ 机器人是否触发等待",
|
|
2605
2698
|
"type": "bool",
|
|
2606
2699
|
},
|
|
2700
|
+
"disable_builtin_commands": {
|
|
2701
|
+
"description": "禁用自带指令",
|
|
2702
|
+
"type": "bool",
|
|
2703
|
+
"hint": "禁用所有 AstrBot 的自带指令,如 help, provider, model 等。",
|
|
2704
|
+
},
|
|
2607
2705
|
},
|
|
2608
2706
|
},
|
|
2609
2707
|
"whitelist": {
|
|
@@ -2818,9 +2916,26 @@ CONFIG_METADATA_3 = {
|
|
|
2818
2916
|
"description": "分段回复字数阈值",
|
|
2819
2917
|
"type": "int",
|
|
2820
2918
|
},
|
|
2919
|
+
"platform_settings.segmented_reply.split_mode": {
|
|
2920
|
+
"description": "分段模式",
|
|
2921
|
+
"type": "string",
|
|
2922
|
+
"options": ["regex", "words"],
|
|
2923
|
+
"labels": ["正则表达式", "分段词列表"],
|
|
2924
|
+
},
|
|
2821
2925
|
"platform_settings.segmented_reply.regex": {
|
|
2822
2926
|
"description": "分段正则表达式",
|
|
2823
2927
|
"type": "string",
|
|
2928
|
+
"condition": {
|
|
2929
|
+
"platform_settings.segmented_reply.split_mode": "regex",
|
|
2930
|
+
},
|
|
2931
|
+
},
|
|
2932
|
+
"platform_settings.segmented_reply.split_words": {
|
|
2933
|
+
"description": "分段词列表",
|
|
2934
|
+
"type": "list",
|
|
2935
|
+
"hint": "检测到列表中的任意词时进行分段,如:。、?、!等",
|
|
2936
|
+
"condition": {
|
|
2937
|
+
"platform_settings.segmented_reply.split_mode": "words",
|
|
2938
|
+
},
|
|
2824
2939
|
},
|
|
2825
2940
|
"platform_settings.segmented_reply.content_cleanup_rule": {
|
|
2826
2941
|
"description": "内容过滤正则表达式",
|
astrbot/core/core_lifecycle.py
CHANGED
|
@@ -197,7 +197,7 @@ class AstrBotCoreLifecycle:
|
|
|
197
197
|
# 把插件中注册的所有协程函数注册到事件总线中并执行
|
|
198
198
|
extra_tasks = []
|
|
199
199
|
for task in self.star_context._register_tasks:
|
|
200
|
-
extra_tasks.append(asyncio.create_task(task, name=task.__name__))
|
|
200
|
+
extra_tasks.append(asyncio.create_task(task, name=task.__name__)) # type: ignore
|
|
201
201
|
|
|
202
202
|
tasks_ = [event_bus_task, *extra_tasks]
|
|
203
203
|
for task in tasks_:
|
astrbot/core/db/__init__.py
CHANGED
|
@@ -5,8 +5,7 @@ from contextlib import asynccontextmanager
|
|
|
5
5
|
from dataclasses import dataclass
|
|
6
6
|
|
|
7
7
|
from deprecated import deprecated
|
|
8
|
-
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
|
|
9
|
-
from sqlalchemy.orm import sessionmaker
|
|
8
|
+
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
|
|
10
9
|
|
|
11
10
|
from astrbot.core.db.po import (
|
|
12
11
|
Attachment,
|
|
@@ -32,7 +31,7 @@ class BaseDatabase(abc.ABC):
|
|
|
32
31
|
echo=False,
|
|
33
32
|
future=True,
|
|
34
33
|
)
|
|
35
|
-
self.AsyncSessionLocal =
|
|
34
|
+
self.AsyncSessionLocal = async_sessionmaker(
|
|
36
35
|
self.engine,
|
|
37
36
|
class_=AsyncSession,
|
|
38
37
|
expire_on_commit=False,
|
|
@@ -173,7 +172,7 @@ class BaseDatabase(abc.ABC):
|
|
|
173
172
|
content: dict,
|
|
174
173
|
sender_id: str | None = None,
|
|
175
174
|
sender_name: str | None = None,
|
|
176
|
-
) ->
|
|
175
|
+
) -> PlatformMessageHistory:
|
|
177
176
|
"""Insert a new platform message history record."""
|
|
178
177
|
...
|
|
179
178
|
|
|
@@ -198,6 +197,14 @@ class BaseDatabase(abc.ABC):
|
|
|
198
197
|
"""Get platform message history for a specific user."""
|
|
199
198
|
...
|
|
200
199
|
|
|
200
|
+
@abc.abstractmethod
|
|
201
|
+
async def get_platform_message_history_by_id(
|
|
202
|
+
self,
|
|
203
|
+
message_id: int,
|
|
204
|
+
) -> PlatformMessageHistory | None:
|
|
205
|
+
"""Get a platform message history record by its ID."""
|
|
206
|
+
...
|
|
207
|
+
|
|
201
208
|
@abc.abstractmethod
|
|
202
209
|
async def insert_attachment(
|
|
203
210
|
self,
|
|
@@ -213,6 +220,27 @@ class BaseDatabase(abc.ABC):
|
|
|
213
220
|
"""Get an attachment by its ID."""
|
|
214
221
|
...
|
|
215
222
|
|
|
223
|
+
@abc.abstractmethod
|
|
224
|
+
async def get_attachments(self, attachment_ids: list[str]) -> list[Attachment]:
|
|
225
|
+
"""Get multiple attachments by their IDs."""
|
|
226
|
+
...
|
|
227
|
+
|
|
228
|
+
@abc.abstractmethod
|
|
229
|
+
async def delete_attachment(self, attachment_id: str) -> bool:
|
|
230
|
+
"""Delete an attachment by its ID.
|
|
231
|
+
|
|
232
|
+
Returns True if the attachment was deleted, False if it was not found.
|
|
233
|
+
"""
|
|
234
|
+
...
|
|
235
|
+
|
|
236
|
+
@abc.abstractmethod
|
|
237
|
+
async def delete_attachments(self, attachment_ids: list[str]) -> int:
|
|
238
|
+
"""Delete multiple attachments by their IDs.
|
|
239
|
+
|
|
240
|
+
Returns the number of attachments deleted.
|
|
241
|
+
"""
|
|
242
|
+
...
|
|
243
|
+
|
|
216
244
|
@abc.abstractmethod
|
|
217
245
|
async def insert_persona(
|
|
218
246
|
self,
|
|
@@ -70,6 +70,7 @@ async def migration_conversation_table(
|
|
|
70
70
|
logger.info(
|
|
71
71
|
f"未找到该条旧会话对应的具体数据: {conversation}, 跳过。",
|
|
72
72
|
)
|
|
73
|
+
continue
|
|
73
74
|
if ":" not in conv.user_id:
|
|
74
75
|
continue
|
|
75
76
|
session = MessageSesion.from_str(session_str=conv.user_id)
|
|
@@ -207,6 +208,7 @@ async def migration_webchat_data(
|
|
|
207
208
|
logger.info(
|
|
208
209
|
f"未找到该条旧会话对应的具体数据: {conversation}, 跳过。",
|
|
209
210
|
)
|
|
211
|
+
continue
|
|
210
212
|
if ":" in conv.user_id:
|
|
211
213
|
continue
|
|
212
214
|
platform_id = "webchat"
|
|
@@ -127,7 +127,7 @@ class SQLiteDatabase:
|
|
|
127
127
|
conn.text_factory = str
|
|
128
128
|
return conn
|
|
129
129
|
|
|
130
|
-
def _exec_sql(self, sql: str, params: tuple = None):
|
|
130
|
+
def _exec_sql(self, sql: str, params: tuple | None = None):
|
|
131
131
|
conn = self.conn
|
|
132
132
|
try:
|
|
133
133
|
c = self.conn.cursor()
|
|
@@ -224,9 +224,11 @@ class SQLiteDatabase:
|
|
|
224
224
|
|
|
225
225
|
c.close()
|
|
226
226
|
|
|
227
|
-
return Stats(platform
|
|
227
|
+
return Stats(platform)
|
|
228
228
|
|
|
229
|
-
def get_conversation_by_user_id(
|
|
229
|
+
def get_conversation_by_user_id(
|
|
230
|
+
self, user_id: str, cid: str
|
|
231
|
+
) -> Conversation | None:
|
|
230
232
|
try:
|
|
231
233
|
c = self.conn.cursor()
|
|
232
234
|
except sqlite3.ProgrammingError:
|
|
@@ -258,7 +260,7 @@ class SQLiteDatabase:
|
|
|
258
260
|
(user_id, cid, history, updated_at, created_at),
|
|
259
261
|
)
|
|
260
262
|
|
|
261
|
-
def get_conversations(self, user_id: str) ->
|
|
263
|
+
def get_conversations(self, user_id: str) -> list[Conversation]:
|
|
262
264
|
try:
|
|
263
265
|
c = self.conn.cursor()
|
|
264
266
|
except sqlite3.ProgrammingError:
|
astrbot/core/db/po.py
CHANGED
|
@@ -12,7 +12,7 @@ class PlatformStat(SQLModel, table=True):
|
|
|
12
12
|
Note: In astrbot v4, we moved `platform` table to here.
|
|
13
13
|
"""
|
|
14
14
|
|
|
15
|
-
__tablename__ = "platform_stats"
|
|
15
|
+
__tablename__: str = "platform_stats"
|
|
16
16
|
|
|
17
17
|
id: int = Field(primary_key=True, sa_column_kwargs={"autoincrement": True})
|
|
18
18
|
timestamp: datetime = Field(nullable=False)
|
|
@@ -31,9 +31,10 @@ class PlatformStat(SQLModel, table=True):
|
|
|
31
31
|
|
|
32
32
|
|
|
33
33
|
class ConversationV2(SQLModel, table=True):
|
|
34
|
-
__tablename__ = "conversations"
|
|
34
|
+
__tablename__: str = "conversations"
|
|
35
35
|
|
|
36
|
-
inner_conversation_id: int = Field(
|
|
36
|
+
inner_conversation_id: int | None = Field(
|
|
37
|
+
default=None,
|
|
37
38
|
primary_key=True,
|
|
38
39
|
sa_column_kwargs={"autoincrement": True},
|
|
39
40
|
)
|
|
@@ -68,7 +69,7 @@ class Persona(SQLModel, table=True):
|
|
|
68
69
|
It can be used to customize the behavior of LLMs.
|
|
69
70
|
"""
|
|
70
71
|
|
|
71
|
-
__tablename__ = "personas"
|
|
72
|
+
__tablename__: str = "personas"
|
|
72
73
|
|
|
73
74
|
id: int | None = Field(
|
|
74
75
|
primary_key=True,
|
|
@@ -98,7 +99,7 @@ class Persona(SQLModel, table=True):
|
|
|
98
99
|
class Preference(SQLModel, table=True):
|
|
99
100
|
"""This class represents preferences for bots."""
|
|
100
101
|
|
|
101
|
-
__tablename__ = "preferences"
|
|
102
|
+
__tablename__: str = "preferences"
|
|
102
103
|
|
|
103
104
|
id: int | None = Field(
|
|
104
105
|
default=None,
|
|
@@ -134,7 +135,7 @@ class PlatformMessageHistory(SQLModel, table=True):
|
|
|
134
135
|
or platform-specific messages.
|
|
135
136
|
"""
|
|
136
137
|
|
|
137
|
-
__tablename__ = "platform_message_history"
|
|
138
|
+
__tablename__: str = "platform_message_history"
|
|
138
139
|
|
|
139
140
|
id: int | None = Field(
|
|
140
141
|
primary_key=True,
|
|
@@ -162,7 +163,7 @@ class PlatformSession(SQLModel, table=True):
|
|
|
162
163
|
Each session can have multiple conversations (对话) associated with it.
|
|
163
164
|
"""
|
|
164
165
|
|
|
165
|
-
__tablename__ = "platform_sessions"
|
|
166
|
+
__tablename__: str = "platform_sessions"
|
|
166
167
|
|
|
167
168
|
inner_id: int | None = Field(
|
|
168
169
|
primary_key=True,
|
|
@@ -203,7 +204,7 @@ class Attachment(SQLModel, table=True):
|
|
|
203
204
|
Attachments can be images, files, or other media types.
|
|
204
205
|
"""
|
|
205
206
|
|
|
206
|
-
__tablename__ = "attachments"
|
|
207
|
+
__tablename__: str = "attachments"
|
|
207
208
|
|
|
208
209
|
inner_attachment_id: int | None = Field(
|
|
209
210
|
primary_key=True,
|
|
@@ -261,17 +262,17 @@ class Personality(TypedDict):
|
|
|
261
262
|
在 v4.0.0 版本及之后,推荐使用上面的 Persona 类。并且, mood_imitation_dialogs 字段已被废弃。
|
|
262
263
|
"""
|
|
263
264
|
|
|
264
|
-
prompt: str
|
|
265
|
-
name: str
|
|
266
|
-
begin_dialogs: list[str]
|
|
267
|
-
mood_imitation_dialogs: list[str]
|
|
265
|
+
prompt: str
|
|
266
|
+
name: str
|
|
267
|
+
begin_dialogs: list[str]
|
|
268
|
+
mood_imitation_dialogs: list[str]
|
|
268
269
|
"""情感模拟对话预设。在 v4.0.0 版本及之后,已被废弃。"""
|
|
269
|
-
tools: list[str] | None
|
|
270
|
+
tools: list[str] | None
|
|
270
271
|
"""工具列表。None 表示使用所有工具,空列表表示不使用任何工具"""
|
|
271
272
|
|
|
272
273
|
# cache
|
|
273
|
-
_begin_dialogs_processed: list[dict]
|
|
274
|
-
_mood_imitation_dialogs_processed: str
|
|
274
|
+
_begin_dialogs_processed: list[dict]
|
|
275
|
+
_mood_imitation_dialogs_processed: str
|
|
275
276
|
|
|
276
277
|
|
|
277
278
|
# ====
|
astrbot/core/db/sqlite.py
CHANGED
|
@@ -3,6 +3,7 @@ import threading
|
|
|
3
3
|
import typing as T
|
|
4
4
|
from datetime import datetime, timedelta, timezone
|
|
5
5
|
|
|
6
|
+
from sqlalchemy import CursorResult
|
|
6
7
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
7
8
|
from sqlmodel import col, delete, desc, func, or_, select, text, update
|
|
8
9
|
|
|
@@ -105,8 +106,8 @@ class SQLiteDatabase(BaseDatabase):
|
|
|
105
106
|
text("""
|
|
106
107
|
SELECT * FROM platform_stats
|
|
107
108
|
WHERE timestamp >= :start_time
|
|
108
|
-
ORDER BY timestamp DESC
|
|
109
109
|
GROUP BY platform_id
|
|
110
|
+
ORDER BY timestamp DESC
|
|
110
111
|
"""),
|
|
111
112
|
{"start_time": start_time},
|
|
112
113
|
)
|
|
@@ -449,6 +450,18 @@ class SQLiteDatabase(BaseDatabase):
|
|
|
449
450
|
result = await session.execute(query.offset(offset).limit(page_size))
|
|
450
451
|
return result.scalars().all()
|
|
451
452
|
|
|
453
|
+
async def get_platform_message_history_by_id(
|
|
454
|
+
self, message_id: int
|
|
455
|
+
) -> PlatformMessageHistory | None:
|
|
456
|
+
"""Get a platform message history record by its ID."""
|
|
457
|
+
async with self.get_db() as session:
|
|
458
|
+
session: AsyncSession
|
|
459
|
+
query = select(PlatformMessageHistory).where(
|
|
460
|
+
PlatformMessageHistory.id == message_id
|
|
461
|
+
)
|
|
462
|
+
result = await session.execute(query)
|
|
463
|
+
return result.scalar_one_or_none()
|
|
464
|
+
|
|
452
465
|
async def insert_attachment(self, path, type, mime_type):
|
|
453
466
|
"""Insert a new attachment record."""
|
|
454
467
|
async with self.get_db() as session:
|
|
@@ -470,6 +483,48 @@ class SQLiteDatabase(BaseDatabase):
|
|
|
470
483
|
result = await session.execute(query)
|
|
471
484
|
return result.scalar_one_or_none()
|
|
472
485
|
|
|
486
|
+
async def get_attachments(self, attachment_ids: list[str]) -> list:
|
|
487
|
+
"""Get multiple attachments by their IDs."""
|
|
488
|
+
if not attachment_ids:
|
|
489
|
+
return []
|
|
490
|
+
async with self.get_db() as session:
|
|
491
|
+
session: AsyncSession
|
|
492
|
+
query = select(Attachment).where(
|
|
493
|
+
col(Attachment.attachment_id).in_(attachment_ids)
|
|
494
|
+
)
|
|
495
|
+
result = await session.execute(query)
|
|
496
|
+
return list(result.scalars().all())
|
|
497
|
+
|
|
498
|
+
async def delete_attachment(self, attachment_id: str) -> bool:
|
|
499
|
+
"""Delete an attachment by its ID.
|
|
500
|
+
|
|
501
|
+
Returns True if the attachment was deleted, False if it was not found.
|
|
502
|
+
"""
|
|
503
|
+
async with self.get_db() as session:
|
|
504
|
+
session: AsyncSession
|
|
505
|
+
async with session.begin():
|
|
506
|
+
query = delete(Attachment).where(
|
|
507
|
+
col(Attachment.attachment_id) == attachment_id
|
|
508
|
+
)
|
|
509
|
+
result = T.cast(CursorResult, await session.execute(query))
|
|
510
|
+
return result.rowcount > 0
|
|
511
|
+
|
|
512
|
+
async def delete_attachments(self, attachment_ids: list[str]) -> int:
|
|
513
|
+
"""Delete multiple attachments by their IDs.
|
|
514
|
+
|
|
515
|
+
Returns the number of attachments deleted.
|
|
516
|
+
"""
|
|
517
|
+
if not attachment_ids:
|
|
518
|
+
return 0
|
|
519
|
+
async with self.get_db() as session:
|
|
520
|
+
session: AsyncSession
|
|
521
|
+
async with session.begin():
|
|
522
|
+
query = delete(Attachment).where(
|
|
523
|
+
col(Attachment.attachment_id).in_(attachment_ids)
|
|
524
|
+
)
|
|
525
|
+
result = T.cast(CursorResult, await session.execute(query))
|
|
526
|
+
return result.rowcount
|
|
527
|
+
|
|
473
528
|
async def insert_persona(
|
|
474
529
|
self,
|
|
475
530
|
persona_id,
|
astrbot/core/event_bus.py
CHANGED
|
@@ -27,7 +27,7 @@ class EventBus:
|
|
|
27
27
|
self,
|
|
28
28
|
event_queue: Queue,
|
|
29
29
|
pipeline_scheduler_mapping: dict[str, PipelineScheduler],
|
|
30
|
-
astrbot_config_mgr: AstrBotConfigManager
|
|
30
|
+
astrbot_config_mgr: AstrBotConfigManager,
|
|
31
31
|
):
|
|
32
32
|
self.event_queue = event_queue # 事件队列
|
|
33
33
|
# abconf uuid -> scheduler
|
|
@@ -40,6 +40,11 @@ class EventBus:
|
|
|
40
40
|
conf_info = self.astrbot_config_mgr.get_conf_info(event.unified_msg_origin)
|
|
41
41
|
self._print_event(event, conf_info["name"])
|
|
42
42
|
scheduler = self.pipeline_scheduler_mapping.get(conf_info["id"])
|
|
43
|
+
if not scheduler:
|
|
44
|
+
logger.error(
|
|
45
|
+
f"PipelineScheduler not found for id: {conf_info['id']}, event ignored."
|
|
46
|
+
)
|
|
47
|
+
continue
|
|
43
48
|
asyncio.create_task(scheduler.execute(event))
|
|
44
49
|
|
|
45
50
|
def _print_event(self, event: AstrMessageEvent, conf_name: str):
|
|
@@ -166,7 +166,11 @@ class RetrievalManager:
|
|
|
166
166
|
# 5. Rerank
|
|
167
167
|
first_rerank = None
|
|
168
168
|
for kb_id in kb_ids:
|
|
169
|
-
vec_db
|
|
169
|
+
vec_db = kb_options[kb_id]["vec_db"]
|
|
170
|
+
if not isinstance(vec_db, FaissVecDB):
|
|
171
|
+
logger.warning(f"vec_db for kb_id {kb_id} is not FaissVecDB")
|
|
172
|
+
continue
|
|
173
|
+
|
|
170
174
|
rerank_pi = kb_options[kb_id]["rerank_provider_id"]
|
|
171
175
|
if (
|
|
172
176
|
vec_db
|