AstrBot 3.5.6__py3-none-any.whl → 4.7.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 (288) hide show
  1. astrbot/api/__init__.py +16 -4
  2. astrbot/api/all.py +2 -1
  3. astrbot/api/event/__init__.py +5 -6
  4. astrbot/api/event/filter/__init__.py +37 -34
  5. astrbot/api/platform/__init__.py +7 -8
  6. astrbot/api/provider/__init__.py +8 -7
  7. astrbot/api/star/__init__.py +3 -4
  8. astrbot/api/util/__init__.py +2 -2
  9. astrbot/cli/__init__.py +1 -0
  10. astrbot/cli/__main__.py +18 -197
  11. astrbot/cli/commands/__init__.py +6 -0
  12. astrbot/cli/commands/cmd_conf.py +209 -0
  13. astrbot/cli/commands/cmd_init.py +56 -0
  14. astrbot/cli/commands/cmd_plug.py +245 -0
  15. astrbot/cli/commands/cmd_run.py +62 -0
  16. astrbot/cli/utils/__init__.py +18 -0
  17. astrbot/cli/utils/basic.py +76 -0
  18. astrbot/cli/utils/plugin.py +246 -0
  19. astrbot/cli/utils/version_comparator.py +90 -0
  20. astrbot/core/__init__.py +17 -19
  21. astrbot/core/agent/agent.py +14 -0
  22. astrbot/core/agent/handoff.py +38 -0
  23. astrbot/core/agent/hooks.py +30 -0
  24. astrbot/core/agent/mcp_client.py +385 -0
  25. astrbot/core/agent/message.py +175 -0
  26. astrbot/core/agent/response.py +14 -0
  27. astrbot/core/agent/run_context.py +22 -0
  28. astrbot/core/agent/runners/__init__.py +3 -0
  29. astrbot/core/agent/runners/base.py +65 -0
  30. astrbot/core/agent/runners/coze/coze_agent_runner.py +367 -0
  31. astrbot/core/agent/runners/coze/coze_api_client.py +324 -0
  32. astrbot/core/agent/runners/dashscope/dashscope_agent_runner.py +403 -0
  33. astrbot/core/agent/runners/dify/dify_agent_runner.py +336 -0
  34. astrbot/core/agent/runners/dify/dify_api_client.py +195 -0
  35. astrbot/core/agent/runners/tool_loop_agent_runner.py +400 -0
  36. astrbot/core/agent/tool.py +285 -0
  37. astrbot/core/agent/tool_executor.py +17 -0
  38. astrbot/core/astr_agent_context.py +19 -0
  39. astrbot/core/astr_agent_hooks.py +36 -0
  40. astrbot/core/astr_agent_run_util.py +80 -0
  41. astrbot/core/astr_agent_tool_exec.py +246 -0
  42. astrbot/core/astrbot_config_mgr.py +275 -0
  43. astrbot/core/config/__init__.py +2 -2
  44. astrbot/core/config/astrbot_config.py +60 -20
  45. astrbot/core/config/default.py +1972 -453
  46. astrbot/core/config/i18n_utils.py +110 -0
  47. astrbot/core/conversation_mgr.py +285 -75
  48. astrbot/core/core_lifecycle.py +167 -62
  49. astrbot/core/db/__init__.py +305 -102
  50. astrbot/core/db/migration/helper.py +69 -0
  51. astrbot/core/db/migration/migra_3_to_4.py +357 -0
  52. astrbot/core/db/migration/migra_45_to_46.py +44 -0
  53. astrbot/core/db/migration/migra_webchat_session.py +131 -0
  54. astrbot/core/db/migration/shared_preferences_v3.py +48 -0
  55. astrbot/core/db/migration/sqlite_v3.py +497 -0
  56. astrbot/core/db/po.py +259 -55
  57. astrbot/core/db/sqlite.py +773 -528
  58. astrbot/core/db/vec_db/base.py +73 -0
  59. astrbot/core/db/vec_db/faiss_impl/__init__.py +3 -0
  60. astrbot/core/db/vec_db/faiss_impl/document_storage.py +392 -0
  61. astrbot/core/db/vec_db/faiss_impl/embedding_storage.py +93 -0
  62. astrbot/core/db/vec_db/faiss_impl/sqlite_init.sql +17 -0
  63. astrbot/core/db/vec_db/faiss_impl/vec_db.py +204 -0
  64. astrbot/core/event_bus.py +26 -22
  65. astrbot/core/exceptions.py +9 -0
  66. astrbot/core/file_token_service.py +98 -0
  67. astrbot/core/initial_loader.py +19 -10
  68. astrbot/core/knowledge_base/chunking/__init__.py +9 -0
  69. astrbot/core/knowledge_base/chunking/base.py +25 -0
  70. astrbot/core/knowledge_base/chunking/fixed_size.py +59 -0
  71. astrbot/core/knowledge_base/chunking/recursive.py +161 -0
  72. astrbot/core/knowledge_base/kb_db_sqlite.py +301 -0
  73. astrbot/core/knowledge_base/kb_helper.py +642 -0
  74. astrbot/core/knowledge_base/kb_mgr.py +330 -0
  75. astrbot/core/knowledge_base/models.py +120 -0
  76. astrbot/core/knowledge_base/parsers/__init__.py +13 -0
  77. astrbot/core/knowledge_base/parsers/base.py +51 -0
  78. astrbot/core/knowledge_base/parsers/markitdown_parser.py +26 -0
  79. astrbot/core/knowledge_base/parsers/pdf_parser.py +101 -0
  80. astrbot/core/knowledge_base/parsers/text_parser.py +42 -0
  81. astrbot/core/knowledge_base/parsers/url_parser.py +103 -0
  82. astrbot/core/knowledge_base/parsers/util.py +13 -0
  83. astrbot/core/knowledge_base/prompts.py +65 -0
  84. astrbot/core/knowledge_base/retrieval/__init__.py +14 -0
  85. astrbot/core/knowledge_base/retrieval/hit_stopwords.txt +767 -0
  86. astrbot/core/knowledge_base/retrieval/manager.py +276 -0
  87. astrbot/core/knowledge_base/retrieval/rank_fusion.py +142 -0
  88. astrbot/core/knowledge_base/retrieval/sparse_retriever.py +136 -0
  89. astrbot/core/log.py +21 -15
  90. astrbot/core/message/components.py +413 -287
  91. astrbot/core/message/message_event_result.py +35 -24
  92. astrbot/core/persona_mgr.py +192 -0
  93. astrbot/core/pipeline/__init__.py +14 -14
  94. astrbot/core/pipeline/content_safety_check/stage.py +13 -9
  95. astrbot/core/pipeline/content_safety_check/strategies/__init__.py +1 -2
  96. astrbot/core/pipeline/content_safety_check/strategies/baidu_aip.py +13 -14
  97. astrbot/core/pipeline/content_safety_check/strategies/keywords.py +2 -1
  98. astrbot/core/pipeline/content_safety_check/strategies/strategy.py +6 -6
  99. astrbot/core/pipeline/context.py +7 -1
  100. astrbot/core/pipeline/context_utils.py +107 -0
  101. astrbot/core/pipeline/preprocess_stage/stage.py +63 -36
  102. astrbot/core/pipeline/process_stage/method/agent_request.py +48 -0
  103. astrbot/core/pipeline/process_stage/method/agent_sub_stages/internal.py +464 -0
  104. astrbot/core/pipeline/process_stage/method/agent_sub_stages/third_party.py +202 -0
  105. astrbot/core/pipeline/process_stage/method/star_request.py +26 -32
  106. astrbot/core/pipeline/process_stage/stage.py +21 -15
  107. astrbot/core/pipeline/process_stage/utils.py +125 -0
  108. astrbot/core/pipeline/rate_limit_check/stage.py +34 -36
  109. astrbot/core/pipeline/respond/stage.py +142 -101
  110. astrbot/core/pipeline/result_decorate/stage.py +124 -57
  111. astrbot/core/pipeline/scheduler.py +21 -16
  112. astrbot/core/pipeline/session_status_check/stage.py +37 -0
  113. astrbot/core/pipeline/stage.py +11 -76
  114. astrbot/core/pipeline/waking_check/stage.py +69 -33
  115. astrbot/core/pipeline/whitelist_check/stage.py +10 -7
  116. astrbot/core/platform/__init__.py +6 -6
  117. astrbot/core/platform/astr_message_event.py +107 -129
  118. astrbot/core/platform/astrbot_message.py +32 -12
  119. astrbot/core/platform/manager.py +62 -18
  120. astrbot/core/platform/message_session.py +30 -0
  121. astrbot/core/platform/platform.py +16 -24
  122. astrbot/core/platform/platform_metadata.py +9 -4
  123. astrbot/core/platform/register.py +12 -7
  124. astrbot/core/platform/sources/aiocqhttp/aiocqhttp_message_event.py +136 -60
  125. astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py +126 -46
  126. astrbot/core/platform/sources/dingtalk/dingtalk_adapter.py +63 -31
  127. astrbot/core/platform/sources/dingtalk/dingtalk_event.py +30 -26
  128. astrbot/core/platform/sources/discord/client.py +129 -0
  129. astrbot/core/platform/sources/discord/components.py +139 -0
  130. astrbot/core/platform/sources/discord/discord_platform_adapter.py +473 -0
  131. astrbot/core/platform/sources/discord/discord_platform_event.py +313 -0
  132. astrbot/core/platform/sources/lark/lark_adapter.py +27 -18
  133. astrbot/core/platform/sources/lark/lark_event.py +39 -13
  134. astrbot/core/platform/sources/misskey/misskey_adapter.py +770 -0
  135. astrbot/core/platform/sources/misskey/misskey_api.py +964 -0
  136. astrbot/core/platform/sources/misskey/misskey_event.py +163 -0
  137. astrbot/core/platform/sources/misskey/misskey_utils.py +550 -0
  138. astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py +149 -33
  139. astrbot/core/platform/sources/qqofficial/qqofficial_platform_adapter.py +41 -26
  140. astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_adapter.py +36 -17
  141. astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_event.py +3 -1
  142. astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_server.py +14 -8
  143. astrbot/core/platform/sources/satori/satori_adapter.py +792 -0
  144. astrbot/core/platform/sources/satori/satori_event.py +432 -0
  145. astrbot/core/platform/sources/slack/client.py +164 -0
  146. astrbot/core/platform/sources/slack/slack_adapter.py +416 -0
  147. astrbot/core/platform/sources/slack/slack_event.py +253 -0
  148. astrbot/core/platform/sources/telegram/tg_adapter.py +100 -43
  149. astrbot/core/platform/sources/telegram/tg_event.py +136 -36
  150. astrbot/core/platform/sources/webchat/webchat_adapter.py +72 -22
  151. astrbot/core/platform/sources/webchat/webchat_event.py +46 -22
  152. astrbot/core/platform/sources/webchat/webchat_queue_mgr.py +35 -0
  153. astrbot/core/platform/sources/wechatpadpro/wechatpadpro_adapter.py +926 -0
  154. astrbot/core/platform/sources/wechatpadpro/wechatpadpro_message_event.py +178 -0
  155. astrbot/core/platform/sources/wechatpadpro/xml_data_parser.py +159 -0
  156. astrbot/core/platform/sources/wecom/wecom_adapter.py +169 -27
  157. astrbot/core/platform/sources/wecom/wecom_event.py +162 -77
  158. astrbot/core/platform/sources/wecom/wecom_kf.py +279 -0
  159. astrbot/core/platform/sources/wecom/wecom_kf_message.py +196 -0
  160. astrbot/core/platform/sources/wecom_ai_bot/WXBizJsonMsgCrypt.py +297 -0
  161. astrbot/core/platform/sources/wecom_ai_bot/__init__.py +15 -0
  162. astrbot/core/platform/sources/wecom_ai_bot/ierror.py +19 -0
  163. astrbot/core/platform/sources/wecom_ai_bot/wecomai_adapter.py +472 -0
  164. astrbot/core/platform/sources/wecom_ai_bot/wecomai_api.py +417 -0
  165. astrbot/core/platform/sources/wecom_ai_bot/wecomai_event.py +152 -0
  166. astrbot/core/platform/sources/wecom_ai_bot/wecomai_queue_mgr.py +153 -0
  167. astrbot/core/platform/sources/wecom_ai_bot/wecomai_server.py +168 -0
  168. astrbot/core/platform/sources/wecom_ai_bot/wecomai_utils.py +209 -0
  169. astrbot/core/platform/sources/weixin_official_account/weixin_offacc_adapter.py +306 -0
  170. astrbot/core/platform/sources/weixin_official_account/weixin_offacc_event.py +186 -0
  171. astrbot/core/platform_message_history_mgr.py +49 -0
  172. astrbot/core/provider/__init__.py +2 -3
  173. astrbot/core/provider/entites.py +8 -8
  174. astrbot/core/provider/entities.py +154 -98
  175. astrbot/core/provider/func_tool_manager.py +446 -458
  176. astrbot/core/provider/manager.py +345 -207
  177. astrbot/core/provider/provider.py +188 -73
  178. astrbot/core/provider/register.py +9 -7
  179. astrbot/core/provider/sources/anthropic_source.py +295 -115
  180. astrbot/core/provider/sources/azure_tts_source.py +224 -0
  181. astrbot/core/provider/sources/bailian_rerank_source.py +236 -0
  182. astrbot/core/provider/sources/dashscope_tts.py +138 -14
  183. astrbot/core/provider/sources/edge_tts_source.py +24 -19
  184. astrbot/core/provider/sources/fishaudio_tts_api_source.py +58 -13
  185. astrbot/core/provider/sources/gemini_embedding_source.py +61 -0
  186. astrbot/core/provider/sources/gemini_source.py +310 -132
  187. astrbot/core/provider/sources/gemini_tts_source.py +81 -0
  188. astrbot/core/provider/sources/groq_source.py +15 -0
  189. astrbot/core/provider/sources/gsv_selfhosted_source.py +151 -0
  190. astrbot/core/provider/sources/gsvi_tts_source.py +14 -7
  191. astrbot/core/provider/sources/minimax_tts_api_source.py +159 -0
  192. astrbot/core/provider/sources/openai_embedding_source.py +40 -0
  193. astrbot/core/provider/sources/openai_source.py +241 -145
  194. astrbot/core/provider/sources/openai_tts_api_source.py +18 -7
  195. astrbot/core/provider/sources/sensevoice_selfhosted_source.py +13 -11
  196. astrbot/core/provider/sources/vllm_rerank_source.py +71 -0
  197. astrbot/core/provider/sources/volcengine_tts.py +115 -0
  198. astrbot/core/provider/sources/whisper_api_source.py +18 -13
  199. astrbot/core/provider/sources/whisper_selfhosted_source.py +19 -12
  200. astrbot/core/provider/sources/xinference_rerank_source.py +116 -0
  201. astrbot/core/provider/sources/xinference_stt_provider.py +197 -0
  202. astrbot/core/provider/sources/zhipu_source.py +6 -73
  203. astrbot/core/star/__init__.py +43 -11
  204. astrbot/core/star/config.py +17 -18
  205. astrbot/core/star/context.py +362 -138
  206. astrbot/core/star/filter/__init__.py +4 -3
  207. astrbot/core/star/filter/command.py +111 -35
  208. astrbot/core/star/filter/command_group.py +46 -34
  209. astrbot/core/star/filter/custom_filter.py +6 -5
  210. astrbot/core/star/filter/event_message_type.py +4 -2
  211. astrbot/core/star/filter/permission.py +4 -2
  212. astrbot/core/star/filter/platform_adapter_type.py +45 -12
  213. astrbot/core/star/filter/regex.py +4 -2
  214. astrbot/core/star/register/__init__.py +19 -15
  215. astrbot/core/star/register/star.py +41 -13
  216. astrbot/core/star/register/star_handler.py +236 -86
  217. astrbot/core/star/session_llm_manager.py +280 -0
  218. astrbot/core/star/session_plugin_manager.py +170 -0
  219. astrbot/core/star/star.py +36 -43
  220. astrbot/core/star/star_handler.py +47 -85
  221. astrbot/core/star/star_manager.py +442 -260
  222. astrbot/core/star/star_tools.py +167 -45
  223. astrbot/core/star/updator.py +17 -20
  224. astrbot/core/umop_config_router.py +106 -0
  225. astrbot/core/updator.py +38 -13
  226. astrbot/core/utils/astrbot_path.py +39 -0
  227. astrbot/core/utils/command_parser.py +1 -1
  228. astrbot/core/utils/io.py +119 -60
  229. astrbot/core/utils/log_pipe.py +1 -1
  230. astrbot/core/utils/metrics.py +11 -10
  231. astrbot/core/utils/migra_helper.py +73 -0
  232. astrbot/core/utils/path_util.py +63 -62
  233. astrbot/core/utils/pip_installer.py +37 -15
  234. astrbot/core/utils/session_lock.py +29 -0
  235. astrbot/core/utils/session_waiter.py +19 -20
  236. astrbot/core/utils/shared_preferences.py +174 -34
  237. astrbot/core/utils/t2i/__init__.py +4 -1
  238. astrbot/core/utils/t2i/local_strategy.py +386 -238
  239. astrbot/core/utils/t2i/network_strategy.py +109 -49
  240. astrbot/core/utils/t2i/renderer.py +29 -14
  241. astrbot/core/utils/t2i/template/astrbot_powershell.html +184 -0
  242. astrbot/core/utils/t2i/template_manager.py +111 -0
  243. astrbot/core/utils/tencent_record_helper.py +115 -1
  244. astrbot/core/utils/version_comparator.py +10 -13
  245. astrbot/core/zip_updator.py +112 -65
  246. astrbot/dashboard/routes/__init__.py +20 -13
  247. astrbot/dashboard/routes/auth.py +20 -9
  248. astrbot/dashboard/routes/chat.py +297 -141
  249. astrbot/dashboard/routes/config.py +652 -55
  250. astrbot/dashboard/routes/conversation.py +107 -37
  251. astrbot/dashboard/routes/file.py +26 -0
  252. astrbot/dashboard/routes/knowledge_base.py +1244 -0
  253. astrbot/dashboard/routes/log.py +27 -2
  254. astrbot/dashboard/routes/persona.py +202 -0
  255. astrbot/dashboard/routes/plugin.py +197 -139
  256. astrbot/dashboard/routes/route.py +27 -7
  257. astrbot/dashboard/routes/session_management.py +354 -0
  258. astrbot/dashboard/routes/stat.py +85 -18
  259. astrbot/dashboard/routes/static_file.py +5 -2
  260. astrbot/dashboard/routes/t2i.py +233 -0
  261. astrbot/dashboard/routes/tools.py +184 -120
  262. astrbot/dashboard/routes/update.py +59 -36
  263. astrbot/dashboard/server.py +96 -36
  264. astrbot/dashboard/utils.py +165 -0
  265. astrbot-4.7.0.dist-info/METADATA +294 -0
  266. astrbot-4.7.0.dist-info/RECORD +274 -0
  267. {astrbot-3.5.6.dist-info → astrbot-4.7.0.dist-info}/WHEEL +1 -1
  268. astrbot/core/db/plugin/sqlite_impl.py +0 -112
  269. astrbot/core/db/sqlite_init.sql +0 -50
  270. astrbot/core/pipeline/platform_compatibility/stage.py +0 -56
  271. astrbot/core/pipeline/process_stage/method/llm_request.py +0 -606
  272. astrbot/core/platform/sources/gewechat/client.py +0 -806
  273. astrbot/core/platform/sources/gewechat/downloader.py +0 -55
  274. astrbot/core/platform/sources/gewechat/gewechat_event.py +0 -255
  275. astrbot/core/platform/sources/gewechat/gewechat_platform_adapter.py +0 -103
  276. astrbot/core/platform/sources/gewechat/xml_data_parser.py +0 -110
  277. astrbot/core/provider/sources/dashscope_source.py +0 -203
  278. astrbot/core/provider/sources/dify_source.py +0 -281
  279. astrbot/core/provider/sources/llmtuner_source.py +0 -132
  280. astrbot/core/rag/embedding/openai_source.py +0 -20
  281. astrbot/core/rag/knowledge_db_mgr.py +0 -94
  282. astrbot/core/rag/store/__init__.py +0 -9
  283. astrbot/core/rag/store/chroma_db.py +0 -42
  284. astrbot/core/utils/dify_api_client.py +0 -152
  285. astrbot-3.5.6.dist-info/METADATA +0 -249
  286. astrbot-3.5.6.dist-info/RECORD +0 -158
  287. {astrbot-3.5.6.dist-info → astrbot-4.7.0.dist-info}/entry_points.txt +0 -0
  288. {astrbot-3.5.6.dist-info → astrbot-4.7.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,110 @@
1
+ """
2
+ 配置元数据国际化工具
3
+
4
+ 提供配置元数据的国际化键转换功能
5
+ """
6
+
7
+ from typing import Any
8
+
9
+
10
+ class ConfigMetadataI18n:
11
+ """配置元数据国际化转换器"""
12
+
13
+ @staticmethod
14
+ def _get_i18n_key(group: str, section: str, field: str, attr: str) -> str:
15
+ """
16
+ 生成国际化键
17
+
18
+ Args:
19
+ group: 配置组,如 'ai_group', 'platform_group'
20
+ section: 配置节,如 'agent_runner', 'general'
21
+ field: 字段名,如 'enable', 'default_provider'
22
+ attr: 属性类型,如 'description', 'hint', 'labels'
23
+
24
+ Returns:
25
+ 国际化键,格式如: 'ai_group.agent_runner.enable.description'
26
+ """
27
+ if field:
28
+ return f"{group}.{section}.{field}.{attr}"
29
+ else:
30
+ return f"{group}.{section}.{attr}"
31
+
32
+ @staticmethod
33
+ def convert_to_i18n_keys(metadata: dict[str, Any]) -> dict[str, Any]:
34
+ """
35
+ 将配置元数据转换为使用国际化键
36
+
37
+ Args:
38
+ metadata: 原始配置元数据字典
39
+
40
+ Returns:
41
+ 使用国际化键的配置元数据字典
42
+ """
43
+ result = {}
44
+
45
+ for group_key, group_data in metadata.items():
46
+ group_result = {
47
+ "name": f"{group_key}.name",
48
+ "metadata": {},
49
+ }
50
+
51
+ for section_key, section_data in group_data.get("metadata", {}).items():
52
+ section_result = {
53
+ "description": f"{group_key}.{section_key}.description",
54
+ "type": section_data.get("type"),
55
+ }
56
+
57
+ # 复制其他属性
58
+ for key in ["items", "condition", "_special", "invisible"]:
59
+ if key in section_data:
60
+ section_result[key] = section_data[key]
61
+
62
+ # 处理 hint
63
+ if "hint" in section_data:
64
+ section_result["hint"] = f"{group_key}.{section_key}.hint"
65
+
66
+ # 处理 items 中的字段
67
+ if "items" in section_data and isinstance(section_data["items"], dict):
68
+ items_result = {}
69
+ for field_key, field_data in section_data["items"].items():
70
+ # 处理嵌套的点号字段名(如 provider_settings.enable)
71
+ field_name = field_key
72
+
73
+ field_result = {}
74
+
75
+ # 复制基本属性
76
+ for attr in [
77
+ "type",
78
+ "condition",
79
+ "_special",
80
+ "invisible",
81
+ "options",
82
+ ]:
83
+ if attr in field_data:
84
+ field_result[attr] = field_data[attr]
85
+
86
+ # 转换文本属性为国际化键
87
+ if "description" in field_data:
88
+ field_result["description"] = (
89
+ f"{group_key}.{section_key}.{field_name}.description"
90
+ )
91
+
92
+ if "hint" in field_data:
93
+ field_result["hint"] = (
94
+ f"{group_key}.{section_key}.{field_name}.hint"
95
+ )
96
+
97
+ if "labels" in field_data:
98
+ field_result["labels"] = (
99
+ f"{group_key}.{section_key}.{field_name}.labels"
100
+ )
101
+
102
+ items_result[field_key] = field_result
103
+
104
+ section_result["items"] = items_result
105
+
106
+ group_result["metadata"][section_key] = section_result
107
+
108
+ result[group_key] = group_result
109
+
110
+ return result
@@ -1,56 +1,109 @@
1
- """
2
- AstrBot 会话-对话管理器, 维护两个本地存储, 其中一个是 json 格式的shared_preferences, 另外一个是数据库
1
+ """AstrBot 会话-对话管理器, 维护两个本地存储, 其中一个是 json 格式的shared_preferences, 另外一个是数据库.
3
2
 
4
3
  在 AstrBot 中, 会话和对话是独立的, 会话用于标记对话窗口, 例如群聊"123456789"可以建立一个会话,
5
4
  在一个会话中可以建立多个对话, 并且支持对话的切换和删除
6
5
  """
7
6
 
8
- import uuid
9
7
  import json
10
- import asyncio
8
+ from collections.abc import Awaitable, Callable
9
+
11
10
  from astrbot.core import sp
12
- from typing import Dict, List
11
+ from astrbot.core.agent.message import AssistantMessageSegment, UserMessageSegment
13
12
  from astrbot.core.db import BaseDatabase
14
- from astrbot.core.db.po import Conversation
13
+ from astrbot.core.db.po import Conversation, ConversationV2
15
14
 
16
15
 
17
16
  class ConversationManager:
18
17
  """负责管理会话与 LLM 的对话,某个会话当前正在用哪个对话。"""
19
18
 
20
19
  def __init__(self, db_helper: BaseDatabase):
21
- # session_conversations 字典记录会话ID-对话ID 映射关系
22
- self.session_conversations: Dict[str, str] = sp.get("session_conversation", {})
20
+ self.session_conversations: dict[str, str] = {}
23
21
  self.db = db_helper
24
22
  self.save_interval = 60 # 每 60 秒保存一次
25
- self._start_periodic_save()
26
23
 
27
- def _start_periodic_save(self):
28
- """启动定时保存任务"""
29
- asyncio.create_task(self._periodic_save())
24
+ # 会话删除回调函数列表(用于级联清理,如知识库配置)
25
+ self._on_session_deleted_callbacks: list[Callable[[str], Awaitable[None]]] = []
26
+
27
+ def register_on_session_deleted(
28
+ self,
29
+ callback: Callable[[str], Awaitable[None]],
30
+ ) -> None:
31
+ """注册会话删除回调函数.
32
+
33
+ 其他模块可以注册回调来响应会话删除事件,实现级联清理。
34
+ 例如:知识库模块可以注册回调来清理会话的知识库配置。
35
+
36
+ Args:
37
+ callback: 回调函数,接收会话ID (unified_msg_origin) 作为参数
38
+
39
+ """
40
+ self._on_session_deleted_callbacks.append(callback)
30
41
 
31
- async def _periodic_save(self):
32
- """定时保存会话对话映射关系到存储中"""
33
- while True:
34
- await asyncio.sleep(self.save_interval)
35
- self._save_to_storage()
42
+ async def _trigger_session_deleted(self, unified_msg_origin: str) -> None:
43
+ """触发会话删除回调.
36
44
 
37
- def _save_to_storage(self):
38
- """保存会话对话映射关系到存储中"""
39
- sp.put("session_conversation", self.session_conversations)
45
+ Args:
46
+ unified_msg_origin: 会话ID
40
47
 
41
- async def new_conversation(self, unified_msg_origin: str) -> str:
42
- """新建对话,并将当前会话的对话转移到新对话
48
+ """
49
+ for callback in self._on_session_deleted_callbacks:
50
+ try:
51
+ await callback(unified_msg_origin)
52
+ except Exception as e:
53
+ from astrbot.core import logger
54
+
55
+ logger.error(
56
+ f"会话删除回调执行失败 (session: {unified_msg_origin}): {e}",
57
+ )
58
+
59
+ def _convert_conv_from_v2_to_v1(self, conv_v2: ConversationV2) -> Conversation:
60
+ """将 ConversationV2 对象转换为 Conversation 对象"""
61
+ created_at = int(conv_v2.created_at.timestamp())
62
+ updated_at = int(conv_v2.updated_at.timestamp())
63
+ return Conversation(
64
+ platform_id=conv_v2.platform_id,
65
+ user_id=conv_v2.user_id,
66
+ cid=conv_v2.conversation_id,
67
+ history=json.dumps(conv_v2.content or []),
68
+ title=conv_v2.title,
69
+ persona_id=conv_v2.persona_id,
70
+ created_at=created_at,
71
+ updated_at=updated_at,
72
+ )
73
+
74
+ async def new_conversation(
75
+ self,
76
+ unified_msg_origin: str,
77
+ platform_id: str | None = None,
78
+ content: list[dict] | None = None,
79
+ title: str | None = None,
80
+ persona_id: str | None = None,
81
+ ) -> str:
82
+ """新建对话,并将当前会话的对话转移到新对话.
43
83
 
44
84
  Args:
45
85
  unified_msg_origin (str): 统一的消息来源字符串。格式为 platform_name:message_type:session_id
46
86
  Returns:
47
87
  conversation_id (str): 对话 ID, 是 uuid 格式的字符串
88
+
48
89
  """
49
- conversation_id = str(uuid.uuid4())
50
- self.db.new_conversation(user_id=unified_msg_origin, cid=conversation_id)
51
- self.session_conversations[unified_msg_origin] = conversation_id
52
- sp.put("session_conversation", self.session_conversations)
53
- return conversation_id
90
+ if not platform_id:
91
+ # 如果没有提供 platform_id,则从 unified_msg_origin 中解析
92
+ parts = unified_msg_origin.split(":")
93
+ if len(parts) >= 3:
94
+ platform_id = parts[0]
95
+ if not platform_id:
96
+ platform_id = "unknown"
97
+ conv = await self.db.create_conversation(
98
+ user_id=unified_msg_origin,
99
+ platform_id=platform_id,
100
+ content=content,
101
+ title=title,
102
+ persona_id=persona_id,
103
+ )
104
+ self.session_conversations[unified_msg_origin] = conv.conversation_id
105
+ await sp.session_put(unified_msg_origin, "sel_conv_id", conv.conversation_id)
106
+ return conv.conversation_id
54
107
 
55
108
  async def switch_conversation(self, unified_msg_origin: str, conversation_id: str):
56
109
  """切换会话的对话
@@ -58,137 +111,294 @@ class ConversationManager:
58
111
  Args:
59
112
  unified_msg_origin (str): 统一的消息来源字符串。格式为 platform_name:message_type:session_id
60
113
  conversation_id (str): 对话 ID, 是 uuid 格式的字符串
114
+
61
115
  """
62
116
  self.session_conversations[unified_msg_origin] = conversation_id
63
- sp.put("session_conversation", self.session_conversations)
117
+ await sp.session_put(unified_msg_origin, "sel_conv_id", conversation_id)
64
118
 
65
119
  async def delete_conversation(
66
- self, unified_msg_origin: str, conversation_id: str = None
120
+ self,
121
+ unified_msg_origin: str,
122
+ conversation_id: str | None = None,
67
123
  ):
68
124
  """删除会话的对话,当 conversation_id 为 None 时删除会话当前的对话
69
125
 
70
126
  Args:
71
127
  unified_msg_origin (str): 统一的消息来源字符串。格式为 platform_name:message_type:session_id
72
128
  conversation_id (str): 对话 ID, 是 uuid 格式的字符串
129
+
73
130
  """
74
- conversation_id = self.session_conversations.get(unified_msg_origin)
131
+ if not conversation_id:
132
+ conversation_id = self.session_conversations.get(unified_msg_origin)
75
133
  if conversation_id:
76
- self.db.delete_conversation(user_id=unified_msg_origin, cid=conversation_id)
77
- del self.session_conversations[unified_msg_origin]
78
- sp.put("session_conversation", self.session_conversations)
134
+ await self.db.delete_conversation(cid=conversation_id)
135
+ curr_cid = await self.get_curr_conversation_id(unified_msg_origin)
136
+ if curr_cid == conversation_id:
137
+ self.session_conversations.pop(unified_msg_origin, None)
138
+ await sp.session_remove(unified_msg_origin, "sel_conv_id")
139
+
140
+ async def delete_conversations_by_user_id(self, unified_msg_origin: str):
141
+ """删除会话的所有对话
142
+
143
+ Args:
144
+ unified_msg_origin (str): 统一的消息来源字符串。格式为 platform_name:message_type:session_id
79
145
 
80
- async def get_curr_conversation_id(self, unified_msg_origin: str) -> str:
146
+ """
147
+ await self.db.delete_conversations_by_user_id(user_id=unified_msg_origin)
148
+ self.session_conversations.pop(unified_msg_origin, None)
149
+ await sp.session_remove(unified_msg_origin, "sel_conv_id")
150
+
151
+ # 触发会话删除回调(级联清理)
152
+ await self._trigger_session_deleted(unified_msg_origin)
153
+
154
+ async def get_curr_conversation_id(self, unified_msg_origin: str) -> str | None:
81
155
  """获取会话当前的对话 ID
82
156
 
83
157
  Args:
84
158
  unified_msg_origin (str): 统一的消息来源字符串。格式为 platform_name:message_type:session_id
85
159
  Returns:
86
160
  conversation_id (str): 对话 ID, 是 uuid 格式的字符串
161
+
87
162
  """
88
- return self.session_conversations.get(unified_msg_origin, None)
163
+ ret = self.session_conversations.get(unified_msg_origin, None)
164
+ if not ret:
165
+ ret = await sp.session_get(unified_msg_origin, "sel_conv_id", None)
166
+ if ret:
167
+ self.session_conversations[unified_msg_origin] = ret
168
+ return ret
89
169
 
90
170
  async def get_conversation(
91
- self, unified_msg_origin: str, conversation_id: str
92
- ) -> Conversation:
93
- """获取会话的对话
171
+ self,
172
+ unified_msg_origin: str,
173
+ conversation_id: str,
174
+ create_if_not_exists: bool = False,
175
+ ) -> Conversation | None:
176
+ """获取会话的对话.
94
177
 
95
178
  Args:
96
179
  unified_msg_origin (str): 统一的消息来源字符串。格式为 platform_name:message_type:session_id
97
180
  conversation_id (str): 对话 ID, 是 uuid 格式的字符串
181
+ create_if_not_exists (bool): 如果对话不存在,是否创建一个新的对话
98
182
  Returns:
99
183
  conversation (Conversation): 对话对象
100
- """
101
- return self.db.get_conversation_by_user_id(unified_msg_origin, conversation_id)
102
184
 
103
- async def get_conversations(self, unified_msg_origin: str) -> List[Conversation]:
104
- """获取会话的所有对话
185
+ """
186
+ conv = await self.db.get_conversation_by_id(cid=conversation_id)
187
+ if not conv and create_if_not_exists:
188
+ # 如果对话不存在且需要创建,则新建一个对话
189
+ conversation_id = await self.new_conversation(unified_msg_origin)
190
+ conv = await self.db.get_conversation_by_id(cid=conversation_id)
191
+ conv_res = None
192
+ if conv:
193
+ conv_res = self._convert_conv_from_v2_to_v1(conv)
194
+ return conv_res
195
+
196
+ async def get_conversations(
197
+ self,
198
+ unified_msg_origin: str | None = None,
199
+ platform_id: str | None = None,
200
+ ) -> list[Conversation]:
201
+ """获取对话列表.
105
202
 
106
203
  Args:
107
- unified_msg_origin (str): 统一的消息来源字符串。格式为 platform_name:message_type:session_id
204
+ unified_msg_origin (str): 统一的消息来源字符串。格式为 platform_name:message_type:session_id,可选
205
+ platform_id (str): 平台 ID, 可选参数, 用于过滤对话
108
206
  Returns:
109
207
  conversations (List[Conversation]): 对话对象列表
208
+
209
+ """
210
+ convs = await self.db.get_conversations(
211
+ user_id=unified_msg_origin,
212
+ platform_id=platform_id,
213
+ )
214
+ convs_res = []
215
+ for conv in convs:
216
+ conv_res = self._convert_conv_from_v2_to_v1(conv)
217
+ convs_res.append(conv_res)
218
+ return convs_res
219
+
220
+ async def get_filtered_conversations(
221
+ self,
222
+ page: int = 1,
223
+ page_size: int = 20,
224
+ platform_ids: list[str] | None = None,
225
+ search_query: str = "",
226
+ **kwargs,
227
+ ) -> tuple[list[Conversation], int]:
228
+ """获取过滤后的对话列表.
229
+
230
+ Args:
231
+ page (int): 页码, 默认为 1
232
+ page_size (int): 每页大小, 默认为 20
233
+ platform_ids (list[str]): 平台 ID 列表, 可选
234
+ search_query (str): 搜索查询字符串, 可选
235
+ Returns:
236
+ conversations (list[Conversation]): 对话对象列表
237
+
110
238
  """
111
- return self.db.get_conversations(unified_msg_origin)
239
+ convs, cnt = await self.db.get_filtered_conversations(
240
+ page=page,
241
+ page_size=page_size,
242
+ platform_ids=platform_ids,
243
+ search_query=search_query,
244
+ **kwargs,
245
+ )
246
+ convs_res = []
247
+ for conv in convs:
248
+ conv_res = self._convert_conv_from_v2_to_v1(conv)
249
+ convs_res.append(conv_res)
250
+ return convs_res, cnt
112
251
 
113
252
  async def update_conversation(
114
- self, unified_msg_origin: str, conversation_id: str, history: List[Dict]
115
- ):
116
- """更新会话的对话
253
+ self,
254
+ unified_msg_origin: str,
255
+ conversation_id: str | None = None,
256
+ history: list[dict] | None = None,
257
+ title: str | None = None,
258
+ persona_id: str | None = None,
259
+ ) -> None:
260
+ """更新会话的对话.
117
261
 
118
262
  Args:
119
263
  unified_msg_origin (str): 统一的消息来源字符串。格式为 platform_name:message_type:session_id
120
264
  conversation_id (str): 对话 ID, 是 uuid 格式的字符串
121
265
  history (List[Dict]): 对话历史记录, 是一个字典列表, 每个字典包含 role 和 content 字段
266
+
122
267
  """
