AstrBot 4.8.0__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.
Files changed (104) hide show
  1. astrbot/cli/__init__.py +1 -1
  2. astrbot/core/agent/runners/tool_loop_agent_runner.py +0 -1
  3. astrbot/core/agent/tool.py +7 -2
  4. astrbot/core/astr_agent_tool_exec.py +5 -1
  5. astrbot/core/config/astrbot_config.py +4 -0
  6. astrbot/core/config/default.py +59 -1
  7. astrbot/core/core_lifecycle.py +1 -1
  8. astrbot/core/db/__init__.py +2 -3
  9. astrbot/core/db/migration/migra_3_to_4.py +2 -0
  10. astrbot/core/db/migration/sqlite_v3.py +6 -4
  11. astrbot/core/db/po.py +16 -15
  12. astrbot/core/db/sqlite.py +4 -3
  13. astrbot/core/db/vec_db/faiss_impl/embedding_storage.py +2 -0
  14. astrbot/core/event_bus.py +6 -1
  15. astrbot/core/knowledge_base/retrieval/manager.py +5 -1
  16. astrbot/core/log.py +2 -1
  17. astrbot/core/message/components.py +9 -3
  18. astrbot/core/persona_mgr.py +2 -2
  19. astrbot/core/pipeline/content_safety_check/stage.py +1 -1
  20. astrbot/core/pipeline/context_utils.py +2 -1
  21. astrbot/core/pipeline/process_stage/method/star_request.py +1 -2
  22. astrbot/core/pipeline/process_stage/stage.py +1 -1
  23. astrbot/core/pipeline/respond/stage.py +4 -2
  24. astrbot/core/pipeline/result_decorate/stage.py +68 -21
  25. astrbot/core/pipeline/scheduler.py +5 -1
  26. astrbot/core/pipeline/waking_check/stage.py +10 -0
  27. astrbot/core/platform/astr_message_event.py +5 -3
  28. astrbot/core/platform/astrbot_message.py +2 -2
  29. astrbot/core/platform/manager.py +4 -0
  30. astrbot/core/platform/platform.py +11 -3
  31. astrbot/core/platform/platform_metadata.py +1 -1
  32. astrbot/core/platform/register.py +1 -0
  33. astrbot/core/platform/sources/aiocqhttp/aiocqhttp_message_event.py +8 -6
  34. astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py +9 -5
  35. astrbot/core/platform/sources/dingtalk/dingtalk_adapter.py +24 -16
  36. astrbot/core/platform/sources/dingtalk/dingtalk_event.py +5 -2
  37. astrbot/core/platform/sources/discord/client.py +16 -4
  38. astrbot/core/platform/sources/discord/components.py +2 -2
  39. astrbot/core/platform/sources/discord/discord_platform_adapter.py +52 -24
  40. astrbot/core/platform/sources/discord/discord_platform_event.py +29 -8
  41. astrbot/core/platform/sources/lark/lark_adapter.py +177 -19
  42. astrbot/core/platform/sources/lark/lark_event.py +39 -4
  43. astrbot/core/platform/sources/lark/server.py +206 -0
  44. astrbot/core/platform/sources/misskey/misskey_adapter.py +2 -3
  45. astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py +62 -18
  46. astrbot/core/platform/sources/qqofficial/qqofficial_platform_adapter.py +13 -7
  47. astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_adapter.py +5 -3
  48. astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_server.py +2 -1
  49. astrbot/core/platform/sources/slack/client.py +9 -2
  50. astrbot/core/platform/sources/slack/slack_adapter.py +15 -9
  51. astrbot/core/platform/sources/slack/slack_event.py +8 -7
  52. astrbot/core/platform/sources/telegram/tg_adapter.py +1 -1
  53. astrbot/core/platform/sources/telegram/tg_event.py +23 -27
  54. astrbot/core/platform/sources/webchat/webchat_adapter.py +2 -2
  55. astrbot/core/platform/sources/webchat/webchat_event.py +2 -2
  56. astrbot/core/platform/sources/wechatpadpro/wechatpadpro_adapter.py +26 -9
  57. astrbot/core/platform/sources/wecom/wecom_adapter.py +25 -28
  58. astrbot/core/platform/sources/wecom/wecom_event.py +2 -2
  59. astrbot/core/platform/sources/wecom_ai_bot/wecomai_event.py +3 -3
  60. astrbot/core/platform/sources/weixin_official_account/weixin_offacc_adapter.py +30 -25
  61. astrbot/core/platform/sources/weixin_official_account/weixin_offacc_event.py +10 -7
  62. astrbot/core/provider/func_tool_manager.py +3 -3
  63. astrbot/core/provider/manager.py +130 -74
  64. astrbot/core/provider/provider.py +12 -1
  65. astrbot/core/provider/sources/azure_tts_source.py +31 -9
  66. astrbot/core/provider/sources/bailian_rerank_source.py +4 -0
  67. astrbot/core/provider/sources/dashscope_tts.py +3 -2
  68. astrbot/core/provider/sources/edge_tts_source.py +1 -1
  69. astrbot/core/provider/sources/fishaudio_tts_api_source.py +5 -4
  70. astrbot/core/provider/sources/gemini_embedding_source.py +15 -5
  71. astrbot/core/provider/sources/gemini_source.py +12 -10
  72. astrbot/core/provider/sources/minimax_tts_api_source.py +4 -2
  73. astrbot/core/provider/sources/openai_embedding_source.py +2 -2
  74. astrbot/core/provider/sources/openai_source.py +4 -0
  75. astrbot/core/provider/sources/sensevoice_selfhosted_source.py +5 -2
  76. astrbot/core/provider/sources/vllm_rerank_source.py +1 -0
  77. astrbot/core/provider/sources/whisper_api_source.py +1 -1
  78. astrbot/core/provider/sources/whisper_selfhosted_source.py +6 -2
  79. astrbot/core/provider/sources/xinference_rerank_source.py +10 -2
  80. astrbot/core/star/context.py +2 -2
  81. astrbot/core/star/register/star_handler.py +22 -5
  82. astrbot/core/star/star_handler.py +85 -4
  83. astrbot/core/updator.py +3 -3
  84. astrbot/core/utils/io.py +1 -1
  85. astrbot/core/utils/session_waiter.py +17 -10
  86. astrbot/core/utils/shared_preferences.py +32 -0
  87. astrbot/core/utils/t2i/__init__.py +2 -2
  88. astrbot/core/utils/t2i/local_strategy.py +25 -31
  89. astrbot/core/utils/tencent_record_helper.py +1 -1
  90. astrbot/core/utils/version_comparator.py +6 -3
  91. astrbot/core/utils/webhook_utils.py +19 -0
  92. astrbot/dashboard/routes/chat.py +14 -9
  93. astrbot/dashboard/routes/config.py +10 -20
  94. astrbot/dashboard/routes/knowledge_base.py +253 -78
  95. astrbot/dashboard/routes/log.py +13 -8
  96. astrbot/dashboard/routes/platform.py +1 -1
  97. astrbot/dashboard/routes/plugin.py +108 -51
  98. astrbot/dashboard/routes/route.py +2 -0
  99. astrbot/dashboard/server.py +6 -3
  100. {astrbot-4.8.0.dist-info → astrbot-4.9.0.dist-info}/METADATA +3 -1
  101. {astrbot-4.8.0.dist-info → astrbot-4.9.0.dist-info}/RECORD +104 -103
  102. {astrbot-4.8.0.dist-info → astrbot-4.9.0.dist-info}/WHEEL +0 -0
  103. {astrbot-4.8.0.dist-info → astrbot-4.9.0.dist-info}/entry_points.txt +0 -0
  104. {astrbot-4.8.0.dist-info → astrbot-4.9.0.dist-info}/licenses/LICENSE +0 -0
