AstrBot 4.5.0__py3-none-any.whl → 4.5.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (244) hide show
  1. astrbot/api/__init__.py +10 -11
  2. astrbot/api/event/__init__.py +5 -6
  3. astrbot/api/event/filter/__init__.py +37 -36
  4. astrbot/api/platform/__init__.py +7 -8
  5. astrbot/api/provider/__init__.py +7 -7
  6. astrbot/api/star/__init__.py +3 -4
  7. astrbot/api/util/__init__.py +2 -2
  8. astrbot/cli/__main__.py +5 -5
  9. astrbot/cli/commands/__init__.py +3 -3
  10. astrbot/cli/commands/cmd_conf.py +19 -16
  11. astrbot/cli/commands/cmd_init.py +3 -2
  12. astrbot/cli/commands/cmd_plug.py +8 -10
  13. astrbot/cli/commands/cmd_run.py +5 -6
  14. astrbot/cli/utils/__init__.py +6 -6
  15. astrbot/cli/utils/basic.py +14 -14
  16. astrbot/cli/utils/plugin.py +24 -15
  17. astrbot/cli/utils/version_comparator.py +10 -12
  18. astrbot/core/__init__.py +8 -6
  19. astrbot/core/agent/agent.py +3 -2
  20. astrbot/core/agent/handoff.py +6 -2
  21. astrbot/core/agent/hooks.py +9 -6
  22. astrbot/core/agent/mcp_client.py +50 -15
  23. astrbot/core/agent/message.py +168 -0
  24. astrbot/core/agent/response.py +2 -1
  25. astrbot/core/agent/run_context.py +2 -3
  26. astrbot/core/agent/runners/base.py +10 -13
  27. astrbot/core/agent/runners/tool_loop_agent_runner.py +52 -51
  28. astrbot/core/agent/tool.py +60 -41
  29. astrbot/core/agent/tool_executor.py +9 -3
  30. astrbot/core/astr_agent_context.py +3 -1
  31. astrbot/core/astrbot_config_mgr.py +29 -9
  32. astrbot/core/config/__init__.py +2 -2
  33. astrbot/core/config/astrbot_config.py +28 -26
  34. astrbot/core/config/default.py +44 -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 +18 -13
  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 +47 -29
  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 +40 -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 +102 -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 +116 -0
  181. astrbot/core/provider/sources/xinference_stt_provider.py +197 -0
  182. astrbot/core/star/__init__.py +16 -11
  183. astrbot/core/star/config.py +10 -15
  184. astrbot/core/star/context.py +109 -84
  185. astrbot/core/star/filter/__init__.py +4 -3
  186. astrbot/core/star/filter/command.py +30 -28
  187. astrbot/core/star/filter/command_group.py +27 -24
  188. astrbot/core/star/filter/custom_filter.py +6 -5
  189. astrbot/core/star/filter/event_message_type.py +4 -2
  190. astrbot/core/star/filter/permission.py +4 -2
  191. astrbot/core/star/filter/platform_adapter_type.py +4 -2
  192. astrbot/core/star/filter/regex.py +4 -2
  193. astrbot/core/star/register/__init__.py +19 -19
  194. astrbot/core/star/register/star.py +6 -2
  195. astrbot/core/star/register/star_handler.py +96 -73
  196. astrbot/core/star/session_llm_manager.py +48 -14
  197. astrbot/core/star/session_plugin_manager.py +29 -15
  198. astrbot/core/star/star.py +1 -2
  199. astrbot/core/star/star_handler.py +13 -8
  200. astrbot/core/star/star_manager.py +151 -59
  201. astrbot/core/star/star_tools.py +44 -37
  202. astrbot/core/star/updator.py +10 -10
  203. astrbot/core/umop_config_router.py +10 -4
  204. astrbot/core/updator.py +13 -5
  205. astrbot/core/utils/astrbot_path.py +3 -5
  206. astrbot/core/utils/dify_api_client.py +33 -15
  207. astrbot/core/utils/io.py +66 -42
  208. astrbot/core/utils/log_pipe.py +1 -1
  209. astrbot/core/utils/metrics.py +7 -7
  210. astrbot/core/utils/path_util.py +15 -16
  211. astrbot/core/utils/pip_installer.py +5 -5
  212. astrbot/core/utils/session_waiter.py +19 -20
  213. astrbot/core/utils/shared_preferences.py +45 -20
  214. astrbot/core/utils/t2i/__init__.py +4 -1
  215. astrbot/core/utils/t2i/network_strategy.py +35 -26
  216. astrbot/core/utils/t2i/renderer.py +11 -5
  217. astrbot/core/utils/t2i/template_manager.py +14 -15
  218. astrbot/core/utils/tencent_record_helper.py +19 -13
  219. astrbot/core/utils/version_comparator.py +10 -13
  220. astrbot/core/zip_updator.py +43 -40
  221. astrbot/dashboard/routes/__init__.py +18 -18
  222. astrbot/dashboard/routes/auth.py +10 -8
  223. astrbot/dashboard/routes/chat.py +30 -21
  224. astrbot/dashboard/routes/config.py +92 -75
  225. astrbot/dashboard/routes/conversation.py +46 -39
  226. astrbot/dashboard/routes/file.py +4 -2
  227. astrbot/dashboard/routes/knowledge_base.py +47 -40
  228. astrbot/dashboard/routes/log.py +9 -4
  229. astrbot/dashboard/routes/persona.py +19 -16
  230. astrbot/dashboard/routes/plugin.py +69 -55
  231. astrbot/dashboard/routes/route.py +3 -1
  232. astrbot/dashboard/routes/session_management.py +130 -116
  233. astrbot/dashboard/routes/stat.py +34 -34
  234. astrbot/dashboard/routes/t2i.py +15 -12
  235. astrbot/dashboard/routes/tools.py +47 -52
  236. astrbot/dashboard/routes/update.py +32 -28
  237. astrbot/dashboard/server.py +30 -26
  238. astrbot/dashboard/utils.py +8 -4
  239. {astrbot-4.5.0.dist-info → astrbot-4.5.2.dist-info}/METADATA +4 -2
  240. astrbot-4.5.2.dist-info/RECORD +261 -0
  241. astrbot-4.5.0.dist-info/RECORD +0 -258
  242. {astrbot-4.5.0.dist-info → astrbot-4.5.2.dist-info}/WHEEL +0 -0
  243. {astrbot-4.5.0.dist-info → astrbot-4.5.2.dist-info}/entry_points.txt +0 -0
  244. {astrbot-4.5.0.dist-info → astrbot-4.5.2.dist-info}/licenses/LICENSE +0 -0
