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,403 @@
1
+ import asyncio
2
+ import functools
3
+ import queue
4
+ import re
5
+ import sys
6
+ import threading
7
+ import typing as T
8
+
9
+ from dashscope import Application
10
+ from dashscope.app.application_response import ApplicationResponse
11
+
12
+ import astrbot.core.message.components as Comp
13
+ from astrbot.core import logger, sp
14
+ from astrbot.core.message.message_event_result import MessageChain
15
+ from astrbot.core.provider.entities import (
16
+ LLMResponse,
17
+ ProviderRequest,
18
+ )
19
+
20
+ from ...hooks import BaseAgentRunHooks
21
+ from ...response import AgentResponseData
22
+ from ...run_context import ContextWrapper, TContext
23
+ from ..base import AgentResponse, AgentState, BaseAgentRunner
24
+
25
+ if sys.version_info >= (3, 12):
26
+ from typing import override
27
+ else:
28
+ from typing_extensions import override
29
+
30
+
31
+ class DashscopeAgentRunner(BaseAgentRunner[TContext]):
32
+ """Dashscope Agent Runner"""
33
+
34
+ @override
35
+ async def reset(
36
+ self,
37
+ request: ProviderRequest,
38
+ run_context: ContextWrapper[TContext],
39
+ agent_hooks: BaseAgentRunHooks[TContext],
40
+ provider_config: dict,
41
+ **kwargs: T.Any,
42
+ ) -> None:
43
+ self.req = request
44
+ self.streaming = kwargs.get("streaming", False)
45
+ self.final_llm_resp = None
46
+ self._state = AgentState.IDLE
47
+ self.agent_hooks = agent_hooks
48
+ self.run_context = run_context
49
+
50
+ self.api_key = provider_config.get("dashscope_api_key", "")
51
+ if not self.api_key:
52
+ raise Exception("阿里云百炼 API Key 不能为空。")
53
+ self.app_id = provider_config.get("dashscope_app_id", "")
54
+ if not self.app_id:
55
+ raise Exception("阿里云百炼 APP ID 不能为空。")
56
+ self.dashscope_app_type = provider_config.get("dashscope_app_type", "")
57
+ if not self.dashscope_app_type:
58
+ raise Exception("阿里云百炼 APP 类型不能为空。")
59
+
60
+ self.variables: dict = provider_config.get("variables", {}) or {}
61
+ self.rag_options: dict = provider_config.get("rag_options", {})
62
+ self.output_reference = self.rag_options.get("output_reference", False)
63
+ self.rag_options = self.rag_options.copy()
64
+ self.rag_options.pop("output_reference", None)
65
+
66
+ self.timeout = provider_config.get("timeout", 120)
67
+ if isinstance(self.timeout, str):
68
+ self.timeout = int(self.timeout)
69
+
70
+ def has_rag_options(self):
71
+ """判断是否有 RAG 选项
72
+
73
+ Returns:
74
+ bool: 是否有 RAG 选项
75
+
76
+ """
77
+ if self.rag_options and (
78
+ len(self.rag_options.get("pipeline_ids", [])) > 0
79
+ or len(self.rag_options.get("file_ids", [])) > 0
80
+ ):
81
+ return True
82
+ return False
83
+
84
+ @override
85
+ async def step(self):
86
+ """
87
+ 执行 Dashscope Agent 的一个步骤
88
+ """
89
+ if not self.req:
90
+ raise ValueError("Request is not set. Please call reset() first.")
91
+
92
+ if self._state == AgentState.IDLE:
93
+ try:
94
+ await self.agent_hooks.on_agent_begin(self.run_context)
95
+ except Exception as e:
96
+ logger.error(f"Error in on_agent_begin hook: {e}", exc_info=True)
97
+
98
+ # 开始处理,转换到运行状态
99
+ self._transition_state(AgentState.RUNNING)
100
+
101
+ try:
102
+ # 执行 Dashscope 请求并处理结果
103
+ async for response in self._execute_dashscope_request():
104
+ yield response
105
+ except Exception as e:
106
+ logger.error(f"阿里云百炼请求失败:{str(e)}")
107
+ self._transition_state(AgentState.ERROR)
108
+ self.final_llm_resp = LLMResponse(
109
+ role="err", completion_text=f"阿里云百炼请求失败:{str(e)}"
110
+ )
111
+ yield AgentResponse(
112
+ type="err",
113
+ data=AgentResponseData(
114
+ chain=MessageChain().message(f"阿里云百炼请求失败:{str(e)}")
115
+ ),
116
+ )
117
+
118
+ @override
119
+ async def step_until_done(
120
+ self, max_step: int = 30
121
+ ) -> T.AsyncGenerator[AgentResponse, None]:
122
+ while not self.done():
123
+ async for resp in self.step():
124
+ yield resp
125
+
126
+ def _consume_sync_generator(
127
+ self, response: T.Any, response_queue: queue.Queue
128
+ ) -> None:
129
+ """在线程中消费同步generator,将结果放入队列
130
+
131
+ Args:
132
+ response: 同步generator对象
133
+ response_queue: 用于传递数据的队列
134
+
135
+ """
136
+ try:
137
+ if self.streaming:
138
+ for chunk in response:
139
+ response_queue.put(("data", chunk))
140
+ else:
141
+ response_queue.put(("data", response))
142
+ except Exception as e:
143
+ response_queue.put(("error", e))
144
+ finally:
145
+ response_queue.put(("done", None))
146
+
147
+ async def _process_stream_chunk(
148
+ self, chunk: ApplicationResponse, output_text: str
149
+ ) -> tuple[str, list | None, AgentResponse | None]:
150
+ """处理流式响应的单个chunk
151
+
152
+ Args:
153
+ chunk: Dashscope响应chunk
154
+ output_text: 当前累积的输出文本
155
+
156
+ Returns:
157
+ (更新后的output_text, doc_references, AgentResponse或None)
158
+
159
+ """
160
+ logger.debug(f"dashscope stream chunk: {chunk}")
161
+
162
+ if chunk.status_code != 200:
163
+ logger.error(
164
+ f"阿里云百炼请求失败: request_id={chunk.request_id}, code={chunk.status_code}, message={chunk.message}, 请参考文档:https://help.aliyun.com/zh/model-studio/developer-reference/error-code",
165
+ )
166
+ self._transition_state(AgentState.ERROR)
167
+ error_msg = (
168
+ f"阿里云百炼请求失败: message={chunk.message} code={chunk.status_code}"
169
+ )
170
+ self.final_llm_resp = LLMResponse(
171
+ role="err",
172
+ result_chain=MessageChain().message(error_msg),
173
+ )
174
+ return (
175
+ output_text,
176
+ None,
177
+ AgentResponse(
178
+ type="err",
179
+ data=AgentResponseData(chain=MessageChain().message(error_msg)),
180
+ ),
181
+ )
182
+
183
+ chunk_text = chunk.output.get("text", "") or ""
184
+ # RAG 引用脚标格式化
185
+ chunk_text = re.sub(r"<ref>\[(\d+)\]</ref>", r"[\1]", chunk_text)
186
+
187
+ response = None
188
+ if chunk_text:
189
+ output_text += chunk_text
190
+ response = AgentResponse(
191
+ type="streaming_delta",
192
+ data=AgentResponseData(chain=MessageChain().message(chunk_text)),
193
+ )
194
+
195
+ # 获取文档引用
196
+ doc_references = chunk.output.get("doc_references", None)
197
+
198
+ return output_text, doc_references, response
199
+
200
+ def _format_doc_references(self, doc_references: list) -> str:
201
+ """格式化文档引用为文本
202
+
203
+ Args:
204
+ doc_references: 文档引用列表
205
+
206
+ Returns:
207
+ 格式化后的引用文本
208
+
209
+ """
210
+ ref_parts = []
211
+ for ref in doc_references:
212
+ ref_title = (
213
+ ref.get("title", "") if ref.get("title") else ref.get("doc_name", "")
214
+ )
215
+ ref_parts.append(f"{ref['index_id']}. {ref_title}\n")
216
+ ref_str = "".join(ref_parts)
217
+ return f"\n\n回答来源:\n{ref_str}"
218
+
219
+ async def _build_request_payload(
220
+ self, prompt: str, session_id: str, contexts: list, system_prompt: str
221
+ ) -> dict:
222
+ """构建请求payload
223
+
224
+ Args:
225
+ prompt: 用户输入
226
+ session_id: 会话ID
227
+ contexts: 上下文列表
228
+ system_prompt: 系统提示词
229
+
230
+ Returns:
231
+ 请求payload字典
232
+
233
+ """
234
+ conversation_id = await sp.get_async(
235
+ scope="umo",
236
+ scope_id=session_id,
237
+ key="dashscope_conversation_id",
238
+ default="",
239
+ )
240
+ # 获得会话变量
241
+ payload_vars = self.variables.copy()
242
+ session_var = await sp.get_async(
243
+ scope="umo",
244
+ scope_id=session_id,
245
+ key="session_variables",
246
+ default={},
247
+ )
248
+ payload_vars.update(session_var)
249
+
250
+ if (
251
+ self.dashscope_app_type in ["agent", "dialog-workflow"]
252
+ and not self.has_rag_options()
253
+ ):
254
+ # 支持多轮对话的
255
+ p = {
256
+ "app_id": self.app_id,
257
+ "api_key": self.api_key,
258
+ "prompt": prompt,
259
+ "biz_params": payload_vars or None,
260
+ "stream": self.streaming,
261
+ "incremental_output": True,
262
+ }
263
+ if conversation_id:
264
+ p["session_id"] = conversation_id
265
+ return p
266
+ else:
267
+ # 不支持多轮对话的
268
+ payload = {
269
+ "app_id": self.app_id,
270
+ "prompt": prompt,
271
+ "api_key": self.api_key,
272
+ "biz_params": payload_vars or None,
273
+ "stream": self.streaming,
274
+ "incremental_output": True,
275
+ }
276
+ if self.rag_options:
277
+ payload["rag_options"] = self.rag_options
278
+ return payload
279
+
280
+ async def _handle_streaming_response(
281
+ self, response: T.Any, session_id: str
282
+ ) -> T.AsyncGenerator[AgentResponse, None]:
283
+ """处理流式响应
284
+
285
+ Args:
286
+ response: Dashscope 流式响应 generator
287
+
288
+ Yields:
289
+ AgentResponse 对象
290
+
291
+ """
292
+ response_queue = queue.Queue()
293
+ consumer_thread = threading.Thread(
294
+ target=self._consume_sync_generator,
295
+ args=(response, response_queue),
296
+ daemon=True,
297
+ )
298
+ consumer_thread.start()
299
+
300
+ output_text = ""
301
+ doc_references = None
302
+
303
+ while True:
304
+ try:
305
+ item_type, item_data = await asyncio.get_event_loop().run_in_executor(
306
+ None, response_queue.get, True, 1
307
+ )
308
+ except queue.Empty:
309
+ continue
310
+
311
+ if item_type == "done":
312
+ break
313
+ elif item_type == "error":
314
+ raise item_data
315
+ elif item_type == "data":
316
+ chunk = item_data
317
+ assert isinstance(chunk, ApplicationResponse)
318
+
319
+ (
320
+ output_text,
321
+ chunk_doc_refs,
322
+ response,
323
+ ) = await self._process_stream_chunk(chunk, output_text)
324
+
325
+ if response:
326
+ if response.type == "err":
327
+ yield response
328
+ return
329
+ yield response
330
+
331
+ if chunk_doc_refs:
332
+ doc_references = chunk_doc_refs
333
+
334
+ if chunk.output.session_id:
335
+ await sp.put_async(
336
+ scope="umo",
337
+ scope_id=session_id,
338
+ key="dashscope_conversation_id",
339
+ value=chunk.output.session_id,
340
+ )
341
+
342
+ # 添加 RAG 引用
343
+ if self.output_reference and doc_references:
344
+ ref_text = self._format_doc_references(doc_references)
345
+ output_text += ref_text
346
+
347
+ if self.streaming:
348
+ yield AgentResponse(
349
+ type="streaming_delta",
350
+ data=AgentResponseData(chain=MessageChain().message(ref_text)),
351
+ )
352
+
353
+ # 创建最终响应
354
+ chain = MessageChain(chain=[Comp.Plain(output_text)])
355
+ self.final_llm_resp = LLMResponse(role="assistant", result_chain=chain)
356
+ self._transition_state(AgentState.DONE)
357
+
358
+ try:
359
+ await self.agent_hooks.on_agent_done(self.run_context, self.final_llm_resp)
360
+ except Exception as e:
361
+ logger.error(f"Error in on_agent_done hook: {e}", exc_info=True)
362
+
363
+ # 返回最终结果
364
+ yield AgentResponse(
365
+ type="llm_result",
366
+ data=AgentResponseData(chain=chain),
367
+ )
368
+
369
+ async def _execute_dashscope_request(self):
370
+ """执行 Dashscope 请求的核心逻辑"""
371
+ prompt = self.req.prompt or ""
372
+ session_id = self.req.session_id or "unknown"
373
+ image_urls = self.req.image_urls or []
374
+ contexts = self.req.contexts or []
375
+ system_prompt = self.req.system_prompt
376
+
377
+ # 检查图片输入
378
+ if image_urls:
379
+ logger.warning("阿里云百炼暂不支持图片输入,将自动忽略图片内容。")
380
+
381
+ # 构建请求payload
382
+ payload = await self._build_request_payload(
383
+ prompt, session_id, contexts, system_prompt
384
+ )
385
+
386
+ if not self.streaming:
387
+ payload["incremental_output"] = False
388
+
389
+ # 发起请求
390
+ partial = functools.partial(Application.call, **payload)
391
+ response = await asyncio.get_event_loop().run_in_executor(None, partial)
392
+
393
+ async for resp in self._handle_streaming_response(response, session_id):
394
+ yield resp
395
+
396
+ @override
397
+ def done(self) -> bool:
398
+ """检查 Agent 是否已完成工作"""
399
+ return self._state in (AgentState.DONE, AgentState.ERROR)
400
+
401
+ @override
402
+ def get_final_llm_resp(self) -> LLMResponse | None:
403
+ return self.final_llm_resp