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,233 @@
1
+ # astrbot/dashboard/routes/t2i.py
2
+
3
+ from dataclasses import asdict
4
+
5
+ from quart import jsonify, request
6
+
7
+ from astrbot.core import logger
8
+ from astrbot.core.core_lifecycle import AstrBotCoreLifecycle
9
+ from astrbot.core.utils.t2i.template_manager import TemplateManager
10
+
11
+ from .route import Response, Route, RouteContext
12
+
13
+
14
+ class T2iRoute(Route):
15
+ def __init__(self, context: RouteContext, core_lifecycle: AstrBotCoreLifecycle):
16
+ super().__init__(context)
17
+ self.core_lifecycle = core_lifecycle
18
+ self.config = core_lifecycle.astrbot_config
19
+ self.manager = TemplateManager()
20
+ # 使用列表保证路由注册顺序,避免 /<name> 路由优先匹配 /reset_default
21
+ self.routes = [
22
+ ("/t2i/templates", ("GET", self.list_templates)),
23
+ ("/t2i/templates/active", ("GET", self.get_active_template)),
24
+ ("/t2i/templates/create", ("POST", self.create_template)),
25
+ ("/t2i/templates/reset_default", ("POST", self.reset_default_template)),
26
+ ("/t2i/templates/set_active", ("POST", self.set_active_template)),
27
+ # 动态路由应该在静态路由之后注册
28
+ (
29
+ "/t2i/templates/<name>",
30
+ [
31
+ ("GET", self.get_template),
32
+ ("PUT", self.update_template),
33
+ ("DELETE", self.delete_template),
34
+ ],
35
+ ),
36
+ ]
37
+ self.register_routes()
38
+
39
+ async def list_templates(self):
40
+ """获取所有T2I模板列表"""
41
+ try:
42
+ templates = self.manager.list_templates()
43
+ return jsonify(asdict(Response().ok(data=templates)))
44
+ except Exception as e:
45
+ response = jsonify(asdict(Response().error(str(e))))
46
+ response.status_code = 500
47
+ return response
48
+
49
+ async def get_active_template(self):
50
+ """获取当前激活的T2I模板"""
51
+ try:
52
+ active_template = self.config.get("t2i_active_template", "base")
53
+ return jsonify(
54
+ asdict(Response().ok(data={"active_template": active_template})),
55
+ )
56
+ except Exception as e:
57
+ logger.error("Error in get_active_template", exc_info=True)
58
+ response = jsonify(asdict(Response().error(str(e))))
59
+ response.status_code = 500
60
+ return response
61
+
62
+ async def get_template(self, name: str):
63
+ """获取指定名称的T2I模板内容"""
64
+ try:
65
+ content = self.manager.get_template(name)
66
+ return jsonify(
67
+ asdict(Response().ok(data={"name": name, "content": content})),
68
+ )
69
+ except FileNotFoundError:
70
+ response = jsonify(asdict(Response().error("Template not found")))
71
+ response.status_code = 404
72
+ return response
73
+ except Exception as e:
74
+ response = jsonify(asdict(Response().error(str(e))))
75
+ response.status_code = 500
76
+ return response
77
+
78
+ async def create_template(self):
79
+ """创建一个新的T2I模板"""
80
+ try:
81
+ data = await request.json
82
+ name = data.get("name")
83
+ content = data.get("content")
84
+ if not name or not content:
85
+ response = jsonify(
86
+ asdict(Response().error("Name and content are required.")),
87
+ )
88
+ response.status_code = 400
89
+ return response
90
+ name = name.strip()
91
+
92
+ self.manager.create_template(name, content)
93
+ response = jsonify(
94
+ asdict(
95
+ Response().ok(
96
+ data={"name": name},
97
+ message="Template created successfully.",
98
+ ),
99
+ ),
100
+ )
101
+ response.status_code = 201
102
+ return response
103
+ except FileExistsError:
104
+ response = jsonify(
105
+ asdict(Response().error("Template with this name already exists.")),
106
+ )
107
+ response.status_code = 409
108
+ return response
109
+ except ValueError as e:
110
+ response = jsonify(asdict(Response().error(str(e))))
111
+ response.status_code = 400
112
+ return response
113
+ except Exception as e:
114
+ response = jsonify(asdict(Response().error(str(e))))
115
+ response.status_code = 500
116
+ return response
117
+
118
+ async def update_template(self, name: str):
119
+ """更新一个已存在的T2I模板"""
120
+ try:
121
+ name = name.strip()
122
+ data = await request.json
123
+ content = data.get("content")
124
+ if content is None:
125
+ response = jsonify(asdict(Response().error("Content is required.")))
126
+ response.status_code = 400
127
+ return response
128
+
129
+ self.manager.update_template(name, content)
130
+
131
+ # 检查更新的是否为当前激活的模板,如果是,则热重载
132
+ active_template = self.config.get("t2i_active_template", "base")
133
+ if name == active_template:
134
+ await self.core_lifecycle.reload_pipeline_scheduler("default")
135
+ message = f"模板 '{name}' 已更新并重新加载。"
136
+ else:
137
+ message = f"模板 '{name}' 已更新。"
138
+
139
+ return jsonify(asdict(Response().ok(data={"name": name}, message=message)))
140
+ except ValueError as e:
141
+ response = jsonify(asdict(Response().error(str(e))))
142
+ response.status_code = 400
143
+ return response
144
+ except Exception as e:
145
+ response = jsonify(asdict(Response().error(str(e))))
146
+ response.status_code = 500
147
+ return response
148
+
149
+ async def delete_template(self, name: str):
150
+ """删除一个T2I模板"""
151
+ try:
152
+ name = name.strip()
153
+ self.manager.delete_template(name)
154
+ return jsonify(
155
+ asdict(Response().ok(message="Template deleted successfully.")),
156
+ )
157
+ except FileNotFoundError:
158
+ response = jsonify(asdict(Response().error("Template not found.")))
159
+ response.status_code = 404
160
+ return response
161
+ except ValueError as e:
162
+ response = jsonify(asdict(Response().error(str(e))))
163
+ response.status_code = 400
164
+ return response
165
+ except Exception as e:
166
+ response = jsonify(asdict(Response().error(str(e))))
167
+ response.status_code = 500
168
+ return response
169
+
170
+ async def set_active_template(self):
171
+ """设置当前活动的T2I模板"""
172
+ try:
173
+ data = await request.json
174
+ name = data.get("name")
175
+ if not name:
176
+ response = jsonify(asdict(Response().error("模板名称(name)不能为空。")))
177
+ response.status_code = 400
178
+ return response
179
+
180
+ # 验证模板文件是否存在
181
+ self.manager.get_template(name)
182
+
183
+ # 更新配置
184
+ config = self.config
185
+ config["t2i_active_template"] = name
186
+ config.save_config(config)
187
+
188
+ # 热重载以应用更改
189
+ await self.core_lifecycle.reload_pipeline_scheduler("default")
190
+
191
+ return jsonify(asdict(Response().ok(message=f"模板 '{name}' 已成功应用。")))
192
+
193
+ except FileNotFoundError:
194
+ response = jsonify(
195
+ asdict(Response().error(f"模板 '{name}' 不存在,无法应用。")),
196
+ )
197
+ response.status_code = 404
198
+ return response
199
+ except Exception as e:
200
+ logger.error("Error in set_active_template", exc_info=True)
201
+ response = jsonify(asdict(Response().error(str(e))))
202
+ response.status_code = 500
203
+ return response
204
+
205
+ async def reset_default_template(self):
206
+ """重置默认的'base'模板"""
207
+ try:
208
+ self.manager.reset_default_template()
209
+
210
+ # 更新配置,将激活模板也重置为'base'
211
+ config = self.config
212
+ config["t2i_active_template"] = "base"
213
+ config.save_config(config)
214
+
215
+ # 热重载以应用更改
216
+ await self.core_lifecycle.reload_pipeline_scheduler("default")
217
+
218
+ return jsonify(
219
+ asdict(
220
+ Response().ok(
221
+ message="Default template has been reset and activated.",
222
+ ),
223
+ ),
224
+ )
225
+ except FileNotFoundError as e:
226
+ response = jsonify(asdict(Response().error(str(e))))
227
+ response.status_code = 404
228
+ return response
229
+ except Exception as e:
230
+ logger.error("Error in reset_default_template", exc_info=True)
231
+ response = jsonify(asdict(Response().error(str(e))))
232
+ response.status_code = 500
233
+ return response
@@ -1,18 +1,21 @@
1
- import os
2
- import json
3
- import aiohttp
4
1
  import traceback