@@ -1,8 +1,11 @@
1
- import json
2
1
  import asyncio
3
- import aiohttp
4
2
  import io
5
- from typing import Dict, List, Any, AsyncGenerator
3
+ import json
4
+ from collections.abc import AsyncGenerator
5
+ from typing import Any
6
+
7
+ import aiohttp
8
+
6
9
  from astrbot.core import logger
7
10
 
8
11
 
@@ -32,7 +35,9 @@ class CozeAPIClient:
32
35
  "Accept": "text/event-stream",
33
36
  }
34
37
  self.session = aiohttp.ClientSession(
35
- headers=headers, timeout=timeout, connector=connector
38
+ headers=headers,
39
+ timeout=timeout,
40
+ connector=connector,
36
41
  )
37
42
  return self.session
38
43
 
@@ -46,6 +51,7 @@ class CozeAPIClient:
46
51
  file_data (bytes): 文件的二进制数据
47
52
  Returns:
48
53
  str: 上传成功后返回的 file_id
54
+
49
55
  """
50
56
  session = await self._ensure_session()
51
57
  url = f"{self.api_base}/v1/files/upload"
@@ -64,12 +70,12 @@ class CozeAPIClient:
64
70
 
65
71
  response_text = await response.text()
66
72
  logger.debug(
67
- f"文件上传响应状态: {response.status}, 内容: {response_text}"
73
+ f"文件上传响应状态: {response.status}, 内容: {response_text}",
68
74
  )
69
75
 
70
76
  if response.status != 200:
71
77
  raise Exception(
72
- f"文件上传失败,状态码: {response.status}, 响应: {response_text}"
78
+ f"文件上传失败,状态码: {response.status}, 响应: {response_text}",
73
79
  )
74
80
 
75
81
  try:
@@ -88,8 +94,8 @@ class CozeAPIClient:
88
94
  logger.error("文件上传超时")
89
95
  raise Exception("文件上传超时")
90
96
  except Exception as e:
91
- logger.error(f"文件上传失败: {str(e)}")
92
- raise Exception(f"文件上传失败: {str(e)}")
97
+ logger.error(f"文件上传失败: {e!s}")
98
+ raise Exception(f"文件上传失败: {e!s}")
93
99
 
94
100
  async def download_image(self, image_url: str) -> bytes:
95
101
  """下载图片并返回字节数据