268
+ if not conversation_id:
269
+ # 如果没有提供 conversation_id,则获取当前的
270
+ conversation_id = await self.get_curr_conversation_id(unified_msg_origin)
123
271
  if conversation_id:
124
- self.db.update_conversation(
125
- user_id=unified_msg_origin,
272
+ await self.db.update_conversation(
126
273
  cid=conversation_id,
127
- history=json.dumps(history),
274
+ title=title,
275
+ persona_id=persona_id,
276
+ content=history,
128
277
  )
129
278
 
130
- async def update_conversation_title(self, unified_msg_origin: str, title: str):
131
- """更新会话的对话标题
279
+ async def update_conversation_title(
280
+ self,
281
+ unified_msg_origin: str,
282
+ title: str,
283
+ conversation_id: str | None = None,
284
+ ) -> None:
285
+ """更新会话的对话标题.
132
286
 
133
287
  Args:
134
288
  unified_msg_origin (str): 统一的消息来源字符串。格式为 platform_name:message_type:session_id
135
289
  title (str): 对话标题
290
+ conversation_id (str): 对话 ID, 是 uuid 格式的字符串
291
+ Deprecated:
292
+ Use `update_conversation` with `title` parameter instead.
293
+
136
294
  """
137
- conversation_id = self.session_conversations.get(unified_msg_origin)
138
- if conversation_id:
139
- self.db.update_conversation_title(
140
- user_id=unified_msg_origin, cid=conversation_id, title=title
141
- )
295
+ await self.update_conversation(
296
+ unified_msg_origin=unified_msg_origin,
297
+ conversation_id=conversation_id,
298
+ title=title,
299
+ )
142
300
 
143
301
  async def update_conversation_persona_id(
144
- self, unified_msg_origin: str, persona_id: str
145
- ):
146
- """更新会话的对话 Persona ID
302
+ self,
303
+ unified_msg_origin: str,
304
+ persona_id: str,
305
+ conversation_id: str | None = None,
306
+ ) -> None:
307
+ """更新会话的对话 Persona ID.
147
308
 
148
309
  Args:
149
310
  unified_msg_origin (str): 统一的消息来源字符串。格式为 platform_name:message_type:session_id
150
311
  persona_id (str): 对话 Persona ID
312
+ conversation_id (str): 对话 ID, 是 uuid 格式的字符串
313
+ Deprecated:
314
+ Use `update_conversation` with `persona_id` parameter instead.
315
+
151
316
  """
