AstrBot 4.8.0__py3-none-any.whl → 4.9.1__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_tool_exec.py +5 -1
- astrbot/core/config/astrbot_config.py +4 -0
- astrbot/core/config/default.py +72 -1
- astrbot/core/config/i18n_utils.py +1 -0
- astrbot/core/core_lifecycle.py +1 -1
- astrbot/core/db/__init__.py +2 -3
- 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 +4 -3
- 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/star_request.py +1 -2
- astrbot/core/pipeline/process_stage/stage.py +1 -1
- astrbot/core/pipeline/respond/stage.py +8 -2
- astrbot/core/pipeline/result_decorate/stage.py +89 -22
- 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 +4 -0
- astrbot/core/platform/platform.py +11 -3
- 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 +9 -5
- astrbot/core/platform/sources/dingtalk/dingtalk_adapter.py +24 -16
- 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 +52 -24
- astrbot/core/platform/sources/discord/discord_platform_event.py +29 -8
- astrbot/core/platform/sources/lark/lark_adapter.py +183 -20
- 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 +2 -3
- astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py +62 -18
- astrbot/core/platform/sources/qqofficial/qqofficial_platform_adapter.py +13 -7
- astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_adapter.py +5 -3
- astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_server.py +2 -1
- astrbot/core/platform/sources/slack/client.py +9 -2
- astrbot/core/platform/sources/slack/slack_adapter.py +15 -9
- astrbot/core/platform/sources/slack/slack_event.py +8 -7
- astrbot/core/platform/sources/telegram/tg_adapter.py +1 -1
- astrbot/core/platform/sources/telegram/tg_event.py +23 -27
- astrbot/core/platform/sources/webchat/webchat_adapter.py +2 -2
- astrbot/core/platform/sources/webchat/webchat_event.py +2 -2
- astrbot/core/platform/sources/wechatpadpro/wechatpadpro_adapter.py +26 -9
- astrbot/core/platform/sources/wecom/wecom_adapter.py +25 -28
- astrbot/core/platform/sources/wecom/wecom_event.py +2 -2
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_event.py +3 -3
- astrbot/core/platform/sources/weixin_official_account/weixin_offacc_adapter.py +30 -25
- astrbot/core/platform/sources/weixin_official_account/weixin_offacc_event.py +10 -7
- 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 +1 -1
- 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 +1 -1
- astrbot/core/utils/version_comparator.py +6 -3
- astrbot/core/utils/webhook_utils.py +19 -0
- astrbot/dashboard/routes/chat.py +14 -9
- astrbot/dashboard/routes/config.py +10 -20
- astrbot/dashboard/routes/conversation.py +91 -1
- astrbot/dashboard/routes/knowledge_base.py +253 -78
- astrbot/dashboard/routes/log.py +13 -8
- astrbot/dashboard/routes/platform.py +1 -1
- astrbot/dashboard/routes/plugin.py +113 -52
- astrbot/dashboard/routes/route.py +2 -0
- astrbot/dashboard/server.py +6 -3
- {astrbot-4.8.0.dist-info → astrbot-4.9.1.dist-info}/METADATA +9 -1
- {astrbot-4.8.0.dist-info → astrbot-4.9.1.dist-info}/RECORD +106 -105
- {astrbot-4.8.0.dist-info → astrbot-4.9.1.dist-info}/WHEEL +0 -0
- {astrbot-4.8.0.dist-info → astrbot-4.9.1.dist-info}/entry_points.txt +0 -0
- {astrbot-4.8.0.dist-info → astrbot-4.9.1.dist-info}/licenses/LICENSE +0 -0
astrbot/cli/__init__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "4.
|
|
1
|
+
__version__ = "4.9.1"
|
|
@@ -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
|
|
@@ -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,7 +4,7 @@ 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.1"
|
|
8
8
|
DB_PATH = os.path.join(get_astrbot_data_path(), "data_v4.db")
|
|
9
9
|
|
|
10
10
|
WEBHOOK_SUPPORTED_PLATFORMS = [
|
|
@@ -13,6 +13,7 @@ WEBHOOK_SUPPORTED_PLATFORMS = [
|
|
|
13
13
|
"wecom",
|
|
14
14
|
"wecom_ai_bot",
|
|
15
15
|
"slack",
|
|
16
|
+
"lark",
|
|
16
17
|
]
|
|
17
18
|
|
|
18
19
|
# 默认配置
|
|
@@ -42,7 +43,15 @@ DEFAULT_CONFIG = {
|
|
|
42
43
|
"interval": "1.5,3.5",
|
|
43
44
|
"log_base": 2.6,
|
|
44
45
|
"words_count_threshold": 150,
|
|
46
|
+
"split_mode": "regex", # regex 或 words
|
|
45
47
|
"regex": ".*?[。?!~…]+|.+$",
|
|
48
|
+
"split_words": [
|
|
49
|
+
"。",
|
|
50
|
+
"?",
|
|
51
|
+
"!",
|
|
52
|
+
"~",
|
|
53
|
+
"…",
|
|
54
|
+
], # 当 split_mode 为 words 时使用
|
|
46
55
|
"content_cleanup_rule": "",
|
|
47
56
|
},
|
|
48
57
|
"no_permission_reply": True,
|
|
@@ -99,6 +108,7 @@ DEFAULT_CONFIG = {
|
|
|
99
108
|
"provider_id": "",
|
|
100
109
|
"dual_output": False,
|
|
101
110
|
"use_file_service": False,
|
|
111
|
+
"trigger_probability": 1.0,
|
|
102
112
|
},
|
|
103
113
|
"provider_ltm_settings": {
|
|
104
114
|
"group_icl_enable": False,
|
|
@@ -157,6 +167,7 @@ DEFAULT_CONFIG = {
|
|
|
157
167
|
"kb_fusion_top_k": 20, # 知识库检索融合阶段返回结果数量
|
|
158
168
|
"kb_final_top_k": 5, # 知识库检索最终返回结果数量
|
|
159
169
|
"kb_agentic_mode": False,
|
|
170
|
+
"disable_builtin_commands": False,
|
|
160
171
|
}
|
|
161
172
|
|
|
162
173
|
|
|
@@ -268,6 +279,10 @@ CONFIG_METADATA_2 = {
|
|
|
268
279
|
"app_id": "",
|
|
269
280
|
"app_secret": "",
|
|
270
281
|
"domain": "https://open.feishu.cn",
|
|
282
|
+
"lark_connection_mode": "socket", # webhook, socket
|
|
283
|
+
"webhook_uuid": "",
|
|
284
|
+
"lark_encrypt_key": "",
|
|
285
|
+
"lark_verification_token": "",
|
|
271
286
|
},
|
|
272
287
|
"钉钉(DingTalk)": {
|
|
273
288
|
"id": "dingtalk",
|
|
@@ -361,6 +376,28 @@ CONFIG_METADATA_2 = {
|
|
|
361
376
|
# "type": "string",
|
|
362
377
|
# "options": ["fullscreen", "embedded"],
|
|
363
378
|
# },
|
|
379
|
+
"lark_connection_mode": {
|
|
380
|
+
"description": "订阅方式",
|
|
381
|
+
"type": "string",
|
|
382
|
+
"options": ["socket", "webhook"],
|
|
383
|
+
"labels": ["长连接模式", "推送至服务器模式"],
|
|
384
|
+
},
|
|
385
|
+
"lark_encrypt_key": {
|
|
386
|
+
"description": "Encrypt Key",
|
|
387
|
+
"type": "string",
|
|
388
|
+
"hint": "用于解密飞书回调数据的加密密钥",
|
|
389
|
+
"condition": {
|
|
390
|
+
"lark_connection_mode": "webhook",
|
|
391
|
+
},
|
|
392
|
+
},
|
|
393
|
+
"lark_verification_token": {
|
|
394
|
+
"description": "Verification Token",
|
|
395
|
+
"type": "string",
|
|
396
|
+
"hint": "用于验证飞书回调请求的令牌",
|
|
397
|
+
"condition": {
|
|
398
|
+
"lark_connection_mode": "webhook",
|
|
399
|
+
},
|
|
400
|
+
},
|
|
364
401
|
"is_sandbox": {
|
|
365
402
|
"description": "沙箱模式",
|
|
366
403
|
"type": "bool",
|
|
@@ -2173,6 +2210,9 @@ CONFIG_METADATA_2 = {
|
|
|
2173
2210
|
"use_file_service": {
|
|
2174
2211
|
"type": "bool",
|
|
2175
2212
|
},
|
|
2213
|
+
"trigger_probability": {
|
|
2214
|
+
"type": "float",
|
|
2215
|
+
},
|
|
2176
2216
|
},
|
|
2177
2217
|
},
|
|
2178
2218
|
"provider_ltm_settings": {
|
|
@@ -2383,6 +2423,14 @@ CONFIG_METADATA_3 = {
|
|
|
2383
2423
|
"provider_tts_settings.enable": True,
|
|
2384
2424
|
},
|
|
2385
2425
|
},
|
|
2426
|
+
"provider_tts_settings.trigger_probability": {
|
|
2427
|
+
"description": "TTS 触发概率",
|
|
2428
|
+
"type": "float",
|
|
2429
|
+
"slider": {"min": 0, "max": 1, "step": 0.05},
|
|
2430
|
+
"condition": {
|
|
2431
|
+
"provider_tts_settings.enable": True,
|
|
2432
|
+
},
|
|
2433
|
+
},
|
|
2386
2434
|
"provider_settings.image_caption_prompt": {
|
|
2387
2435
|
"description": "图片转述提示词",
|
|
2388
2436
|
"type": "text",
|
|
@@ -2661,6 +2709,11 @@ CONFIG_METADATA_3 = {
|
|
|
2661
2709
|
"description": "只 @ 机器人是否触发等待",
|
|
2662
2710
|
"type": "bool",
|
|
2663
2711
|
},
|
|
2712
|
+
"disable_builtin_commands": {
|
|
2713
|
+
"description": "禁用自带指令",
|
|
2714
|
+
"type": "bool",
|
|
2715
|
+
"hint": "禁用所有 AstrBot 的自带指令,如 help, provider, model 等。",
|
|
2716
|
+
},
|
|
2664
2717
|
},
|
|
2665
2718
|
},
|
|
2666
2719
|
"whitelist": {
|
|
@@ -2875,9 +2928,26 @@ CONFIG_METADATA_3 = {
|
|
|
2875
2928
|
"description": "分段回复字数阈值",
|
|
2876
2929
|
"type": "int",
|
|
2877
2930
|
},
|
|
2931
|
+
"platform_settings.segmented_reply.split_mode": {
|
|
2932
|
+
"description": "分段模式",
|
|
2933
|
+
"type": "string",
|
|
2934
|
+
"options": ["regex", "words"],
|
|
2935
|
+
"labels": ["正则表达式", "分段词列表"],
|
|
2936
|
+
},
|
|
2878
2937
|
"platform_settings.segmented_reply.regex": {
|
|
2879
2938
|
"description": "分段正则表达式",
|
|
2880
2939
|
"type": "string",
|
|
2940
|
+
"condition": {
|
|
2941
|
+
"platform_settings.segmented_reply.split_mode": "regex",
|
|
2942
|
+
},
|
|
2943
|
+
},
|
|
2944
|
+
"platform_settings.segmented_reply.split_words": {
|
|
2945
|
+
"description": "分段词列表",
|
|
2946
|
+
"type": "list",
|
|
2947
|
+
"hint": "检测到列表中的任意词时进行分段,如:。、?、!等",
|
|
2948
|
+
"condition": {
|
|
2949
|
+
"platform_settings.segmented_reply.split_mode": "words",
|
|
2950
|
+
},
|
|
2881
2951
|
},
|
|
2882
2952
|
"platform_settings.segmented_reply.content_cleanup_rule": {
|
|
2883
2953
|
"description": "内容过滤正则表达式",
|
|
@@ -2928,6 +2998,7 @@ CONFIG_METADATA_3 = {
|
|
|
2928
2998
|
"description": "回复概率",
|
|
2929
2999
|
"type": "float",
|
|
2930
3000
|
"hint": "0.0-1.0 之间的数值",
|
|
3001
|
+
"slider": {"min": 0, "max": 1, "step": 0.05},
|
|
2931
3002
|
"condition": {
|
|
2932
3003
|
"provider_ltm_settings.active_reply.enable": True,
|
|
2933
3004
|
},
|
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,
|
|
@@ -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
|
|
|
@@ -489,7 +490,7 @@ class SQLiteDatabase(BaseDatabase):
|
|
|
489
490
|
async with self.get_db() as session:
|
|
490
491
|
session: AsyncSession
|
|
491
492
|
query = select(Attachment).where(
|
|
492
|
-
Attachment.attachment_id.in_(attachment_ids)
|
|
493
|
+
col(Attachment.attachment_id).in_(attachment_ids)
|
|
493
494
|
)
|
|
494
495
|
result = await session.execute(query)
|
|
495
496
|
return list(result.scalars().all())
|
|
@@ -505,7 +506,7 @@ class SQLiteDatabase(BaseDatabase):
|
|
|
505
506
|
query = delete(Attachment).where(
|
|
506
507
|
col(Attachment.attachment_id) == attachment_id
|
|
507
508
|
)
|
|
508
|
-
result = await session.execute(query)
|
|
509
|
+
result = T.cast(CursorResult, await session.execute(query))
|
|
509
510
|
return result.rowcount > 0
|
|
510
511
|
|
|
511
512
|
async def delete_attachments(self, attachment_ids: list[str]) -> int:
|
|
@@ -521,7 +522,7 @@ class SQLiteDatabase(BaseDatabase):
|
|
|
521
522
|
query = delete(Attachment).where(
|
|
522
523
|
col(Attachment.attachment_id).in_(attachment_ids)
|
|
523
524
|
)
|
|
524
|
-
result = await session.execute(query)
|
|
525
|
+
result = T.cast(CursorResult, await session.execute(query))
|
|
525
526
|
return result.rowcount
|
|
526
527
|
|
|
527
528
|
async def insert_persona(
|
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
|
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
|
)
|
|
@@ -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 [
|
|
@@ -156,7 +158,11 @@ class RespondStage(Stage):
|
|
|
156
158
|
result = event.get_result()
|
|
157
159
|
if result is None:
|
|
158
160
|
return
|
|
161
|
+
if event.get_extra("_streaming_finished", False):
|
|
162
|
+
# prevent some plugin make result content type to LLM_RESULT after streaming finished, lead to send again
|
|
163
|
+
return
|
|
159
164
|
if result.result_content_type == ResultContentType.STREAMING_FINISH:
|
|
165
|
+
event.set_extra("_streaming_finished", True)
|
|
160
166
|
return
|
|
161
167
|
|
|
162
168
|
logger.info(
|
|
@@ -185,7 +191,7 @@ class RespondStage(Stage):
|
|
|
185
191
|
if isinstance(component, Comp.File) and component.file:
|
|
186
192
|
# 支持 File 消息段的路径映射。
|
|
187
193
|
component.file = path_Mapping(mappings, component.file)
|
|
188
|
-
|
|
194
|
+
result.chain[idx] = component
|
|
189
195
|
|
|
190
196
|
# 检查消息链是否为空
|
|
191
197
|
try:
|