@@ -98,6 +104,7 @@ class CozeAPIClient:
98
104
  image_url (str): 图片的URL
99
105
  Returns:
100
106
  bytes: 图片的二进制数据
107
+
101
108
  """
102
109
  session = await self._ensure_session()
103
110
 
@@ -110,19 +117,19 @@ class CozeAPIClient:
110
117
  return image_data
111
118
 
112
119
  except Exception as e:
113
- logger.error(f"下载图片失败 {image_url}: {str(e)}")
114
- raise Exception(f"下载图片失败: {str(e)}")
120
+ logger.error(f"下载图片失败 {image_url}: {e!s}")
121
+ raise Exception(f"下载图片失败: {e!s}")
115
122
 
116
123
  async def chat_messages(
117
124
  self,
118
125
  bot_id: str,
119
126
  user_id: str,
120
- additional_messages: List[Dict] | None = None,
127
+ additional_messages: list[dict] | None = None,
121
128
  conversation_id: str | None = None,
122
129
  auto_save_history: bool = True,
123
130
  stream: bool = True,
124
131
  timeout: float = 120,
125
- ) -> AsyncGenerator[Dict[str, Any], None]:
132
+ ) -> AsyncGenerator[dict[str, Any], None]:
126
133
  """发送聊天消息并返回流式响应
127
134
 
128
135
  Args:
@@ -133,6 +140,7 @@ class CozeAPIClient:
133
140
  auto_save_history: 是否自动保存历史
134
141
  stream: 是否流式响应
135
142
  timeout: 超时时间
143
+
136
144
  """
137
145
  session = await self._ensure_session()
138
146
  url = f"{self.api_base}/v3/chat"
@@ -198,7 +206,7 @@ class CozeAPIClient:
198
206
  except asyncio.TimeoutError:
199
207
  raise Exception(f"Coze API 流式请求超时 ({timeout}秒)")
200
208
  except Exception as e:
201
- raise Exception(f"Coze API 流式请求失败: {str(e)}")
209
+ raise Exception(f"Coze API 流式请求失败: {e!s}")
202
210
 
203
211
  async def clear_context(self, conversation_id: str):
204
212
  """清空会话上下文
@@ -207,6 +215,7 @@ class CozeAPIClient:
207
215
  conversation_id: 会话ID
208
216
  Returns:
209
217
  dict: API响应结果
218
+
210
219
  """
211
220
  session = await self._ensure_session()
212
221
  url = f"{self.api_base}/v3/conversation/message/clear_context"
@@ -230,7 +239,7 @@ class CozeAPIClient:
230
239
  except asyncio.TimeoutError:
231
240
  raise Exception("Coze API 请求超时")
232
241
  except aiohttp.ClientError as e:
233
- raise Exception(f"Coze API 请求失败: {str(e)}")
242
+ raise Exception(f"Coze API 请求失败: {e!s}")
234
243
 
235
244
  async def get_message_list(
236
245
  self,
@@ -248,6 +257,7 @@ class CozeAPIClient:
248
257
  offset: 偏移量
249
258
  Returns:
250
259
  dict: API响应结果
260
+
251
261
  """
