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.
Files changed (52) hide show
  1. astrbot/cli/__init__.py +1 -1
  2. astrbot/core/agent/message.py +21 -5
  3. astrbot/core/astr_agent_run_util.py +15 -1
  4. astrbot/core/config/default.py +113 -1
  5. astrbot/core/db/__init__.py +30 -1
  6. astrbot/core/db/sqlite.py +55 -1
  7. astrbot/core/message/components.py +6 -1
  8. astrbot/core/pipeline/process_stage/method/agent_sub_stages/internal.py +64 -5
  9. astrbot/core/pipeline/process_stage/method/agent_sub_stages/third_party.py +1 -1
  10. astrbot/core/platform/manager.py +67 -9
  11. astrbot/core/platform/platform.py +99 -2
  12. astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py +19 -5
  13. astrbot/core/platform/sources/dingtalk/dingtalk_adapter.py +5 -7
  14. astrbot/core/platform/sources/discord/discord_platform_adapter.py +1 -2
  15. astrbot/core/platform/sources/lark/lark_adapter.py +1 -3
  16. astrbot/core/platform/sources/misskey/misskey_adapter.py +1 -2
  17. astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py +2 -0
  18. astrbot/core/platform/sources/qqofficial/qqofficial_platform_adapter.py +1 -3
  19. astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_adapter.py +32 -9
  20. astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_server.py +13 -1
  21. astrbot/core/platform/sources/satori/satori_adapter.py +1 -2
  22. astrbot/core/platform/sources/slack/client.py +50 -39
  23. astrbot/core/platform/sources/slack/slack_adapter.py +21 -7
  24. astrbot/core/platform/sources/slack/slack_event.py +3 -3
  25. astrbot/core/platform/sources/telegram/tg_adapter.py +4 -3
  26. astrbot/core/platform/sources/webchat/webchat_adapter.py +95 -29
  27. astrbot/core/platform/sources/webchat/webchat_event.py +33 -33
  28. astrbot/core/platform/sources/wechatpadpro/wechatpadpro_adapter.py +1 -2
  29. astrbot/core/platform/sources/wecom/wecom_adapter.py +51 -9
  30. astrbot/core/platform/sources/wecom/wecom_event.py +1 -1
  31. astrbot/core/platform/sources/wecom_ai_bot/wecomai_adapter.py +26 -9
  32. astrbot/core/platform/sources/wecom_ai_bot/wecomai_server.py +27 -5
  33. astrbot/core/platform/sources/weixin_official_account/weixin_offacc_adapter.py +52 -11
  34. astrbot/core/platform/sources/weixin_official_account/weixin_offacc_event.py +1 -1
  35. astrbot/core/platform_message_history_mgr.py +3 -3
  36. astrbot/core/provider/provider.py +35 -0
  37. astrbot/core/provider/sources/whisper_api_source.py +43 -11
  38. astrbot/core/utils/file_extract.py +23 -0
  39. astrbot/core/utils/tencent_record_helper.py +1 -1
  40. astrbot/core/utils/webhook_utils.py +47 -0
  41. astrbot/dashboard/routes/__init__.py +2 -0
  42. astrbot/dashboard/routes/chat.py +300 -70
  43. astrbot/dashboard/routes/config.py +32 -165
  44. astrbot/dashboard/routes/knowledge_base.py +1 -1
  45. astrbot/dashboard/routes/platform.py +100 -0
  46. astrbot/dashboard/routes/plugin.py +65 -6
  47. astrbot/dashboard/server.py +3 -1
  48. {astrbot-4.7.3.dist-info → astrbot-4.8.0.dist-info}/METADATA +48 -37
  49. {astrbot-4.7.3.dist-info → astrbot-4.8.0.dist-info}/RECORD +52 -49
  50. {astrbot-4.7.3.dist-info → astrbot-4.8.0.dist-info}/WHEEL +0 -0
  51. {astrbot-4.7.3.dist-info → astrbot-4.8.0.dist-info}/entry_points.txt +0 -0
  52. {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.7.3"
1
+ __version__ = "4.8.0"
@@ -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
- err_msg = f"\n\nAstrBot 请求失败。\n错误类型: {type(e).__name__}\n错误信息: {e!s}\n\n请在控制台查看和分享错误详情。\n"
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:
@@ -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.3"
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,
@@ -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
- ) -> None:
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
- file_path = os.path.join(download_dir, f"{uuid.uuid4().hex}")
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请在控制台查看和分享错误详情。\n"
60
+ f"错误信息: {e!s}\n\n请在平台日志查看和分享错误详情。\n"
61
61
  )
62
62
  yield MessageChain().message(err_msg)
63
63