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.
- astrbot/api/__init__.py +10 -11
- astrbot/api/event/__init__.py +5 -6
- astrbot/api/event/filter/__init__.py +37 -36
- astrbot/api/platform/__init__.py +7 -8
- astrbot/api/provider/__init__.py +7 -7
- astrbot/api/star/__init__.py +3 -4
- astrbot/api/util/__init__.py +2 -2
- astrbot/cli/__main__.py +5 -5
- astrbot/cli/commands/__init__.py +3 -3
- astrbot/cli/commands/cmd_conf.py +19 -16
- astrbot/cli/commands/cmd_init.py +3 -2
- astrbot/cli/commands/cmd_plug.py +8 -10
- astrbot/cli/commands/cmd_run.py +5 -6
- astrbot/cli/utils/__init__.py +6 -6
- astrbot/cli/utils/basic.py +14 -14
- astrbot/cli/utils/plugin.py +24 -15
- astrbot/cli/utils/version_comparator.py +10 -12
- astrbot/core/__init__.py +8 -6
- astrbot/core/agent/agent.py +3 -2
- astrbot/core/agent/handoff.py +6 -2
- astrbot/core/agent/hooks.py +9 -6
- astrbot/core/agent/mcp_client.py +50 -15
- astrbot/core/agent/message.py +168 -0
- astrbot/core/agent/response.py +2 -1
- astrbot/core/agent/run_context.py +2 -3
- astrbot/core/agent/runners/base.py +10 -13
- astrbot/core/agent/runners/tool_loop_agent_runner.py +52 -51
- astrbot/core/agent/tool.py +60 -41
- astrbot/core/agent/tool_executor.py +9 -3
- astrbot/core/astr_agent_context.py +3 -1
- astrbot/core/astrbot_config_mgr.py +29 -9
- astrbot/core/config/__init__.py +2 -2
- astrbot/core/config/astrbot_config.py +28 -26
- astrbot/core/config/default.py +44 -6
- astrbot/core/conversation_mgr.py +105 -36
- astrbot/core/core_lifecycle.py +68 -54
- astrbot/core/db/__init__.py +33 -18
- astrbot/core/db/migration/helper.py +18 -13
- astrbot/core/db/migration/migra_3_to_4.py +53 -34
- astrbot/core/db/migration/migra_45_to_46.py +1 -1
- astrbot/core/db/migration/shared_preferences_v3.py +2 -1
- astrbot/core/db/migration/sqlite_v3.py +26 -23
- astrbot/core/db/po.py +27 -18
- astrbot/core/db/sqlite.py +74 -45
- astrbot/core/db/vec_db/base.py +10 -14
- astrbot/core/db/vec_db/faiss_impl/document_storage.py +90 -77
- astrbot/core/db/vec_db/faiss_impl/embedding_storage.py +9 -3
- astrbot/core/db/vec_db/faiss_impl/vec_db.py +36 -31
- astrbot/core/event_bus.py +8 -6
- astrbot/core/file_token_service.py +6 -5
- astrbot/core/initial_loader.py +7 -5
- astrbot/core/knowledge_base/chunking/__init__.py +1 -3
- astrbot/core/knowledge_base/chunking/base.py +1 -0
- astrbot/core/knowledge_base/chunking/fixed_size.py +2 -0
- astrbot/core/knowledge_base/chunking/recursive.py +16 -10
- astrbot/core/knowledge_base/kb_db_sqlite.py +50 -48
- astrbot/core/knowledge_base/kb_helper.py +30 -17
- astrbot/core/knowledge_base/kb_mgr.py +6 -7
- astrbot/core/knowledge_base/models.py +10 -4
- astrbot/core/knowledge_base/parsers/__init__.py +3 -5
- astrbot/core/knowledge_base/parsers/base.py +1 -0
- astrbot/core/knowledge_base/parsers/markitdown_parser.py +2 -1
- astrbot/core/knowledge_base/parsers/pdf_parser.py +2 -1
- astrbot/core/knowledge_base/parsers/text_parser.py +1 -0
- astrbot/core/knowledge_base/parsers/util.py +1 -1
- astrbot/core/knowledge_base/retrieval/__init__.py +6 -8
- astrbot/core/knowledge_base/retrieval/manager.py +17 -14
- astrbot/core/knowledge_base/retrieval/rank_fusion.py +7 -3
- astrbot/core/knowledge_base/retrieval/sparse_retriever.py +11 -5
- astrbot/core/log.py +21 -13
- astrbot/core/message/components.py +123 -217
- astrbot/core/message/message_event_result.py +24 -24
- astrbot/core/persona_mgr.py +20 -11
- astrbot/core/pipeline/__init__.py +7 -7
- astrbot/core/pipeline/content_safety_check/stage.py +13 -9
- astrbot/core/pipeline/content_safety_check/strategies/__init__.py +1 -2
- astrbot/core/pipeline/content_safety_check/strategies/baidu_aip.py +12 -13
- astrbot/core/pipeline/content_safety_check/strategies/keywords.py +1 -0
- astrbot/core/pipeline/content_safety_check/strategies/strategy.py +6 -6
- astrbot/core/pipeline/context.py +4 -1
- astrbot/core/pipeline/context_utils.py +77 -7
- astrbot/core/pipeline/preprocess_stage/stage.py +12 -9
- astrbot/core/pipeline/process_stage/method/llm_request.py +125 -72
- astrbot/core/pipeline/process_stage/method/star_request.py +19 -17
- astrbot/core/pipeline/process_stage/stage.py +13 -10
- astrbot/core/pipeline/process_stage/utils.py +6 -5
- astrbot/core/pipeline/rate_limit_check/stage.py +37 -36
- astrbot/core/pipeline/respond/stage.py +23 -20
- astrbot/core/pipeline/result_decorate/stage.py +31 -23
- astrbot/core/pipeline/scheduler.py +12 -8
- astrbot/core/pipeline/session_status_check/stage.py +12 -8
- astrbot/core/pipeline/stage.py +10 -4
- astrbot/core/pipeline/waking_check/stage.py +24 -18
- astrbot/core/pipeline/whitelist_check/stage.py +10 -7
- astrbot/core/platform/__init__.py +6 -6
- astrbot/core/platform/astr_message_event.py +76 -110
- astrbot/core/platform/astrbot_message.py +11 -13
- astrbot/core/platform/manager.py +16 -15
- astrbot/core/platform/message_session.py +5 -3
- astrbot/core/platform/platform.py +16 -24
- astrbot/core/platform/platform_metadata.py +4 -4
- astrbot/core/platform/register.py +8 -8
- astrbot/core/platform/sources/aiocqhttp/aiocqhttp_message_event.py +23 -15
- astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py +51 -33
- astrbot/core/platform/sources/dingtalk/dingtalk_adapter.py +47 -29
- astrbot/core/platform/sources/dingtalk/dingtalk_event.py +7 -3
- astrbot/core/platform/sources/discord/client.py +9 -6
- astrbot/core/platform/sources/discord/components.py +18 -14
- astrbot/core/platform/sources/discord/discord_platform_adapter.py +45 -30
- astrbot/core/platform/sources/discord/discord_platform_event.py +38 -30
- astrbot/core/platform/sources/lark/lark_adapter.py +23 -17
- astrbot/core/platform/sources/lark/lark_event.py +21 -14
- astrbot/core/platform/sources/misskey/misskey_adapter.py +107 -67
- astrbot/core/platform/sources/misskey/misskey_api.py +153 -129
- astrbot/core/platform/sources/misskey/misskey_event.py +20 -15
- astrbot/core/platform/sources/misskey/misskey_utils.py +74 -62
- astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py +63 -44
- astrbot/core/platform/sources/qqofficial/qqofficial_platform_adapter.py +41 -26
- astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_adapter.py +36 -17
- astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_event.py +3 -1
- astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_server.py +12 -7
- astrbot/core/platform/sources/satori/satori_adapter.py +56 -38
- astrbot/core/platform/sources/satori/satori_event.py +34 -25
- astrbot/core/platform/sources/slack/client.py +11 -9
- astrbot/core/platform/sources/slack/slack_adapter.py +52 -36
- astrbot/core/platform/sources/slack/slack_event.py +34 -24
- astrbot/core/platform/sources/telegram/tg_adapter.py +38 -18
- astrbot/core/platform/sources/telegram/tg_event.py +32 -18
- astrbot/core/platform/sources/webchat/webchat_adapter.py +27 -17
- astrbot/core/platform/sources/webchat/webchat_event.py +14 -10
- astrbot/core/platform/sources/wechatpadpro/wechatpadpro_adapter.py +115 -120
- astrbot/core/platform/sources/wechatpadpro/wechatpadpro_message_event.py +9 -8
- astrbot/core/platform/sources/wechatpadpro/xml_data_parser.py +15 -16
- astrbot/core/platform/sources/wecom/wecom_adapter.py +35 -18
- astrbot/core/platform/sources/wecom/wecom_event.py +55 -48
- astrbot/core/platform/sources/wecom/wecom_kf.py +34 -44
- astrbot/core/platform/sources/wecom/wecom_kf_message.py +26 -10
- astrbot/core/platform/sources/wecom_ai_bot/WXBizJsonMsgCrypt.py +18 -10
- astrbot/core/platform/sources/wecom_ai_bot/__init__.py +3 -5
- astrbot/core/platform/sources/wecom_ai_bot/ierror.py +0 -1
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_adapter.py +61 -37
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_api.py +67 -28
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_event.py +8 -9
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_queue_mgr.py +18 -9
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_server.py +14 -12
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_utils.py +22 -12
- astrbot/core/platform/sources/weixin_official_account/weixin_offacc_adapter.py +40 -26
- astrbot/core/platform/sources/weixin_official_account/weixin_offacc_event.py +47 -45
- astrbot/core/platform_message_history_mgr.py +5 -3
- astrbot/core/provider/__init__.py +2 -3
- astrbot/core/provider/entites.py +8 -8
- astrbot/core/provider/entities.py +61 -75
- astrbot/core/provider/func_tool_manager.py +59 -55
- astrbot/core/provider/manager.py +40 -22
- astrbot/core/provider/provider.py +72 -46
- astrbot/core/provider/register.py +7 -7
- astrbot/core/provider/sources/anthropic_source.py +48 -30
- astrbot/core/provider/sources/azure_tts_source.py +17 -13
- astrbot/core/provider/sources/coze_api_client.py +27 -17
- astrbot/core/provider/sources/coze_source.py +104 -87
- astrbot/core/provider/sources/dashscope_source.py +18 -11
- astrbot/core/provider/sources/dashscope_tts.py +36 -23
- astrbot/core/provider/sources/dify_source.py +25 -20
- astrbot/core/provider/sources/edge_tts_source.py +21 -17
- astrbot/core/provider/sources/fishaudio_tts_api_source.py +22 -14
- astrbot/core/provider/sources/gemini_embedding_source.py +12 -13
- astrbot/core/provider/sources/gemini_source.py +72 -58
- astrbot/core/provider/sources/gemini_tts_source.py +8 -6
- astrbot/core/provider/sources/gsv_selfhosted_source.py +17 -14
- astrbot/core/provider/sources/gsvi_tts_source.py +11 -7
- astrbot/core/provider/sources/minimax_tts_api_source.py +50 -40
- astrbot/core/provider/sources/openai_embedding_source.py +6 -8
- astrbot/core/provider/sources/openai_source.py +102 -69
- astrbot/core/provider/sources/openai_tts_api_source.py +14 -6
- astrbot/core/provider/sources/sensevoice_selfhosted_source.py +13 -11
- astrbot/core/provider/sources/vllm_rerank_source.py +10 -4
- astrbot/core/provider/sources/volcengine_tts.py +38 -31
- astrbot/core/provider/sources/whisper_api_source.py +14 -12
- astrbot/core/provider/sources/whisper_selfhosted_source.py +15 -11
- astrbot/core/provider/sources/xinference_rerank_source.py +116 -0
- astrbot/core/provider/sources/xinference_stt_provider.py +197 -0
- astrbot/core/star/__init__.py +16 -11
- astrbot/core/star/config.py +10 -15
- astrbot/core/star/context.py +109 -84
- astrbot/core/star/filter/__init__.py +4 -3
- astrbot/core/star/filter/command.py +30 -28
- astrbot/core/star/filter/command_group.py +27 -24
- astrbot/core/star/filter/custom_filter.py +6 -5
- astrbot/core/star/filter/event_message_type.py +4 -2
- astrbot/core/star/filter/permission.py +4 -2
- astrbot/core/star/filter/platform_adapter_type.py +4 -2
- astrbot/core/star/filter/regex.py +4 -2
- astrbot/core/star/register/__init__.py +19 -19
- astrbot/core/star/register/star.py +6 -2
- astrbot/core/star/register/star_handler.py +96 -73
- astrbot/core/star/session_llm_manager.py +48 -14
- astrbot/core/star/session_plugin_manager.py +29 -15
- astrbot/core/star/star.py +1 -2
- astrbot/core/star/star_handler.py +13 -8
- astrbot/core/star/star_manager.py +151 -59
- astrbot/core/star/star_tools.py +44 -37
- astrbot/core/star/updator.py +10 -10
- astrbot/core/umop_config_router.py +10 -4
- astrbot/core/updator.py +13 -5
- astrbot/core/utils/astrbot_path.py +3 -5
- astrbot/core/utils/dify_api_client.py +33 -15
- astrbot/core/utils/io.py +66 -42
- astrbot/core/utils/log_pipe.py +1 -1
- astrbot/core/utils/metrics.py +7 -7
- astrbot/core/utils/path_util.py +15 -16
- astrbot/core/utils/pip_installer.py +5 -5
- astrbot/core/utils/session_waiter.py +19 -20
- astrbot/core/utils/shared_preferences.py +45 -20
- astrbot/core/utils/t2i/__init__.py +4 -1
- astrbot/core/utils/t2i/network_strategy.py +35 -26
- astrbot/core/utils/t2i/renderer.py +11 -5
- astrbot/core/utils/t2i/template_manager.py +14 -15
- astrbot/core/utils/tencent_record_helper.py +19 -13
- astrbot/core/utils/version_comparator.py +10 -13
- astrbot/core/zip_updator.py +43 -40
- astrbot/dashboard/routes/__init__.py +18 -18
- astrbot/dashboard/routes/auth.py +10 -8
- astrbot/dashboard/routes/chat.py +30 -21
- astrbot/dashboard/routes/config.py +92 -75
- astrbot/dashboard/routes/conversation.py +46 -39
- astrbot/dashboard/routes/file.py +4 -2
- astrbot/dashboard/routes/knowledge_base.py +47 -40
- astrbot/dashboard/routes/log.py +9 -4
- astrbot/dashboard/routes/persona.py +19 -16
- astrbot/dashboard/routes/plugin.py +69 -55
- astrbot/dashboard/routes/route.py +3 -1
- astrbot/dashboard/routes/session_management.py +130 -116
- astrbot/dashboard/routes/stat.py +34 -34
- astrbot/dashboard/routes/t2i.py +15 -12
- astrbot/dashboard/routes/tools.py +47 -52
- astrbot/dashboard/routes/update.py +32 -28
- astrbot/dashboard/server.py +30 -26
- astrbot/dashboard/utils.py +8 -4
- {astrbot-4.5.0.dist-info → astrbot-4.5.2.dist-info}/METADATA +4 -2
- astrbot-4.5.2.dist-info/RECORD +261 -0
- astrbot-4.5.0.dist-info/RECORD +0 -258
- {astrbot-4.5.0.dist-info → astrbot-4.5.2.dist-info}/WHEEL +0 -0
- {astrbot-4.5.0.dist-info → astrbot-4.5.2.dist-info}/entry_points.txt +0 -0
- {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
|
-
|
|
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,
|
|
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"文件上传失败: {
|
|
92
|
-
raise Exception(f"文件上传失败: {
|
|
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}: {
|
|
114
|
-
raise Exception(f"下载图片失败: {
|
|
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:
|
|
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[
|
|
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 流式请求失败: {
|
|
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 请求失败: {
|
|
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消息列表失败: {
|
|
268
|
-
raise Exception(f"获取Coze消息列表失败: {
|
|
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
|
-
|
|
6
|
-
|
|
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:
|
|
48
|
-
self.file_id_cache:
|
|
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
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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,
|
|
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}: {
|
|
146
|
-
raise Exception(f"处理图片失败: {
|
|
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,
|
|
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,
|
|
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,
|
|
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"处理上下文图片失败: {
|
|
241
|
+
logger.error(f"处理上下文图片失败: {e!s}")
|
|
237
242
|
if isinstance(content, str):
|
|
238
243
|
return content
|
|
239
|
-
|
|
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
|
-
|
|
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
|
-
{
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
423
|
+
image_data,
|
|
424
|
+
user_id,
|
|
425
|
+
cache_key,
|
|
396
426
|
)
|
|
397
427
|
else:
|
|
398
|
-
|
|
399
|
-
|
|
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}: {
|
|
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 流式请求失败: {
|
|
552
|
+
logger.error(f"Coze 流式请求失败: {e!s}")
|
|
538
553
|
yield LLMResponse(
|
|
539
554
|
role="err",
|
|
540
|
-
completion_text=f"Coze 流式请求失败: {
|
|
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
|
-
|
|
562
|
-
|
|
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 会话失败: {
|
|
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,
|
|
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 消息历史失败: {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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")
|