5
- from .route import Route, Response, RouteContext
2
+
6
3
  from quart import request
7
- from astrbot.core.core_lifecycle import AstrBotCoreLifecycle
4
+
8
5
  from astrbot.core import logger
6
+ from astrbot.core.core_lifecycle import AstrBotCoreLifecycle
7
+ from astrbot.core.star import star_map
8
+
9
+ from .route import Response, Route, RouteContext
9
10
 
10
11
  DEFAULT_MCP_CONFIG = {"mcpServers": {}}
11
12
 
12
13
 
13
14
  class ToolsRoute(Route):
14
15
  def __init__(
15
- self, context: RouteContext, core_lifecycle: AstrBotCoreLifecycle
16
+ self,
17
+ context: RouteContext,
18
+ core_lifecycle: AstrBotCoreLifecycle,
16
19
  ) -> None:
17
20
  super().__init__(context)
18
21
  self.core_lifecycle = core_lifecycle
@@ -21,44 +24,17 @@ class ToolsRoute(Route):
21
24
  "/tools/mcp/add": ("POST", self.add_mcp_server),
22
25
  "/tools/mcp/update": ("POST", self.update_mcp_server),
23
26
  "/tools/mcp/delete": ("POST", self.delete_mcp_server),
24
- "/tools/mcp/market": ("GET", self.get_mcp_markets),
27
+ "/tools/mcp/test": ("POST", self.test_mcp_connection),
28
+ "/tools/list": ("GET", self.get_tool_list),
29
+ "/tools/toggle-tool": ("POST", self.toggle_tool),
30
+ "/tools/mcp/sync-provider": ("POST", self.sync_provider),
25
31
  }
