AstrBot 4.5.1__py3-none-any.whl → 4.5.2__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 (244) hide show
  1. astrbot/api/__init__.py +10 -11
  2. astrbot/api/event/__init__.py +5 -6
  3. astrbot/api/event/filter/__init__.py +37 -36
  4. astrbot/api/platform/__init__.py +7 -8
  5. astrbot/api/provider/__init__.py +7 -7
  6. astrbot/api/star/__init__.py +3 -4
  7. astrbot/api/util/__init__.py +2 -2
  8. astrbot/cli/__main__.py +5 -5
  9. astrbot/cli/commands/__init__.py +3 -3
  10. astrbot/cli/commands/cmd_conf.py +19 -16
  11. astrbot/cli/commands/cmd_init.py +3 -2
  12. astrbot/cli/commands/cmd_plug.py +8 -10
  13. astrbot/cli/commands/cmd_run.py +5 -6
  14. astrbot/cli/utils/__init__.py +6 -6
  15. astrbot/cli/utils/basic.py +14 -14
  16. astrbot/cli/utils/plugin.py +24 -15
  17. astrbot/cli/utils/version_comparator.py +10 -12
  18. astrbot/core/__init__.py +8 -6
  19. astrbot/core/agent/agent.py +3 -2
  20. astrbot/core/agent/handoff.py +6 -2
  21. astrbot/core/agent/hooks.py +9 -6
  22. astrbot/core/agent/mcp_client.py +50 -15
  23. astrbot/core/agent/message.py +168 -0
  24. astrbot/core/agent/response.py +2 -1
  25. astrbot/core/agent/run_context.py +2 -3
  26. astrbot/core/agent/runners/base.py +10 -13
  27. astrbot/core/agent/runners/tool_loop_agent_runner.py +52 -51
  28. astrbot/core/agent/tool.py +60 -41
  29. astrbot/core/agent/tool_executor.py +9 -3
  30. astrbot/core/astr_agent_context.py +3 -1
  31. astrbot/core/astrbot_config_mgr.py +29 -9
  32. astrbot/core/config/__init__.py +2 -2
  33. astrbot/core/config/astrbot_config.py +28 -26
  34. astrbot/core/config/default.py +4 -6
  35. astrbot/core/conversation_mgr.py +105 -36
  36. astrbot/core/core_lifecycle.py +68 -54
  37. astrbot/core/db/__init__.py +33 -18
  38. astrbot/core/db/migration/helper.py +12 -10
  39. astrbot/core/db/migration/migra_3_to_4.py +53 -34
  40. astrbot/core/db/migration/migra_45_to_46.py +1 -1
  41. astrbot/core/db/migration/shared_preferences_v3.py +2 -1
  42. astrbot/core/db/migration/sqlite_v3.py +26 -23
  43. astrbot/core/db/po.py +27 -18
  44. astrbot/core/db/sqlite.py +74 -45
  45. astrbot/core/db/vec_db/base.py +10 -14
  46. astrbot/core/db/vec_db/faiss_impl/document_storage.py +90 -77
  47. astrbot/core/db/vec_db/faiss_impl/embedding_storage.py +9 -3
  48. astrbot/core/db/vec_db/faiss_impl/vec_db.py +36 -31
  49. astrbot/core/event_bus.py +8 -6
  50. astrbot/core/file_token_service.py +6 -5
  51. astrbot/core/initial_loader.py +7 -5
  52. astrbot/core/knowledge_base/chunking/__init__.py +1 -3
  53. astrbot/core/knowledge_base/chunking/base.py +1 -0
  54. astrbot/core/knowledge_base/chunking/fixed_size.py +2 -0
  55. astrbot/core/knowledge_base/chunking/recursive.py +16 -10
  56. astrbot/core/knowledge_base/kb_db_sqlite.py +50 -48
  57. astrbot/core/knowledge_base/kb_helper.py +30 -17
  58. astrbot/core/knowledge_base/kb_mgr.py +6 -7
  59. astrbot/core/knowledge_base/models.py +10 -4
  60. astrbot/core/knowledge_base/parsers/__init__.py +3 -5
  61. astrbot/core/knowledge_base/parsers/base.py +1 -0
  62. astrbot/core/knowledge_base/parsers/markitdown_parser.py +2 -1
  63. astrbot/core/knowledge_base/parsers/pdf_parser.py +2 -1
  64. astrbot/core/knowledge_base/parsers/text_parser.py +1 -0
  65. astrbot/core/knowledge_base/parsers/util.py +1 -1
  66. astrbot/core/knowledge_base/retrieval/__init__.py +6 -8
  67. astrbot/core/knowledge_base/retrieval/manager.py +17 -14
  68. astrbot/core/knowledge_base/retrieval/rank_fusion.py +7 -3
  69. astrbot/core/knowledge_base/retrieval/sparse_retriever.py +11 -5
  70. astrbot/core/log.py +21 -13
  71. astrbot/core/message/components.py +123 -217
  72. astrbot/core/message/message_event_result.py +24 -24
  73. astrbot/core/persona_mgr.py +20 -11
  74. astrbot/core/pipeline/__init__.py +7 -7
  75. astrbot/core/pipeline/content_safety_check/stage.py +13 -9
  76. astrbot/core/pipeline/content_safety_check/strategies/__init__.py +1 -2
  77. astrbot/core/pipeline/content_safety_check/strategies/baidu_aip.py +12 -13
  78. astrbot/core/pipeline/content_safety_check/strategies/keywords.py +1 -0
  79. astrbot/core/pipeline/content_safety_check/strategies/strategy.py +6 -6
  80. astrbot/core/pipeline/context.py +4 -1
  81. astrbot/core/pipeline/context_utils.py +77 -7
  82. astrbot/core/pipeline/preprocess_stage/stage.py +12 -9
  83. astrbot/core/pipeline/process_stage/method/llm_request.py +125 -72
  84. astrbot/core/pipeline/process_stage/method/star_request.py +19 -17
  85. astrbot/core/pipeline/process_stage/stage.py +13 -10
  86. astrbot/core/pipeline/process_stage/utils.py +6 -5
  87. astrbot/core/pipeline/rate_limit_check/stage.py +37 -36
  88. astrbot/core/pipeline/respond/stage.py +23 -20
  89. astrbot/core/pipeline/result_decorate/stage.py +31 -23
  90. astrbot/core/pipeline/scheduler.py +12 -8
  91. astrbot/core/pipeline/session_status_check/stage.py +12 -8
  92. astrbot/core/pipeline/stage.py +10 -4
  93. astrbot/core/pipeline/waking_check/stage.py +24 -18
  94. astrbot/core/pipeline/whitelist_check/stage.py +10 -7
  95. astrbot/core/platform/__init__.py +6 -6
  96. astrbot/core/platform/astr_message_event.py +76 -110
  97. astrbot/core/platform/astrbot_message.py +11 -13
  98. astrbot/core/platform/manager.py +16 -15
  99. astrbot/core/platform/message_session.py +5 -3
  100. astrbot/core/platform/platform.py +16 -24
  101. astrbot/core/platform/platform_metadata.py +4 -4
  102. astrbot/core/platform/register.py +8 -8
  103. astrbot/core/platform/sources/aiocqhttp/aiocqhttp_message_event.py +23 -15
  104. astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py +51 -33
  105. astrbot/core/platform/sources/dingtalk/dingtalk_adapter.py +42 -27
  106. astrbot/core/platform/sources/dingtalk/dingtalk_event.py +7 -3
  107. astrbot/core/platform/sources/discord/client.py +9 -6
  108. astrbot/core/platform/sources/discord/components.py +18 -14
  109. astrbot/core/platform/sources/discord/discord_platform_adapter.py +45 -30
  110. astrbot/core/platform/sources/discord/discord_platform_event.py +38 -30
  111. astrbot/core/platform/sources/lark/lark_adapter.py +23 -17
  112. astrbot/core/platform/sources/lark/lark_event.py +21 -14
  113. astrbot/core/platform/sources/misskey/misskey_adapter.py +107 -67
  114. astrbot/core/platform/sources/misskey/misskey_api.py +153 -129
  115. astrbot/core/platform/sources/misskey/misskey_event.py +20 -15
  116. astrbot/core/platform/sources/misskey/misskey_utils.py +74 -62
  117. astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py +63 -44
  118. astrbot/core/platform/sources/qqofficial/qqofficial_platform_adapter.py +41 -26
  119. astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_adapter.py +36 -17
  120. astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_event.py +3 -1
  121. astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_server.py +12 -7
  122. astrbot/core/platform/sources/satori/satori_adapter.py +56 -38
  123. astrbot/core/platform/sources/satori/satori_event.py +34 -25
  124. astrbot/core/platform/sources/slack/client.py +11 -9
  125. astrbot/core/platform/sources/slack/slack_adapter.py +52 -36
  126. astrbot/core/platform/sources/slack/slack_event.py +34 -24
  127. astrbot/core/platform/sources/telegram/tg_adapter.py +38 -18
  128. astrbot/core/platform/sources/telegram/tg_event.py +32 -18
  129. astrbot/core/platform/sources/webchat/webchat_adapter.py +27 -17
  130. astrbot/core/platform/sources/webchat/webchat_event.py +14 -10
  131. astrbot/core/platform/sources/wechatpadpro/wechatpadpro_adapter.py +115 -120
  132. astrbot/core/platform/sources/wechatpadpro/wechatpadpro_message_event.py +9 -8
  133. astrbot/core/platform/sources/wechatpadpro/xml_data_parser.py +15 -16
  134. astrbot/core/platform/sources/wecom/wecom_adapter.py +35 -18
  135. astrbot/core/platform/sources/wecom/wecom_event.py +55 -48
  136. astrbot/core/platform/sources/wecom/wecom_kf.py +34 -44
  137. astrbot/core/platform/sources/wecom/wecom_kf_message.py +26 -10
  138. astrbot/core/platform/sources/wecom_ai_bot/WXBizJsonMsgCrypt.py +18 -10
  139. astrbot/core/platform/sources/wecom_ai_bot/__init__.py +3 -5
  140. astrbot/core/platform/sources/wecom_ai_bot/ierror.py +0 -1
  141. astrbot/core/platform/sources/wecom_ai_bot/wecomai_adapter.py +61 -37
  142. astrbot/core/platform/sources/wecom_ai_bot/wecomai_api.py +67 -28
  143. astrbot/core/platform/sources/wecom_ai_bot/wecomai_event.py +8 -9
  144. astrbot/core/platform/sources/wecom_ai_bot/wecomai_queue_mgr.py +18 -9
  145. astrbot/core/platform/sources/wecom_ai_bot/wecomai_server.py +14 -12
  146. astrbot/core/platform/sources/wecom_ai_bot/wecomai_utils.py +22 -12
  147. astrbot/core/platform/sources/weixin_official_account/weixin_offacc_adapter.py +40 -26
  148. astrbot/core/platform/sources/weixin_official_account/weixin_offacc_event.py +47 -45
  149. astrbot/core/platform_message_history_mgr.py +5 -3
  150. astrbot/core/provider/__init__.py +2 -3
  151. astrbot/core/provider/entites.py +8 -8
  152. astrbot/core/provider/entities.py +61 -75
  153. astrbot/core/provider/func_tool_manager.py +59 -55
  154. astrbot/core/provider/manager.py +32 -22
  155. astrbot/core/provider/provider.py +72 -46
  156. astrbot/core/provider/register.py +7 -7
  157. astrbot/core/provider/sources/anthropic_source.py +48 -30
  158. astrbot/core/provider/sources/azure_tts_source.py +17 -13
  159. astrbot/core/provider/sources/coze_api_client.py +27 -17
  160. astrbot/core/provider/sources/coze_source.py +104 -87
  161. astrbot/core/provider/sources/dashscope_source.py +18 -11
  162. astrbot/core/provider/sources/dashscope_tts.py +36 -23
  163. astrbot/core/provider/sources/dify_source.py +25 -20
  164. astrbot/core/provider/sources/edge_tts_source.py +21 -17
  165. astrbot/core/provider/sources/fishaudio_tts_api_source.py +22 -14
  166. astrbot/core/provider/sources/gemini_embedding_source.py +12 -13
  167. astrbot/core/provider/sources/gemini_source.py +72 -58
  168. astrbot/core/provider/sources/gemini_tts_source.py +8 -6
  169. astrbot/core/provider/sources/gsv_selfhosted_source.py +17 -14
  170. astrbot/core/provider/sources/gsvi_tts_source.py +11 -7
  171. astrbot/core/provider/sources/minimax_tts_api_source.py +50 -40
  172. astrbot/core/provider/sources/openai_embedding_source.py +6 -8
  173. astrbot/core/provider/sources/openai_source.py +77 -69
  174. astrbot/core/provider/sources/openai_tts_api_source.py +14 -6
  175. astrbot/core/provider/sources/sensevoice_selfhosted_source.py +13 -11
  176. astrbot/core/provider/sources/vllm_rerank_source.py +10 -4
  177. astrbot/core/provider/sources/volcengine_tts.py +38 -31
  178. astrbot/core/provider/sources/whisper_api_source.py +14 -12
  179. astrbot/core/provider/sources/whisper_selfhosted_source.py +15 -11
  180. astrbot/core/provider/sources/xinference_rerank_source.py +16 -8
  181. astrbot/core/provider/sources/xinference_stt_provider.py +35 -25
  182. astrbot/core/star/__init__.py +16 -11
  183. astrbot/core/star/config.py +10 -15
  184. astrbot/core/star/context.py +97 -75
  185. astrbot/core/star/filter/__init__.py +4 -3
  186. astrbot/core/star/filter/command.py +30 -28
  187. astrbot/core/star/filter/command_group.py +27 -24
  188. astrbot/core/star/filter/custom_filter.py +6 -5
  189. astrbot/core/star/filter/event_message_type.py +4 -2
  190. astrbot/core/star/filter/permission.py +4 -2
  191. astrbot/core/star/filter/platform_adapter_type.py +4 -2
  192. astrbot/core/star/filter/regex.py +4 -2
  193. astrbot/core/star/register/__init__.py +19 -19
  194. astrbot/core/star/register/star.py +6 -2
  195. astrbot/core/star/register/star_handler.py +96 -73
  196. astrbot/core/star/session_llm_manager.py +48 -14
  197. astrbot/core/star/session_plugin_manager.py +29 -15
  198. astrbot/core/star/star.py +1 -2
  199. astrbot/core/star/star_handler.py +13 -8
  200. astrbot/core/star/star_manager.py +151 -59
  201. astrbot/core/star/star_tools.py +44 -37
  202. astrbot/core/star/updator.py +10 -10
  203. astrbot/core/umop_config_router.py +10 -4
  204. astrbot/core/updator.py +13 -5
  205. astrbot/core/utils/astrbot_path.py +3 -5
  206. astrbot/core/utils/dify_api_client.py +33 -15
  207. astrbot/core/utils/io.py +66 -42
  208. astrbot/core/utils/log_pipe.py +1 -1
  209. astrbot/core/utils/metrics.py +7 -7
  210. astrbot/core/utils/path_util.py +15 -16
  211. astrbot/core/utils/pip_installer.py +5 -5
  212. astrbot/core/utils/session_waiter.py +19 -20
  213. astrbot/core/utils/shared_preferences.py +45 -20
  214. astrbot/core/utils/t2i/__init__.py +4 -1
  215. astrbot/core/utils/t2i/network_strategy.py +35 -26
  216. astrbot/core/utils/t2i/renderer.py +11 -5
  217. astrbot/core/utils/t2i/template_manager.py +14 -15
  218. astrbot/core/utils/tencent_record_helper.py +19 -13
  219. astrbot/core/utils/version_comparator.py +10 -13
  220. astrbot/core/zip_updator.py +43 -40
  221. astrbot/dashboard/routes/__init__.py +18 -18
  222. astrbot/dashboard/routes/auth.py +10 -8
  223. astrbot/dashboard/routes/chat.py +30 -21
  224. astrbot/dashboard/routes/config.py +92 -75
  225. astrbot/dashboard/routes/conversation.py +46 -39
  226. astrbot/dashboard/routes/file.py +4 -2
  227. astrbot/dashboard/routes/knowledge_base.py +47 -40
  228. astrbot/dashboard/routes/log.py +9 -4
  229. astrbot/dashboard/routes/persona.py +19 -16
  230. astrbot/dashboard/routes/plugin.py +69 -55
  231. astrbot/dashboard/routes/route.py +3 -1
  232. astrbot/dashboard/routes/session_management.py +130 -116
  233. astrbot/dashboard/routes/stat.py +34 -34
  234. astrbot/dashboard/routes/t2i.py +15 -12
  235. astrbot/dashboard/routes/tools.py +47 -52
  236. astrbot/dashboard/routes/update.py +32 -28
  237. astrbot/dashboard/server.py +30 -26
  238. astrbot/dashboard/utils.py +8 -4
  239. {astrbot-4.5.1.dist-info → astrbot-4.5.2.dist-info}/METADATA +2 -1
  240. astrbot-4.5.2.dist-info/RECORD +261 -0
  241. astrbot-4.5.1.dist-info/RECORD +0 -260
  242. {astrbot-4.5.1.dist-info → astrbot-4.5.2.dist-info}/WHEEL +0 -0
  243. {astrbot-4.5.1.dist-info → astrbot-4.5.2.dist-info}/entry_points.txt +0 -0
  244. {astrbot-4.5.1.dist-info → astrbot-4.5.2.dist-info}/licenses/LICENSE +0 -0
