AstrBot 4.5.1__py3-none-any.whl → 4.5.3__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 +56 -53
  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.3.dist-info}/METADATA +2 -1
  240. astrbot-4.5.3.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.3.dist-info}/WHEEL +0 -0
  243. {astrbot-4.5.1.dist-info → astrbot-4.5.3.dist-info}/entry_points.txt +0 -0
  244. {astrbot-4.5.1.dist-info → astrbot-4.5.3.dist-info}/licenses/LICENSE +0 -0
@@ -1,28 +1,28 @@
1
+ import asyncio
1
2
  import sys
2
3
  import uuid
3
- import asyncio
4
+
4
5
  import quart
6
+ from requests import Response
7
+ from wechatpy import WeChatClient, parse_message
8
+ from wechatpy.crypto import WeChatCrypto
9
+ from wechatpy.exceptions import InvalidSignatureException
10
+ from wechatpy.messages import BaseMessage, ImageMessage, TextMessage, VoiceMessage
11
+ from wechatpy.utils import check_signature
5
12
 
13
+ from astrbot.api.event import MessageChain
14
+ from astrbot.api.message_components import Image, Plain, Record
6
15
  from astrbot.api.platform import (
7
- Platform,
8
16
  AstrBotMessage,
9
17
  MessageMember,
10
- PlatformMetadata,
11
18
  MessageType,
19
+ Platform,
20
+ PlatformMetadata,
21
+ register_platform_adapter,
12
22
  )
13
- from astrbot.api.event import MessageChain
14
- from astrbot.api.message_components import Plain, Image, Record
15
- from astrbot.core.platform.astr_message_event import MessageSesion
16
- from astrbot.api.platform import register_platform_adapter
17
23
  from astrbot.core import logger
18
- from requests import Response
24
+ from astrbot.core.platform.astr_message_event import MessageSesion
19
25
 
20
- from wechatpy.utils import check_signature
21
- from wechatpy.crypto import WeChatCrypto
22
- from wechatpy import WeChatClient
23
- from wechatpy.messages import TextMessage, ImageMessage, VoiceMessage, BaseMessage
24
- from wechatpy.exceptions import InvalidSignatureException
25
- from wechatpy import parse_message
26
26
  from .weixin_offacc_event import WeixinOfficialAccountPlatformEvent
27
27
 
28
28
  if sys.version_info >= (3, 12):
@@ -40,10 +40,14 @@ class WecomServer:
40
40
  self.encoding_aes_key = config.get("encoding_aes_key")
41
41
  self.appid = config.get("appid")
42
42
  self.server.add_url_rule(
43
- "/callback/command", view_func=self.verify, methods=["GET"]
43
+ "/callback/command",
44
+ view_func=self.verify,
45
+ methods=["GET"],
44
46
  )
45
47
  self.server.add_url_rule(
46
- "/callback/command", view_func=self.callback_command, methods=["POST"]
48
+ "/callback/command",
49
+ view_func=self.callback_command,
50
+ methods=["POST"],
47
51
  )
48
52
  self.crypto = WeChatCrypto(self.token, self.encoding_aes_key, self.appid)
49
53
 
@@ -97,7 +101,7 @@ class WecomServer:
97
101
 
98
102
  async def start_polling(self):
99
103
  logger.info(
100
- f"将在 {self.callback_server_host}:{self.port} 端口启动 微信公众平台 适配器。"
104
+ f"将在 {self.callback_server_host}:{self.port} 端口启动 微信公众平台 适配器。",
101
105
  )