26
32
  self.register_routes()
27
33
  self.tool_mgr = self.core_lifecycle.provider_manager.llm_tools
28
34
 
29
- @property
30
- def mcp_config_path(self):
31
- current_dir = os.path.dirname(os.path.abspath(__file__))
32
- data_dir = os.path.abspath(os.path.join(current_dir, "../../../data"))
33
- return os.path.join(data_dir, "mcp_server.json")
34
-
35
- def load_mcp_config(self):
36
- if not os.path.exists(self.mcp_config_path):
37
- # 配置文件不存在,创建默认配置
38
- os.makedirs(os.path.dirname(self.mcp_config_path), exist_ok=True)
39
- with open(self.mcp_config_path, "w", encoding="utf-8") as f:
40
- json.dump(DEFAULT_MCP_CONFIG, f, ensure_ascii=False, indent=4)
41
- return DEFAULT_MCP_CONFIG
42
-
43
- try:
44
- with open(self.mcp_config_path, "r", encoding="utf-8") as f:
45
- return json.load(f)
46
- except Exception as e:
47
- logger.error(f"加载 MCP 配置失败: {e}")
48
- return DEFAULT_MCP_CONFIG
49
-
50
- def save_mcp_config(self, config):
51
- try:
52
- with open(self.mcp_config_path, "w", encoding="utf-8") as f:
53
- json.dump(config, f, ensure_ascii=False, indent=4)
54
- return True
55
- except Exception as e:
56
- logger.error(f"保存 MCP 配置失败: {e}")
57
- return False
58
-
59
35
  async def get_mcp_servers(self):
60
36
  try:
61
- config = self.load_mcp_config()
37
+ config = self.tool_mgr.load_mcp_config()
62
38
  servers = []
63
39
 
64
40
  # 获取所有服务器并添加它们的工具列表
@@ -90,7 +66,7 @@ class ToolsRoute(Route):
90
66
  return Response().ok(servers).__dict__
91
67
  except Exception as e:
92
68
  logger.error(traceback.format_exc())
93
- return Response().error(f"获取 MCP 服务器列表失败: {str(e)}").__dict__
69
+ return Response().error(f"获取 MCP 服务器列表失败: {e!s}").__dict__
94
70
 