252
262
  session = await self._ensure_session()
253
263
  url = f"{self.api_base}/v3/conversation/message/list"
@@ -264,8 +274,8 @@ class CozeAPIClient:
264
274
  return await response.json()
265
275
 
266
276
  except Exception as e:
267
- logger.error(f"获取Coze消息列表失败: {str(e)}")
268
- raise Exception(f"获取Coze消息列表失败: {str(e)}")
277
+ logger.error(f"获取Coze消息列表失败: {e!s}")
278
+ raise Exception(f"获取Coze消息列表失败: {e!s}")
269
279
 
270
280
  async def close(self):
271
281
  """关闭会话"""
@@ -275,8 +285,8 @@ class CozeAPIClient:
275
285
 
276
286
 
277
287
  if __name__ == "__main__":
278
- import os
279
288
  import asyncio
289
+ import os
280
290
 
281
291
  async def test_coze_api_client():
282
292
  api_key = os.getenv("COZE_API_KEY", "")
@@ -1,13 +1,15 @@
1
- import json
2
- import os
3
1
  import base64
4
2
  import hashlib
5
- from typing import AsyncGenerator, Dict
6
- from astrbot.core.message.message_event_result import MessageChain
3
+ import json
4
+ import os
5
+ from collections.abc import AsyncGenerator
6
+
7
7
  import astrbot.core.message.components as Comp
8
- from astrbot.api.provider import Provider
9
8
  from astrbot import logger
9
+ from astrbot.api.provider import Provider
10
+ from astrbot.core.message.message_event_result import MessageChain
10
11
  from astrbot.core.provider.entities import LLMResponse
12
+
11
13
  from ..register import register_provider_adapter
12
14
  from .coze_api_client import CozeAPIClient
13
15
 
@@ -34,18 +36,18 @@ class ProviderCoze(Provider):
34
36
  self.api_base: str = provider_config.get("coze_api_base", "https://api.coze.cn")
35
37
 
36
38
  if not isinstance(self.api_base, str) or not self.api_base.startswith(
37
- ("http://", "https://")
39
+ ("http://", "https://"),
38
40
  ):
39
41
  raise Exception(
40
- "Coze API Base URL 格式不正确,必须以 http:// 或 https:// 开头。"
42
+ "Coze API Base URL 格式不正确,必须以 http:// 或 https:// 开头。",
41
43
  )
42
44
 
43
45
  self.timeout = provider_config.get("timeout", 120)
44
46
  if isinstance(self.timeout, str):
45
47
  self.timeout = int(self.timeout)
46
48
  self.auto_save_history = provider_config.get("auto_save_history", True)
47
- self.conversation_ids: Dict[str, str] = {}
48
- self.file_id_cache: Dict[str, Dict[str, str]] = {}
49
+ self.conversation_ids: dict[str, str] = {}
50
+ self.file_id_cache: dict[str, dict[str, str]] = {}
49
51
 
50
52
  # 创建 API 客户端
51
53
  self.api_client = CozeAPIClient(api_key=self.api_key, api_base=self.api_base)
@@ -59,8 +61,8 @@ class ProviderCoze(Provider):
59
61
 
60
62
  Returns:
61
63
  str: 缓存键
62
- """
63
64
 
65
+ """
64
66
  try:
65
67
  if is_base64 and data.startswith("data:image/"):
66
68
  try:
@@ -71,26 +73,24 @@ class ProviderCoze(Provider):
71
73
  except Exception:
72
74
  cache_key = hashlib.md5(encoded.encode("utf-8")).hexdigest()
73
75
  return cache_key
76
+ elif data.startswith(("http://", "https://")):
77
+ # URL图片,使用URL作为缓存键
78
+ cache_key = hashlib.md5(data.encode("utf-8")).hexdigest()
79
+ return cache_key
74
80
  else:
75
- if data.startswith(("http://", "https://")):
76
- # URL图片,使用URL作为缓存键
77
- cache_key = hashlib.md5(data.encode("utf-8")).hexdigest()
78
- return cache_key
79
- else:
80
- clean_path = (
81
- data.split("_")[0]
82
- if "_" in data and len(data.split("_")) >= 3
83
- else data
84
- )
81
+ clean_path = (
82
+ data.split("_")[0]
83
+ if "_" in data and len(data.split("_")) >= 3
84
+ else data
85
+ )
85
86
 
86
- if os.path.exists(clean_path):
87
- with open(clean_path, "rb") as f:
88
- file_content = f.read()
89
- cache_key = hashlib.md5(file_content).hexdigest()
90
- return cache_key
91
- else:
92
- cache_key = hashlib.md5(clean_path.encode("utf-8")).hexdigest()
93
- return cache_key
87
+ if os.path.exists(clean_path):
88
+ with open(clean_path, "rb") as f:
89
+ file_content = f.read()
90
+ cache_key = hashlib.md5(file_content).hexdigest()
91
+ return cache_key
92
+ cache_key = hashlib.md5(clean_path.encode("utf-8")).hexdigest()
93
+ return cache_key
94
94
 
95
95
  except Exception as e:
96
96
  cache_key = hashlib.md5(data.encode("utf-8")).hexdigest()
@@ -117,7 +117,9 @@ class ProviderCoze(Provider):
117
117
  return file_id
118
118
 
119
119
  async def _download_and_upload_image(
120
- self, image_url: str, session_id: str | None = None
120
+ self,
121
+ image_url: str,
122
+ session_id: str | None = None,
121
123
  ) -> str:
122
124
  """下载图片并上传到 Coze,返回 file_id"""
123
125
  # 计算哈希实现缓存
@@ -142,14 +144,15 @@ class ProviderCoze(Provider):
142
144
  return file_id
143
145
 
144
146
  except Exception as e:
145
- logger.error(f"处理图片失败 {image_url}: {str(e)}")
146
- raise Exception(f"处理图片失败: {str(e)}")
147
+ logger.error(f"处理图片失败 {image_url}: {e!s}")
148
+ raise Exception(f"处理图片失败: {e!s}")
147
149
 
148
150
  async def _process_context_images(
149
- self, content: str | list, session_id: str
151
+ self,
152
+ content: str | list,
153
+ session_id: str,
150
154
  ) -> str:
151
155
  """处理上下文中的图片内容,将 base64 图片上传并替换为 file_id"""
152
-
153
156
  try:
154
157
  if isinstance(content, str):
155
158
  return content
@@ -184,14 +187,15 @@ class ProviderCoze(Provider):
184
187
  continue
185
188
  # 计算哈希用于缓存
186
189
  cache_key = self._generate_cache_key(
187
- image_data, is_base64=image_data.startswith("data:image/")
190
+ image_data,
191
+ is_base64=image_data.startswith("data:image/"),
188
192
  )
189
193
 
190
194
  # 检查缓存
191
195
  if cache_key in self.file_id_cache[session_id]:
192
196
  file_id = self.file_id_cache[session_id][cache_key]
193
197
  processed_content.append(
194
- {"type": "image", "file_id": file_id}
198
+ {"type": "image", "file_id": file_id},
195
199
  )
196
200
  else:
197
201
  # 上传图片并缓存
@@ -207,7 +211,8 @@ class ProviderCoze(Provider):
207
211
  elif image_data.startswith(("http://", "https://")):
208
212
  # URL 图片
209
213
  file_id = await self._download_and_upload_image(
210
- image_data, session_id
214
+ image_data,
215
+ session_id,
211
216
  )
212
217
  # 为URL图片也添加缓存
213
218
  self.file_id_cache[session_id][cache_key] = file_id
@@ -222,22 +227,21 @@ class ProviderCoze(Provider):
222
227
  )
223
228
  else:
224
229
  logger.warning(
225
- f"无法处理的图片格式: {image_data[:50]}..."
230
+ f"无法处理的图片格式: {image_data[:50]}...",
226
231
  )
227
232
  continue
228
233
 
229
234
  processed_content.append(
230
- {"type": "image", "file_id": file_id}
235
+ {"type": "image", "file_id": file_id},
231
236
  )