@@ -1,12 +1,13 @@
1
- import os
2
1
  import json
3
- from datetime import datetime
2
+ import os
4
3
  from contextlib import asynccontextmanager
4
+ from datetime import datetime
5
5
 
6
- from sqlalchemy import Text, Column
6
+ from sqlalchemy import Column, Text
7
7
  from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession, create_async_engine
8
8
  from sqlalchemy.orm import sessionmaker
9
- from sqlmodel import Field, SQLModel, select, col, func, text, MetaData
9
+ from sqlmodel import Field, MetaData, SQLModel, col, func, select, text
10
+
10
11
  from astrbot.core import logger
11
12
 
12
13
 
@@ -20,7 +21,9 @@ class Document(BaseDocModel, table=True):
20
21
  __tablename__ = "documents" # type: ignore
21
22
 
22
23
  id: int | None = Field(
23
- default=None, primary_key=True, sa_column_kwargs={"autoincrement": True}
24
+ default=None,
25
+ primary_key=True,
26
+ sa_column_kwargs={"autoincrement": True},
24
27
  )
25
28
  doc_id: str = Field(nullable=False)
26
29
  text: str = Field(nullable=False)
@@ -36,7 +39,8 @@ class DocumentStorage:
36
39
  self.engine: AsyncEngine | None = None