astrbot/cli/__init__.py CHANGED
@@ -1 +1 @@
1
- __version__ = "4.8.0"
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(
@@ -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: Callable[..., Awaitable[Any]] | None = None
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[..., T.Awaitable[T.Any]],
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,
@@ -24,6 +24,10 @@ class AstrBotConfig(dict):
24
24
  - 如果传入了 schema,将会通过 schema 解析出 default_config,此时传入的 default_config 会被忽略。
25
25
  """
26
26
 
27
+ config_path: str
28
+ default_config: dict
29
+ schema: dict | None
30
+
27
31
  def __init__(
28
32
  self,
29
33
  config_path: str = ASTRBOT_CONFIG_PATH,
@@ -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.8.0"
7
+ VERSION = "4.9.0"
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,
@@ -157,6 +166,7 @@ DEFAULT_CONFIG = {
157
166
  "kb_fusion_top_k": 20, # 知识库检索融合阶段返回结果数量
158
167
  "kb_final_top_k": 5, # 知识库检索最终返回结果数量
159
168
  "kb_agentic_mode": False,
169
+ "disable_builtin_commands": False,
160
170
  }
161
171
 
162
172
 
@@ -268,6 +278,10 @@ CONFIG_METADATA_2 = {
268
278
  "app_id": "",
269
279
  "app_secret": "",
270
280
  "domain": "https://open.feishu.cn",
281
+ "lark_connection_mode": "socket", # webhook, socket
282
+ "webhook_uuid": "",
283
+ "lark_encrypt_key": "",
284
+ "lark_verification_token": "",
271
285
  },
272
286
  "钉钉(DingTalk)": {
273
287
  "id": "dingtalk",
@@ -361,6 +375,28 @@ CONFIG_METADATA_2 = {
361
375
  # "type": "string",
362
376
  # "options": ["fullscreen", "embedded"],
363
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
+ },
364
400
  "is_sandbox": {
365
401
  "description": "沙箱模式",
366
402
  "type": "bool",
@@ -2661,6 +2697,11 @@ CONFIG_METADATA_3 = {
2661
2697
  "description": "只 @ 机器人是否触发等待",
2662
2698
  "type": "bool",
2663
2699
  },
2700
+ "disable_builtin_commands": {
2701
+ "description": "禁用自带指令",
2702
+ "type": "bool",
2703
+ "hint": "禁用所有 AstrBot 的自带指令,如 help, provider, model 等。",
2704
+ },
2664
2705
  },
2665
2706
  },
2666
2707
  "whitelist": {
@@ -2875,9 +2916,26 @@ CONFIG_METADATA_3 = {
2875
2916
  "description": "分段回复字数阈值",
2876
2917
  "type": "int",
2877
2918
  },
2919
+ "platform_settings.segmented_reply.split_mode": {
2920
+ "description": "分段模式",
2921
+ "type": "string",
2922
+ "options": ["regex", "words"],
2923
+ "labels": ["正则表达式", "分段词列表"],
2924
+ },
2878
2925
  "platform_settings.segmented_reply.regex": {
2879
2926
  "description": "分段正则表达式",
2880
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
+ },
2881
2939
  },
2882
2940
  "platform_settings.segmented_reply.content_cleanup_rule": {
2883
2941
  "description": "内容过滤正则表达式",
@@ -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_:
@@ -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 = sessionmaker(
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(self, user_id: str, cid: str) -> Conversation:
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) -> tuple:
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" # type: ignore
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" # type: ignore
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" # type: ignore
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" # type: ignore
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" # type: ignore
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" # type: ignore
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" # type: ignore
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 = 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(
@@ -90,4 +90,6 @@ class EmbeddingStorage:
90
90
  path (str): 保存索引的路径
91
91
 
92
92
  """
93
+ if self.index is None:
94
+ return
93
95
  faiss.write_index(self.index, self.path)
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 = None,
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: FaissVecDB = kb_options[kb_id]["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": record.asctime,
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] | None = []
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
- return os.path.abspath(self.file_)
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:
@@ -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
- ) -> None | AsyncGenerator[None, None]:
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[None, None]:
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.get_result().is_stopped()
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 self.only_llm_result and not event.get_result().is_llm_result():
120
+ if (result := event.get_result()) is None:
121
+ return False
122
+ if self.only_llm_result and result.is_llm_result():
121
123
  return False
122
124
 
123
125
  if event.get_platform_name() in [
@@ -185,7 +187,7 @@ class RespondStage(Stage):
185
187
  if isinstance(component, Comp.File) and component.file:
186
188
  # 支持 File 消息段的路径映射。
187
189
  component.file = path_Mapping(mappings, component.file)
188
- event.get_result().chain[idx] = component
190
+ result.chain[idx] = component
189
191
 
190
192
  # 检查消息链是否为空
191
193
  try: