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.
- astrbot/api/__init__.py +16 -4
- astrbot/api/all.py +2 -1
- astrbot/api/event/__init__.py +5 -6
- astrbot/api/event/filter/__init__.py +37 -34
- astrbot/api/platform/__init__.py +7 -8
- astrbot/api/provider/__init__.py +8 -7
- astrbot/api/star/__init__.py +3 -4
- astrbot/api/util/__init__.py +2 -2
- astrbot/cli/__init__.py +1 -0
- astrbot/cli/__main__.py +18 -197
- astrbot/cli/commands/__init__.py +6 -0
- astrbot/cli/commands/cmd_conf.py +209 -0
- astrbot/cli/commands/cmd_init.py +56 -0
- astrbot/cli/commands/cmd_plug.py +245 -0
- astrbot/cli/commands/cmd_run.py +62 -0
- astrbot/cli/utils/__init__.py +18 -0
- astrbot/cli/utils/basic.py +76 -0
- astrbot/cli/utils/plugin.py +246 -0
- astrbot/cli/utils/version_comparator.py +90 -0
- astrbot/core/__init__.py +17 -19
- astrbot/core/agent/agent.py +14 -0
- astrbot/core/agent/handoff.py +38 -0
- astrbot/core/agent/hooks.py +30 -0
- astrbot/core/agent/mcp_client.py +385 -0
- astrbot/core/agent/message.py +175 -0
- astrbot/core/agent/response.py +14 -0
- astrbot/core/agent/run_context.py +22 -0
- astrbot/core/agent/runners/__init__.py +3 -0
- astrbot/core/agent/runners/base.py +65 -0
- astrbot/core/agent/runners/coze/coze_agent_runner.py +367 -0
- astrbot/core/agent/runners/coze/coze_api_client.py +324 -0
- astrbot/core/agent/runners/dashscope/dashscope_agent_runner.py +403 -0
- astrbot/core/agent/runners/dify/dify_agent_runner.py +336 -0
- astrbot/core/agent/runners/dify/dify_api_client.py +195 -0
- astrbot/core/agent/runners/tool_loop_agent_runner.py +400 -0
- astrbot/core/agent/tool.py +285 -0
- astrbot/core/agent/tool_executor.py +17 -0
- astrbot/core/astr_agent_context.py +19 -0
- astrbot/core/astr_agent_hooks.py +36 -0
- astrbot/core/astr_agent_run_util.py +80 -0
- astrbot/core/astr_agent_tool_exec.py +246 -0
- astrbot/core/astrbot_config_mgr.py +275 -0
- astrbot/core/config/__init__.py +2 -2
- astrbot/core/config/astrbot_config.py +60 -20
- astrbot/core/config/default.py +1972 -453
- astrbot/core/config/i18n_utils.py +110 -0
- astrbot/core/conversation_mgr.py +285 -75
- astrbot/core/core_lifecycle.py +167 -62
- astrbot/core/db/__init__.py +305 -102
- astrbot/core/db/migration/helper.py +69 -0
- astrbot/core/db/migration/migra_3_to_4.py +357 -0
- astrbot/core/db/migration/migra_45_to_46.py +44 -0
- astrbot/core/db/migration/migra_webchat_session.py +131 -0
- astrbot/core/db/migration/shared_preferences_v3.py +48 -0
- astrbot/core/db/migration/sqlite_v3.py +497 -0
- astrbot/core/db/po.py +259 -55
- astrbot/core/db/sqlite.py +773 -528
- astrbot/core/db/vec_db/base.py +73 -0
- astrbot/core/db/vec_db/faiss_impl/__init__.py +3 -0
- astrbot/core/db/vec_db/faiss_impl/document_storage.py +392 -0
- astrbot/core/db/vec_db/faiss_impl/embedding_storage.py +93 -0
- astrbot/core/db/vec_db/faiss_impl/sqlite_init.sql +17 -0
- astrbot/core/db/vec_db/faiss_impl/vec_db.py +204 -0
- astrbot/core/event_bus.py +26 -22
- astrbot/core/exceptions.py +9 -0
- astrbot/core/file_token_service.py +98 -0
- astrbot/core/initial_loader.py +19 -10
- astrbot/core/knowledge_base/chunking/__init__.py +9 -0
- astrbot/core/knowledge_base/chunking/base.py +25 -0
- astrbot/core/knowledge_base/chunking/fixed_size.py +59 -0
- astrbot/core/knowledge_base/chunking/recursive.py +161 -0
- astrbot/core/knowledge_base/kb_db_sqlite.py +301 -0
- astrbot/core/knowledge_base/kb_helper.py +642 -0
- astrbot/core/knowledge_base/kb_mgr.py +330 -0
- astrbot/core/knowledge_base/models.py +120 -0
- astrbot/core/knowledge_base/parsers/__init__.py +13 -0
- astrbot/core/knowledge_base/parsers/base.py +51 -0
- astrbot/core/knowledge_base/parsers/markitdown_parser.py +26 -0
- astrbot/core/knowledge_base/parsers/pdf_parser.py +101 -0
- astrbot/core/knowledge_base/parsers/text_parser.py +42 -0
- astrbot/core/knowledge_base/parsers/url_parser.py +103 -0
- astrbot/core/knowledge_base/parsers/util.py +13 -0
- astrbot/core/knowledge_base/prompts.py +65 -0
- astrbot/core/knowledge_base/retrieval/__init__.py +14 -0
- astrbot/core/knowledge_base/retrieval/hit_stopwords.txt +767 -0
- astrbot/core/knowledge_base/retrieval/manager.py +276 -0
- astrbot/core/knowledge_base/retrieval/rank_fusion.py +142 -0
- astrbot/core/knowledge_base/retrieval/sparse_retriever.py +136 -0
- astrbot/core/log.py +21 -15
- astrbot/core/message/components.py +413 -287
- astrbot/core/message/message_event_result.py +35 -24
- astrbot/core/persona_mgr.py +192 -0
- astrbot/core/pipeline/__init__.py +14 -14
- 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 +13 -14
- astrbot/core/pipeline/content_safety_check/strategies/keywords.py +2 -1
- astrbot/core/pipeline/content_safety_check/strategies/strategy.py +6 -6
- astrbot/core/pipeline/context.py +7 -1
- astrbot/core/pipeline/context_utils.py +107 -0
- astrbot/core/pipeline/preprocess_stage/stage.py +63 -36
- astrbot/core/pipeline/process_stage/method/agent_request.py +48 -0
- astrbot/core/pipeline/process_stage/method/agent_sub_stages/internal.py +464 -0
- astrbot/core/pipeline/process_stage/method/agent_sub_stages/third_party.py +202 -0
- astrbot/core/pipeline/process_stage/method/star_request.py +26 -32
- astrbot/core/pipeline/process_stage/stage.py +21 -15
- astrbot/core/pipeline/process_stage/utils.py +125 -0
- astrbot/core/pipeline/rate_limit_check/stage.py +34 -36
- astrbot/core/pipeline/respond/stage.py +142 -101
- astrbot/core/pipeline/result_decorate/stage.py +124 -57
- astrbot/core/pipeline/scheduler.py +21 -16
- astrbot/core/pipeline/session_status_check/stage.py +37 -0
- astrbot/core/pipeline/stage.py +11 -76
- astrbot/core/pipeline/waking_check/stage.py +69 -33
- astrbot/core/pipeline/whitelist_check/stage.py +10 -7
- astrbot/core/platform/__init__.py +6 -6
- astrbot/core/platform/astr_message_event.py +107 -129
- astrbot/core/platform/astrbot_message.py +32 -12
- astrbot/core/platform/manager.py +62 -18
- astrbot/core/platform/message_session.py +30 -0
- astrbot/core/platform/platform.py +16 -24
- astrbot/core/platform/platform_metadata.py +9 -4
- astrbot/core/platform/register.py +12 -7
- astrbot/core/platform/sources/aiocqhttp/aiocqhttp_message_event.py +136 -60
- astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py +126 -46
- astrbot/core/platform/sources/dingtalk/dingtalk_adapter.py +63 -31
- astrbot/core/platform/sources/dingtalk/dingtalk_event.py +30 -26
- astrbot/core/platform/sources/discord/client.py +129 -0
- astrbot/core/platform/sources/discord/components.py +139 -0
- astrbot/core/platform/sources/discord/discord_platform_adapter.py +473 -0
- astrbot/core/platform/sources/discord/discord_platform_event.py +313 -0
- astrbot/core/platform/sources/lark/lark_adapter.py +27 -18
- astrbot/core/platform/sources/lark/lark_event.py +39 -13
- astrbot/core/platform/sources/misskey/misskey_adapter.py +770 -0
- astrbot/core/platform/sources/misskey/misskey_api.py +964 -0
- astrbot/core/platform/sources/misskey/misskey_event.py +163 -0
- astrbot/core/platform/sources/misskey/misskey_utils.py +550 -0
- astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py +149 -33
- 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 +14 -8
- astrbot/core/platform/sources/satori/satori_adapter.py +792 -0
- astrbot/core/platform/sources/satori/satori_event.py +432 -0
- astrbot/core/platform/sources/slack/client.py +164 -0
- astrbot/core/platform/sources/slack/slack_adapter.py +416 -0
- astrbot/core/platform/sources/slack/slack_event.py +253 -0
- astrbot/core/platform/sources/telegram/tg_adapter.py +100 -43
- astrbot/core/platform/sources/telegram/tg_event.py +136 -36
- astrbot/core/platform/sources/webchat/webchat_adapter.py +72 -22
- astrbot/core/platform/sources/webchat/webchat_event.py +46 -22
- astrbot/core/platform/sources/webchat/webchat_queue_mgr.py +35 -0
- astrbot/core/platform/sources/wechatpadpro/wechatpadpro_adapter.py +926 -0
- astrbot/core/platform/sources/wechatpadpro/wechatpadpro_message_event.py +178 -0
- astrbot/core/platform/sources/wechatpadpro/xml_data_parser.py +159 -0
- astrbot/core/platform/sources/wecom/wecom_adapter.py +169 -27
- astrbot/core/platform/sources/wecom/wecom_event.py +162 -77
- astrbot/core/platform/sources/wecom/wecom_kf.py +279 -0
- astrbot/core/platform/sources/wecom/wecom_kf_message.py +196 -0
- astrbot/core/platform/sources/wecom_ai_bot/WXBizJsonMsgCrypt.py +297 -0
- astrbot/core/platform/sources/wecom_ai_bot/__init__.py +15 -0
- astrbot/core/platform/sources/wecom_ai_bot/ierror.py +19 -0
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_adapter.py +472 -0
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_api.py +417 -0
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_event.py +152 -0
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_queue_mgr.py +153 -0
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_server.py +168 -0
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_utils.py +209 -0
- astrbot/core/platform/sources/weixin_official_account/weixin_offacc_adapter.py +306 -0
- astrbot/core/platform/sources/weixin_official_account/weixin_offacc_event.py +186 -0
- astrbot/core/platform_message_history_mgr.py +49 -0
- astrbot/core/provider/__init__.py +2 -3
- astrbot/core/provider/entites.py +8 -8
- astrbot/core/provider/entities.py +154 -98
- astrbot/core/provider/func_tool_manager.py +446 -458
- astrbot/core/provider/manager.py +345 -207
- astrbot/core/provider/provider.py +188 -73
- astrbot/core/provider/register.py +9 -7
- astrbot/core/provider/sources/anthropic_source.py +295 -115
- astrbot/core/provider/sources/azure_tts_source.py +224 -0
- astrbot/core/provider/sources/bailian_rerank_source.py +236 -0
- astrbot/core/provider/sources/dashscope_tts.py +138 -14
- astrbot/core/provider/sources/edge_tts_source.py +24 -19
- astrbot/core/provider/sources/fishaudio_tts_api_source.py +58 -13
- astrbot/core/provider/sources/gemini_embedding_source.py +61 -0
- astrbot/core/provider/sources/gemini_source.py +310 -132
- astrbot/core/provider/sources/gemini_tts_source.py +81 -0
- astrbot/core/provider/sources/groq_source.py +15 -0
- astrbot/core/provider/sources/gsv_selfhosted_source.py +151 -0
- astrbot/core/provider/sources/gsvi_tts_source.py +14 -7
- astrbot/core/provider/sources/minimax_tts_api_source.py +159 -0
- astrbot/core/provider/sources/openai_embedding_source.py +40 -0
- astrbot/core/provider/sources/openai_source.py +241 -145
- astrbot/core/provider/sources/openai_tts_api_source.py +18 -7
- astrbot/core/provider/sources/sensevoice_selfhosted_source.py +13 -11
- astrbot/core/provider/sources/vllm_rerank_source.py +71 -0
- astrbot/core/provider/sources/volcengine_tts.py +115 -0
- astrbot/core/provider/sources/whisper_api_source.py +18 -13
- astrbot/core/provider/sources/whisper_selfhosted_source.py +19 -12
- astrbot/core/provider/sources/xinference_rerank_source.py +116 -0
- astrbot/core/provider/sources/xinference_stt_provider.py +197 -0
- astrbot/core/provider/sources/zhipu_source.py +6 -73
- astrbot/core/star/__init__.py +43 -11
- astrbot/core/star/config.py +17 -18
- astrbot/core/star/context.py +362 -138
- astrbot/core/star/filter/__init__.py +4 -3
- astrbot/core/star/filter/command.py +111 -35
- astrbot/core/star/filter/command_group.py +46 -34
- 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 +45 -12
- astrbot/core/star/filter/regex.py +4 -2
- astrbot/core/star/register/__init__.py +19 -15
- astrbot/core/star/register/star.py +41 -13
- astrbot/core/star/register/star_handler.py +236 -86
- astrbot/core/star/session_llm_manager.py +280 -0
- astrbot/core/star/session_plugin_manager.py +170 -0
- astrbot/core/star/star.py +36 -43
- astrbot/core/star/star_handler.py +47 -85
- astrbot/core/star/star_manager.py +442 -260
- astrbot/core/star/star_tools.py +167 -45
- astrbot/core/star/updator.py +17 -20
- astrbot/core/umop_config_router.py +106 -0
- astrbot/core/updator.py +38 -13
- astrbot/core/utils/astrbot_path.py +39 -0
- astrbot/core/utils/command_parser.py +1 -1
- astrbot/core/utils/io.py +119 -60
- astrbot/core/utils/log_pipe.py +1 -1
- astrbot/core/utils/metrics.py +11 -10
- astrbot/core/utils/migra_helper.py +73 -0
- astrbot/core/utils/path_util.py +63 -62
- astrbot/core/utils/pip_installer.py +37 -15
- astrbot/core/utils/session_lock.py +29 -0
- astrbot/core/utils/session_waiter.py +19 -20
- astrbot/core/utils/shared_preferences.py +174 -34
- astrbot/core/utils/t2i/__init__.py +4 -1
- astrbot/core/utils/t2i/local_strategy.py +386 -238
- astrbot/core/utils/t2i/network_strategy.py +109 -49
- astrbot/core/utils/t2i/renderer.py +29 -14
- astrbot/core/utils/t2i/template/astrbot_powershell.html +184 -0
- astrbot/core/utils/t2i/template_manager.py +111 -0
- astrbot/core/utils/tencent_record_helper.py +115 -1
- astrbot/core/utils/version_comparator.py +10 -13
- astrbot/core/zip_updator.py +112 -65
- astrbot/dashboard/routes/__init__.py +20 -13
- astrbot/dashboard/routes/auth.py +20 -9
- astrbot/dashboard/routes/chat.py +297 -141
- astrbot/dashboard/routes/config.py +652 -55
- astrbot/dashboard/routes/conversation.py +107 -37
- astrbot/dashboard/routes/file.py +26 -0
- astrbot/dashboard/routes/knowledge_base.py +1244 -0
- astrbot/dashboard/routes/log.py +27 -2
- astrbot/dashboard/routes/persona.py +202 -0
- astrbot/dashboard/routes/plugin.py +197 -139
- astrbot/dashboard/routes/route.py +27 -7
- astrbot/dashboard/routes/session_management.py +354 -0
- astrbot/dashboard/routes/stat.py +85 -18
- astrbot/dashboard/routes/static_file.py +5 -2
- astrbot/dashboard/routes/t2i.py +233 -0
- astrbot/dashboard/routes/tools.py +184 -120
- astrbot/dashboard/routes/update.py +59 -36
- astrbot/dashboard/server.py +96 -36
- astrbot/dashboard/utils.py +165 -0
- astrbot-4.7.0.dist-info/METADATA +294 -0
- astrbot-4.7.0.dist-info/RECORD +274 -0
- {astrbot-3.5.6.dist-info → astrbot-4.7.0.dist-info}/WHEEL +1 -1
- astrbot/core/db/plugin/sqlite_impl.py +0 -112
- astrbot/core/db/sqlite_init.sql +0 -50
- astrbot/core/pipeline/platform_compatibility/stage.py +0 -56
- astrbot/core/pipeline/process_stage/method/llm_request.py +0 -606
- astrbot/core/platform/sources/gewechat/client.py +0 -806
- astrbot/core/platform/sources/gewechat/downloader.py +0 -55
- astrbot/core/platform/sources/gewechat/gewechat_event.py +0 -255
- astrbot/core/platform/sources/gewechat/gewechat_platform_adapter.py +0 -103
- astrbot/core/platform/sources/gewechat/xml_data_parser.py +0 -110
- astrbot/core/provider/sources/dashscope_source.py +0 -203
- astrbot/core/provider/sources/dify_source.py +0 -281
- astrbot/core/provider/sources/llmtuner_source.py +0 -132
- astrbot/core/rag/embedding/openai_source.py +0 -20
- astrbot/core/rag/knowledge_db_mgr.py +0 -94
- astrbot/core/rag/store/__init__.py +0 -9
- astrbot/core/rag/store/chroma_db.py +0 -42
- astrbot/core/utils/dify_api_client.py +0 -152
- astrbot-3.5.6.dist-info/METADATA +0 -249
- astrbot-3.5.6.dist-info/RECORD +0 -158
- {astrbot-3.5.6.dist-info → astrbot-4.7.0.dist-info}/entry_points.txt +0 -0
- {astrbot-3.5.6.dist-info → astrbot-4.7.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import json
|
|
3
|
+
import sys
|
|
4
|
+
import typing as T
|
|
5
|
+
|
|
6
|
+
import astrbot.core.message.components as Comp
|
|
7
|
+
from astrbot import logger
|
|
8
|
+
from astrbot.core import sp
|
|
9
|
+
from astrbot.core.message.message_event_result import MessageChain
|
|
10
|
+
from astrbot.core.provider.entities import (
|
|
11
|
+
LLMResponse,
|
|
12
|
+
ProviderRequest,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
from ...hooks import BaseAgentRunHooks
|
|
16
|
+
from ...response import AgentResponseData
|
|
17
|
+
from ...run_context import ContextWrapper, TContext
|
|
18
|
+
from ..base import AgentResponse, AgentState, BaseAgentRunner
|
|
19
|
+
from .coze_api_client import CozeAPIClient
|
|
20
|
+
|
|
21
|
+
if sys.version_info >= (3, 12):
|
|
22
|
+
from typing import override
|
|
23
|
+
else:
|
|
24
|
+
from typing_extensions import override
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class CozeAgentRunner(BaseAgentRunner[TContext]):
|
|
28
|
+
"""Coze Agent Runner"""
|
|
29
|
+
|
|
30
|
+
@override
|
|
31
|
+
async def reset(
|
|
32
|
+
self,
|
|
33
|
+
request: ProviderRequest,
|
|
34
|
+
run_context: ContextWrapper[TContext],
|
|
35
|
+
agent_hooks: BaseAgentRunHooks[TContext],
|
|
36
|
+
provider_config: dict,
|
|
37
|
+
**kwargs: T.Any,
|
|
38
|
+
) -> None:
|
|
39
|
+
self.req = request
|
|
40
|
+
self.streaming = kwargs.get("streaming", False)
|
|
41
|
+
self.final_llm_resp = None
|
|
42
|
+
self._state = AgentState.IDLE
|
|
43
|
+
self.agent_hooks = agent_hooks
|
|
44
|
+
self.run_context = run_context
|
|
45
|
+
|
|
46
|
+
self.api_key = provider_config.get("coze_api_key", "")
|
|
47
|
+
if not self.api_key:
|
|
48
|
+
raise Exception("Coze API Key 不能为空。")
|
|
49
|
+
self.bot_id = provider_config.get("bot_id", "")
|
|
50
|
+
if not self.bot_id:
|
|
51
|
+
raise Exception("Coze Bot ID 不能为空。")
|
|
52
|
+
self.api_base: str = provider_config.get("coze_api_base", "https://api.coze.cn")
|
|
53
|
+
|
|
54
|
+
if not isinstance(self.api_base, str) or not self.api_base.startswith(
|
|
55
|
+
("http://", "https://"),
|
|
56
|
+
):
|
|
57
|
+
raise Exception(
|
|
58
|
+
"Coze API Base URL 格式不正确,必须以 http:// 或 https:// 开头。",
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
self.timeout = provider_config.get("timeout", 120)
|
|
62
|
+
if isinstance(self.timeout, str):
|
|
63
|
+
self.timeout = int(self.timeout)
|
|
64
|
+
self.auto_save_history = provider_config.get("auto_save_history", True)
|
|
65
|
+
|
|
66
|
+
# 创建 API 客户端
|
|
67
|
+
self.api_client = CozeAPIClient(api_key=self.api_key, api_base=self.api_base)
|
|
68
|
+
|
|
69
|
+
# 会话相关缓存
|
|
70
|
+
self.file_id_cache: dict[str, dict[str, str]] = {}
|
|
71
|
+
|
|
72
|
+
@override
|
|
73
|
+
async def step(self):
|
|
74
|
+
"""
|
|
75
|
+
执行 Coze Agent 的一个步骤
|
|
76
|
+
"""
|
|
77
|
+
if not self.req:
|
|
78
|
+
raise ValueError("Request is not set. Please call reset() first.")
|
|
79
|
+
|
|
80
|
+
if self._state == AgentState.IDLE:
|
|
81
|
+
try:
|
|
82
|
+
await self.agent_hooks.on_agent_begin(self.run_context)
|
|
83
|
+
except Exception as e:
|
|
84
|
+
logger.error(f"Error in on_agent_begin hook: {e}", exc_info=True)
|
|
85
|
+
|
|
86
|
+
# 开始处理,转换到运行状态
|
|
87
|
+
self._transition_state(AgentState.RUNNING)
|
|
88
|
+
|
|
89
|
+
try:
|
|
90
|
+
# 执行 Coze 请求并处理结果
|
|
91
|
+
async for response in self._execute_coze_request():
|
|
92
|
+
yield response
|
|
93
|
+
except Exception as e:
|
|
94
|
+
logger.error(f"Coze 请求失败:{str(e)}")
|
|
95
|
+
self._transition_state(AgentState.ERROR)
|
|
96
|
+
self.final_llm_resp = LLMResponse(
|
|
97
|
+
role="err", completion_text=f"Coze 请求失败:{str(e)}"
|
|
98
|
+
)
|
|
99
|
+
yield AgentResponse(
|
|
100
|
+
type="err",
|
|
101
|
+
data=AgentResponseData(
|
|
102
|
+
chain=MessageChain().message(f"Coze 请求失败:{str(e)}")
|
|
103
|
+
),
|
|
104
|
+
)
|
|
105
|
+
finally:
|
|
106
|
+
await self.api_client.close()
|
|
107
|
+
|
|
108
|
+
@override
|
|
109
|
+
async def step_until_done(
|
|
110
|
+
self, max_step: int = 30
|
|
111
|
+
) -> T.AsyncGenerator[AgentResponse, None]:
|
|
112
|
+
while not self.done():
|
|
113
|
+
async for resp in self.step():
|
|
114
|
+
yield resp
|
|
115
|
+
|
|
116
|
+
async def _execute_coze_request(self):
|
|
117
|
+
"""执行 Coze 请求的核心逻辑"""
|
|
118
|
+
prompt = self.req.prompt or ""
|
|
119
|
+
session_id = self.req.session_id or "unknown"
|
|
120
|
+
image_urls = self.req.image_urls or []
|
|
121
|
+
contexts = self.req.contexts or []
|
|
122
|
+
system_prompt = self.req.system_prompt
|
|
123
|
+
|
|
124
|
+
# 用户ID参数
|
|
125
|
+
user_id = session_id
|
|
126
|
+
|
|
127
|
+
# 获取或创建会话ID
|
|
128
|
+
conversation_id = await sp.get_async(
|
|
129
|
+
scope="umo",
|
|
130
|
+
scope_id=user_id,
|
|
131
|
+
key="coze_conversation_id",
|
|
132
|
+
default="",
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
# 构建消息
|
|
136
|
+
additional_messages = []
|
|
137
|
+
|
|
138
|
+
if system_prompt:
|
|
139
|
+
if not self.auto_save_history or not conversation_id:
|
|
140
|
+
additional_messages.append(
|
|
141
|
+
{
|
|
142
|
+
"role": "system",
|
|
143
|
+
"content": system_prompt,
|
|
144
|
+
"content_type": "text",
|
|
145
|
+
},
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
# 处理历史上下文
|
|
149
|
+
if not self.auto_save_history and contexts:
|
|
150
|
+
for ctx in contexts:
|
|
151
|
+
if isinstance(ctx, dict) and "role" in ctx and "content" in ctx:
|
|
152
|
+
# 处理上下文中的图片
|
|
153
|
+
content = ctx["content"]
|
|
154
|
+
if isinstance(content, list):
|
|
155
|
+
# 多模态内容,需要处理图片
|
|
156
|
+
processed_content = []
|
|
157
|
+
for item in content:
|
|
158
|
+
if isinstance(item, dict):
|
|
159
|
+
if item.get("type") == "text":
|
|
160
|
+
processed_content.append(item)
|
|
161
|
+
elif item.get("type") == "image_url":
|
|
162
|
+
# 处理图片上传
|
|
163
|
+
try:
|
|
164
|
+
image_data = item.get("image_url", {})
|
|
165
|
+
url = image_data.get("url", "")
|
|
166
|
+
if url:
|
|
167
|
+
file_id = (
|
|
168
|
+
await self._download_and_upload_image(
|
|
169
|
+
url, session_id
|
|
170
|
+
)
|
|
171
|
+
)
|
|
172
|
+
processed_content.append(
|
|
173
|
+
{
|
|
174
|
+
"type": "file",
|
|
175
|
+
"file_id": file_id,
|
|
176
|
+
"file_url": url,
|
|
177
|
+
}
|
|
178
|
+
)
|
|
179
|
+
except Exception as e:
|
|
180
|
+
logger.warning(f"处理上下文图片失败: {e}")
|
|
181
|
+
continue
|
|
182
|
+
|
|
183
|
+
if processed_content:
|
|
184
|
+
additional_messages.append(
|
|
185
|
+
{
|
|
186
|
+
"role": ctx["role"],
|
|
187
|
+
"content": processed_content,
|
|
188
|
+
"content_type": "object_string",
|
|
189
|
+
}
|
|
190
|
+
)
|
|
191
|
+
else:
|
|
192
|
+
# 纯文本内容
|
|
193
|
+
additional_messages.append(
|
|
194
|
+
{
|
|
195
|
+
"role": ctx["role"],
|
|
196
|
+
"content": content,
|
|
197
|
+
"content_type": "text",
|
|
198
|
+
}
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
# 构建当前消息
|
|
202
|
+
if prompt or image_urls:
|
|
203
|
+
if image_urls:
|
|
204
|
+
# 多模态
|
|
205
|
+
object_string_content = []
|
|
206
|
+
if prompt:
|
|
207
|
+
object_string_content.append({"type": "text", "text": prompt})
|
|
208
|
+
|
|
209
|
+
for url in image_urls:
|
|
210
|
+
# the url is a base64 string
|
|
211
|
+
try:
|
|
212
|
+
image_data = base64.b64decode(url)
|
|
213
|
+
file_id = await self.api_client.upload_file(image_data)
|
|
214
|
+
object_string_content.append(
|
|
215
|
+
{
|
|
216
|
+
"type": "image",
|
|
217
|
+
"file_id": file_id,
|
|
218
|
+
}
|
|
219
|
+
)
|
|
220
|
+
except Exception as e:
|
|
221
|
+
logger.warning(f"处理图片失败 {url}: {e}")
|
|
222
|
+
continue
|
|
223
|
+
|
|
224
|
+
if object_string_content:
|
|
225
|
+
content = json.dumps(object_string_content, ensure_ascii=False)
|
|
226
|
+
additional_messages.append(
|
|
227
|
+
{
|
|
228
|
+
"role": "user",
|
|
229
|
+
"content": content,
|
|
230
|
+
"content_type": "object_string",
|
|
231
|
+
}
|
|
232
|
+
)
|
|
233
|
+
elif prompt:
|
|
234
|
+
# 纯文本
|
|
235
|
+
additional_messages.append(
|
|
236
|
+
{
|
|
237
|
+
"role": "user",
|
|
238
|
+
"content": prompt,
|
|
239
|
+
"content_type": "text",
|
|
240
|
+
},
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
# 执行 Coze API 请求
|
|
244
|
+
accumulated_content = ""
|
|
245
|
+
message_started = False
|
|
246
|
+
|
|
247
|
+
async for chunk in self.api_client.chat_messages(
|
|
248
|
+
bot_id=self.bot_id,
|
|
249
|
+
user_id=user_id,
|
|
250
|
+
additional_messages=additional_messages,
|
|
251
|
+
conversation_id=conversation_id,
|
|
252
|
+
auto_save_history=self.auto_save_history,
|
|
253
|
+
stream=True,
|
|
254
|
+
timeout=self.timeout,
|
|
255
|
+
):
|
|
256
|
+
event_type = chunk.get("event")
|
|
257
|
+
data = chunk.get("data", {})
|
|
258
|
+
|
|
259
|
+
if event_type == "conversation.chat.created":
|
|
260
|
+
if isinstance(data, dict) and "conversation_id" in data:
|
|
261
|
+
await sp.put_async(
|
|
262
|
+
scope="umo",
|
|
263
|
+
scope_id=user_id,
|
|
264
|
+
key="coze_conversation_id",
|
|
265
|
+
value=data["conversation_id"],
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
if event_type == "conversation.message.delta":
|
|
269
|
+
# 增量消息
|
|
270
|
+
content = data.get("content", "")
|
|
271
|
+
if not content and "delta" in data:
|
|
272
|
+
content = data["delta"].get("content", "")
|
|
273
|
+
if not content and "text" in data:
|
|
274
|
+
content = data.get("text", "")
|
|
275
|
+
|
|
276
|
+
if content:
|
|
277
|
+
accumulated_content += content
|
|
278
|
+
message_started = True
|
|
279
|
+
|
|
280
|
+
# 如果是流式响应,发送增量数据
|
|
281
|
+
if self.streaming:
|
|
282
|
+
yield AgentResponse(
|
|
283
|
+
type="streaming_delta",
|
|
284
|
+
data=AgentResponseData(
|
|
285
|
+
chain=MessageChain().message(content)
|
|
286
|
+
),
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
elif event_type == "conversation.message.completed":
|
|
290
|
+
# 消息完成
|
|
291
|
+
logger.debug("Coze message completed")
|
|
292
|
+
message_started = True
|
|
293
|
+
|
|
294
|
+
elif event_type == "conversation.chat.completed":
|
|
295
|
+
# 对话完成
|
|
296
|
+
logger.debug("Coze chat completed")
|
|
297
|
+
break
|
|
298
|
+
|
|
299
|
+
elif event_type == "error":
|
|
300
|
+
# 错误处理
|
|
301
|
+
error_msg = data.get("msg", "未知错误")
|
|
302
|
+
error_code = data.get("code", "UNKNOWN")
|
|
303
|
+
logger.error(f"Coze 出现错误: {error_code} - {error_msg}")
|
|
304
|
+
raise Exception(f"Coze 出现错误: {error_code} - {error_msg}")
|
|
305
|
+
|
|
306
|
+
if not message_started and not accumulated_content:
|
|
307
|
+
logger.warning("Coze 未返回任何内容")
|
|
308
|
+
accumulated_content = ""
|
|
309
|
+
|
|
310
|
+
# 创建最终响应
|
|
311
|
+
chain = MessageChain(chain=[Comp.Plain(accumulated_content)])
|
|
312
|
+
self.final_llm_resp = LLMResponse(role="assistant", result_chain=chain)
|
|
313
|
+
self._transition_state(AgentState.DONE)
|
|
314
|
+
|
|
315
|
+
try:
|
|
316
|
+
await self.agent_hooks.on_agent_done(self.run_context, self.final_llm_resp)
|
|
317
|
+
except Exception as e:
|
|
318
|
+
logger.error(f"Error in on_agent_done hook: {e}", exc_info=True)
|
|
319
|
+
|
|
320
|
+
# 返回最终结果
|
|
321
|
+
yield AgentResponse(
|
|
322
|
+
type="llm_result",
|
|
323
|
+
data=AgentResponseData(chain=chain),
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
async def _download_and_upload_image(
|
|
327
|
+
self,
|
|
328
|
+
image_url: str,
|
|
329
|
+
session_id: str | None = None,
|
|
330
|
+
) -> str:
|
|
331
|
+
"""下载图片并上传到 Coze,返回 file_id"""
|
|
332
|
+
import hashlib
|
|
333
|
+
|
|
334
|
+
# 计算哈希实现缓存
|
|
335
|
+
cache_key = hashlib.md5(image_url.encode("utf-8")).hexdigest()
|
|
336
|
+
|
|
337
|
+
if session_id:
|
|
338
|
+
if session_id not in self.file_id_cache:
|
|
339
|
+
self.file_id_cache[session_id] = {}
|
|
340
|
+
|
|
341
|
+
if cache_key in self.file_id_cache[session_id]:
|
|
342
|
+
file_id = self.file_id_cache[session_id][cache_key]
|
|
343
|
+
logger.debug(f"[Coze] 使用缓存的 file_id: {file_id}")
|
|
344
|
+
return file_id
|
|
345
|
+
|
|
346
|
+
try:
|
|
347
|
+
image_data = await self.api_client.download_image(image_url)
|
|
348
|
+
file_id = await self.api_client.upload_file(image_data)
|
|
349
|
+
|
|
350
|
+
if session_id:
|
|
351
|
+
self.file_id_cache[session_id][cache_key] = file_id
|
|
352
|
+
logger.debug(f"[Coze] 图片上传成功并缓存,file_id: {file_id}")
|
|
353
|
+
|
|
354
|
+
return file_id
|
|
355
|
+
|
|
356
|
+
except Exception as e:
|
|
357
|
+
logger.error(f"处理图片失败 {image_url}: {e!s}")
|
|
358
|
+
raise Exception(f"处理图片失败: {e!s}")
|
|
359
|
+
|
|
360
|
+
@override
|
|
361
|
+
def done(self) -> bool:
|
|
362
|
+
"""检查 Agent 是否已完成工作"""
|
|
363
|
+
return self._state in (AgentState.DONE, AgentState.ERROR)
|
|
364
|
+
|
|
365
|
+
@override
|
|
366
|
+
def get_final_llm_resp(self) -> LLMResponse | None:
|
|
367
|
+
return self.final_llm_resp
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import io
|
|
3
|
+
import json
|
|
4
|
+
from collections.abc import AsyncGenerator
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
import aiohttp
|
|
8
|
+
|
|
9
|
+
from astrbot.core import logger
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class CozeAPIClient:
|
|
13
|
+
def __init__(self, api_key: str, api_base: str = "https://api.coze.cn"):
|
|
14
|
+
self.api_key = api_key
|
|
15
|
+
self.api_base = api_base
|
|
16
|
+
self.session = None
|
|
17
|
+
|
|
18
|
+
async def _ensure_session(self):
|
|
19
|
+
"""确保HTTP session存在"""
|
|
20
|
+
if self.session is None:
|
|
21
|
+
connector = aiohttp.TCPConnector(
|
|
22
|
+
ssl=False if self.api_base.startswith("http://") else True,
|
|
23
|
+
limit=100,
|
|
24
|
+
limit_per_host=30,
|
|
25
|
+
keepalive_timeout=30,
|
|
26
|
+
enable_cleanup_closed=True,
|
|
27
|
+
)
|
|
28
|
+
timeout = aiohttp.ClientTimeout(
|
|
29
|
+
total=120, # 默认超时时间
|
|
30
|
+
connect=30,
|
|
31
|
+
sock_read=120,
|
|
32
|
+
)
|
|
33
|
+
headers = {
|
|
34
|
+
"Authorization": f"Bearer {self.api_key}",
|
|
35
|
+
"Accept": "text/event-stream",
|
|
36
|
+
}
|
|
37
|
+
self.session = aiohttp.ClientSession(
|
|
38
|
+
headers=headers,
|
|
39
|
+
timeout=timeout,
|
|
40
|
+
connector=connector,
|
|
41
|
+
)
|
|
42
|
+
return self.session
|
|
43
|
+
|
|
44
|
+
async def upload_file(
|
|
45
|
+
self,
|
|
46
|
+
file_data: bytes,
|
|
47
|
+
) -> str:
|
|
48
|
+
"""上传文件到 Coze 并返回 file_id
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
file_data (bytes): 文件的二进制数据
|
|
52
|
+
Returns:
|
|
53
|
+
str: 上传成功后返回的 file_id
|
|
54
|
+
|
|
55
|
+
"""
|
|
56
|
+
session = await self._ensure_session()
|
|
57
|
+
url = f"{self.api_base}/v1/files/upload"
|
|
58
|
+
|
|
59
|
+
try:
|
|
60
|
+
file_io = io.BytesIO(file_data)
|
|
61
|
+
async with session.post(
|
|
62
|
+
url,
|
|
63
|
+
data={
|
|
64
|
+
"file": file_io,
|
|
65
|
+
},
|
|
66
|
+
timeout=aiohttp.ClientTimeout(total=60),
|
|
67
|
+
) as response:
|
|
68
|
+
if response.status == 401:
|
|
69
|
+
raise Exception("Coze API 认证失败,请检查 API Key 是否正确")
|
|
70
|
+
|
|
71
|
+
response_text = await response.text()
|
|
72
|
+
logger.debug(
|
|
73
|
+
f"文件上传响应状态: {response.status}, 内容: {response_text}",
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
if response.status != 200:
|
|
77
|
+
raise Exception(
|
|
78
|
+
f"文件上传失败,状态码: {response.status}, 响应: {response_text}",
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
try:
|
|
82
|
+
result = await response.json()
|
|
83
|
+
except json.JSONDecodeError:
|
|
84
|
+
raise Exception(f"文件上传响应解析失败: {response_text}")
|
|
85
|
+
|
|
86
|
+
if result.get("code") != 0:
|
|
87
|
+
raise Exception(f"文件上传失败: {result.get('msg', '未知错误')}")
|
|
88
|
+
|
|
89
|
+
file_id = result["data"]["id"]
|
|
90
|
+
logger.debug(f"[Coze] 图片上传成功,file_id: {file_id}")
|
|
91
|
+
return file_id
|
|
92
|
+
|
|
93
|
+
except asyncio.TimeoutError:
|
|
94
|
+
logger.error("文件上传超时")
|
|
95
|
+
raise Exception("文件上传超时")
|
|
96
|
+
except Exception as e:
|
|
97
|
+
logger.error(f"文件上传失败: {e!s}")
|
|
98
|
+
raise Exception(f"文件上传失败: {e!s}")
|
|
99
|
+
|
|
100
|
+
async def download_image(self, image_url: str) -> bytes:
|
|
101
|
+
"""下载图片并返回字节数据
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
image_url (str): 图片的URL
|
|
105
|
+
Returns:
|
|
106
|
+
bytes: 图片的二进制数据
|
|
107
|
+
|
|
108
|
+
"""
|
|
109
|
+
session = await self._ensure_session()
|
|
110
|
+
|
|
111
|
+
try:
|
|
112
|
+
async with session.get(image_url) as response:
|
|
113
|
+
if response.status != 200:
|
|
114
|
+
raise Exception(f"下载图片失败,状态码: {response.status}")
|
|
115
|
+
|
|
116
|
+
image_data = await response.read()
|
|
117
|
+
return image_data
|
|
118
|
+
|
|
119
|
+
except Exception as e:
|
|
120
|
+
logger.error(f"下载图片失败 {image_url}: {e!s}")
|
|
121
|
+
raise Exception(f"下载图片失败: {e!s}")
|
|
122
|
+
|
|
123
|
+
async def chat_messages(
|
|
124
|
+
self,
|
|
125
|
+
bot_id: str,
|
|
126
|
+
user_id: str,
|
|
127
|
+
additional_messages: list[dict] | None = None,
|
|
128
|
+
conversation_id: str | None = None,
|
|
129
|
+
auto_save_history: bool = True,
|
|
130
|
+
stream: bool = True,
|
|
131
|
+
timeout: float = 120,
|
|
132
|
+
) -> AsyncGenerator[dict[str, Any], None]:
|
|
133
|
+
"""发送聊天消息并返回流式响应
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
bot_id: Bot ID
|
|
137
|
+
user_id: 用户ID
|
|
138
|
+
additional_messages: 额外消息列表
|
|
139
|
+
conversation_id: 会话ID
|
|
140
|
+
auto_save_history: 是否自动保存历史
|
|
141
|
+
stream: 是否流式响应
|
|
142
|
+
timeout: 超时时间
|
|
143
|
+
|
|
144
|
+
"""
|
|
145
|
+
session = await self._ensure_session()
|
|
146
|
+
url = f"{self.api_base}/v3/chat"
|
|
147
|
+
|
|
148
|
+
payload = {
|
|
149
|
+
"bot_id": bot_id,
|
|
150
|
+
"user_id": user_id,
|
|
151
|
+
"stream": stream,
|
|
152
|
+
"auto_save_history": auto_save_history,
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if additional_messages:
|
|
156
|
+
payload["additional_messages"] = additional_messages
|
|
157
|
+
|
|
158
|
+
params = {}
|
|
159
|
+
if conversation_id:
|
|
160
|
+
params["conversation_id"] = conversation_id
|
|
161
|
+
|
|
162
|
+
logger.debug(f"Coze chat_messages payload: {payload}, params: {params}")
|
|
163
|
+
|
|
164
|
+
try:
|
|
165
|
+
async with session.post(
|
|
166
|
+
url,
|
|
167
|
+
json=payload,
|
|
168
|
+
params=params,
|
|
169
|
+
timeout=aiohttp.ClientTimeout(total=timeout),
|
|
170
|
+
) as response:
|
|
171
|
+
if response.status == 401:
|
|
172
|
+
raise Exception("Coze API 认证失败,请检查 API Key 是否正确")
|
|
173
|
+
|
|
174
|
+
if response.status != 200:
|
|
175
|
+
raise Exception(f"Coze API 流式请求失败,状态码: {response.status}")
|
|
176
|
+
|
|
177
|
+
# SSE
|
|
178
|
+
buffer = ""
|
|
179
|
+
event_type = None
|
|
180
|
+
event_data = None
|
|
181
|
+
|
|
182
|
+
async for chunk in response.content:
|
|
183
|
+
if chunk:
|
|
184
|
+
buffer += chunk.decode("utf-8", errors="ignore")
|
|
185
|
+
lines = buffer.split("\n")
|
|
186
|
+
buffer = lines[-1]
|
|
187
|
+
|
|
188
|
+
for line in lines[:-1]:
|
|
189
|
+
line = line.strip()
|
|
190
|
+
|
|
191
|
+
if not line:
|
|
192
|
+
if event_type and event_data:
|
|
193
|
+
yield {"event": event_type, "data": event_data}
|
|
194
|
+
event_type = None
|
|
195
|
+
event_data = None
|
|
196
|
+
elif line.startswith("event:"):
|
|
197
|
+
event_type = line[6:].strip()
|
|
198
|
+
elif line.startswith("data:"):
|
|
199
|
+
data_str = line[5:].strip()
|
|
200
|
+
if data_str and data_str != "[DONE]":
|
|
201
|
+
try:
|
|
202
|
+
event_data = json.loads(data_str)
|
|
203
|
+
except json.JSONDecodeError:
|
|
204
|
+
event_data = {"content": data_str}
|
|
205
|
+
|
|
206
|
+
except asyncio.TimeoutError:
|
|
207
|
+
raise Exception(f"Coze API 流式请求超时 ({timeout}秒)")
|
|
208
|
+
except Exception as e:
|
|
209
|
+
raise Exception(f"Coze API 流式请求失败: {e!s}")
|
|
210
|
+
|
|
211
|
+
async def clear_context(self, conversation_id: str):
|
|
212
|
+
"""清空会话上下文
|
|
213
|
+
|
|
214
|
+
Args:
|
|
215
|
+
conversation_id: 会话ID
|
|
216
|
+
Returns:
|
|
217
|
+
dict: API响应结果
|
|
218
|
+
|
|
219
|
+
"""
|
|
220
|
+
session = await self._ensure_session()
|
|
221
|
+
url = f"{self.api_base}/v3/conversation/message/clear_context"
|
|
222
|
+
payload = {"conversation_id": conversation_id}
|
|
223
|
+
|
|
224
|
+
try:
|
|
225
|
+
async with session.post(url, json=payload) as response:
|
|
226
|
+
response_text = await response.text()
|
|
227
|
+
|
|
228
|
+
if response.status == 401:
|
|
229
|
+
raise Exception("Coze API 认证失败,请检查 API Key 是否正确")
|
|
230
|
+
|
|
231
|
+
if response.status != 200:
|
|
232
|
+
raise Exception(f"Coze API 请求失败,状态码: {response.status}")
|
|
233
|
+
|
|
234
|
+
try:
|
|
235
|
+
return json.loads(response_text)
|
|
236
|
+
except json.JSONDecodeError:
|
|
237
|
+
raise Exception("Coze API 返回非JSON格式")
|
|
238
|
+
|
|
239
|
+
except asyncio.TimeoutError:
|
|
240
|
+
raise Exception("Coze API 请求超时")
|
|
241
|
+
except aiohttp.ClientError as e:
|
|
242
|
+
raise Exception(f"Coze API 请求失败: {e!s}")
|
|
243
|
+
|
|
244
|
+
async def get_message_list(
|
|
245
|
+
self,
|
|
246
|
+
conversation_id: str,
|
|
247
|
+
order: str = "desc",
|
|
248
|
+
limit: int = 10,
|
|
249
|
+
offset: int = 0,
|
|
250
|
+
):
|
|
251
|
+
"""获取消息列表
|
|
252
|
+
|
|
253
|
+
Args:
|
|
254
|
+
conversation_id: 会话ID
|
|
255
|
+
order: 排序方式 (asc/desc)
|
|
256
|
+
limit: 限制数量
|
|
257
|
+
offset: 偏移量
|
|
258
|
+
Returns:
|
|
259
|
+
dict: API响应结果
|
|
260
|
+
|
|
261
|
+
"""
|
|
262
|
+
session = await self._ensure_session()
|
|
263
|
+
url = f"{self.api_base}/v3/conversation/message/list"
|
|
264
|
+
params = {
|
|
265
|
+
"conversation_id": conversation_id,
|
|
266
|
+
"order": order,
|
|
267
|
+
"limit": limit,
|
|
268
|
+
"offset": offset,
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
try:
|
|
272
|
+
async with session.get(url, params=params) as response:
|
|
273
|
+
response.raise_for_status()
|
|
274
|
+
return await response.json()
|
|
275
|
+
|
|
276
|
+
except Exception as e:
|
|
277
|
+
logger.error(f"获取Coze消息列表失败: {e!s}")
|
|
278
|
+
raise Exception(f"获取Coze消息列表失败: {e!s}")
|
|
279
|
+
|
|
280
|
+
async def close(self):
|
|
281
|
+
"""关闭会话"""
|
|
282
|
+
if self.session:
|
|
283
|
+
await self.session.close()
|
|
284
|
+
self.session = None
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
if __name__ == "__main__":
|
|
288
|
+
import asyncio
|
|
289
|
+
import os
|
|
290
|
+
|
|
291
|
+
async def test_coze_api_client():
|
|
292
|
+
api_key = os.getenv("COZE_API_KEY", "")
|
|
293
|
+
bot_id = os.getenv("COZE_BOT_ID", "")
|
|
294
|
+
client = CozeAPIClient(api_key=api_key)
|
|
295
|
+
|
|
296
|
+
try:
|
|
297
|
+
with open("README.md", "rb") as f:
|
|
298
|
+
file_data = f.read()
|
|
299
|
+
file_id = await client.upload_file(file_data)
|
|
300
|
+
print(f"Uploaded file_id: {file_id}")
|
|
301
|
+
async for event in client.chat_messages(
|
|
302
|
+
bot_id=bot_id,
|
|
303
|
+
user_id="test_user",
|
|
304
|
+
additional_messages=[
|
|
305
|
+
{
|
|
306
|
+
"role": "user",
|
|
307
|
+
"content": json.dumps(
|
|
308
|
+
[
|
|
309
|
+
{"type": "text", "text": "这是什么"},
|
|
310
|
+
{"type": "file", "file_id": file_id},
|
|
311
|
+
],
|
|
312
|
+
ensure_ascii=False,
|
|
313
|
+
),
|
|
314
|
+
"content_type": "object_string",
|
|
315
|
+
},
|
|
316
|
+
],
|
|
317
|
+
stream=True,
|
|
318
|
+
):
|
|
319
|
+
print(f"Event: {event}")
|
|
320
|
+
|
|
321
|
+
finally:
|
|
322
|
+
await client.close()
|
|
323
|
+
|
|
324
|
+
asyncio.run(test_coze_api_client())
|