37
40
  self.async_session_maker: sessionmaker | None = None
38
41
  self.sqlite_init_path = os.path.join(
39
- os.path.dirname(__file__), "sqlite_init.sql"
42
+ os.path.dirname(__file__),
43
+ "sqlite_init.sql",
40
44
  )
41
45
 
42
46
  async def initialize(self):
@@ -50,26 +54,26 @@ class DocumentStorage:
50
54
  await conn.execute(
51
55
  text(
52
56
  "ALTER TABLE documents ADD COLUMN kb_doc_id TEXT "
53
- "GENERATED ALWAYS AS (json_extract(metadata, '$.kb_doc_id')) STORED"
54
- )
57
+ "GENERATED ALWAYS AS (json_extract(metadata, '$.kb_doc_id')) STORED",
58
+ ),
55
59
  )
56
60
  await conn.execute(
57
61
  text(
58
62
  "ALTER TABLE documents ADD COLUMN user_id TEXT "
59
- "GENERATED ALWAYS AS (json_extract(metadata, '$.user_id')) STORED"
60
- )
63
+ "GENERATED ALWAYS AS (json_extract(metadata, '$.user_id')) STORED",
64
+ ),
61
65
  )
62
66
 
63
67
  # Create indexes
64
68
  await conn.execute(
65
69
  text(
66
- "CREATE INDEX IF NOT EXISTS idx_documents_kb_doc_id ON documents(kb_doc_id)"
67
- )
70
+ "CREATE INDEX IF NOT EXISTS idx_documents_kb_doc_id ON documents(kb_doc_id)",
71
+ ),
68
72
  )