152
- conversation_id = self.session_conversations.get(unified_msg_origin)
153
- if conversation_id:
154
- self.db.update_conversation_persona_id(
155
- user_id=unified_msg_origin, cid=conversation_id, persona_id=persona_id
156
- )
317
+ await self.update_conversation(
318
+ unified_msg_origin=unified_msg_origin,
319
+ conversation_id=conversation_id,
320
+ persona_id=persona_id,
321
+ )
322
+
323
+ async def add_message_pair(
324
+ self,
325
+ cid: str,
326
+ user_message: UserMessageSegment | dict,
327
+ assistant_message: AssistantMessageSegment | dict,
328
+ ) -> None:
329
+ """Add a user-assistant message pair to the conversation history.
330
+
331
+ Args:
332
+ cid (str): Conversation ID
333
+ user_message (UserMessageSegment | dict): OpenAI-format user message object or dict
334
+ assistant_message (AssistantMessageSegment | dict): OpenAI-format assistant message object or dict
335
+
336
+ Raises:
337
+ Exception: If the conversation with the given ID is not found
338
+ """
339
+ conv = await self.db.get_conversation_by_id(cid=cid)
340
+ if not conv:
341
+ raise Exception(f"Conversation with id {cid} not found")
342
+ history = conv.content or []
343
+ if isinstance(user_message, UserMessageSegment):
344
+ user_msg_dict = user_message.model_dump()
345
+ else:
346
+ user_msg_dict = user_message
347
+ if isinstance(assistant_message, AssistantMessageSegment):
348
+ assistant_msg_dict = assistant_message.model_dump()
349
+ else:
350
+ assistant_msg_dict = assistant_message
351
+ history.append(user_msg_dict)
352
+ history.append(assistant_msg_dict)
353
+ await self.db.update_conversation(
354
+ cid=cid,
355
+ content=history,
356
+ )
157
357
 