232
237
 
233
238
  result = json.dumps(processed_content, ensure_ascii=False)
234
239
  return result
235
240
  except Exception as e:
236
- logger.error(f"处理上下文图片失败: {str(e)}")
241
+ logger.error(f"处理上下文图片失败: {e!s}")
237
242
  if isinstance(content, str):
238
243
  return content
239
- else:
240
- return json.dumps(content, ensure_ascii=False)
244
+ return json.dumps(content, ensure_ascii=False)
241
245
 
242
246
  async def text_chat(
243
247
  self,
@@ -262,8 +266,10 @@ class ProviderCoze(Provider):
262
266
  system_prompt (str): 系统提示语
263
267
  tool_calls_result (ToolCallsResult | List[ToolCallsResult]): 工具调用结果(不支持)
264
268
  model (str): 模型名称(不支持)
269
+
265
270
  Returns:
266
271
  LLMResponse: LLM响应对象
272
+
267
273
  """
268
274
  accumulated_content = ""
269
275
  final_response = None
@@ -291,8 +297,7 @@ class ProviderCoze(Provider):
291
297
  if accumulated_content:
292
298
  chain = MessageChain(chain=[Comp.Plain(accumulated_content)])
293
299
  return LLMResponse(role="assistant", result_chain=chain)
294
- else:
295
- return LLMResponse(role="assistant", completion_text="")
300
+ return LLMResponse(role="assistant", completion_text="")
296
301
 
297
302
  async def text_chat_stream(
298
303
  self,
@@ -319,9 +324,14 @@ class ProviderCoze(Provider):
319
324
  if system_prompt:
320
325
  if not self.auto_save_history or not conversation_id:
321
326
  additional_messages.append(
322
- {"role": "system", "content": system_prompt, "content_type": "text"}
327
+ {
328
+ "role": "system",
329
+ "content": system_prompt,
330
+ "content_type": "text",
331
+ },
323
332
  )
324
333
 
334
+ contexts = self._ensure_message_to_dicts(contexts)
325
335
  if not self.auto_save_history and contexts:
326
336
  # 如果关闭了自动保存历史,传入上下文
327
337
  for ctx in contexts:
@@ -343,14 +353,15 @@ class ProviderCoze(Provider):
343
353
  )
344
354
  ):
345
355
  processed_content = await self._process_context_images(
346
- content, user_id
356
+ content,
357
+ user_id,
347
358
  )
348
359
  additional_messages.append(
349
360
  {
350
361
  "role": ctx["role"],
351
362
  "content": processed_content,
352
363
  "content_type": "object_string",
353
- }
364
+ },
354
365
  )
355
366
  else:
356
367
  # 纯文本
@@ -363,7 +374,7 @@ class ProviderCoze(Provider):
363
374
  else json.dumps(content, ensure_ascii=False)
364
375
  ),
365
376
  "content_type": "text",
366
- }
377
+ },
367
378
  )
368
379
  else:
369
380
  logger.info(f"[Coze] 跳过格式不正确的上下文: {ctx}")
@@ -380,7 +391,8 @@ class ProviderCoze(Provider):
380
391
  if url.startswith(("http://", "https://")):
381
392
  # 网络图片
382
393
  file_id = await self._download_and_upload_image(
383
- url, user_id
394
+ url,
395
+ user_id,
384
396
  )
385
397
  else:
386
398
  # 本地文件或 base64
@@ -389,37 +401,41 @@ class ProviderCoze(Provider):
389
401
  _, encoded = url.split(",", 1)
390
402
  image_data = base64.b64decode(encoded)
391
403
  cache_key = self._generate_cache_key(
392
- url, is_base64=True
404
+ url,
405
+ is_base64=True,
406
+ )
407
+ file_id = await self._upload_file(
408
+ image_data,
409
+ user_id,
410
+ cache_key,
411
+ )
412
+ # 本地文件
413
+ elif os.path.exists(url):
414
+ with open(url, "rb") as f:
415
+ image_data = f.read()
416
+ # 用文件路径和修改时间来缓存
417
+ file_stat = os.stat(url)
418
+ cache_key = self._generate_cache_key(
419
+ f"{url}_{file_stat.st_mtime}_{file_stat.st_size}",
420
+ is_base64=False,
393
421
  )
394
422
  file_id = await self._upload_file(
395
- image_data, user_id, cache_key
423
+ image_data,
424
+ user_id,
425
+ cache_key,
396
426
  )
397
427
  else:
398
- # 本地文件
399
- if os.path.exists(url):
400
- with open(url, "rb") as f:
401
- image_data = f.read()
402
- # 用文件路径和修改时间来缓存
403
- file_stat = os.stat(url)
404
- cache_key = self._generate_cache_key(
405
- f"{url}_{file_stat.st_mtime}_{file_stat.st_size}",
406
- is_base64=False,
407
- )
408
- file_id = await self._upload_file(
409
- image_data, user_id, cache_key
410
- )
411
- else:
412
- logger.warning(f"图片文件不存在: {url}")
413
- continue
428
+ logger.warning(f"图片文件不存在: {url}")
429
+ continue
414
430
 
415
431
  object_string_content.append(
416
432
  {
417
433
  "type": "image",
418
434
  "file_id": file_id,
419
- }
435
+ },
420
436
  )
421
437
  except Exception as e:
422
- logger.error(f"处理图片失败 {url}: {str(e)}")
438
+ logger.error(f"处理图片失败 {url}: {e!s}")
423
439
  continue
424
440
 
425
441
  if object_string_content:
@@ -429,18 +445,17 @@ class ProviderCoze(Provider):
429
445
  "role": "user",
430
446
  "content": content,
431
447
  "content_type": "object_string",
432
- }
433
- )
434
- else:
435
- # 纯文本
436
- if prompt:
437
- additional_messages.append(
438
- {
439
- "role": "user",
440
- "content": prompt,
441
- "content_type": "text",
442
- }
448
+ },
443
449
  )
450
+ # 纯文本
451
+ elif prompt:
452
+ additional_messages.append(
453
+ {
454
+ "role": "user",
455
+ "content": prompt,
456
+ "content_type": "text",
457
+ },
458
+ )
444
459
 
445
460
  try:
446
461
  accumulated_content = ""
@@ -534,10 +549,10 @@ class ProviderCoze(Provider):
534
549
  )
535
550
 
536
551
  except Exception as e:
537
- logger.error(f"Coze 流式请求失败: {str(e)}")
552
+ logger.error(f"Coze 流式请求失败: {e!s}")
538
553
  yield LLMResponse(
539
554
  role="err",
540
- completion_text=f"Coze 流式请求失败: {str(e)}",
555
+ completion_text=f"Coze 流式请求失败: {e!s}",
541
556
  is_chunk=False,
542
557
  )
543
558
 
@@ -558,12 +573,11 @@ class ProviderCoze(Provider):
558
573
  if "code" in response and response["code"] == 0:
559
574
  self.conversation_ids.pop(user_id, None)
560
575
  return True
561
- else:
562
- logger.warning(f"清空 Coze 会话上下文失败: {response}")
563
- return False
576
+ logger.warning(f"清空 Coze 会话上下文失败: {response}")
577
+ return False
564
578
 
565
579
  except Exception as e:
566
- logger.error(f"清空 Coze 会话失败: {str(e)}")
580
+ logger.error(f"清空 Coze 会话失败: {e!s}")
567
581
  return False
568
582
 
569
583
  async def get_current_key(self):
@@ -590,7 +604,10 @@ class ProviderCoze(Provider):
590
604
  self.bot_id = model
591
605
 
592
606
  async def get_human_readable_context(
593
- self, session_id: str, page: int = 1, page_size: int = 10
607
+ self,
608
+ session_id: str,
609
+ page: int = 1,
610
+ page_size: int = 10,
594
611
  ):
595
612
  """获取人类可读的上下文历史"""
596
613
  user_id = session_id
@@ -627,7 +644,7 @@ class ProviderCoze(Provider):
627
644
  return readable_history
628
645
 
629
646
  except Exception as e:
630
- logger.error(f"获取 Coze 消息历史失败: {str(e)}")
647
+ logger.error(f"获取 Coze 消息历史失败: {e!s}")
631
648
  return []
632
649
 
633
650
  async def terminate(self):
@@ -1,14 +1,17 @@
1
- import re
2
1
  import asyncio
3
2
  import functools
4
- from .. import Provider, Personality
3
+ import re
4
+
5
+ from dashscope import Application
6
+ from dashscope.app.application_response import ApplicationResponse
7
+
8
+ from astrbot.core import logger, sp
9
+ from astrbot.core.message.message_event_result import MessageChain
10
+
11
+ from .. import Personality, Provider
5
12
  from ..entities import LLMResponse
6
13
  from ..register import register_provider_adapter
7
- from astrbot.core.message.message_event_result import MessageChain
8
14
  from .openai_source import ProviderOpenAIOfficial
9
- from astrbot.core import logger, sp
10
- from dashscope import Application
11
- from dashscope.app.application_response import ApplicationResponse
12
15
 
13
16
 
14
17
  @register_provider_adapter("dashscope", "Dashscope APP 适配器。")
@@ -50,6 +53,7 @@ class ProviderDashscope(ProviderOpenAIOfficial):
50
53
 
51
54
  Returns:
52
55
  bool: 是否有 RAG 选项
56
+
53
57
  """
54
58
  if self.rag_options and (
55
59
  len(self.rag_options.get("pipeline_ids", [])) > 0
@@ -62,13 +66,15 @@ class ProviderDashscope(ProviderOpenAIOfficial):
62
66
  self,
63
67
  prompt: str,
64
68
  session_id=None,
65
- image_urls=[],
69
+ image_urls=None,
66
70
  func_tool=None,
67
71
  contexts=None,
68
72
  system_prompt=None,
69
73
  model=None,
70
74
  **kwargs,
71
75
  ) -> LLMResponse:
76
+ if image_urls is None:
77
+ image_urls = []
72
78
  if contexts is None:
73
79
  contexts = []
74
80
  # 获得会话变量
@@ -127,12 +133,12 @@ class ProviderDashscope(ProviderOpenAIOfficial):
127
133
 
128
134
  if response.status_code != 200:
129
135
  logger.error(
130
- f"阿里云百炼请求失败: request_id={response.request_id}, code={response.status_code}, message={response.message}, 请参考文档:https://help.aliyun.com/zh/model-studio/developer-reference/error-code"
136
+ f"阿里云百炼请求失败: request_id={response.request_id}, code={response.status_code}, message={response.message}, 请参考文档:https://help.aliyun.com/zh/model-studio/developer-reference/error-code",
131
137
  )
132
138
  return LLMResponse(
133
139
  role="err",
134
140
  result_chain=MessageChain().message(
135
- f"阿里云百炼请求失败: message={response.message} code={response.status_code}"
141
+ f"阿里云百炼请求失败: message={response.message} code={response.status_code}",
136
142
  ),
137
143
  )
138
144
 
@@ -140,14 +146,15 @@ class ProviderDashscope(ProviderOpenAIOfficial):
140
146
  # RAG 引用脚标格式化
141
147
  output_text = re.sub(r"<ref>\[(\d+)\]</ref>", r"[\1]", output_text)
142
148
  if self.output_reference and response.output.get("doc_references", None):
143
- ref_str = ""
149
+ ref_parts = []
144
150
  for ref in response.output.get("doc_references", []) or []:
145
151
  ref_title = (
146
152
  ref.get("title", "")
147
153
  if ref.get("title")
148
154
  else ref.get("doc_name", "")
149
155
  )
150
- ref_str += f"{ref['index_id']}. {ref_title}\n"
156
+ ref_parts.append(f"{ref['index_id']}. {ref_title}\n")
157
+ ref_str = "".join(ref_parts)
151
158
  output_text += f"\n\n回答来源:\n{ref_str}"
152
159
 
153
160
  llm_response = LLMResponse("assistant")