69
73
  await conn.execute(
70
74
  text(
71
- "CREATE INDEX IF NOT EXISTS idx_documents_user_id ON documents(user_id)"
72
- )
75
+ "CREATE INDEX IF NOT EXISTS idx_documents_user_id ON documents(user_id)",
76
+ ),
73
77
  )
74
78
  except BaseException:
75
79
  pass
@@ -113,10 +117,11 @@ class DocumentStorage:
113
117
 
114
118
  Returns:
115
119
  list: The list of documents that match the filters.
120
+
116
121
  """
117
122
  if self.engine is None:
118
123
  logger.warning(
119
- "Database connection is not initialized, returning empty result"
124
+ "Database connection is not initialized, returning empty result",
120
125
  )
121
126
  return []
122
127
 
@@ -125,7 +130,7 @@ class DocumentStorage:
125
130
 
126
131
  for key, val in metadata_filters.items():
127
132
  query = query.where(
128
- text(f"json_extract(metadata, '$.{key}') = :filter_{key}")
133
+ text(f"json_extract(metadata, '$.{key}') = :filter_{key}"),
129
134
  ).params(**{f"filter_{key}": val})
130
135
 
131
136
  if ids is not None and len(ids) > 0:
@@ -153,24 +158,27 @@ class DocumentStorage:
153
158
 
154
159
  Returns:
155
160
  int: The integer ID of the inserted document.
161
+
156
162
  """
157
163
  assert self.engine is not None, "Database connection is not initialized."
158
164
 
159
- async with self.get_session() as session:
160
- async with session.begin():
161
- document = Document(
162
- doc_id=doc_id,
163
- text=text,
164
- metadata_=json.dumps(metadata),
165
- created_at=datetime.now(),
166
- updated_at=datetime.now(),
167
- )
168
- session.add(document)
169
- await session.flush() # Flush to get the ID
170
- return document.id # type: ignore
165
+ async with self.get_session() as session, session.begin():
166
+ document = Document(
167
+ doc_id=doc_id,
168
+ text=text,
169
+ metadata_=json.dumps(metadata),
170
+ created_at=datetime.now(),
171
+ updated_at=datetime.now(),
172
+ )
173
+ session.add(document)
174
+ await session.flush() # Flush to get the ID
175
+ return document.id # type: ignore
171
176
 