95
71
  async def add_mcp_server(self):
96
72
  try:
@@ -121,28 +97,32 @@ class ToolsRoute(Route):
121
97
  if not has_valid_config:
122
98
  return Response().error("必须提供有效的服务器配置").__dict__
123
99
 
124
- config = self.load_mcp_config()
100
+ config = self.tool_mgr.load_mcp_config()
125
101
 
126
102
  if name in config["mcpServers"]:
127
103
  return Response().error(f"服务器 {name} 已存在").__dict__
128
104
 
129
105
  config["mcpServers"][name] = server_config
130
106
 
131
- if self.save_mcp_config(config):
132
- # 动态初始化新MCP客户端
133
- await self.tool_mgr.mcp_service_queue.put(
134
- {
135
- "type": "init",
136
- "name": name,
137
- "cfg": config["mcpServers"][name],
138
- }
139
- )
107
+ if self.tool_mgr.save_mcp_config(config):
108
+ try:
109
+ await self.tool_mgr.enable_mcp_server(
110
+ name,
111
+ server_config,
112
+ timeout=30,
113
+ )
114
+ except TimeoutError:
115
+ return Response().error(f"启用 MCP 服务器 {name} 超时。").__dict__
116
+ except Exception as e:
117
+ logger.error(traceback.format_exc())
118
+ return (
119
+ Response().error(f"启用 MCP 服务器 {name} 失败: {e!s}").__dict__
120
+ )
140
121
  return Response().ok(None, f"成功添加 MCP 服务器 {name}").__dict__
141
- else:
142
- return Response().error("保存配置失败").__dict__
122
+ return Response().error("保存配置失败").__dict__
143
123
  except Exception as e:
144
124
  logger.error(traceback.format_exc())
145
- return Response().error(f"添加 MCP 服务器失败: {str(e)}").__dict__
125
+ return Response().error(f"添加 MCP 服务器失败: {e!s}").__dict__
146
126
 
147
127
  async def update_mcp_server(self):
148
128
  try:
@@ -153,14 +133,15 @@ class ToolsRoute(Route):
153
133
  if not name:
154
134
  return Response().error("服务器名称不能为空").__dict__
155
135
 
156
- config = self.load_mcp_config()
136
+ config = self.tool_mgr.load_mcp_config()
157
137
 
158
138
  if name not in config["mcpServers"]:
159
139
  return Response().error(f"服务器 {name} 不存在").__dict__
160
140
 
161
141
  # 获取活动状态
162
142
  active = server_data.get(
163
- "active", config["mcpServers"][name].get("active", True)
143
+ "active",
144
+ config["mcpServers"][name].get("active", True),
164
145
  )
165
146
 
166
147
  # 创建新的配置对象
@@ -189,49 +170,63 @@ class ToolsRoute(Route):
189
170
 
190
171
  config["mcpServers"][name] = server_config
191
172
 
192
- if self.save_mcp_config(config):
173
+ if self.tool_mgr.save_mcp_config(config):
193
174
  # 处理MCP客户端状态变化
194
175
  if active:
195
- # 如果要激活服务器或者配置已更改
196
176
  if name in self.tool_mgr.mcp_client_dict or not only_update_active:
