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,464 @@
|
|
|
1
|
+
"""本地 Agent 模式的 LLM 调用 Stage"""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import copy
|
|
5
|
+
import json
|
|
6
|
+
from collections.abc import AsyncGenerator
|
|
7
|
+
|
|
8
|
+
from astrbot.core import logger
|
|
9
|
+
from astrbot.core.agent.tool import ToolSet
|
|
10
|
+
from astrbot.core.astr_agent_context import AstrAgentContext
|
|
11
|
+
from astrbot.core.conversation_mgr import Conversation
|
|
12
|
+
from astrbot.core.message.components import Image
|
|
13
|
+
from astrbot.core.message.message_event_result import (
|
|
14
|
+
MessageChain,
|
|
15
|
+
MessageEventResult,
|
|
16
|
+
ResultContentType,
|
|
17
|
+
)
|
|
18
|
+
from astrbot.core.platform.astr_message_event import AstrMessageEvent
|
|
19
|
+
from astrbot.core.provider import Provider
|
|
20
|
+
from astrbot.core.provider.entities import (
|
|
21
|
+
LLMResponse,
|
|
22
|
+
ProviderRequest,
|
|
23
|
+
)
|
|
24
|
+
from astrbot.core.star.star_handler import EventType, star_map
|
|
25
|
+
from astrbot.core.utils.metrics import Metric
|
|
26
|
+
from astrbot.core.utils.session_lock import session_lock_manager
|
|
27
|
+
|
|
28
|
+
from .....astr_agent_context import AgentContextWrapper
|
|
29
|
+
from .....astr_agent_hooks import MAIN_AGENT_HOOKS
|
|
30
|
+
from .....astr_agent_run_util import AgentRunner, run_agent
|
|
31
|
+
from .....astr_agent_tool_exec import FunctionToolExecutor
|
|
32
|
+
from ....context import PipelineContext, call_event_hook
|
|
33
|
+
from ...stage import Stage
|
|
34
|
+
from ...utils import KNOWLEDGE_BASE_QUERY_TOOL, retrieve_knowledge_base
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class InternalAgentSubStage(Stage):
|
|
38
|
+
async def initialize(self, ctx: PipelineContext) -> None:
|
|
39
|
+
self.ctx = ctx
|
|
40
|
+
conf = ctx.astrbot_config
|
|
41
|
+
settings = conf["provider_settings"]
|
|
42
|
+
self.max_context_length = settings["max_context_length"] # int
|
|
43
|
+
self.dequeue_context_length: int = min(
|
|
44
|
+
max(1, settings["dequeue_context_length"]),
|
|
45
|
+
self.max_context_length - 1,
|
|
46
|
+
)
|
|
47
|
+
self.streaming_response: bool = settings["streaming_response"]
|
|
48
|
+
self.unsupported_streaming_strategy: str = settings[
|
|
49
|
+
"unsupported_streaming_strategy"
|
|
50
|
+
]
|
|
51
|
+
self.max_step: int = settings.get("max_agent_step", 30)
|
|
52
|
+
self.tool_call_timeout: int = settings.get("tool_call_timeout", 60)
|
|
53
|
+
if isinstance(self.max_step, bool): # workaround: #2622
|
|
54
|
+
self.max_step = 30
|
|
55
|
+
self.show_tool_use: bool = settings.get("show_tool_use_status", True)
|
|
56
|
+
self.show_reasoning = settings.get("display_reasoning_text", False)
|
|
57
|
+
self.kb_agentic_mode: bool = conf.get("kb_agentic_mode", False)
|
|
58
|
+
|
|
59
|
+
self.conv_manager = ctx.plugin_manager.context.conversation_manager
|
|
60
|
+
|
|
61
|
+
def _select_provider(self, event: AstrMessageEvent):
|
|
62
|
+
"""选择使用的 LLM 提供商"""
|
|
63
|
+
sel_provider = event.get_extra("selected_provider")
|
|
64
|
+
_ctx = self.ctx.plugin_manager.context
|
|
65
|
+
if sel_provider and isinstance(sel_provider, str):
|
|
66
|
+
provider = _ctx.get_provider_by_id(sel_provider)
|
|
67
|
+
if not provider:
|
|
68
|
+
logger.error(f"未找到指定的提供商: {sel_provider}。")
|
|
69
|
+
return provider
|
|
70
|
+
|
|
71
|
+
return _ctx.get_using_provider(umo=event.unified_msg_origin)
|
|
72
|
+
|
|
73
|
+
async def _get_session_conv(self, event: AstrMessageEvent) -> Conversation:
|
|
74
|
+
umo = event.unified_msg_origin
|
|
75
|
+
conv_mgr = self.conv_manager
|
|
76
|
+
|
|
77
|
+
# 获取对话上下文
|
|
78
|
+
cid = await conv_mgr.get_curr_conversation_id(umo)
|
|
79
|
+
if not cid:
|
|
80
|
+
cid = await conv_mgr.new_conversation(umo, event.get_platform_id())
|
|
81
|
+
conversation = await conv_mgr.get_conversation(umo, cid)
|
|
82
|
+
if not conversation:
|
|
83
|
+
cid = await conv_mgr.new_conversation(umo, event.get_platform_id())
|
|
84
|
+
conversation = await conv_mgr.get_conversation(umo, cid)
|
|
85
|
+
if not conversation:
|
|
86
|
+
raise RuntimeError("无法创建新的对话。")
|
|
87
|
+
return conversation
|
|
88
|
+
|
|
89
|
+
async def _apply_kb(
|
|
90
|
+
self,
|
|
91
|
+
event: AstrMessageEvent,
|
|
92
|
+
req: ProviderRequest,
|
|
93
|
+
):
|
|
94
|
+
"""Apply knowledge base context to the provider request"""
|
|
95
|
+
if not self.kb_agentic_mode:
|
|
96
|
+
if req.prompt is None:
|
|
97
|
+
return
|
|
98
|
+
try:
|
|
99
|
+
kb_result = await retrieve_knowledge_base(
|
|
100
|
+
query=req.prompt,
|
|
101
|
+
umo=event.unified_msg_origin,
|
|
102
|
+
context=self.ctx.plugin_manager.context,
|
|
103
|
+
)
|
|
104
|
+
if not kb_result:
|
|
105
|
+
return
|
|
106
|
+
if req.system_prompt is not None:
|
|
107
|
+
req.system_prompt += (
|
|
108
|
+
f"\n\n[Related Knowledge Base Results]:\n{kb_result}"
|
|
109
|
+
)
|
|
110
|
+
except Exception as e:
|
|
111
|
+
logger.error(f"Error occurred while retrieving knowledge base: {e}")
|
|
112
|
+
else:
|
|
113
|
+
if req.func_tool is None:
|
|
114
|
+
req.func_tool = ToolSet()
|
|
115
|
+
req.func_tool.add_tool(KNOWLEDGE_BASE_QUERY_TOOL)
|
|
116
|
+
|
|
117
|
+
def _truncate_contexts(
|
|
118
|
+
self,
|
|
119
|
+
contexts: list[dict],
|
|
120
|
+
) -> list[dict]:
|
|
121
|
+
"""截断上下文列表,确保不超过最大长度"""
|
|
122
|
+
if self.max_context_length == -1:
|
|
123
|
+
return contexts
|
|
124
|
+
|
|
125
|
+
if len(contexts) // 2 <= self.max_context_length:
|
|
126
|
+
return contexts
|
|
127
|
+
|
|
128
|
+
truncated_contexts = contexts[
|
|
129
|
+
-(self.max_context_length - self.dequeue_context_length + 1) * 2 :
|
|
130
|
+
]
|
|
131
|
+
# 找到第一个role 为 user 的索引,确保上下文格式正确
|
|
132
|
+
index = next(
|
|
133
|
+
(
|
|
134
|
+
i
|
|
135
|
+
for i, item in enumerate(truncated_contexts)
|
|
136
|
+
if item.get("role") == "user"
|
|
137
|
+
),
|
|
138
|
+
None,
|
|
139
|
+
)
|
|
140
|
+
if index is not None and index > 0:
|
|
141
|
+
truncated_contexts = truncated_contexts[index:]
|
|
142
|
+
|
|
143
|
+
return truncated_contexts
|
|
144
|
+
|
|
145
|
+
def _modalities_fix(
|
|
146
|
+
self,
|
|
147
|
+
provider: Provider,
|
|
148
|
+
req: ProviderRequest,
|
|
149
|
+
):
|
|
150
|
+
"""检查提供商的模态能力,清理请求中的不支持内容"""
|
|
151
|
+
if req.image_urls:
|
|
152
|
+
provider_cfg = provider.provider_config.get("modalities", ["image"])
|
|
153
|
+
if "image" not in provider_cfg:
|
|
154
|
+
logger.debug(f"用户设置提供商 {provider} 不支持图像,清空图像列表。")
|
|
155
|
+
req.image_urls = []
|
|
156
|
+
if req.func_tool:
|
|
157
|
+
provider_cfg = provider.provider_config.get("modalities", ["tool_use"])
|
|
158
|
+
# 如果模型不支持工具使用,但请求中包含工具列表,则清空。
|
|
159
|
+
if "tool_use" not in provider_cfg:
|
|
160
|
+
logger.debug(
|
|
161
|
+
f"用户设置提供商 {provider} 不支持工具使用,清空工具列表。",
|
|
162
|
+
)
|
|
163
|
+
req.func_tool = None
|
|
164
|
+
|
|
165
|
+
def _plugin_tool_fix(
|
|
166
|
+
self,
|
|
167
|
+
event: AstrMessageEvent,
|
|
168
|
+
req: ProviderRequest,
|
|
169
|
+
):
|
|
170
|
+
"""根据事件中的插件设置,过滤请求中的工具列表"""
|
|
171
|
+
if event.plugins_name is not None and req.func_tool:
|
|
172
|
+
new_tool_set = ToolSet()
|
|
173
|
+
for tool in req.func_tool.tools:
|
|
174
|
+
mp = tool.handler_module_path
|
|
175
|
+
if not mp:
|
|
176
|
+
continue
|
|
177
|
+
plugin = star_map.get(mp)
|
|
178
|
+
if not plugin:
|
|
179
|
+
continue
|
|
180
|
+
if plugin.name in event.plugins_name or plugin.reserved:
|
|
181
|
+
new_tool_set.add_tool(tool)
|
|
182
|
+
req.func_tool = new_tool_set
|
|
183
|
+
|
|
184
|
+
async def _handle_webchat(
|
|
185
|
+
self,
|
|
186
|
+
event: AstrMessageEvent,
|
|
187
|
+
req: ProviderRequest,
|
|
188
|
+
prov: Provider,
|
|
189
|
+
):
|
|
190
|
+
"""处理 WebChat 平台的特殊情况,包括第一次 LLM 对话时总结对话内容生成 title"""
|
|
191
|
+
if not req.conversation:
|
|
192
|
+
return
|
|
193
|
+
conversation = await self.conv_manager.get_conversation(
|
|
194
|
+
event.unified_msg_origin,
|
|
195
|
+
req.conversation.cid,
|
|
196
|
+
)
|
|
197
|
+
if conversation and not req.conversation.title:
|
|
198
|
+
messages = json.loads(conversation.history)
|
|
199
|
+
latest_pair = messages[-2:]
|
|
200
|
+
if not latest_pair:
|
|
201
|
+
return
|
|
202
|
+
content = latest_pair[0].get("content", "")
|
|
203
|
+
if isinstance(content, list):
|
|
204
|
+
# 多模态
|
|
205
|
+
text_parts = []
|
|
206
|
+
for item in content:
|
|
207
|
+
if isinstance(item, dict):
|
|
208
|
+
if item.get("type") == "text":
|
|
209
|
+
text_parts.append(item.get("text", ""))
|
|
210
|
+
elif item.get("type") == "image":
|
|
211
|
+
text_parts.append("[图片]")
|
|
212
|
+
elif isinstance(item, str):
|
|
213
|
+
text_parts.append(item)
|
|
214
|
+
cleaned_text = "User: " + " ".join(text_parts).strip()
|
|
215
|
+
elif isinstance(content, str):
|
|
216
|
+
cleaned_text = "User: " + content.strip()
|
|
217
|
+
else:
|
|
218
|
+
return
|
|
219
|
+
logger.debug(f"WebChat 对话标题生成请求,清理后的文本: {cleaned_text}")
|
|
220
|
+
llm_resp = await prov.text_chat(
|
|
221
|
+
system_prompt="You are expert in summarizing user's query.",
|
|
222
|
+
prompt=(
|
|
223
|
+
f"Please summarize the following query of user:\n"
|
|
224
|
+
f"{cleaned_text}\n"
|
|
225
|
+
"Only output the summary within 10 words, DO NOT INCLUDE any other text."
|
|
226
|
+
"You must use the same language as the user."
|
|
227
|
+
"If you think the dialog is too short to summarize, only output a special mark: `<None>`"
|
|
228
|
+
),
|
|
229
|
+
)
|
|
230
|
+
if llm_resp and llm_resp.completion_text:
|
|
231
|
+
title = llm_resp.completion_text.strip()
|
|
232
|
+
if not title or "<None>" in title:
|
|
233
|
+
return
|
|
234
|
+
await self.conv_manager.update_conversation_title(
|
|
235
|
+
unified_msg_origin=event.unified_msg_origin,
|
|
236
|
+
title=title,
|
|
237
|
+
conversation_id=req.conversation.cid,
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
async def _save_to_history(
|
|
241
|
+
self,
|
|
242
|
+
event: AstrMessageEvent,
|
|
243
|
+
req: ProviderRequest,
|
|
244
|
+
llm_response: LLMResponse | None,
|
|
245
|
+
):
|
|
246
|
+
if (
|
|
247
|
+
not req
|
|
248
|
+
or not req.conversation
|
|
249
|
+
or not llm_response
|
|
250
|
+
or llm_response.role != "assistant"
|
|
251
|
+
):
|
|
252
|
+
return
|
|
253
|
+
|
|
254
|
+
if not llm_response.completion_text and not req.tool_calls_result:
|
|
255
|
+
logger.debug("LLM 响应为空,不保存记录。")
|
|
256
|
+
return
|
|
257
|
+
|
|
258
|
+
if req.contexts is None:
|
|
259
|
+
req.contexts = []
|
|
260
|
+
|
|
261
|
+
# 历史上下文
|
|
262
|
+
messages = copy.deepcopy(req.contexts)
|
|
263
|
+
# 这一轮对话请求的用户输入
|
|
264
|
+
messages.append(await req.assemble_context())
|
|
265
|
+
# 这一轮对话的 LLM 响应
|
|
266
|
+
if req.tool_calls_result:
|
|
267
|
+
if not isinstance(req.tool_calls_result, list):
|
|
268
|
+
messages.extend(req.tool_calls_result.to_openai_messages())
|
|
269
|
+
elif isinstance(req.tool_calls_result, list):
|
|
270
|
+
for tcr in req.tool_calls_result:
|
|
271
|
+
messages.extend(tcr.to_openai_messages())
|
|
272
|
+
messages.append({"role": "assistant", "content": llm_response.completion_text})
|
|
273
|
+
messages = list(filter(lambda item: "_no_save" not in item, messages))
|
|
274
|
+
await self.conv_manager.update_conversation(
|
|
275
|
+
event.unified_msg_origin,
|
|
276
|
+
req.conversation.cid,
|
|
277
|
+
history=messages,
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
def _fix_messages(self, messages: list[dict]) -> list[dict]:
|
|
281
|
+
"""验证并且修复上下文"""
|
|
282
|
+
fixed_messages = []
|
|
283
|
+
for message in messages:
|
|
284
|
+
if message.get("role") == "tool":
|
|
285
|
+
# tool block 前面必须要有 user 和 assistant block
|
|
286
|
+
if len(fixed_messages) < 2:
|
|
287
|
+
# 这种情况可能是上下文被截断导致的
|
|
288
|
+
# 我们直接将之前的上下文都清空
|
|
289
|
+
fixed_messages = []
|
|
290
|
+
else:
|
|
291
|
+
fixed_messages.append(message)
|
|
292
|
+
else:
|
|
293
|
+
fixed_messages.append(message)
|
|
294
|
+
return fixed_messages
|
|
295
|
+
|
|
296
|
+
async def process(
|
|
297
|
+
self, event: AstrMessageEvent, provider_wake_prefix: str
|
|
298
|
+
) -> AsyncGenerator[None, None]:
|
|
299
|
+
req: ProviderRequest | None = None
|
|
300
|
+
|
|
301
|
+
provider = self._select_provider(event)
|
|
302
|
+
if provider is None:
|
|
303
|
+
return
|
|
304
|
+
if not isinstance(provider, Provider):
|
|
305
|
+
logger.error(f"选择的提供商类型无效({type(provider)}),跳过 LLM 请求处理。")
|
|
306
|
+
return
|
|
307
|
+
|
|
308
|
+
streaming_response = self.streaming_response
|
|
309
|
+
if (enable_streaming := event.get_extra("enable_streaming")) is not None:
|
|
310
|
+
streaming_response = bool(enable_streaming)
|
|
311
|
+
|
|
312
|
+
logger.debug("ready to request llm provider")
|
|
313
|
+
async with session_lock_manager.acquire_lock(event.unified_msg_origin):
|
|
314
|
+
logger.debug("acquired session lock for llm request")
|
|
315
|
+
if event.get_extra("provider_request"):
|
|
316
|
+
req = event.get_extra("provider_request")
|
|
317
|
+
assert isinstance(req, ProviderRequest), (
|
|
318
|
+
"provider_request 必须是 ProviderRequest 类型。"
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
if req.conversation:
|
|
322
|
+
req.contexts = json.loads(req.conversation.history)
|
|
323
|
+
|
|
324
|
+
else:
|
|
325
|
+
req = ProviderRequest()
|
|
326
|
+
req.prompt = ""
|
|
327
|
+
req.image_urls = []
|
|
328
|
+
if sel_model := event.get_extra("selected_model"):
|
|
329
|
+
req.model = sel_model
|
|
330
|
+
if provider_wake_prefix and not event.message_str.startswith(
|
|
331
|
+
provider_wake_prefix
|
|
332
|
+
):
|
|
333
|
+
return
|
|
334
|
+
|
|
335
|
+
req.prompt = event.message_str[len(provider_wake_prefix) :]
|
|
336
|
+
# func_tool selection 现在已经转移到 packages/astrbot 插件中进行选择。
|
|
337
|
+
# req.func_tool = self.ctx.plugin_manager.context.get_llm_tool_manager()
|
|
338
|
+
for comp in event.message_obj.message:
|
|
339
|
+
if isinstance(comp, Image):
|
|
340
|
+
image_path = await comp.convert_to_file_path()
|
|
341
|
+
req.image_urls.append(image_path)
|
|
342
|
+
|
|
343
|
+
conversation = await self._get_session_conv(event)
|
|
344
|
+
req.conversation = conversation
|
|
345
|
+
req.contexts = json.loads(conversation.history)
|
|
346
|
+
|
|
347
|
+
event.set_extra("provider_request", req)
|
|
348
|
+
|
|
349
|
+
if not req.prompt and not req.image_urls:
|
|
350
|
+
return
|
|
351
|
+
|
|
352
|
+
# call event hook
|
|
353
|
+
if await call_event_hook(event, EventType.OnLLMRequestEvent, req):
|
|
354
|
+
return
|
|
355
|
+
|
|
356
|
+
# apply knowledge base feature
|
|
357
|
+
await self._apply_kb(event, req)
|
|
358
|
+
|
|
359
|
+
# fix contexts json str
|
|
360
|
+
if isinstance(req.contexts, str):
|
|
361
|
+
req.contexts = json.loads(req.contexts)
|
|
362
|
+
|
|
363
|
+
# truncate contexts to fit max length
|
|
364
|
+
if req.contexts:
|
|
365
|
+
req.contexts = self._truncate_contexts(req.contexts)
|
|
366
|
+
self._fix_messages(req.contexts)
|
|
367
|
+
|
|
368
|
+
# session_id
|
|
369
|
+
if not req.session_id:
|
|
370
|
+
req.session_id = event.unified_msg_origin
|
|
371
|
+
|
|
372
|
+
# check provider modalities, if provider does not support image/tool_use, clear them in request.
|
|
373
|
+
self._modalities_fix(provider, req)
|
|
374
|
+
|
|
375
|
+
# filter tools, only keep tools from this pipeline's selected plugins
|
|
376
|
+
self._plugin_tool_fix(event, req)
|
|
377
|
+
|
|
378
|
+
stream_to_general = (
|
|
379
|
+
self.unsupported_streaming_strategy == "turn_off"
|
|
380
|
+
and not event.platform_meta.support_streaming_message
|
|
381
|
+
)
|
|
382
|
+
# 备份 req.contexts
|
|
383
|
+
backup_contexts = copy.deepcopy(req.contexts)
|
|
384
|
+
|
|
385
|
+
# run agent
|
|
386
|
+
agent_runner = AgentRunner()
|
|
387
|
+
logger.debug(
|
|
388
|
+
f"handle provider[id: {provider.provider_config['id']}] request: {req}",
|
|
389
|
+
)
|
|
390
|
+
astr_agent_ctx = AstrAgentContext(
|
|
391
|
+
context=self.ctx.plugin_manager.context,
|
|
392
|
+
event=event,
|
|
393
|
+
)
|
|
394
|
+
await agent_runner.reset(
|
|
395
|
+
provider=provider,
|
|
396
|
+
request=req,
|
|
397
|
+
run_context=AgentContextWrapper(
|
|
398
|
+
context=astr_agent_ctx,
|
|
399
|
+
tool_call_timeout=self.tool_call_timeout,
|
|
400
|
+
),
|
|
401
|
+
tool_executor=FunctionToolExecutor(),
|
|
402
|
+
agent_hooks=MAIN_AGENT_HOOKS,
|
|
403
|
+
streaming=streaming_response,
|
|
404
|
+
)
|
|
405
|
+
|
|
406
|
+
if streaming_response and not stream_to_general:
|
|
407
|
+
# 流式响应
|
|
408
|
+
event.set_result(
|
|
409
|
+
MessageEventResult()
|
|
410
|
+
.set_result_content_type(ResultContentType.STREAMING_RESULT)
|
|
411
|
+
.set_async_stream(
|
|
412
|
+
run_agent(
|
|
413
|
+
agent_runner,
|
|
414
|
+
self.max_step,
|
|
415
|
+
self.show_tool_use,
|
|
416
|
+
show_reasoning=self.show_reasoning,
|
|
417
|
+
),
|
|
418
|
+
),
|
|
419
|
+
)
|
|
420
|
+
yield
|
|
421
|
+
if agent_runner.done():
|
|
422
|
+
if final_llm_resp := agent_runner.get_final_llm_resp():
|
|
423
|
+
if final_llm_resp.completion_text:
|
|
424
|
+
chain = (
|
|
425
|
+
MessageChain()
|
|
426
|
+
.message(final_llm_resp.completion_text)
|
|
427
|
+
.chain
|
|
428
|
+
)
|
|
429
|
+
elif final_llm_resp.result_chain:
|
|
430
|
+
chain = final_llm_resp.result_chain.chain
|
|
431
|
+
else:
|
|
432
|
+
chain = MessageChain().chain
|
|
433
|
+
event.set_result(
|
|
434
|
+
MessageEventResult(
|
|
435
|
+
chain=chain,
|
|
436
|
+
result_content_type=ResultContentType.STREAMING_FINISH,
|
|
437
|
+
),
|
|
438
|
+
)
|
|
439
|
+
else:
|
|
440
|
+
async for _ in run_agent(
|
|
441
|
+
agent_runner,
|
|
442
|
+
self.max_step,
|
|
443
|
+
self.show_tool_use,
|
|
444
|
+
stream_to_general,
|
|
445
|
+
show_reasoning=self.show_reasoning,
|
|
446
|
+
):
|
|
447
|
+
yield
|
|
448
|
+
|
|
449
|
+
# 恢复备份的 contexts
|
|
450
|
+
req.contexts = backup_contexts
|
|
451
|
+
|
|
452
|
+
await self._save_to_history(event, req, agent_runner.get_final_llm_resp())
|
|
453
|
+
|
|
454
|
+
# 异步处理 WebChat 特殊情况
|
|
455
|
+
if event.get_platform_name() == "webchat":
|
|
456
|
+
asyncio.create_task(self._handle_webchat(event, req, provider))
|
|
457
|
+
|
|
458
|
+
asyncio.create_task(
|
|
459
|
+
Metric.upload(
|
|
460
|
+
llm_tick=1,
|
|
461
|
+
model_name=agent_runner.provider.get_model(),
|
|
462
|
+
provider_type=agent_runner.provider.meta().type,
|
|
463
|
+
),
|
|
464
|
+
)
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from collections.abc import AsyncGenerator
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
from astrbot.core import logger
|
|
6
|
+
from astrbot.core.agent.runners.coze.coze_agent_runner import CozeAgentRunner
|
|
7
|
+
from astrbot.core.agent.runners.dashscope.dashscope_agent_runner import (
|
|
8
|
+
DashscopeAgentRunner,
|
|
9
|
+
)
|
|
10
|
+
from astrbot.core.agent.runners.dify.dify_agent_runner import DifyAgentRunner
|
|
11
|
+
from astrbot.core.message.components import Image
|
|
12
|
+
from astrbot.core.message.message_event_result import (
|
|
13
|
+
MessageChain,
|
|
14
|
+
MessageEventResult,
|
|
15
|
+
ResultContentType,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
from astrbot.core.agent.runners.base import BaseAgentRunner
|
|
20
|
+
from astrbot.core.platform.astr_message_event import AstrMessageEvent
|
|
21
|
+
from astrbot.core.provider.entities import (
|
|
22
|
+
ProviderRequest,
|
|
23
|
+
)
|
|
24
|
+
from astrbot.core.star.star_handler import EventType
|
|
25
|
+
from astrbot.core.utils.metrics import Metric
|
|
26
|
+
|
|
27
|
+
from .....astr_agent_context import AgentContextWrapper, AstrAgentContext
|
|
28
|
+
from .....astr_agent_hooks import MAIN_AGENT_HOOKS
|
|
29
|
+
from ....context import PipelineContext, call_event_hook
|
|
30
|
+
from ...stage import Stage
|
|
31
|
+
|
|
32
|
+
AGENT_RUNNER_TYPE_KEY = {
|
|
33
|
+
"dify": "dify_agent_runner_provider_id",
|
|
34
|
+
"coze": "coze_agent_runner_provider_id",
|
|
35
|
+
"dashscope": "dashscope_agent_runner_provider_id",
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
async def run_third_party_agent(
|
|
40
|
+
runner: "BaseAgentRunner",
|
|
41
|
+
stream_to_general: bool = False,
|
|
42
|
+
) -> AsyncGenerator[MessageChain | None, None]:
|
|
43
|
+
"""
|
|
44
|
+
运行第三方 agent runner 并转换响应格式
|
|
45
|
+
类似于 run_agent 函数,但专门处理第三方 agent runner
|
|
46
|
+
"""
|
|
47
|
+
try:
|
|
48
|
+
async for resp in runner.step_until_done(max_step=30): # type: ignore[misc]
|
|
49
|
+
if resp.type == "streaming_delta":
|
|
50
|
+
if stream_to_general:
|
|
51
|
+
continue
|
|
52
|
+
yield resp.data["chain"]
|
|
53
|
+
elif resp.type == "llm_result":
|
|
54
|
+
if stream_to_general:
|
|
55
|
+
yield resp.data["chain"]
|
|
56
|
+
except Exception as e:
|
|
57
|
+
logger.error(f"Third party agent runner error: {e}")
|
|
58
|
+
err_msg = (
|
|
59
|
+
f"\nAstrBot 请求失败。\n错误类型: {type(e).__name__}\n"
|
|
60
|
+
f"错误信息: {e!s}\n\n请在控制台查看和分享错误详情。\n"
|
|
61
|
+
)
|
|
62
|
+
yield MessageChain().message(err_msg)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class ThirdPartyAgentSubStage(Stage):
|
|
66
|
+
async def initialize(self, ctx: PipelineContext) -> None:
|
|
67
|
+
self.ctx = ctx
|
|
68
|
+
self.conf = ctx.astrbot_config
|
|
69
|
+
self.runner_type = self.conf["provider_settings"]["agent_runner_type"]
|
|
70
|
+
self.prov_id = self.conf["provider_settings"].get(
|
|
71
|
+
AGENT_RUNNER_TYPE_KEY.get(self.runner_type, ""),
|
|
72
|
+
"",
|
|
73
|
+
)
|
|
74
|
+
settings = ctx.astrbot_config["provider_settings"]
|
|
75
|
+
self.streaming_response: bool = settings["streaming_response"]
|
|
76
|
+
self.unsupported_streaming_strategy: str = settings[
|
|
77
|
+
"unsupported_streaming_strategy"
|
|
78
|
+
]
|
|
79
|
+
|
|
80
|
+
async def process(
|
|
81
|
+
self, event: AstrMessageEvent, provider_wake_prefix: str
|
|
82
|
+
) -> AsyncGenerator[None, None]:
|
|
83
|
+
req: ProviderRequest | None = None
|
|
84
|
+
|
|
85
|
+
if provider_wake_prefix and not event.message_str.startswith(
|
|
86
|
+
provider_wake_prefix
|
|
87
|
+
):
|
|
88
|
+
return
|
|
89
|
+
|
|
90
|
+
self.prov_cfg: dict = next(
|
|
91
|
+
(p for p in self.conf["provider"] if p["id"] == self.prov_id),
|
|
92
|
+
{},
|
|
93
|
+
)
|
|
94
|
+
if not self.prov_id or not self.prov_cfg:
|
|
95
|
+
logger.error(
|
|
96
|
+
"Third Party Agent Runner provider ID is not configured properly."
|
|
97
|
+
)
|
|
98
|
+
return
|
|
99
|
+
|
|
100
|
+
# make provider request
|
|
101
|
+
req = ProviderRequest()
|
|
102
|
+
req.session_id = event.unified_msg_origin
|
|
103
|
+
req.prompt = event.message_str[len(provider_wake_prefix) :]
|
|
104
|
+
for comp in event.message_obj.message:
|
|
105
|
+
if isinstance(comp, Image):
|
|
106
|
+
image_path = await comp.convert_to_base64()
|
|
107
|
+
req.image_urls.append(image_path)
|
|
108
|
+
|
|
109
|
+
if not req.prompt and not req.image_urls:
|
|
110
|
+
return
|
|
111
|
+
|
|
112
|
+
# call event hook
|
|
113
|
+
if await call_event_hook(event, EventType.OnLLMRequestEvent, req):
|
|
114
|
+
return
|
|
115
|
+
|
|
116
|
+
if self.runner_type == "dify":
|
|
117
|
+
runner = DifyAgentRunner[AstrAgentContext]()
|
|
118
|
+
elif self.runner_type == "coze":
|
|
119
|
+
runner = CozeAgentRunner[AstrAgentContext]()
|
|
120
|
+
elif self.runner_type == "dashscope":
|
|
121
|
+
runner = DashscopeAgentRunner[AstrAgentContext]()
|
|
122
|
+
else:
|
|
123
|
+
raise ValueError(
|
|
124
|
+
f"Unsupported third party agent runner type: {self.runner_type}",
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
astr_agent_ctx = AstrAgentContext(
|
|
128
|
+
context=self.ctx.plugin_manager.context,
|
|
129
|
+
event=event,
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
streaming_response = self.streaming_response
|
|
133
|
+
if (enable_streaming := event.get_extra("enable_streaming")) is not None:
|
|
134
|
+
streaming_response = bool(enable_streaming)
|
|
135
|
+
|
|
136
|
+
stream_to_general = (
|
|
137
|
+
self.unsupported_streaming_strategy == "turn_off"
|
|
138
|
+
and not event.platform_meta.support_streaming_message
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
await runner.reset(
|
|
142
|
+
request=req,
|
|
143
|
+
run_context=AgentContextWrapper(
|
|
144
|
+
context=astr_agent_ctx,
|
|
145
|
+
tool_call_timeout=60,
|
|
146
|
+
),
|
|
147
|
+
agent_hooks=MAIN_AGENT_HOOKS,
|
|
148
|
+
provider_config=self.prov_cfg,
|
|
149
|
+
streaming=streaming_response,
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
if streaming_response and not stream_to_general:
|
|
153
|
+
# 流式响应
|
|
154
|
+
event.set_result(
|
|
155
|
+
MessageEventResult()
|
|
156
|
+
.set_result_content_type(ResultContentType.STREAMING_RESULT)
|
|
157
|
+
.set_async_stream(
|
|
158
|
+
run_third_party_agent(
|
|
159
|
+
runner,
|
|
160
|
+
stream_to_general=False,
|
|
161
|
+
),
|
|
162
|
+
),
|
|
163
|
+
)
|
|
164
|
+
yield
|
|
165
|
+
if runner.done():
|
|
166
|
+
final_resp = runner.get_final_llm_resp()
|
|
167
|
+
if final_resp and final_resp.result_chain:
|
|
168
|
+
event.set_result(
|
|
169
|
+
MessageEventResult(
|
|
170
|
+
chain=final_resp.result_chain.chain or [],
|
|
171
|
+
result_content_type=ResultContentType.STREAMING_FINISH,
|
|
172
|
+
),
|
|
173
|
+
)
|
|
174
|
+
else:
|
|
175
|
+
# 非流式响应或转换为普通响应
|
|
176
|
+
async for _ in run_third_party_agent(
|
|
177
|
+
runner,
|
|
178
|
+
stream_to_general=stream_to_general,
|
|
179
|
+
):
|
|
180
|
+
yield
|
|
181
|
+
|
|
182
|
+
final_resp = runner.get_final_llm_resp()
|
|
183
|
+
|
|
184
|
+
if not final_resp or not final_resp.result_chain:
|
|
185
|
+
logger.warning("Agent Runner 未返回最终结果。")
|
|
186
|
+
return
|
|
187
|
+
|
|
188
|
+
event.set_result(
|
|
189
|
+
MessageEventResult(
|
|
190
|
+
chain=final_resp.result_chain.chain or [],
|
|
191
|
+
result_content_type=ResultContentType.LLM_RESULT,
|
|
192
|
+
),
|
|
193
|
+
)
|
|
194
|
+
yield
|
|
195
|
+
|
|
196
|
+
asyncio.create_task(
|
|
197
|
+
Metric.upload(
|
|
198
|
+
llm_tick=1,
|
|
199
|
+
model_name=self.runner_type,
|
|
200
|
+
provider_type=self.runner_type,
|
|
201
|
+
),
|
|
202
|
+
)
|