172
177
  async def insert_documents_batch(
173
- self, doc_ids: list[str], texts: list[str], metadatas: list[dict]
178
+ self,
179
+ doc_ids: list[str],
180
+ texts: list[str],
181
+ metadatas: list[dict],
174
182
  ) -> list[int]:
175
183
  """Batch insert documents and return their integer IDs.
176
184
 
@@ -181,44 +189,44 @@ class DocumentStorage:
181
189
 
182
190
  Returns:
183
191
  list[int]: List of integer IDs of the inserted documents.
192
+
184
193
  """
185
194
  assert self.engine is not None, "Database connection is not initialized."
186
195
 
187
- async with self.get_session() as session:
188
- async with session.begin():
189
- import json
190
-
191
- documents = []
192
- for doc_id, text, metadata in zip(doc_ids, texts, metadatas):
193
- document = Document(
194
- doc_id=doc_id,
195
- text=text,
196
- metadata_=json.dumps(metadata),
197
- created_at=datetime.now(),
198
- updated_at=datetime.now(),
199
- )
200
- documents.append(document)
201
- session.add(document)
202
-
203
- await session.flush() # Flush to get all IDs
204
- return [doc.id for doc in documents] # type: ignore
196
+ async with self.get_session() as session, session.begin():
197
+ import json
198
+
199
+ documents = []
200
+ for doc_id, text, metadata in zip(doc_ids, texts, metadatas):
201
+ document = Document(
202
+ doc_id=doc_id,
203
+ text=text,
204
+ metadata_=json.dumps(metadata),
205
+ created_at=datetime.now(),
206
+ updated_at=datetime.now(),
207
+ )
208
+ documents.append(document)
209
+ session.add(document)
210
+
211
+ await session.flush() # Flush to get all IDs
212
+ return [doc.id for doc in documents] # type: ignore
205
213
 
206
214
  async def delete_document_by_doc_id(self, doc_id: str):
207
215
  """Delete a document by its doc_id.
208
216
 
209
217
  Args:
210
218
  doc_id (str): The doc_id of the document to delete.
219
+
211
220
  """
212
221
  assert self.engine is not None, "Database connection is not initialized."
213
222
 
214
- async with self.get_session() as session:
215
- async with session.begin():
216
- query = select(Document).where(col(Document.doc_id) == doc_id)
217
- result = await session.execute(query)
218
- document = result.scalar_one_or_none()
223
+ async with self.get_session() as session, session.begin():
224
+ query = select(Document).where(col(Document.doc_id) == doc_id)
225
+ result = await session.execute(query)
226
+ document = result.scalar_one_or_none()
219
227
 
220
- if document:
221
- await session.delete(document)
228
+ if document:
229
+ await session.delete(document)
222
230
 
223
231
  async def get_document_by_doc_id(self, doc_id: str):
224
232
  """Retrieve a document by its doc_id.
@@ -228,6 +236,7 @@ class DocumentStorage:
228
236
 
229
237
  Returns:
230
238
  dict: The document data or None if not found.
239
+
231
240
  """
232
241
  assert self.engine is not None, "Database connection is not initialized."
233
242
 
@@ -246,46 +255,46 @@ class DocumentStorage:
246
255
  Args:
247
256
  doc_id (str): The doc_id.
248
257
  new_text (str): The new text to update the document with.
258
+
249
259
  """
250
260
  assert self.engine is not None, "Database connection is not initialized."
251
261
 
252
- async with self.get_session() as session:
253
- async with session.begin():
254
- query = select(Document).where(col(Document.doc_id) == doc_id)
255
- result = await session.execute(query)
256
- document = result.scalar_one_or_none()
262
+ async with self.get_session() as session, session.begin():
263
+ query = select(Document).where(col(Document.doc_id) == doc_id)
264
+ result = await session.execute(query)
265
+ document = result.scalar_one_or_none()
257
266
 
258
- if document:
259
- document.text = new_text
260
- document.updated_at = datetime.now()
261
- session.add(document)
267
+ if document:
268
+ document.text = new_text
269
+ document.updated_at = datetime.now()
270
+ session.add(document)
262
271
 
263
272
  async def delete_documents(self, metadata_filters: dict):
264
273
  """Delete documents by their metadata filters.
265
274
 
266
275
  Args:
267
276
  metadata_filters (dict): The metadata filters to apply.
277
+
268
278
  """
269
279
  if self.engine is None:
270
280
  logger.warning(
271
- "Database connection is not initialized, skipping delete operation"
281
+ "Database connection is not initialized, skipping delete operation",
272
282
  )
273
283
  return
274
284
 
275
- async with self.get_session() as session:
276
- async with session.begin():
277
- query = select(Document)
285
+ async with self.get_session() as session, session.begin():
286
+ query = select(Document)
278
287
 
279
- for key, val in metadata_filters.items():
280
- query = query.where(
281
- text(f"json_extract(metadata, '$.{key}') = :filter_{key}")
282
- ).params(**{f"filter_{key}": val})
288
+ for key, val in metadata_filters.items():
289
+ query = query.where(
290
+ text(f"json_extract(metadata, '$.{key}') = :filter_{key}"),
291
+ ).params(**{f"filter_{key}": val})
283
292
 
284
- result = await session.execute(query)
285
- documents = result.scalars().all()
293
+ result = await session.execute(query)
294
+ documents = result.scalars().all()
286
295
 
287
- for doc in documents:
288
- await session.delete(doc)
296
+ for doc in documents:
297
+ await session.delete(doc)
289
298
 
290
299
  async def count_documents(self, metadata_filters: dict | None = None) -> int:
291
300
  """Count documents in the database.
@@ -295,6 +304,7 @@ class DocumentStorage:
295
304
 