102
106
  await self.server.run_task(
103
107
  host=self.callback_server_host,
@@ -112,22 +116,25 @@ class WecomServer:
112
116
  @register_platform_adapter("weixin_official_account", "微信公众平台 适配器")
113
117
  class WeixinOfficialAccountPlatformAdapter(Platform):
114
118
  def __init__(
115
- self, platform_config: dict, platform_settings: dict, event_queue: asyncio.Queue
119
+ self,
120
+ platform_config: dict,
121
+ platform_settings: dict,
122
+ event_queue: asyncio.Queue,
116
123
  ) -> None:
117
124
  super().__init__(event_queue)
118
125
  self.config = platform_config
119
126
  self.settingss = platform_settings
120
127
  self.client_self_id = uuid.uuid4().hex[:8]
121
128
  self.api_base_url = platform_config.get(
122
- "api_base_url", "https://api.weixin.qq.com/cgi-bin/"
129
+ "api_base_url",
130
+ "https://api.weixin.qq.com/cgi-bin/",
123
131
  )
124
132
  self.active_send_mode = self.config.get("active_send_mode", False)
125
133
 
126
134
  if not self.api_base_url:
127
135
  self.api_base_url = "https://api.weixin.qq.com/cgi-bin/"
128
136
 
129
- if self.api_base_url.endswith("/"):
130
- self.api_base_url = self.api_base_url[:-1]
137
+ self.api_base_url = self.api_base_url.removesuffix("/")
131
138
  if not self.api_base_url.endswith("/cgi-bin"):
132
139
  self.api_base_url += "/cgi-bin"
133
140
 
@@ -161,7 +168,8 @@ class WeixinOfficialAccountPlatformAdapter(Platform):
161
168
  await self.convert_message(msg, future)
162
169
  # I love shield so much!
163
170
  result = await asyncio.wait_for(
164
- asyncio.shield(future), 60
171
+ asyncio.shield(future),
172
+ 60,
165
173
  ) # wait for 60s
166
174
  logger.debug(f"Got future result: {result}")
167
175
  self.wexin_event_workers.pop(msg.id, None)
@@ -175,7 +183,9 @@ class WeixinOfficialAccountPlatformAdapter(Platform):
175
183
 
176
184
  @override
177
185
  async def send_by_session(
178
- self, session: MessageSesion, message_chain: MessageChain
186
+ self,
187
+ session: MessageSesion,
188
+ message_chain: MessageChain,
179
189
  ):
180
190
  await super().send_by_session(session, message_chain)
181
191
 
@@ -192,7 +202,9 @@ class WeixinOfficialAccountPlatformAdapter(Platform):
192
202
  await self.server.start_polling()
193
203
 
194
204
  async def convert_message(
195
- self, msg, future: asyncio.Future = None
205
+ self,
206
+ msg,
207
+ future: asyncio.Future = None,
196
208
  ) -> AstrBotMessage | None:
197
209
  abm = AstrBotMessage()
198
210
  if isinstance(msg, TextMessage):
@@ -224,7 +236,9 @@ class WeixinOfficialAccountPlatformAdapter(Platform):
224
236
  assert isinstance(msg, VoiceMessage)
225
237
 
226
238
  resp: Response = await asyncio.get_event_loop().run_in_executor(
227
- None, self.client.media.download, msg.media_id
239
+ None,
240
+ self.client.media.download,
241
+ msg.media_id,
228
242
  )
229
243
  path = f"data/temp/wecom_{msg.media_id}.amr"
230
244
  with open(path, "wb") as f:
@@ -238,7 +252,7 @@ class WeixinOfficialAccountPlatformAdapter(Platform):
238
252
  audio.export(path_wav, format="wav")
239
253
  except Exception as e:
240
254
  logger.error(
241
- f"转换音频失败: {e}。如果没有安装 pydub 和 ffmpeg 请先安装。"
255
+ f"转换音频失败: {e}。如果没有安装 pydub 和 ffmpeg 请先安装。",
242
256
  )
243
257
  path_wav = path
244
258
  return
@@ -1,21 +1,20 @@
1
- import uuid
2
1
  import asyncio
3
- from astrbot.api.event import AstrMessageEvent, MessageChain
4
- from astrbot.api.platform import AstrBotMessage, PlatformMetadata
5
- from astrbot.api.message_components import Plain, Image, Record
6
- from wechatpy import WeChatClient
7
- from wechatpy.replies import TextReply, ImageReply, VoiceReply
2
+ import uuid
8
3
 
4
+ from wechatpy import WeChatClient
5
+ from wechatpy.replies import ImageReply, TextReply, VoiceReply
9
6
 
10
7
  from astrbot.api import logger
8
+ from astrbot.api.event import AstrMessageEvent, MessageChain
9
+ from astrbot.api.message_components import Image, Plain, Record
10
+ from astrbot.api.platform import AstrBotMessage, PlatformMetadata
11
11
 
12
12
  try:
13
13
  import pydub
14
14
  except Exception:
15
15
  logger.warning(
16
- "检测到 pydub 库未安装,微信公众平台将无法语音收发。如需使用语音,请前往管理面板 -> 控制台 -> 安装 Pip 库安装 pydub。"
16
+ "检测到 pydub 库未安装,微信公众平台将无法语音收发。如需使用语音,请前往管理面板 -> 控制台 -> 安装 Pip 库安装 pydub。",
17
17
  )
18
- pass
19
18
 
20
19
 
21
20
  class WeixinOfficialAccountPlatformEvent(AstrMessageEvent):
@@ -32,7 +31,9 @@ class WeixinOfficialAccountPlatformEvent(AstrMessageEvent):
32
31
 
33
32
  @staticmethod
34
33
  async def send_with_client(
35
- client: WeChatClient, message: MessageChain, user_name: str
34
+ client: WeChatClient,
35
+ message: MessageChain,
36
+ user_name: str,
36
37
  ):
37
38
  pass
38
39
 
@@ -43,44 +44,44 @@ class WeixinOfficialAccountPlatformEvent(AstrMessageEvent):
43
44
  plain (str): 要分割的长文本
44
45
  Returns:
45
46
  list[str]: 分割后的文本列表
47
+
46
48
  """
47
49
  if len(plain) <= 2048:
48
50
  return [plain]
49
- else:
50
- result = []
51
- start = 0
52
- while start < len(plain):
53
- # 剩下的字符串长度<2048时结束
54
- if start + 2048 >= len(plain):
55
- result.append(plain[start:])
51
+ result = []
52
+ start = 0
53
+ while start < len(plain):
54
+ # 剩下的字符串长度<2048时结束
55
+ if start + 2048 >= len(plain):
56
+ result.append(plain[start:])
57
+ break
58
+
59
+ # 向前搜索分割标点符号
60
+ end = min(start + 2048, len(plain))
61
+ cut_position = end
62
+ for i in range(end, start, -1):
63
+ if i < len(plain) and plain[i - 1] in [
64
+ "。",
65
+ "!",
66
+ "?",
67
+ ".",
68
+ "!",
69
+ "?",
70
+ "\n",
71
+ ";",
72
+ ";",
73
+ ]:
74
+ cut_position = i
56
75
  break
57
76
 
58
- # 向前搜索分割标点符号
59
- end = min(start + 2048, len(plain))
77
+ # 没找到合适的位置分割, 直接切分
78
+ if cut_position == end and end < len(plain):
60
79
  cut_position = end
61
- for i in range(end, start, -1):
62
- if i < len(plain) and plain[i - 1] in [
63
- "。",
64
- "!",
65
- "?",
66
- ".",
67
- "!",
68
- "?",
69
- "\n",
70
- ";",
71
- ";",
72
- ]:
73
- cut_position = i
74
- break
75
-
76
- # 没找到合适的位置分割, 直接切分
77
- if cut_position == end and end < len(plain):
78
- cut_position = end
79
-
80
- result.append(plain[start:cut_position])
81
- start = cut_position
82
-
83
- return result
80
+
81
+ result.append(plain[start:cut_position])
82
+ start = cut_position
83
+
84
+ return result
84
85
 
85
86
  async def send(self, message: MessageChain):
86
87
  message_obj = self.message_obj
@@ -111,7 +112,7 @@ class WeixinOfficialAccountPlatformEvent(AstrMessageEvent):
111
112
  except Exception as e:
112
113
  logger.error(f"微信公众平台上传图片失败: {e}")
113
114
  await self.send(
114
- MessageChain().message(f"微信公众平台上传图片失败: {e}")
115
+ MessageChain().message(f"微信公众平台上传图片失败: {e}"),
115
116
  )
116
117
  return
117
118
  logger.debug(f"微信公众平台上传图片返回: {response}")
@@ -136,7 +137,8 @@ class WeixinOfficialAccountPlatformEvent(AstrMessageEvent):
136
137
  # 转成amr
137
138
  record_path_amr = f"data/temp/{uuid.uuid4()}.amr"
138
139
  pydub.AudioSegment.from_wav(record_path).export(
139
- record_path_amr, format="amr"
140
+ record_path_amr,
141
+ format="amr",
140
142
  )
141
143
 
142
144
  with open(record_path_amr, "rb") as f:
@@ -145,7 +147,7 @@ class WeixinOfficialAccountPlatformEvent(AstrMessageEvent):
145
147
  except Exception as e:
146
148
  logger.error(f"微信公众平台上传语音失败: {e}")
147
149
  await self.send(
148
- MessageChain().message(f"微信公众平台上传语音失败: {e}")
150
+ MessageChain().message(f"微信公众平台上传语音失败: {e}"),
149
151
  )
150
152
  return
151
153
  logger.info(f"微信公众平台上传语音返回: {response}")
@@ -178,7 +180,7 @@ class WeixinOfficialAccountPlatformEvent(AstrMessageEvent):
178
180
  else:
179
181
  buffer.chain.extend(chain.chain)
180
182
  if not buffer:
181
- return
183
+ return None
182
184
  buffer.squash_plain()
183
185
  await self.send(buffer)
184
186
  return await super().send_streaming(generator, use_fallback)
@@ -11,8 +11,8 @@ class PlatformMessageHistoryManager:
11
11
  platform_id: str,
12
12
  user_id: str,
13
13
  content: list[dict], # TODO: parse from message chain
14
- sender_id: str = None,
15
- sender_name: str = None,
14
+ sender_id: str | None = None,
15
+ sender_name: str | None = None,
16
16
  ):
17
17
  """Insert a new platform message history record."""
18
18
  await self.db.insert_platform_message_history(
@@ -43,5 +43,7 @@ class PlatformMessageHistoryManager:
43
43
  async def delete(self, platform_id: str, user_id: str, offset_sec: int = 86400):
44
44
  """Delete platform message history records older than the specified offset."""
45
45
  await self.db.delete_platform_message_offset(
46
- platform_id=platform_id, user_id=user_id, offset_sec=offset_sec
46
+ platform_id=platform_id,
47
+ user_id=user_id,
48
+ offset_sec=offset_sec,
47
49
  )
@@ -1,5 +1,4 @@
1
- from .provider import Provider, Personality, STTProvider
2
-
3
1
  from .entities import ProviderMetaData
2
+ from .provider import Personality, Provider, STTProvider
4
3
 
5
- __all__ = ["Provider", "Personality", "ProviderMetaData", "STTProvider"]
4
+ __all__ = ["Personality", "Provider", "ProviderMetaData", "STTProvider"]
@@ -1,19 +1,19 @@
1
1
  from astrbot.core.provider.entities import (
2
+ AssistantMessageSegment,
3
+ LLMResponse,
4
+ ProviderMetaData,
2
5
  ProviderRequest,
3
6
  ProviderType,
4
- ProviderMetaData,
5
- ToolCallsResult,
6
- AssistantMessageSegment,
7
7
  ToolCallMessageSegment,
8
- LLMResponse,
8
+ ToolCallsResult,
9
9
  )
10
10
 
11
11
  __all__ = [
12
+ "AssistantMessageSegment",
13
+ "LLMResponse",
14
+ "ProviderMetaData",
12
15
  "ProviderRequest",
13
16
  "ProviderType",
14
- "ProviderMetaData",
15
- "ToolCallsResult",
16
- "AssistantMessageSegment",
17
17
  "ToolCallMessageSegment",
18
- "LLMResponse",
18
+ "ToolCallsResult",
19
19
  ]
@@ -1,20 +1,24 @@
1
- import enum
2
1
  import base64
2
+ import enum
3
3
  import json
4
- from astrbot.core.utils.io import download_image_by_url
5
- from astrbot import logger
6
4
  from dataclasses import dataclass, field
7
- from typing import List, Dict, Type, Any
8
- from astrbot.core.agent.tool import ToolSet
9
- from openai.types.chat.chat_completion import ChatCompletion
5
+ from typing import Any
6
+
7
+ from anthropic.types import Message as AnthropicMessage
10
8
  from google.genai.types import GenerateContentResponse
11
- from anthropic.types import Message
12
- from openai.types.chat.chat_completion_message_tool_call import (
13
- ChatCompletionMessageToolCall,
9
+ from openai.types.chat.chat_completion import ChatCompletion
10
+
11
+ import astrbot.core.message.components as Comp
12
+ from astrbot import logger
13
+ from astrbot.core.agent.message import (
14
+ AssistantMessageSegment,
15
+ ToolCall,
16
+ ToolCallMessageSegment,
14
17
  )
18
+ from astrbot.core.agent.tool import ToolSet
15
19
  from astrbot.core.db.po import Conversation
16
20
  from astrbot.core.message.message_event_result import MessageChain
17
- import astrbot.core.message.components as Comp
21
+ from astrbot.core.utils.io import download_image_by_url
18
22
 
19
23
 
20
24
  class ProviderType(enum.Enum):
@@ -30,9 +34,9 @@ class ProviderMetaData:
30
34
  type: str
31
35
  """提供商适配器名称,如 openai, ollama"""
32
36
  desc: str = ""
33
- """提供商适配器描述."""
37
+ """提供商适配器描述"""
34
38
  provider_type: ProviderType = ProviderType.CHAT_COMPLETION
35
- cls_type: Type | None = None
39
+ cls_type: Any = None
36
40
 
37
41
  default_config_tmpl: dict | None = None
38
42
  """平台的默认配置模板"""
@@ -40,57 +44,19 @@ class ProviderMetaData:
40
44
  """显示在 WebUI 配置页中的提供商名称,如空则是 type"""
41
45
 
42
46
 
43
- @dataclass
44
- class ToolCallMessageSegment:
45
- """OpenAI 格式的上下文中 role 为 tool 的消息段。参考: https://platform.openai.com/docs/guides/function-calling"""
46
-
47
- tool_call_id: str
48
- content: str
49
- role: str = "tool"
50
-
51
- def to_dict(self):
52
- return {
53
- "tool_call_id": self.tool_call_id,
54
- "content": self.content,
55
- "role": self.role,
56
- }
57
-
58
-
59
- @dataclass
60
- class AssistantMessageSegment:
61
- """OpenAI 格式的上下文中 role 为 assistant 的消息段。参考: https://platform.openai.com/docs/guides/function-calling"""
62
-
63
- content: str | None = None
64
- tool_calls: List[ChatCompletionMessageToolCall | Dict] = field(default_factory=list)
65
- role: str = "assistant"
66
-
67
- def to_dict(self):
68
- ret: dict[str, str | list[dict]] = {
69
- "role": self.role,
70
- }
71
- if self.content:
72
- ret["content"] = self.content
73
- if self.tool_calls:
74
- tool_calls_dict = [
75
- tc if isinstance(tc, dict) else tc.to_dict() for tc in self.tool_calls
76
- ]
77
- ret["tool_calls"] = tool_calls_dict
78
- return ret
79
-
80
-
81
47
  @dataclass
82
48
  class ToolCallsResult:
83
49
  """工具调用结果"""
84
50
 
85
51
  tool_calls_info: AssistantMessageSegment
86
52
  """函数调用的信息"""
87
- tool_calls_result: List[ToolCallMessageSegment]
53
+ tool_calls_result: list[ToolCallMessageSegment]
88
54
  """函数调用的结果"""
89
55
 
90
- def to_openai_messages(self) -> List[Dict]:
56
+ def to_openai_messages(self) -> list[dict]:
91
57
  ret = [
92
- self.tool_calls_info.to_dict(),
93
- *[item.to_dict() for item in self.tool_calls_result],
58
+ self.tool_calls_info.model_dump(),
59
+ *[item.model_dump() for item in self.tool_calls_result],
94
60
  ]
95
61
  return ret
96
62
 
@@ -106,16 +72,16 @@ class ProviderRequest:
106
72
  func_tool: ToolSet | None = None
107
73
  """可用的函数工具"""
108
74
  contexts: list[dict] = field(default_factory=list)
109
- """上下文。格式与 openai 的上下文格式一致:
75
+ """
76
+ OpenAI 格式上下文列表。
110
77
  参考 https://platform.openai.com/docs/api-reference/chat/create#chat-create-messages
111
78
  """
112
79
  system_prompt: str = ""
113
80
  """系统提示词"""
114
81
  conversation: Conversation | None = None
115
-
82
+ """关联的对话对象"""
116
83
  tool_calls_result: list[ToolCallsResult] | ToolCallsResult | None = None
117
84
  """附加的上次请求后工具调用的结果。参考: https://platform.openai.com/docs/guides/function-calling#handling-function-calls"""
118
-
119
85
  model: str | None = None
120
86
  """模型名称,为 None 时使用提供商的默认模型"""
121
87
 
@@ -175,13 +141,13 @@ class ProviderRequest:
175
141
 
176
142
  return result_parts
177
143
 
178
- async def assemble_context(self) -> Dict:
144
+ async def assemble_context(self) -> dict:
179
145
  """将请求(prompt 和 image_urls)包装成 OpenAI 的消息格式。"""
180
146
  if self.image_urls:
181
147
  user_content = {
182
148
  "role": "user",
183
149
  "content": [
184
- {"type": "text", "text": self.prompt if self.prompt else "[图片]"}
150
+ {"type": "text", "text": self.prompt if self.prompt else "[图片]"},
185
151
  ],
186
152
  }
187
153
  for image_url in self.image_urls:
@@ -197,11 +163,10 @@ class ProviderRequest:
197
163
  logger.warning(f"图片 {image_url} 得到的结果为空,将忽略。")
198
164
  continue
199
165
  user_content["content"].append(
200
- {"type": "image_url", "image_url": {"url": image_data}}
166
+ {"type": "image_url", "image_url": {"url": image_data}},
201
167
  )
202
168
  return user_content
203
- else:
204
- return {"role": "user", "content": self.prompt}
169
+ return {"role": "user", "content": self.prompt}
205
170
 
206
171
  async def _encode_image_bs64(self, image_url: str) -> str:
207
172
  """将图片转换为 base64"""
@@ -219,15 +184,17 @@ class LLMResponse:
219
184
  """角色, assistant, tool, err"""
220
185
  result_chain: MessageChain | None = None
221
186
  """返回的消息链"""
222
- tools_call_args: List[Dict[str, Any]] = field(default_factory=list)
187
+ tools_call_args: list[dict[str, Any]] = field(default_factory=list)
223
188
  """工具调用参数"""
224
- tools_call_name: List[str] = field(default_factory=list)
189
+ tools_call_name: list[str] = field(default_factory=list)
225
190
  """工具调用名称"""
226
- tools_call_ids: List[str] = field(default_factory=list)
191
+ tools_call_ids: list[str] = field(default_factory=list)
227
192
  """工具调用 ID"""
228
193
 
229
- raw_completion: ChatCompletion | GenerateContentResponse | Message | None = None
230
- _new_record: Dict[str, Any] | None = None
194
+ raw_completion: (
195
+ ChatCompletion | GenerateContentResponse | AnthropicMessage | None
196
+ ) = None
197
+ _new_record: dict[str, Any] | None = None
231
198
 
232
199
  _completion_text: str = ""
233
200
 
@@ -239,11 +206,14 @@ class LLMResponse:
239
206
  role: str,
240
207
  completion_text: str = "",
241
208
  result_chain: MessageChain | None = None,
242
- tools_call_args: List[Dict[str, Any]] | None = None,
243
- tools_call_name: List[str] | None = None,
244
- tools_call_ids: List[str] | None = None,
245
- raw_completion: ChatCompletion | None = None,
246
- _new_record: Dict[str, Any] | None = None,
209
+ tools_call_args: list[dict[str, Any]] | None = None,
210
+ tools_call_name: list[str] | None = None,
211
+ tools_call_ids: list[str] | None = None,
212
+ raw_completion: ChatCompletion
213
+ | GenerateContentResponse
214
+ | AnthropicMessage
215
+ | None = None,
216
+ _new_record: dict[str, Any] | None = None,
247
217
  is_chunk: bool = False,
248
218
  ):
