AstrBot 4.7.3__py3-none-any.whl → 4.8.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/message.py +21 -5
- astrbot/core/astr_agent_run_util.py +15 -1
- astrbot/core/config/default.py +113 -1
- astrbot/core/db/__init__.py +30 -1
- astrbot/core/db/sqlite.py +55 -1
- astrbot/core/message/components.py +6 -1
- astrbot/core/pipeline/process_stage/method/agent_sub_stages/internal.py +64 -5
- astrbot/core/pipeline/process_stage/method/agent_sub_stages/third_party.py +1 -1
- astrbot/core/platform/manager.py +67 -9
- astrbot/core/platform/platform.py +99 -2
- astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py +19 -5
- astrbot/core/platform/sources/dingtalk/dingtalk_adapter.py +5 -7
- astrbot/core/platform/sources/discord/discord_platform_adapter.py +1 -2
- astrbot/core/platform/sources/lark/lark_adapter.py +1 -3
- astrbot/core/platform/sources/misskey/misskey_adapter.py +1 -2
- astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py +2 -0
- astrbot/core/platform/sources/qqofficial/qqofficial_platform_adapter.py +1 -3
- astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_adapter.py +32 -9
- astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_server.py +13 -1
- astrbot/core/platform/sources/satori/satori_adapter.py +1 -2
- astrbot/core/platform/sources/slack/client.py +50 -39
- astrbot/core/platform/sources/slack/slack_adapter.py +21 -7
- astrbot/core/platform/sources/slack/slack_event.py +3 -3
- astrbot/core/platform/sources/telegram/tg_adapter.py +4 -3
- astrbot/core/platform/sources/webchat/webchat_adapter.py +95 -29
- astrbot/core/platform/sources/webchat/webchat_event.py +33 -33
- astrbot/core/platform/sources/wechatpadpro/wechatpadpro_adapter.py +1 -2
- astrbot/core/platform/sources/wecom/wecom_adapter.py +51 -9
- astrbot/core/platform/sources/wecom/wecom_event.py +1 -1
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_adapter.py +26 -9
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_server.py +27 -5
- astrbot/core/platform/sources/weixin_official_account/weixin_offacc_adapter.py +52 -11
- astrbot/core/platform/sources/weixin_official_account/weixin_offacc_event.py +1 -1
- astrbot/core/platform_message_history_mgr.py +3 -3
- astrbot/core/provider/provider.py +35 -0
- astrbot/core/provider/sources/whisper_api_source.py +43 -11
- astrbot/core/utils/file_extract.py +23 -0
- astrbot/core/utils/tencent_record_helper.py +1 -1
- astrbot/core/utils/webhook_utils.py +47 -0
- astrbot/dashboard/routes/__init__.py +2 -0
- astrbot/dashboard/routes/chat.py +300 -70
- astrbot/dashboard/routes/config.py +32 -165
- astrbot/dashboard/routes/knowledge_base.py +1 -1
- astrbot/dashboard/routes/platform.py +100 -0
- astrbot/dashboard/routes/plugin.py +65 -6
- astrbot/dashboard/server.py +3 -1
- {astrbot-4.7.3.dist-info → astrbot-4.8.0.dist-info}/METADATA +48 -37
- {astrbot-4.7.3.dist-info → astrbot-4.8.0.dist-info}/RECORD +52 -49
- {astrbot-4.7.3.dist-info → astrbot-4.8.0.dist-info}/WHEEL +0 -0
- {astrbot-4.7.3.dist-info → astrbot-4.8.0.dist-info}/entry_points.txt +0 -0
- {astrbot-4.7.3.dist-info → astrbot-4.8.0.dist-info}/licenses/LICENSE +0 -0
astrbot/cli/__init__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "4.
|
|
1
|
+
__version__ = "4.8.0"
|
astrbot/core/agent/message.py
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
from typing import Any, ClassVar, Literal, cast
|
|
5
5
|
|
|
6
|
-
from pydantic import BaseModel, GetCoreSchemaHandler
|
|
6
|
+
from pydantic import BaseModel, GetCoreSchemaHandler, model_validator
|
|
7
7
|
from pydantic_core import core_schema
|
|
8
8
|
|
|
9
9
|
|
|
@@ -145,23 +145,39 @@ class Message(BaseModel):
|
|
|
145
145
|
"tool",
|
|
146
146
|
]
|
|
147
147
|
|
|
148
|
-
content: str | list[ContentPart]
|
|
148
|
+
content: str | list[ContentPart] | None = None
|
|
149
149
|
"""The content of the message."""
|
|
150
150
|
|
|
151
|
+
tool_calls: list[ToolCall] | list[dict] | None = None
|
|
152
|
+
"""The tool calls of the message."""
|
|
153
|
+
|
|
154
|
+
tool_call_id: str | None = None
|
|
155
|
+
"""The ID of the tool call."""
|
|
156
|
+
|
|
157
|
+
@model_validator(mode="after")
|
|
158
|
+
def check_content_required(self):
|
|
159
|
+
# assistant + tool_calls is not None: allow content to be None
|
|
160
|
+
if self.role == "assistant" and self.tool_calls is not None:
|
|
161
|
+
return self
|
|
162
|
+
|
|
163
|
+
# other all cases: content is required
|
|
164
|
+
if self.content is None:
|
|
165
|
+
raise ValueError(
|
|
166
|
+
"content is required unless role='assistant' and tool_calls is not None"
|
|
167
|
+
)
|
|
168
|
+
return self
|
|
169
|
+
|
|
151
170
|
|
|
152
171
|
class AssistantMessageSegment(Message):
|
|
153
172
|
"""A message segment from the assistant."""
|
|
154
173
|
|
|
155
174
|
role: Literal["assistant"] = "assistant"
|
|
156
|
-
content: str | list[ContentPart] | None = None
|
|
157
|
-
tool_calls: list[ToolCall] | list[dict] | None = None
|
|
158
175
|
|
|
159
176
|
|
|
160
177
|
class ToolCallMessageSegment(Message):
|
|
161
178
|
"""A message segment representing a tool call."""
|
|
162
179
|
|
|
163
180
|
role: Literal["tool"] = "tool"
|
|
164
|
-
tool_call_id: str
|
|
165
181
|
|
|
166
182
|
|
|
167
183
|
class UserMessageSegment(Message):
|
|
@@ -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:
|
astrbot/core/config/default.py
CHANGED
|
@@ -4,9 +4,17 @@ 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.8.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
|
+
]
|
|
17
|
+
|
|
10
18
|
# 默认配置
|
|
11
19
|
DEFAULT_CONFIG = {
|
|
12
20
|
"config_version": 2,
|
|
@@ -73,8 +81,14 @@ DEFAULT_CONFIG = {
|
|
|
73
81
|
"coze_agent_runner_provider_id": "",
|
|
74
82
|
"dashscope_agent_runner_provider_id": "",
|
|
75
83
|
"unsupported_streaming_strategy": "realtime_segmenting",
|
|
84
|
+
"reachability_check": False,
|
|
76
85
|
"max_agent_step": 30,
|
|
77
86
|
"tool_call_timeout": 60,
|
|
87
|
+
"file_extract": {
|
|
88
|
+
"enable": False,
|
|
89
|
+
"provider": "moonshotai",
|
|
90
|
+
"moonshotai_api_key": "",
|
|
91
|
+
},
|
|
78
92
|
},
|
|
79
93
|
"provider_stt_settings": {
|
|
80
94
|
"enable": False,
|
|
@@ -179,6 +193,8 @@ CONFIG_METADATA_2 = {
|
|
|
179
193
|
"appid": "",
|
|
180
194
|
"secret": "",
|
|
181
195
|
"is_sandbox": False,
|
|
196
|
+
"unified_webhook_mode": True,
|
|
197
|
+
"webhook_uuid": "",
|
|
182
198
|
"callback_server_host": "0.0.0.0",
|
|
183
199
|
"port": 6196,
|
|
184
200
|
},
|
|
@@ -209,6 +225,8 @@ CONFIG_METADATA_2 = {
|
|
|
209
225
|
"token": "",
|
|
210
226
|
"encoding_aes_key": "",
|
|
211
227
|
"api_base_url": "https://api.weixin.qq.com/cgi-bin/",
|
|
228
|
+
"unified_webhook_mode": True,
|
|
229
|
+
"webhook_uuid": "",
|
|
212
230
|
"callback_server_host": "0.0.0.0",
|
|
213
231
|
"port": 6194,
|
|
214
232
|
"active_send_mode": False,
|
|
@@ -223,6 +241,8 @@ CONFIG_METADATA_2 = {
|
|
|
223
241
|
"encoding_aes_key": "",
|
|
224
242
|
"kf_name": "",
|
|
225
243
|
"api_base_url": "https://qyapi.weixin.qq.com/cgi-bin/",
|
|
244
|
+
"unified_webhook_mode": True,
|
|
245
|
+
"webhook_uuid": "",
|
|
226
246
|
"callback_server_host": "0.0.0.0",
|
|
227
247
|
"port": 6195,
|
|
228
248
|
},
|
|
@@ -235,6 +255,8 @@ CONFIG_METADATA_2 = {
|
|
|
235
255
|
"wecom_ai_bot_name": "",
|
|
236
256
|
"token": "",
|
|
237
257
|
"encoding_aes_key": "",
|
|
258
|
+
"unified_webhook_mode": True,
|
|
259
|
+
"webhook_uuid": "",
|
|
238
260
|
"callback_server_host": "0.0.0.0",
|
|
239
261
|
"port": 6198,
|
|
240
262
|
},
|
|
@@ -302,6 +324,8 @@ CONFIG_METADATA_2 = {
|
|
|
302
324
|
"app_token": "",
|
|
303
325
|
"signing_secret": "",
|
|
304
326
|
"slack_connection_mode": "socket", # webhook, socket
|
|
327
|
+
"unified_webhook_mode": True,
|
|
328
|
+
"webhook_uuid": "",
|
|
305
329
|
"slack_webhook_host": "0.0.0.0",
|
|
306
330
|
"slack_webhook_port": 6197,
|
|
307
331
|
"slack_webhook_path": "/astrbot-slack-webhook/callback",
|
|
@@ -381,16 +405,28 @@ CONFIG_METADATA_2 = {
|
|
|
381
405
|
"description": "Slack Webhook Host",
|
|
382
406
|
"type": "string",
|
|
383
407
|
"hint": "Only valid when Slack connection mode is `webhook`.",
|
|
408
|
+
"condition": {
|
|
409
|
+
"slack_connection_mode": "webhook",
|
|
410
|
+
"unified_webhook_mode": False,
|
|
411
|
+
},
|
|
384
412
|
},
|
|
385
413
|
"slack_webhook_port": {
|
|
386
414
|
"description": "Slack Webhook Port",
|
|
387
415
|
"type": "int",
|
|
388
416
|
"hint": "Only valid when Slack connection mode is `webhook`.",
|
|
417
|
+
"condition": {
|
|
418
|
+
"slack_connection_mode": "webhook",
|
|
419
|
+
"unified_webhook_mode": False,
|
|
420
|
+
},
|
|
389
421
|
},
|
|
390
422
|
"slack_webhook_path": {
|
|
391
423
|
"description": "Slack Webhook Path",
|
|
392
424
|
"type": "string",
|
|
393
425
|
"hint": "Only valid when Slack connection mode is `webhook`.",
|
|
426
|
+
"condition": {
|
|
427
|
+
"slack_connection_mode": "webhook",
|
|
428
|
+
"unified_webhook_mode": False,
|
|
429
|
+
},
|
|
394
430
|
},
|
|
395
431
|
"active_send_mode": {
|
|
396
432
|
"description": "是否换用主动发送接口",
|
|
@@ -581,6 +617,33 @@ CONFIG_METADATA_2 = {
|
|
|
581
617
|
"type": "string",
|
|
582
618
|
"hint": "可选的 Discord 活动名称。留空则不设置活动。",
|
|
583
619
|
},
|
|
620
|
+
"port": {
|
|
621
|
+
"description": "回调服务器端口",
|
|
622
|
+
"type": "int",
|
|
623
|
+
"hint": "回调服务器端口。留空则不启用回调服务器。",
|
|
624
|
+
"condition": {
|
|
625
|
+
"unified_webhook_mode": False,
|
|
626
|
+
},
|
|
627
|
+
},
|
|
628
|
+
"callback_server_host": {
|
|
629
|
+
"description": "回调服务器主机",
|
|
630
|
+
"type": "string",
|
|
631
|
+
"hint": "回调服务器主机。留空则不启用回调服务器。",
|
|
632
|
+
"condition": {
|
|
633
|
+
"unified_webhook_mode": False,
|
|
634
|
+
},
|
|
635
|
+
},
|
|
636
|
+
"unified_webhook_mode": {
|
|
637
|
+
"description": "统一 Webhook 模式",
|
|
638
|
+
"type": "bool",
|
|
639
|
+
"hint": "启用后,将使用 AstrBot 统一 Webhook 入口,无需单独开启端口。回调地址为 /api/platform/webhook/{webhook_uuid}。",
|
|
640
|
+
},
|
|
641
|
+
"webhook_uuid": {
|
|
642
|
+
"invisible": True,
|
|
643
|
+
"description": "Webhook UUID",
|
|
644
|
+
"type": "string",
|
|
645
|
+
"hint": "统一 Webhook 模式下的唯一标识符,创建平台时自动生成。",
|
|
646
|
+
},
|
|
584
647
|
},
|
|
585
648
|
},
|
|
586
649
|
"platform_settings": {
|
|
@@ -2068,6 +2131,20 @@ CONFIG_METADATA_2 = {
|
|
|
2068
2131
|
"tool_call_timeout": {
|
|
2069
2132
|
"type": "int",
|
|
2070
2133
|
},
|
|
2134
|
+
"file_extract": {
|
|
2135
|
+
"type": "object",
|
|
2136
|
+
"items": {
|
|
2137
|
+
"enable": {
|
|
2138
|
+
"type": "bool",
|
|
2139
|
+
},
|
|
2140
|
+
"provider": {
|
|
2141
|
+
"type": "string",
|
|
2142
|
+
},
|
|
2143
|
+
"moonshotai_api_key": {
|
|
2144
|
+
"type": "string",
|
|
2145
|
+
},
|
|
2146
|
+
},
|
|
2147
|
+
},
|
|
2071
2148
|
},
|
|
2072
2149
|
},
|
|
2073
2150
|
"provider_stt_settings": {
|
|
@@ -2402,6 +2479,36 @@ CONFIG_METADATA_3 = {
|
|
|
2402
2479
|
"provider_settings.enable": True,
|
|
2403
2480
|
},
|
|
2404
2481
|
},
|
|
2482
|
+
# "file_extract": {
|
|
2483
|
+
# "description": "文档解析能力 [beta]",
|
|
2484
|
+
# "type": "object",
|
|
2485
|
+
# "items": {
|
|
2486
|
+
# "provider_settings.file_extract.enable": {
|
|
2487
|
+
# "description": "启用文档解析能力",
|
|
2488
|
+
# "type": "bool",
|
|
2489
|
+
# },
|
|
2490
|
+
# "provider_settings.file_extract.provider": {
|
|
2491
|
+
# "description": "文档解析提供商",
|
|
2492
|
+
# "type": "string",
|
|
2493
|
+
# "options": ["moonshotai"],
|
|
2494
|
+
# "condition": {
|
|
2495
|
+
# "provider_settings.file_extract.enable": True,
|
|
2496
|
+
# },
|
|
2497
|
+
# },
|
|
2498
|
+
# "provider_settings.file_extract.moonshotai_api_key": {
|
|
2499
|
+
# "description": "Moonshot AI API Key",
|
|
2500
|
+
# "type": "string",
|
|
2501
|
+
# "condition": {
|
|
2502
|
+
# "provider_settings.file_extract.provider": "moonshotai",
|
|
2503
|
+
# "provider_settings.file_extract.enable": True,
|
|
2504
|
+
# },
|
|
2505
|
+
# },
|
|
2506
|
+
# },
|
|
2507
|
+
# "condition": {
|
|
2508
|
+
# "provider_settings.agent_runner_type": "local",
|
|
2509
|
+
# "provider_settings.enable": True,
|
|
2510
|
+
# },
|
|
2511
|
+
# },
|
|
2405
2512
|
"others": {
|
|
2406
2513
|
"description": "其他配置",
|
|
2407
2514
|
"type": "object",
|
|
@@ -2496,6 +2603,11 @@ CONFIG_METADATA_3 = {
|
|
|
2496
2603
|
"description": "开启 TTS 时同时输出语音和文字内容",
|
|
2497
2604
|
"type": "bool",
|
|
2498
2605
|
},
|
|
2606
|
+
"provider_settings.reachability_check": {
|
|
2607
|
+
"description": "提供商可达性检测",
|
|
2608
|
+
"type": "bool",
|
|
2609
|
+
"hint": "/provider 命令列出模型时是否并发检测连通性。开启后会主动调用模型测试连通性,可能产生额外 token 消耗。",
|
|
2610
|
+
},
|
|
2499
2611
|
},
|
|
2500
2612
|
"condition": {
|
|
2501
2613
|
"provider_settings.enable": True,
|
astrbot/core/db/__init__.py
CHANGED
|
@@ -173,7 +173,7 @@ class BaseDatabase(abc.ABC):
|
|
|
173
173
|
content: dict,
|
|
174
174
|
sender_id: str | None = None,
|
|
175
175
|
sender_name: str | None = None,
|
|
176
|
-
) ->
|
|
176
|
+
) -> PlatformMessageHistory:
|
|
177
177
|
"""Insert a new platform message history record."""
|
|
178
178
|
...
|
|
179
179
|
|
|
@@ -198,6 +198,14 @@ class BaseDatabase(abc.ABC):
|
|
|
198
198
|
"""Get platform message history for a specific user."""
|
|
199
199
|
...
|
|
200
200
|
|
|
201
|
+
@abc.abstractmethod
|
|
202
|
+
async def get_platform_message_history_by_id(
|
|
203
|
+
self,
|
|
204
|
+
message_id: int,
|
|
205
|
+
) -> PlatformMessageHistory | None:
|
|
206
|
+
"""Get a platform message history record by its ID."""
|
|
207
|
+
...
|
|
208
|
+
|
|
201
209
|
@abc.abstractmethod
|
|
202
210
|
async def insert_attachment(
|
|
203
211
|
self,
|
|
@@ -213,6 +221,27 @@ class BaseDatabase(abc.ABC):
|
|
|
213
221
|
"""Get an attachment by its ID."""
|
|
214
222
|
...
|
|
215
223
|
|
|
224
|
+
@abc.abstractmethod
|
|
225
|
+
async def get_attachments(self, attachment_ids: list[str]) -> list[Attachment]:
|
|
226
|
+
"""Get multiple attachments by their IDs."""
|
|
227
|
+
...
|
|
228
|
+
|
|
229
|
+
@abc.abstractmethod
|
|
230
|
+
async def delete_attachment(self, attachment_id: str) -> bool:
|
|
231
|
+
"""Delete an attachment by its ID.
|
|
232
|
+
|
|
233
|
+
Returns True if the attachment was deleted, False if it was not found.
|
|
234
|
+
"""
|
|
235
|
+
...
|
|
236
|
+
|
|
237
|
+
@abc.abstractmethod
|
|
238
|
+
async def delete_attachments(self, attachment_ids: list[str]) -> int:
|
|
239
|
+
"""Delete multiple attachments by their IDs.
|
|
240
|
+
|
|
241
|
+
Returns the number of attachments deleted.
|
|
242
|
+
"""
|
|
243
|
+
...
|
|
244
|
+
|
|
216
245
|
@abc.abstractmethod
|
|
217
246
|
async def insert_persona(
|
|
218
247
|
self,
|
astrbot/core/db/sqlite.py
CHANGED
|
@@ -105,8 +105,8 @@ class SQLiteDatabase(BaseDatabase):
|
|
|
105
105
|
text("""
|
|
106
106
|
SELECT * FROM platform_stats
|
|
107
107
|
WHERE timestamp >= :start_time
|
|
108
|
-
ORDER BY timestamp DESC
|
|
109
108
|
GROUP BY platform_id
|
|
109
|
+
ORDER BY timestamp DESC
|
|
110
110
|
"""),
|
|
111
111
|
{"start_time": start_time},
|
|
112
112
|
)
|
|
@@ -449,6 +449,18 @@ class SQLiteDatabase(BaseDatabase):
|
|
|
449
449
|
result = await session.execute(query.offset(offset).limit(page_size))
|
|
450
450
|
return result.scalars().all()
|
|
451
451
|
|
|
452
|
+
async def get_platform_message_history_by_id(
|
|
453
|
+
self, message_id: int
|
|
454
|
+
) -> PlatformMessageHistory | None:
|
|
455
|
+
"""Get a platform message history record by its ID."""
|
|
456
|
+
async with self.get_db() as session:
|
|
457
|
+
session: AsyncSession
|
|
458
|
+
query = select(PlatformMessageHistory).where(
|
|
459
|
+
PlatformMessageHistory.id == message_id
|
|
460
|
+
)
|
|
461
|
+
result = await session.execute(query)
|
|
462
|
+
return result.scalar_one_or_none()
|
|
463
|
+
|
|
452
464
|
async def insert_attachment(self, path, type, mime_type):
|
|
453
465
|
"""Insert a new attachment record."""
|
|
454
466
|
async with self.get_db() as session:
|
|
@@ -470,6 +482,48 @@ class SQLiteDatabase(BaseDatabase):
|
|
|
470
482
|
result = await session.execute(query)
|
|
471
483
|
return result.scalar_one_or_none()
|
|
472
484
|
|
|
485
|
+
async def get_attachments(self, attachment_ids: list[str]) -> list:
|
|
486
|
+
"""Get multiple attachments by their IDs."""
|
|
487
|
+
if not attachment_ids:
|
|
488
|
+
return []
|
|
489
|
+
async with self.get_db() as session:
|
|
490
|
+
session: AsyncSession
|
|
491
|
+
query = select(Attachment).where(
|
|
492
|
+
Attachment.attachment_id.in_(attachment_ids)
|
|
493
|
+
)
|
|
494
|
+
result = await session.execute(query)
|
|
495
|
+
return list(result.scalars().all())
|
|
496
|
+
|
|
497
|
+
async def delete_attachment(self, attachment_id: str) -> bool:
|
|
498
|
+
"""Delete an attachment by its ID.
|
|
499
|
+
|
|
500
|
+
Returns True if the attachment was deleted, False if it was not found.
|
|
501
|
+
"""
|
|
502
|
+
async with self.get_db() as session:
|
|
503
|
+
session: AsyncSession
|
|
504
|
+
async with session.begin():
|
|
505
|
+
query = delete(Attachment).where(
|
|
506
|
+
col(Attachment.attachment_id) == attachment_id
|
|
507
|
+
)
|
|
508
|
+
result = await session.execute(query)
|
|
509
|
+
return result.rowcount > 0
|
|
510
|
+
|
|
511
|
+
async def delete_attachments(self, attachment_ids: list[str]) -> int:
|
|
512
|
+
"""Delete multiple attachments by their IDs.
|
|
513
|
+
|
|
514
|
+
Returns the number of attachments deleted.
|
|
515
|
+
"""
|
|
516
|
+
if not attachment_ids:
|
|
517
|
+
return 0
|
|
518
|
+
async with self.get_db() as session:
|
|
519
|
+
session: AsyncSession
|
|
520
|
+
async with session.begin():
|
|
521
|
+
query = delete(Attachment).where(
|
|
522
|
+
col(Attachment.attachment_id).in_(attachment_ids)
|
|
523
|
+
)
|
|
524
|
+
result = await session.execute(query)
|
|
525
|
+
return result.rowcount
|
|
526
|
+
|
|
473
527
|
async def insert_persona(
|
|
474
528
|
self,
|
|
475
529
|
persona_id,
|
|
@@ -722,7 +722,12 @@ class File(BaseMessageComponent):
|
|
|
722
722
|
"""下载文件"""
|
|
723
723
|
download_dir = os.path.join(get_astrbot_data_path(), "temp")
|
|
724
724
|
os.makedirs(download_dir, exist_ok=True)
|
|
725
|
-
|
|
725
|
+
if self.name:
|
|
726
|
+
name, ext = os.path.splitext(self.name)
|
|
727
|
+
filename = f"{name}_{uuid.uuid4().hex[:8]}{ext}"
|
|
728
|
+
else:
|
|
729
|
+
filename = f"{uuid.uuid4().hex}"
|
|
730
|
+
file_path = os.path.join(download_dir, filename)
|
|
726
731
|
await download_file(self.url, file_path)
|
|
727
732
|
self.file_ = os.path.abspath(file_path)
|
|
728
733
|
|
|
@@ -9,7 +9,7 @@ from astrbot.core import logger
|
|
|
9
9
|
from astrbot.core.agent.tool import ToolSet
|
|
10
10
|
from astrbot.core.astr_agent_context import AstrAgentContext
|
|
11
11
|
from astrbot.core.conversation_mgr import Conversation
|
|
12
|
-
from astrbot.core.message.components import Image
|
|
12
|
+
from astrbot.core.message.components import File, Image, Reply
|
|
13
13
|
from astrbot.core.message.message_event_result import (
|
|
14
14
|
MessageChain,
|
|
15
15
|
MessageEventResult,
|
|
@@ -22,6 +22,7 @@ from astrbot.core.provider.entities import (
|
|
|
22
22
|
ProviderRequest,
|
|
23
23
|
)
|
|
24
24
|
from astrbot.core.star.star_handler import EventType, star_map
|
|
25
|
+
from astrbot.core.utils.file_extract import extract_file_moonshotai
|
|
25
26
|
from astrbot.core.utils.metrics import Metric
|
|
26
27
|
from astrbot.core.utils.session_lock import session_lock_manager
|
|
27
28
|
|
|
@@ -56,6 +57,13 @@ class InternalAgentSubStage(Stage):
|
|
|
56
57
|
self.show_reasoning = settings.get("display_reasoning_text", False)
|
|
57
58
|
self.kb_agentic_mode: bool = conf.get("kb_agentic_mode", False)
|
|
58
59
|
|
|
60
|
+
file_extract_conf: dict = settings.get("file_extract", {})
|
|
61
|
+
self.file_extract_enabled: bool = file_extract_conf.get("enable", False)
|
|
62
|
+
self.file_extract_prov: str = file_extract_conf.get("provider", "moonshotai")
|
|
63
|
+
self.file_extract_msh_api_key: str = file_extract_conf.get(
|
|
64
|
+
"moonshotai_api_key", ""
|
|
65
|
+
)
|
|
66
|
+
|
|
59
67
|
self.conv_manager = ctx.plugin_manager.context.conversation_manager
|
|
60
68
|
|
|
61
69
|
def _select_provider(self, event: AstrMessageEvent):
|
|
@@ -114,6 +122,50 @@ class InternalAgentSubStage(Stage):
|
|
|
114
122
|
req.func_tool = ToolSet()
|
|
115
123
|
req.func_tool.add_tool(KNOWLEDGE_BASE_QUERY_TOOL)
|
|
116
124
|
|
|
125
|
+
async def _apply_file_extract(
|
|
126
|
+
self,
|
|
127
|
+
event: AstrMessageEvent,
|
|
128
|
+
req: ProviderRequest,
|
|
129
|
+
):
|
|
130
|
+
"""Apply file extract to the provider request"""
|
|
131
|
+
file_paths = []
|
|
132
|
+
file_names = []
|
|
133
|
+
for comp in event.message_obj.message:
|
|
134
|
+
if isinstance(comp, File):
|
|
135
|
+
file_paths.append(await comp.get_file())
|
|
136
|
+
file_names.append(comp.name)
|
|
137
|
+
elif isinstance(comp, Reply) and comp.chain:
|
|
138
|
+
for reply_comp in comp.chain:
|
|
139
|
+
if isinstance(reply_comp, File):
|
|
140
|
+
file_paths.append(await reply_comp.get_file())
|
|
141
|
+
file_names.append(reply_comp.name)
|
|
142
|
+
if not file_paths:
|
|
143
|
+
return
|
|
144
|
+
if not req.prompt:
|
|
145
|
+
req.prompt = "总结一下文件里面讲了什么?"
|
|
146
|
+
if self.file_extract_prov == "moonshotai":
|
|
147
|
+
if not self.file_extract_msh_api_key:
|
|
148
|
+
logger.error("Moonshot AI API key for file extract is not set")
|
|
149
|
+
return
|
|
150
|
+
file_contents = await asyncio.gather(
|
|
151
|
+
*[
|
|
152
|
+
extract_file_moonshotai(file_path, self.file_extract_msh_api_key)
|
|
153
|
+
for file_path in file_paths
|
|
154
|
+
]
|
|
155
|
+
)
|
|
156
|
+
else:
|
|
157
|
+
logger.error(f"Unsupported file extract provider: {self.file_extract_prov}")
|
|
158
|
+
return
|
|
159
|
+
|
|
160
|
+
# add file extract results to contexts
|
|
161
|
+
for file_content, file_name in zip(file_contents, file_names):
|
|
162
|
+
req.contexts.append(
|
|
163
|
+
{
|
|
164
|
+
"role": "system",
|
|
165
|
+
"content": f"File Extract Results of user uploaded files:\n{file_content}\nFile Name: {file_name or 'Unknown'}",
|
|
166
|
+
},
|
|
167
|
+
)
|
|
168
|
+
|
|
117
169
|
def _truncate_contexts(
|
|
118
170
|
self,
|
|
119
171
|
contexts: list[dict],
|
|
@@ -346,6 +398,17 @@ class InternalAgentSubStage(Stage):
|
|
|
346
398
|
|
|
347
399
|
event.set_extra("provider_request", req)
|
|
348
400
|
|
|
401
|
+
# fix contexts json str
|
|
402
|
+
if isinstance(req.contexts, str):
|
|
403
|
+
req.contexts = json.loads(req.contexts)
|
|
404
|
+
|
|
405
|
+
# apply file extract
|
|
406
|
+
if self.file_extract_enabled:
|
|
407
|
+
try:
|
|
408
|
+
await self._apply_file_extract(event, req)
|
|
409
|
+
except Exception as e:
|
|
410
|
+
logger.error(f"Error occurred while applying file extract: {e}")
|
|
411
|
+
|
|
349
412
|
if not req.prompt and not req.image_urls:
|
|
350
413
|
return
|
|
351
414
|
|
|
@@ -356,10 +419,6 @@ class InternalAgentSubStage(Stage):
|
|
|
356
419
|
# apply knowledge base feature
|
|
357
420
|
await self._apply_kb(event, req)
|
|
358
421
|
|
|
359
|
-
# fix contexts json str
|
|
360
|
-
if isinstance(req.contexts, str):
|
|
361
|
-
req.contexts = json.loads(req.contexts)
|
|
362
|
-
|
|
363
422
|
# truncate contexts to fit max length
|
|
364
423
|
if req.contexts:
|
|
365
424
|
req.contexts = self._truncate_contexts(req.contexts)
|
|
@@ -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
|
|