296
305
  Returns:
297
306
  int: The count of documents.
307
+
298
308
  """
299
309
  if self.engine is None:
300
310
  logger.warning("Database connection is not initialized, returning 0")
@@ -306,7 +316,7 @@ class DocumentStorage:
306
316
  if metadata_filters:
307
317
  for key, val in metadata_filters.items():
308
318
  query = query.where(
309
- text(f"json_extract(metadata, '$.{key}') = :filter_{key}")
319
+ text(f"json_extract(metadata, '$.{key}') = :filter_{key}"),
310
320
  ).params(**{f"filter_{key}": val})
311
321
 
312
322
  result = await session.execute(query)
@@ -318,12 +328,13 @@ class DocumentStorage:
318
328
 
319
329
  Returns:
320
330
  list: A list of user IDs.
331
+
321
332
  """
322
333
  assert self.engine is not None, "Database connection is not initialized."
323
334
 
324
335
  async with self.get_session() as session:
325
336
  query = text(
326
- "SELECT DISTINCT user_id FROM documents WHERE user_id IS NOT NULL"
337
+ "SELECT DISTINCT user_id FROM documents WHERE user_id IS NOT NULL",
327
338
  )
328
339
  result = await session.execute(query)
329
340
  rows = result.fetchall()
@@ -337,6 +348,7 @@ class DocumentStorage:
337
348
 
338
349
  Returns:
339
350
  dict: The converted dictionary.
351
+
340
352
  """
341
353
  return {
342
354
  "id": document.id,
@@ -361,6 +373,7 @@ class DocumentStorage:
361
373
  dict: The converted dictionary.
362
374
 
363
375
  Note: This method is kept for backward compatibility but is no longer used internally.
376
+
364
377
  """
365
378
  return {
366
379
  "id": row[0],
@@ -2,9 +2,10 @@ try:
2
2
  import faiss
3
3
  except ModuleNotFoundError:
4
4
  raise ImportError(
5
- "faiss 未安装。请使用 'pip install faiss-cpu' 或 'pip install faiss-gpu' 安装。"
5
+ "faiss 未安装。请使用 'pip install faiss-cpu' 或 'pip install faiss-gpu' 安装。",
6
6
  )
7
7
  import os
8
+
8
9
  import numpy as np
9
10
 
10
11
 
@@ -27,11 +28,12 @@ class EmbeddingStorage:
27
28
  id (int): 向量的ID
28
29
  Raises:
29
30
  ValueError: 如果向量的维度与存储的维度不匹配
31
+
30
32
  """
31
33
  assert self.index is not None, "FAISS index is not initialized."
32
34
  if vector.shape[0] != self.dimension:
33
35
  raise ValueError(
34
- f"向量维度不匹配, 期望: {self.dimension}, 实际: {vector.shape[0]}"
36
+ f"向量维度不匹配, 期望: {self.dimension}, 实际: {vector.shape[0]}",
35
37
  )
36
38
  self.index.add_with_ids(vector.reshape(1, -1), np.array([id]))
37
39
  await self.save_index()
@@ -44,11 +46,12 @@ class EmbeddingStorage:
44
46
  ids (list[int]): 向量的ID列表
45
47
  Raises:
46
48
  ValueError: 如果向量的维度与存储的维度不匹配
49
+
47
50
  """
48
51
  assert self.index is not None, "FAISS index is not initialized."
49
52
  if vectors.shape[1] != self.dimension:
50
53
  raise ValueError(
51
- f"向量维度不匹配, 期望: {self.dimension}, 实际: {vectors.shape[1]}"
54
+ f"向量维度不匹配, 期望: {self.dimension}, 实际: {vectors.shape[1]}",
52
55
  )
53
56
  self.index.add_with_ids(vectors, np.array(ids))
54
57
  await self.save_index()
@@ -61,6 +64,7 @@ class EmbeddingStorage:
61
64
  k (int): 返回的最相似向量的数量
62
65
  Returns:
63
66
  tuple: (距离, 索引)
67
+
64
68
  """
65
69
  assert self.index is not None, "FAISS index is not initialized."
66
70
  faiss.normalize_L2(vector)
@@ -72,6 +76,7 @@ class EmbeddingStorage:
72
76
 
73
77
  Args:
74
78
  ids (list[int]): 要删除的向量ID列表
79
+
75
80
  """
76
81
  assert self.index is not None, "FAISS index is not initialized."
77
82
  id_array = np.array(ids, dtype=np.int64)
@@ -83,5 +88,6 @@ class EmbeddingStorage:
83
88
 
84
89
  Args:
85
90
  path (str): 保存索引的路径
91
+
86
92
  """
87
93
  faiss.write_index(self.index, self.path)
@@ -1,18 +1,18 @@
1
- import uuid
2
1
  import time
2
+ import uuid
3
+
3
4
  import numpy as np
5
+
6
+ from astrbot import logger
7
+ from astrbot.core.provider.provider import EmbeddingProvider, RerankProvider
8
+
9
+ from ..base import BaseVecDB, Result
4
10
  from .document_storage import DocumentStorage
5
11
  from .embedding_storage import EmbeddingStorage
6
- from ..base import Result, BaseVecDB
7
- from astrbot.core.provider.provider import EmbeddingProvider
8
- from astrbot.core.provider.provider import RerankProvider
9
- from astrbot import logger
10
12
 
11
13
 
12
14
  class FaissVecDB(BaseVecDB):
13
- """
14
- A class to represent a vector database.
15
- """
15
+ """A class to represent a vector database."""
16
16
 
17
17
  def __init__(
18
18
  self,
@@ -26,7 +26,8 @@ class FaissVecDB(BaseVecDB):
26
26
  self.embedding_provider = embedding_provider
27
27
  self.document_storage = DocumentStorage(doc_store_path)
28
28
  self.embedding_storage = EmbeddingStorage(
29
- embedding_provider.get_dim(), index_store_path
29
+ embedding_provider.get_dim(),
30
+ index_store_path,
30
31
  )
31
32
  self.embedding_provider = embedding_provider
32
33
  self.rerank_provider = rerank_provider
@@ -35,11 +36,12 @@ class FaissVecDB(BaseVecDB):
35
36
  await self.document_storage.initialize()
36
37
 
37
38
  async def insert(
38
- self, content: str, metadata: dict | None = None, id: str | None = None
39
+ self,
40
+ content: str,
41
+ metadata: dict | None = None,
42
+ id: str | None = None,
39
43
  ) -> int:
40
- """
41
- 插入一条文本和其对应向量,自动生成 ID 并保持一致性。
42
- """
44
+ """插入一条文本和其对应向量,自动生成 ID 并保持一致性。"""
43
45
  metadata = metadata or {}
44
46
  str_id = id or str(uuid.uuid4()) # 使用 UUID 作为原始 ID
45
47
 
@@ -63,11 +65,11 @@ class FaissVecDB(BaseVecDB):
63
65
  max_retries: int = 3,
64
66
  progress_callback=None,
65
67
  ) -> list[int]:
66
- """
67
- 批量插入文本和其对应向量,自动生成 ID 并保持一致性。
68
+ """批量插入文本和其对应向量,自动生成 ID 并保持一致性。
68
69
 
69
70
  Args:
70
71
  progress_callback: 进度回调函数,接收参数 (current, total)
72
+
71
73
  """
72
74
  metadatas = metadatas or [{} for _ in contents]
73
75
  ids = ids or [str(uuid.uuid4()) for _ in contents]
@@ -83,12 +85,14 @@ class FaissVecDB(BaseVecDB):
83
85
  )