158
358
  async def get_human_readable_context(
159
- self, unified_msg_origin, conversation_id, page=1, page_size=10
160
- ):
161
- """获取人类可读的上下文
359
+ self,
360
+ unified_msg_origin: str,
361
+ conversation_id: str,
362
+ page: int = 1,
363
+ page_size: int = 10,
364
+ ) -> tuple[list[str], int]:
365
+ """获取人类可读的上下文.
162
366
 
163
367
  Args:
164
368
  unified_msg_origin (str): 统一的消息来源字符串。格式为 platform_name:message_type:session_id
165
369
  conversation_id (str): 对话 ID, 是 uuid 格式的字符串
166
370
  page (int): 页码
167
371
  page_size (int): 每页大小
372
+
168
373
  """
169
374
  conversation = await self.get_conversation(unified_msg_origin, conversation_id)
375
+ if not conversation:
376
+ return [], 0
170
377
  history = json.loads(conversation.history)
171
378
 
172
- contexts = []
173
- temp_contexts = []
379
+ # contexts_groups 存放按顺序的段落(每个段落是一个 str 列表),
380
+ # 之后会被展平成一个扁平的 str 列表返回。
381
+ contexts_groups: list[list[str]] = []
382
+ temp_contexts: list[str] = []
174
383
  for record in history:
175
384
  if record["role"] == "user":
176
385
  temp_contexts.append(f"User: {record['content']}")
177
386
  elif record["role"] == "assistant":
178
- if "content" in record and record["content"]:
387
+ if record.get("content"):
179
388
  temp_contexts.append(f"Assistant: {record['content']}")
180
389
  elif "tool_calls" in record:
181
390
  tool_calls_str = json.dumps(
182
- record["tool_calls"], ensure_ascii=False
391
+ record["tool_calls"],
392
+ ensure_ascii=False,
183
393
  )
184
394
  temp_contexts.append(f"Assistant: [函数调用] {tool_calls_str}")
185
395
  else:
186
396
  temp_contexts.append("Assistant: [未知的内容]")
187
- contexts.insert(0, temp_contexts)
397
+ contexts_groups.insert(0, temp_contexts)
188
398
  temp_contexts = []
189
399
 
190
- # 展平 contexts 列表
191
- contexts = [item for sublist in contexts for item in sublist]
400
+ # 展平分组后的 contexts 列表为单层字符串列表
401
+ contexts = [item for sublist in contexts_groups for item in sublist]
192
402
 
193
403
  # 计算分页
194
404
  paged_contexts = contexts[(page - 1) * page_size : page * page_size]