249
219
  """初始化 LLMResponse
@@ -255,6 +225,7 @@ class LLMResponse:
255
225
  tools_call_args (List[Dict[str, any]], optional): 工具调用参数. Defaults to None.
256
226
  tools_call_name (List[str], optional): 工具调用名称. Defaults to None.
257
227
  raw_completion (ChatCompletion, optional): 原始响应, OpenAI 格式. Defaults to None.
228
+
258
229
  """
259
230
  if tools_call_args is None:
260
231
  tools_call_args = []
@@ -291,8 +262,8 @@ class LLMResponse:
291
262
  else:
292
263
  self._completion_text = value
293
264
 
294
- def to_openai_tool_calls(self) -> List[Dict]:
295
- """将工具调用信息转换为 OpenAI 格式"""
265
+ def to_openai_tool_calls(self) -> list[dict]:
266
+ """Convert to OpenAI tool calls format. Deprecated, use to_openai_to_calls_model instead."""
296
267
  ret = []
297
268
  for idx, tool_call_arg in enumerate(self.tools_call_args):
298
269
  ret.append(
@@ -303,7 +274,22 @@ class LLMResponse:
303
274
  "arguments": json.dumps(tool_call_arg),
304
275
  },
305
276
  "type": "function",
306
- }
277
+ },
278
+ )
279
+ return ret
280
+
281
+ def to_openai_to_calls_model(self) -> list[ToolCall]:
282
+ """The same as to_openai_tool_calls but return pydantic model."""
283
+ ret = []
284
+ for idx, tool_call_arg in enumerate(self.tools_call_args):
285
+ ret.append(
286
+ ToolCall(
287
+ id=self.tools_call_ids[idx],
288
+ function=ToolCall.FunctionBody(
289
+ name=self.tools_call_name[idx],
290
+ arguments=json.dumps(tool_call_arg),
291
+ ),
292
+ ),
307
293
  )
308
294
  return ret
309
295