84
86
  end = time.time()
85
87
  logger.debug(
86
- f"Generated embeddings for {len(contents)} contents in {end - start:.2f} seconds."
88
+ f"Generated embeddings for {len(contents)} contents in {end - start:.2f} seconds.",
87
89
  )
88
90
 
89
91
  # 使用 DocumentStorage 的批量插入方法
90
92
  int_ids = await self.document_storage.insert_documents_batch(
91
- ids, contents, metadatas
93
+ ids,
94
+ contents,
95
+ metadatas,
92
96
  )
93
97
 
94
98
  # 批量插入向量到 FAISS
@@ -104,8 +108,7 @@ class FaissVecDB(BaseVecDB):
104
108
  rerank: bool = False,
105
109
  metadata_filters: dict | None = None,
106
110
  ) -> list[Result]:
107
- """
108
- 搜索最相似的文档。
111
+ """搜索最相似的文档。
109
112
 
110
113
  Args:
111
114
  query (str): 查询文本
@@ -116,6 +119,7 @@ class FaissVecDB(BaseVecDB):
116
119
 
117
120
  Returns:
118
121
  List[Result]: 查询结果
122
+
119
123
  """
120
124
  embedding = await self.embedding_provider.get_embedding(query)
121
125
  scores, indices = await self.embedding_storage.search(
@@ -128,7 +132,8 @@ class FaissVecDB(BaseVecDB):
128
132
  scores[0] = 1.0 - (scores[0] / 2.0)
129
133
  # NOTE: maybe the size is less than k.
130
134
  fetched_docs = await self.document_storage.get_documents(
131
- metadata_filters=metadata_filters or {}, ids=indices[0]
135
+ metadata_filters=metadata_filters or {},
136
+ ids=indices[0],
132
137
  )
133
138
  if not fetched_docs:
134
139
  return []
@@ -149,7 +154,9 @@ class FaissVecDB(BaseVecDB):
149
154
  documents = [doc.data["text"] for doc in top_k_results]
150
155
  reranked_results = await self.rerank_provider.rerank(query, documents)
151
156
  reranked_results = sorted(
152
- reranked_results, key=lambda x: x.relevance_score, reverse=True
157
+ reranked_results,
158
+ key=lambda x: x.relevance_score,
159
+ reverse=True,
153
160
  )
154
161
  top_k_results = [
155
162
  top_k_results[reranked_result.index]
@@ -159,9 +166,7 @@ class FaissVecDB(BaseVecDB):
159
166
  return top_k_results
160
167
 
161
168
  async def delete(self, doc_id: str):
162
- """
163
- 删除一条文档块(chunk)
164
- """
169
+ """删除一条文档块(chunk)"""
165
170
  # 获得对应的 int id
166
171
  result = await self.document_storage.get_document_by_doc_id(doc_id)
167
172
  int_id = result["id"] if result else None
@@ -176,23 +181,23 @@ class FaissVecDB(BaseVecDB):
176
181
  await self.document_storage.close()
177
182
 
178
183
  async def count_documents(self, metadata_filter: dict | None = None) -> int:
179
- """
180
- 计算文档数量
184
+ """计算文档数量
181
185
 
182
186
  Args:
183
187
  metadata_filter (dict | None): 元数据过滤器
188
+
184
189
  """
185
190
  count = await self.document_storage.count_documents(
186
- metadata_filters=metadata_filter or {}
191
+ metadata_filters=metadata_filter or {},
187
192
  )
188
193
  return count
189
194
 
190
195
  async def delete_documents(self, metadata_filters: dict):
191
- """
192
- 根据元数据过滤器删除文档
193
- """
196
+ """根据元数据过滤器删除文档"""
194
197
  docs = await self.document_storage.get_documents(
195
- metadata_filters=metadata_filters, offset=None, limit=None
198
+ metadata_filters=metadata_filters,
199
+ offset=None,
200
+ limit=None,
196
201
  )