197
- await self.tool_mgr.mcp_service_queue.put(
198
- {
199
- "type": "terminate",
200
- "name": name,
201
- }
177
+ try:
178
+ await self.tool_mgr.disable_mcp_server(name, timeout=10)
179
+ except TimeoutError as e:
180
+ return (
181
+ Response()
182
+ .error(f"启用前停用 MCP 服务器时 {name} 超时: {e!s}")
183
+ .__dict__
184
+ )
185
+ except Exception as e:
186
+ logger.error(traceback.format_exc())
187
+ return (
188
+ Response()
189
+ .error(f"启用前停用 MCP 服务器时 {name} 失败: {e!s}")
190
+ .__dict__
191
+ )
192
+ try:
193
+ await self.tool_mgr.enable_mcp_server(
194
+ name,
195
+ config["mcpServers"][name],
196
+ timeout=30,
202
197
  )
203
- await self.tool_mgr.mcp_service_queue.put(
204
- {
205
- "type": "init",
206
- "name": name,
207
- "cfg": config["mcpServers"][name],
208
- }
198
+ except TimeoutError:
199
+ return (
200
+ Response().error(f"启用 MCP 服务器 {name} 超时。").__dict__
209
201
  )
210
- else:
211
- # 客户端不存在,初始化
212
- await self.tool_mgr.mcp_service_queue.put(
213
- {
214
- "type": "init",
215
- "name": name,
216
- "cfg": config["mcpServers"][name],
217
- }
202
+ except Exception as e:
203
+ logger.error(traceback.format_exc())
204
+ return (
205
+ Response()
206
+ .error(f"启用 MCP 服务器 {name} 失败: {e!s}")
207
+ .__dict__
218
208
  )
219
- else:
220
- # 如果要停用服务器
221
- if name in self.tool_mgr.mcp_client_dict:
222
- self.tool_mgr.mcp_service_queue.put_nowait(
223
- {
224
- "type": "terminate",
225
- "name": name,
226
- }
209
+ # 如果要停用服务器
210
+ elif name in self.tool_mgr.mcp_client_dict:
211
+ try:
212
+ await self.tool_mgr.disable_mcp_server(name, timeout=10)
213
+ except TimeoutError:
214
+ return (
215
+ Response().error(f"停用 MCP 服务器 {name} 超时。").__dict__
216
+ )
217
+ except Exception as e:
218
+ logger.error(traceback.format_exc())
219
+ return (
220
+ Response()
221
+ .error(f"停用 MCP 服务器 {name} 失败: {e!s}")
222
+ .__dict__
227
223
  )
228
224
 
229
225
  return Response().ok(None, f"成功更新 MCP 服务器 {name}").__dict__
230
- else:
231
- return Response().error("保存配置失败").__dict__
226
+ return Response().error("保存配置失败").__dict__
232
227
  except Exception as e:
233
228
  logger.error(traceback.format_exc())
234
- return Response().error(f"更新 MCP 服务器失败: {str(e)}").__dict__
229
+ return Response().error(f"更新 MCP 服务器失败: {e!s}").__dict__
235
230
 
236
231
  async def delete_mcp_server(self):
237
232
  try:
@@ -241,50 +236,119 @@ class ToolsRoute(Route):
241
236
  if not name:
242
237
  return Response().error("服务器名称不能为空").__dict__
243
238
 
244
- config = self.load_mcp_config()
239
+ config = self.tool_mgr.load_mcp_config()
245
240
 
246
241
  if name not in config["mcpServers"]:
247
242
  return Response().error(f"服务器 {name} 不存在").__dict__
248
243
 
249
- # 删除服务器配置
250
244
  del config["mcpServers"][name]
251
245
 
252
- if self.save_mcp_config(config):
253
- # 关闭并删除MCP客户端
246
+ if self.tool_mgr.save_mcp_config(config):
254
247
  if name in self.tool_mgr.mcp_client_dict:
255
- self.tool_mgr.mcp_service_queue.put_nowait(
256
- {
257
- "type": "terminate",
258
- "name": name,
259
- }
260
- )
261
-
248
+ try:
249
+ await self.tool_mgr.disable_mcp_server(name, timeout=10)
250
+ except TimeoutError:
251
+ return (
252
+ Response().error(f"停用 MCP 服务器 {name} 超时。").__dict__
253
+ )
254
+ except Exception as e:
255
+ logger.error(traceback.format_exc())
256
+ return (
257
+ Response()
258
+ .error(f"停用 MCP 服务器 {name} 失败: {e!s}")
259
+ .__dict__
260
+ )
262
261
  return Response().ok(None, f"成功删除 MCP 服务器 {name}").__dict__
262
+ return Response().error("保存配置失败").__dict__
263
+ except Exception as e:
264
+ logger.error(traceback.format_exc())
265
+ return Response().error(f"删除 MCP 服务器失败: {e!s}").__dict__
266
+
267
+ async def test_mcp_connection(self):
268
+ """测试 MCP 服务器连接"""
269
+ try:
270
+ server_data = await request.json
271
+ config = server_data.get("mcp_server_config", None)
272
+
273
+ if not isinstance(config, dict) or not config:
274
+ return Response().error("无效的 MCP 服务器配置").__dict__
275
+
276
+ if "mcpServers" in config:
277
+ keys = list(config["mcpServers"].keys())
278
+ if not keys:
279
+ return Response().error("MCP 服务器配置不能为空").__dict__
280
+ if len(keys) > 1:
281
+ return Response().error("一次只能配置一个 MCP 服务器配置").__dict__
282
+ config = config["mcpServers"][keys[0]]
283
+ elif not config:
284
+ return Response().error("MCP 服务器配置不能为空").__dict__
285
+
286
+ tools_name = await self.tool_mgr.test_mcp_server_connection(config)
287
+ return (
288
+ Response().ok(data=tools_name, message="🎉 MCP 服务器可用!").__dict__
289
+ )
290
+
291
+ except Exception as e:
292
+ logger.error(traceback.format_exc())
293
+ return Response().error(f"测试 MCP 连接失败: {e!s}").__dict__
294
+
295
+ async def get_tool_list(self):
296
+ """获取所有注册的工具列表"""
297
+ try:
298
+ tools = self.tool_mgr.func_list
299
+ tools_dict = [
300
+ {
301
+ "name": tool.name,
302
+ "description": tool.description,
303
+ "parameters": tool.parameters,
304
+ "active": tool.active,
305
+ }
306
+ for tool in tools
307
+ ]
308
+ return Response().ok(data=tools_dict).__dict__
309
+ except Exception as e:
310
+ logger.error(traceback.format_exc())
311
+ return Response().error(f"获取工具列表失败: {e!s}").__dict__
312
+
313
+ async def toggle_tool(self):
314
+ """启用或停用指定的工具"""
315
+ try:
316
+ data = await request.json
317
+ tool_name = data.get("name")
318
+ action = data.get("activate") # True or False
319
+
320
+ if not tool_name or action is None:
321
+ return Response().error("缺少必要参数: name 或 action").__dict__
322
+
323
+ if action:
324
+ try:
325
+ ok = self.tool_mgr.activate_llm_tool(tool_name, star_map=star_map)
326
+ except ValueError as e:
327
+ return Response().error(f"启用工具失败: {e!s}").__dict__
263
328
  else:
264
- return Response().error("保存配置失败").__dict__
329
+ ok = self.tool_mgr.deactivate_llm_tool(tool_name)
330
+
331
+ if ok:
332
+ return Response().ok(None, "操作成功。").__dict__
333
+ return Response().error(f"工具 {tool_name} 不存在或操作失败。").__dict__
334
+
265
335
  except Exception as e:
266
336
  logger.error(traceback.format_exc())
267
- return Response().error(f"删除 MCP 服务器失败: {str(e)}").__dict__
268
-
269
- async def get_mcp_markets(self):
270
- page = request.args.get("page", 1, type=int)
271
- page_size = request.args.get("page_size", 10, type=int)
272
- BASE_URL = "https://api.soulter.top/astrbot/mcpservers?page={}&page_size={}".format(
273
- page,
274
- page_size,
275
- )
337
+ return Response().error(f"操作工具失败: {e!s}").__dict__
338
+
339
+ async def sync_provider(self):
340
+ """同步 MCP 提供者配置"""
276
341
  try:
277
- async with aiohttp.ClientSession() as session:
278
- async with session.get(f"{BASE_URL}") as response:
279
- if response.status == 200:
280
- data = await response.json()
281
- return Response().ok(data["data"]).__dict__
282
- else:
283
- return (
284
- Response()
285
- .error(f"获取市场数据失败: HTTP {response.status}")
286
- .__dict__
287
- )
288
- except Exception as _:
342
+ data = await request.json
343
+ provider_name = data.get("name") # modelscope, or others
344
+ match provider_name:
345
+ case "modelscope":
346
+ access_token = data.get("access_token", "")
347
+ await self.tool_mgr.sync_modelscope_mcp_servers(access_token)
348
+ case _:
349
+ return Response().error(f"未知: {provider_name}").__dict__
350
+
351
+ return Response().ok(message="同步成功").__dict__
352
+ except Exception as e:
289
353
  logger.error(traceback.format_exc())
290
- return Response().error("获取市场数据失败").__dict__
354
+ return Response().error(f"同步失败: {e!s}").__dict__