197
202
  doc_ids: list[int] = [doc["id"] for doc in docs]
198
203
  await self.embedding_storage.delete(doc_ids)
astrbot/core/event_bus.py CHANGED
@@ -1,5 +1,4 @@
1
- """
2
- 事件总线, 用于处理事件的分发和处理
1
+ """事件总线, 用于处理事件的分发和处理
3
2
  事件总线是一个异步队列, 用于接收各种消息事件, 并将其发送到Scheduler调度器进行处理
4
3
  其中包含了一个无限循环的调度函数, 用于从事件队列中获取新的事件, 并创建一个新的异步任务来执行管道调度器的处理逻辑
5
4
 
@@ -13,10 +12,12 @@ class:
13
12
 
14
13
  import asyncio
15
14
  from asyncio import Queue
16
- from astrbot.core.pipeline.scheduler import PipelineScheduler
15
+
17
16
  from astrbot.core import logger
18
- from .platform import AstrMessageEvent
19
17
  from astrbot.core.astrbot_config_mgr import AstrBotConfigManager
18
+ from astrbot.core.pipeline.scheduler import PipelineScheduler
19
+
20
+ from .platform import AstrMessageEvent
20
21
 
21
22
 
22
23
  class EventBus:
@@ -46,14 +47,15 @@ class EventBus:
46
47
 
47
48
  Args:
48
49
  event (AstrMessageEvent): 事件对象
50
+
49
51
  """
50
52
  # 如果有发送者名称: [平台名] 发送者名称/发送者ID: 消息概要
51
53
  if event.get_sender_name():
52
54
  logger.info(
53
- f"[{conf_name}] [{event.get_platform_id()}({event.get_platform_name()})] {event.get_sender_name()}/{event.get_sender_id()}: {event.get_message_outline()}"
55
+ f"[{conf_name}] [{event.get_platform_id()}({event.get_platform_name()})] {event.get_sender_name()}/{event.get_sender_id()}: {event.get_message_outline()}",
54
56
  )
55
57
  # 没有发送者名称: [平台名] 发送者ID: 消息概要
56
58
  else:
57
59
  logger.info(
58
- f"[{conf_name}] [{event.get_platform_id()}({event.get_platform_name()})] {event.get_sender_id()}: {event.get_message_outline()}"
60
+ f"[{conf_name}] [{event.get_platform_id()}({event.get_platform_name()})] {event.get_sender_id()}: {event.get_message_outline()}",
59
61
  )
@@ -1,9 +1,9 @@
1
1
  import asyncio
2
2
  import os
3
- import uuid
4
- import time
5
- from urllib.parse import urlparse, unquote
6
3
  import platform
4
+ import time
5
+ import uuid
6
+ from urllib.parse import unquote, urlparse
7
7
 
8
8
 
9
9
  class FileTokenService:
@@ -40,8 +40,8 @@ class FileTokenService:
40
40
 
41
41
  Raises:
42
42
  FileNotFoundError: 当路径不存在时抛出
43
- """
44
43
 
44
+ """
45
45
  # 处理 file:///
46
46
  try:
47
47
  parsed_uri = urlparse(file_path)
@@ -61,7 +61,7 @@ class FileTokenService:
61
61
 
62
62
  if not os.path.exists(local_path):
63
63
  raise FileNotFoundError(
64
- f"文件不存在: {local_path} (原始输入: {file_path})"
64
+ f"文件不存在: {local_path} (原始输入: {file_path})",
65
65
  )
66
66
 
67
67
  file_token = str(uuid.uuid4())
@@ -84,6 +84,7 @@ class FileTokenService:
84
84
  Raises:
85
85
  KeyError: 当令牌不存在或已过期时抛出
86
86
  FileNotFoundError: 当文件本身已被删除时抛出
87
+
87
88
  """
88
89
  async with self.lock:
89
90
  await self._cleanup_expired_tokens()
@@ -1,5 +1,4 @@
1
- """
2
- AstrBot 启动器,负责初始化和启动核心组件和仪表板服务器。
1
+ """AstrBot 启动器,负责初始化和启动核心组件和仪表板服务器。
3
2
 
4
3
  工作流程:
5
4
  1. 初始化核心生命周期, 传递数据库和日志代理实例到核心生命周期
@@ -8,10 +7,10 @@ AstrBot 启动器,负责初始化和启动核心组件和仪表板服务器。
8
7
 
9
8
  import asyncio
10
9
  import traceback
11
- from astrbot.core import logger
10
+
11
+ from astrbot.core import LogBroker, logger
12
12
  from astrbot.core.core_lifecycle import AstrBotCoreLifecycle
13
13
  from astrbot.core.db import BaseDatabase
14
- from astrbot.core import LogBroker
15
14
  from astrbot.dashboard.server import AstrBotDashboard
16
15
 
17
16
 
@@ -39,7 +38,10 @@ class InitialLoader:
39
38
  webui_dir = self.webui_dir
40
39
 
41
40
  self.dashboard_server = AstrBotDashboard(
42
- core_lifecycle, self.db, core_lifecycle.dashboard_shutdown_event, webui_dir
41
+ core_lifecycle,
42
+ self.db,
43
+ core_lifecycle.dashboard_shutdown_event,
44
+ webui_dir,
43
45
  )
44
46
 
45
47
  coro = self.dashboard_server.run()
@@ -1,6 +1,4 @@
1
- """
2
- 文档分块模块
3
- """
1
+ """文档分块模块"""
4
2
 
5
3
  from .base import BaseChunker
6
4
  from .fixed_size import FixedSizeChunker
@@ -21,4 +21,5 @@ class BaseChunker(ABC):
21
21
 
22
22
  Returns:
23
23
  list[str]: 分块后的文本列表
24
+
24
25
  """