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,19 @@
|
|
|
1
|
+
from pydantic import Field
|
|
2
|
+
from pydantic.dataclasses import dataclass
|
|
3
|
+
|
|
4
|
+
from astrbot.core.agent.run_context import ContextWrapper
|
|
5
|
+
from astrbot.core.platform.astr_message_event import AstrMessageEvent
|
|
6
|
+
from astrbot.core.star.context import Context
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass(config={"arbitrary_types_allowed": True})
|
|
10
|
+
class AstrAgentContext:
|
|
11
|
+
context: Context
|
|
12
|
+
"""The star context instance"""
|
|
13
|
+
event: AstrMessageEvent
|
|
14
|
+
"""The message event associated with the agent context."""
|
|
15
|
+
extra: dict[str, str] = Field(default_factory=dict)
|
|
16
|
+
"""Customized extra data."""
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
AgentContextWrapper = ContextWrapper[AstrAgentContext]
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from mcp.types import CallToolResult
|
|
4
|
+
|
|
5
|
+
from astrbot.core.agent.hooks import BaseAgentRunHooks
|
|
6
|
+
from astrbot.core.agent.run_context import ContextWrapper
|
|
7
|
+
from astrbot.core.agent.tool import FunctionTool
|
|
8
|
+
from astrbot.core.astr_agent_context import AstrAgentContext
|
|
9
|
+
from astrbot.core.pipeline.context_utils import call_event_hook
|
|
10
|
+
from astrbot.core.star.star_handler import EventType
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class MainAgentHooks(BaseAgentRunHooks[AstrAgentContext]):
|
|
14
|
+
async def on_agent_done(self, run_context, llm_response):
|
|
15
|
+
# 执行事件钩子
|
|
16
|
+
await call_event_hook(
|
|
17
|
+
run_context.context.event,
|
|
18
|
+
EventType.OnLLMResponseEvent,
|
|
19
|
+
llm_response,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
async def on_tool_end(
|
|
23
|
+
self,
|
|
24
|
+
run_context: ContextWrapper[AstrAgentContext],
|
|
25
|
+
tool: FunctionTool[Any],
|
|
26
|
+
tool_args: dict | None,
|
|
27
|
+
tool_result: CallToolResult | None,
|
|
28
|
+
):
|
|
29
|
+
run_context.context.event.clear_result()
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class EmptyAgentHooks(BaseAgentRunHooks[AstrAgentContext]):
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
MAIN_AGENT_HOOKS = MainAgentHooks()
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import traceback
|
|
2
|
+
from collections.abc import AsyncGenerator
|
|
3
|
+
|
|
4
|
+
from astrbot.core import logger
|
|
5
|
+
from astrbot.core.agent.runners.tool_loop_agent_runner import ToolLoopAgentRunner
|
|
6
|
+
from astrbot.core.astr_agent_context import AstrAgentContext
|
|
7
|
+
from astrbot.core.message.message_event_result import (
|
|
8
|
+
MessageChain,
|
|
9
|
+
MessageEventResult,
|
|
10
|
+
ResultContentType,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
AgentRunner = ToolLoopAgentRunner[AstrAgentContext]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
async def run_agent(
|
|
17
|
+
agent_runner: AgentRunner,
|
|
18
|
+
max_step: int = 30,
|
|
19
|
+
show_tool_use: bool = True,
|
|
20
|
+
stream_to_general: bool = False,
|
|
21
|
+
show_reasoning: bool = False,
|
|
22
|
+
) -> AsyncGenerator[MessageChain | None, None]:
|
|
23
|
+
step_idx = 0
|
|
24
|
+
astr_event = agent_runner.run_context.context.event
|
|
25
|
+
while step_idx < max_step:
|
|
26
|
+
step_idx += 1
|
|
27
|
+
try:
|
|
28
|
+
async for resp in agent_runner.step():
|
|
29
|
+
if astr_event.is_stopped():
|
|
30
|
+
return
|
|
31
|
+
if resp.type == "tool_call_result":
|
|
32
|
+
msg_chain = resp.data["chain"]
|
|
33
|
+
if msg_chain.type == "tool_direct_result":
|
|
34
|
+
# tool_direct_result 用于标记 llm tool 需要直接发送给用户的内容
|
|
35
|
+
await astr_event.send(resp.data["chain"])
|
|
36
|
+
continue
|
|
37
|
+
# 对于其他情况,暂时先不处理
|
|
38
|
+
continue
|
|
39
|
+
elif resp.type == "tool_call":
|
|
40
|
+
if agent_runner.streaming:
|
|
41
|
+
# 用来标记流式响应需要分节
|
|
42
|
+
yield MessageChain(chain=[], type="break")
|
|
43
|
+
if show_tool_use:
|
|
44
|
+
await astr_event.send(resp.data["chain"])
|
|
45
|
+
continue
|
|
46
|
+
|
|
47
|
+
if stream_to_general and resp.type == "streaming_delta":
|
|
48
|
+
continue
|
|
49
|
+
|
|
50
|
+
if stream_to_general or not agent_runner.streaming:
|
|
51
|
+
content_typ = (
|
|
52
|
+
ResultContentType.LLM_RESULT
|
|
53
|
+
if resp.type == "llm_result"
|
|
54
|
+
else ResultContentType.GENERAL_RESULT
|
|
55
|
+
)
|
|
56
|
+
astr_event.set_result(
|
|
57
|
+
MessageEventResult(
|
|
58
|
+
chain=resp.data["chain"].chain,
|
|
59
|
+
result_content_type=content_typ,
|
|
60
|
+
),
|
|
61
|
+
)
|
|
62
|
+
yield
|
|
63
|
+
astr_event.clear_result()
|
|
64
|
+
elif resp.type == "streaming_delta":
|
|
65
|
+
chain = resp.data["chain"]
|
|
66
|
+
if chain.type == "reasoning" and not show_reasoning:
|
|
67
|
+
# display the reasoning content only when configured
|
|
68
|
+
continue
|
|
69
|
+
yield resp.data["chain"] # MessageChain
|
|
70
|
+
if agent_runner.done():
|
|
71
|
+
break
|
|
72
|
+
|
|
73
|
+
except Exception as e:
|
|
74
|
+
logger.error(traceback.format_exc())
|
|
75
|
+
err_msg = f"\n\nAstrBot 请求失败。\n错误类型: {type(e).__name__}\n错误信息: {e!s}\n\n请在控制台查看和分享错误详情。\n"
|
|
76
|
+
if agent_runner.streaming:
|
|
77
|
+
yield MessageChain().message(err_msg)
|
|
78
|
+
else:
|
|
79
|
+
astr_event.set_result(MessageEventResult().message(err_msg))
|
|
80
|
+
return
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import inspect
|
|
3
|
+
import traceback
|
|
4
|
+
import typing as T
|
|
5
|
+
|
|
6
|
+
import mcp
|
|
7
|
+
|
|
8
|
+
from astrbot import logger
|
|
9
|
+
from astrbot.core.agent.handoff import HandoffTool
|
|
10
|
+
from astrbot.core.agent.mcp_client import MCPTool
|
|
11
|
+
from astrbot.core.agent.run_context import ContextWrapper
|
|
12
|
+
from astrbot.core.agent.tool import FunctionTool, ToolSet
|
|
13
|
+
from astrbot.core.agent.tool_executor import BaseFunctionToolExecutor
|
|
14
|
+
from astrbot.core.astr_agent_context import AstrAgentContext
|
|
15
|
+
from astrbot.core.message.message_event_result import (
|
|
16
|
+
CommandResult,
|
|
17
|
+
MessageChain,
|
|
18
|
+
MessageEventResult,
|
|
19
|
+
)
|
|
20
|
+
from astrbot.core.provider.register import llm_tools
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class FunctionToolExecutor(BaseFunctionToolExecutor[AstrAgentContext]):
|
|
24
|
+
@classmethod
|
|
25
|
+
async def execute(cls, tool, run_context, **tool_args):
|
|
26
|
+
"""执行函数调用。
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
event (AstrMessageEvent): 事件对象, 当 origin 为 local 时必须提供。
|
|
30
|
+
**kwargs: 函数调用的参数。
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
AsyncGenerator[None | mcp.types.CallToolResult, None]
|
|
34
|
+
|
|
35
|
+
"""
|
|
36
|
+
if isinstance(tool, HandoffTool):
|
|
37
|
+
async for r in cls._execute_handoff(tool, run_context, **tool_args):
|
|
38
|
+
yield r
|
|
39
|
+
return
|
|
40
|
+
|
|
41
|
+
elif isinstance(tool, MCPTool):
|
|
42
|
+
async for r in cls._execute_mcp(tool, run_context, **tool_args):
|
|
43
|
+
yield r
|
|
44
|
+
return
|
|
45
|
+
|
|
46
|
+
else:
|
|
47
|
+
async for r in cls._execute_local(tool, run_context, **tool_args):
|
|
48
|
+
yield r
|
|
49
|
+
return
|
|
50
|
+
|
|
51
|
+
@classmethod
|
|
52
|
+
async def _execute_handoff(
|
|
53
|
+
cls,
|
|
54
|
+
tool: HandoffTool,
|
|
55
|
+
run_context: ContextWrapper[AstrAgentContext],
|
|
56
|
+
**tool_args,
|
|
57
|
+
):
|
|
58
|
+
input_ = tool_args.get("input")
|
|
59
|
+
|
|
60
|
+
# make toolset for the agent
|
|
61
|
+
tools = tool.agent.tools
|
|
62
|
+
if tools:
|
|
63
|
+
toolset = ToolSet()
|
|
64
|
+
for t in tools:
|
|
65
|
+
if isinstance(t, str):
|
|
66
|
+
_t = llm_tools.get_func(t)
|
|
67
|
+
if _t:
|
|
68
|
+
toolset.add_tool(_t)
|
|
69
|
+
elif isinstance(t, FunctionTool):
|
|
70
|
+
toolset.add_tool(t)
|
|
71
|
+
else:
|
|
72
|
+
toolset = None
|
|
73
|
+
|
|
74
|
+
ctx = run_context.context.context
|
|
75
|
+
event = run_context.context.event
|
|
76
|
+
umo = event.unified_msg_origin
|
|
77
|
+
prov_id = await ctx.get_current_chat_provider_id(umo)
|
|
78
|
+
llm_resp = await ctx.tool_loop_agent(
|
|
79
|
+
event=event,
|
|
80
|
+
chat_provider_id=prov_id,
|
|
81
|
+
prompt=input_,
|
|
82
|
+
system_prompt=tool.agent.instructions,
|
|
83
|
+
tools=toolset,
|
|
84
|
+
max_steps=30,
|
|
85
|
+
run_hooks=tool.agent.run_hooks,
|
|
86
|
+
)
|
|
87
|
+
yield mcp.types.CallToolResult(
|
|
88
|
+
content=[mcp.types.TextContent(type="text", text=llm_resp.completion_text)]
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
@classmethod
|
|
92
|
+
async def _execute_local(
|
|
93
|
+
cls,
|
|
94
|
+
tool: FunctionTool,
|
|
95
|
+
run_context: ContextWrapper[AstrAgentContext],
|
|
96
|
+
**tool_args,
|
|
97
|
+
):
|
|
98
|
+
event = run_context.context.event
|
|
99
|
+
if not event:
|
|
100
|
+
raise ValueError("Event must be provided for local function tools.")
|
|
101
|
+
|
|
102
|
+
is_override_call = False
|
|
103
|
+
for ty in type(tool).mro():
|
|
104
|
+
if "call" in ty.__dict__ and ty.__dict__["call"] is not FunctionTool.call:
|
|
105
|
+
is_override_call = True
|
|
106
|
+
break
|
|
107
|
+
|
|
108
|
+
# 检查 tool 下有没有 run 方法
|
|
109
|
+
if not tool.handler and not hasattr(tool, "run") and not is_override_call:
|
|
110
|
+
raise ValueError("Tool must have a valid handler or override 'run' method.")
|
|
111
|
+
|
|
112
|
+
awaitable = None
|
|
113
|
+
method_name = ""
|
|
114
|
+
if tool.handler:
|
|
115
|
+
awaitable = tool.handler
|
|
116
|
+
method_name = "decorator_handler"
|
|
117
|
+
elif is_override_call:
|
|
118
|
+
awaitable = tool.call
|
|
119
|
+
method_name = "call"
|
|
120
|
+
elif hasattr(tool, "run"):
|
|
121
|
+
awaitable = getattr(tool, "run")
|
|
122
|
+
method_name = "run"
|
|
123
|
+
if awaitable is None:
|
|
124
|
+
raise ValueError("Tool must have a valid handler or override 'run' method.")
|
|
125
|
+
|
|
126
|
+
wrapper = call_local_llm_tool(
|
|
127
|
+
context=run_context,
|
|
128
|
+
handler=awaitable,
|
|
129
|
+
method_name=method_name,
|
|
130
|
+
**tool_args,
|
|
131
|
+
)
|
|
132
|
+
while True:
|
|
133
|
+
try:
|
|
134
|
+
resp = await asyncio.wait_for(
|
|
135
|
+
anext(wrapper),
|
|
136
|
+
timeout=run_context.tool_call_timeout,
|
|
137
|
+
)
|
|
138
|
+
if resp is not None:
|
|
139
|
+
if isinstance(resp, mcp.types.CallToolResult):
|
|
140
|
+
yield resp
|
|
141
|
+
else:
|
|
142
|
+
text_content = mcp.types.TextContent(
|
|
143
|
+
type="text",
|
|
144
|
+
text=str(resp),
|
|
145
|
+
)
|
|
146
|
+
yield mcp.types.CallToolResult(content=[text_content])
|
|
147
|
+
else:
|
|
148
|
+
# NOTE: Tool 在这里直接请求发送消息给用户
|
|
149
|
+
# TODO: 是否需要判断 event.get_result() 是否为空?
|
|
150
|
+
# 如果为空,则说明没有发送消息给用户,并且返回值为空,将返回一个特殊的 TextContent,其内容如"工具没有返回内容"
|
|
151
|
+
if res := run_context.context.event.get_result():
|
|
152
|
+
if res.chain:
|
|
153
|
+
try:
|
|
154
|
+
await event.send(
|
|
155
|
+
MessageChain(
|
|
156
|
+
chain=res.chain,
|
|
157
|
+
type="tool_direct_result",
|
|
158
|
+
)
|
|
159
|
+
)
|
|
160
|
+
except Exception as e:
|
|
161
|
+
logger.error(
|
|
162
|
+
f"Tool 直接发送消息失败: {e}",
|
|
163
|
+
exc_info=True,
|
|
164
|
+
)
|
|
165
|
+
yield None
|
|
166
|
+
except asyncio.TimeoutError:
|
|
167
|
+
raise Exception(
|
|
168
|
+
f"tool {tool.name} execution timeout after {run_context.tool_call_timeout} seconds.",
|
|
169
|
+
)
|
|
170
|
+
except StopAsyncIteration:
|
|
171
|
+
break
|
|
172
|
+
|
|
173
|
+
@classmethod
|
|
174
|
+
async def _execute_mcp(
|
|
175
|
+
cls,
|
|
176
|
+
tool: FunctionTool,
|
|
177
|
+
run_context: ContextWrapper[AstrAgentContext],
|
|
178
|
+
**tool_args,
|
|
179
|
+
):
|
|
180
|
+
res = await tool.call(run_context, **tool_args)
|
|
181
|
+
if not res:
|
|
182
|
+
return
|
|
183
|
+
yield res
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
async def call_local_llm_tool(
|
|
187
|
+
context: ContextWrapper[AstrAgentContext],
|
|
188
|
+
handler: T.Callable[..., T.Awaitable[T.Any]],
|
|
189
|
+
method_name: str,
|
|
190
|
+
*args,
|
|
191
|
+
**kwargs,
|
|
192
|
+
) -> T.AsyncGenerator[T.Any, None]:
|
|
193
|
+
"""执行本地 LLM 工具的处理函数并处理其返回结果"""
|
|
194
|
+
ready_to_call = None # 一个协程或者异步生成器
|
|
195
|
+
|
|
196
|
+
trace_ = None
|
|
197
|
+
|
|
198
|
+
event = context.context.event
|
|
199
|
+
|
|
200
|
+
try:
|
|
201
|
+
if method_name == "run" or method_name == "decorator_handler":
|
|
202
|
+
ready_to_call = handler(event, *args, **kwargs)
|
|
203
|
+
elif method_name == "call":
|
|
204
|
+
ready_to_call = handler(context, *args, **kwargs)
|
|
205
|
+
else:
|
|
206
|
+
raise ValueError(f"未知的方法名: {method_name}")
|
|
207
|
+
except ValueError as e:
|
|
208
|
+
logger.error(f"调用本地 LLM 工具时出错: {e}", exc_info=True)
|
|
209
|
+
except TypeError:
|
|
210
|
+
logger.error("处理函数参数不匹配,请检查 handler 的定义。", exc_info=True)
|
|
211
|
+
except Exception as e:
|
|
212
|
+
trace_ = traceback.format_exc()
|
|
213
|
+
logger.error(f"调用本地 LLM 工具时出错: {e}\n{trace_}")
|
|
214
|
+
|
|
215
|
+
if not ready_to_call:
|
|
216
|
+
return
|
|
217
|
+
|
|
218
|
+
if inspect.isasyncgen(ready_to_call):
|
|
219
|
+
_has_yielded = False
|
|
220
|
+
try:
|
|
221
|
+
async for ret in ready_to_call:
|
|
222
|
+
# 这里逐步执行异步生成器, 对于每个 yield 返回的 ret, 执行下面的代码
|
|
223
|
+
# 返回值只能是 MessageEventResult 或者 None(无返回值)
|
|
224
|
+
_has_yielded = True
|
|
225
|
+
if isinstance(ret, (MessageEventResult, CommandResult)):
|
|
226
|
+
# 如果返回值是 MessageEventResult, 设置结果并继续
|
|
227
|
+
event.set_result(ret)
|
|
228
|
+
yield
|
|
229
|
+
else:
|
|
230
|
+
# 如果返回值是 None, 则不设置结果并继续
|
|
231
|
+
# 继续执行后续阶段
|
|
232
|
+
yield ret
|
|
233
|
+
if not _has_yielded:
|
|
234
|
+
# 如果这个异步生成器没有执行到 yield 分支
|
|
235
|
+
yield
|
|
236
|
+
except Exception as e:
|
|
237
|
+
logger.error(f"Previous Error: {trace_}")
|
|
238
|
+
raise e
|
|
239
|
+
elif inspect.iscoroutine(ready_to_call):
|
|
240
|
+
# 如果只是一个协程, 直接执行
|
|
241
|
+
ret = await ready_to_call
|
|
242
|
+
if isinstance(ret, (MessageEventResult, CommandResult)):
|
|
243
|
+
event.set_result(ret)
|
|
244
|
+
yield
|
|
245
|
+
else:
|
|
246
|
+
yield ret
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import uuid
|
|
3
|
+
from typing import TypedDict, TypeVar
|
|
4
|
+
|
|
5
|
+
from astrbot.core import AstrBotConfig, logger
|
|
6
|
+
from astrbot.core.config.astrbot_config import ASTRBOT_CONFIG_PATH
|
|
7
|
+
from astrbot.core.config.default import DEFAULT_CONFIG
|
|
8
|
+
from astrbot.core.platform.message_session import MessageSession
|
|
9
|
+
from astrbot.core.umop_config_router import UmopConfigRouter
|
|
10
|
+
from astrbot.core.utils.astrbot_path import get_astrbot_config_path
|
|
11
|
+
from astrbot.core.utils.shared_preferences import SharedPreferences
|
|
12
|
+
|
|
13
|
+
_VT = TypeVar("_VT")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ConfInfo(TypedDict):
|
|
17
|
+
"""Configuration information for a specific session or platform."""
|
|
18
|
+
|
|
19
|
+
id: str # UUID of the configuration or "default"
|
|
20
|
+
name: str
|
|
21
|
+
path: str # File name to the configuration file
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
DEFAULT_CONFIG_CONF_INFO = ConfInfo(
|
|
25
|
+
id="default",
|
|
26
|
+
name="default",
|
|
27
|
+
path=ASTRBOT_CONFIG_PATH,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class AstrBotConfigManager:
|
|
32
|
+
"""A class to manage the system configuration of AstrBot, aka ACM"""
|
|
33
|
+
|
|
34
|
+
def __init__(
|
|
35
|
+
self,
|
|
36
|
+
default_config: AstrBotConfig,
|
|
37
|
+
ucr: UmopConfigRouter,
|
|
38
|
+
sp: SharedPreferences,
|
|
39
|
+
):
|
|
40
|
+
self.sp = sp
|
|
41
|
+
self.ucr = ucr
|
|
42
|
+
self.confs: dict[str, AstrBotConfig] = {}
|
|
43
|
+
"""uuid / "default" -> AstrBotConfig"""
|
|
44
|
+
self.confs["default"] = default_config
|
|
45
|
+
self.abconf_data = None
|
|
46
|
+
self._load_all_configs()
|
|
47
|
+
|
|
48
|
+
def _get_abconf_data(self) -> dict:
|
|
49
|
+
"""获取所有的 abconf 数据"""
|
|
50
|
+
if self.abconf_data is None:
|
|
51
|
+
self.abconf_data = self.sp.get(
|
|
52
|
+
"abconf_mapping",
|
|
53
|
+
{},
|
|
54
|
+
scope="global",
|
|
55
|
+
scope_id="global",
|
|
56
|
+
)
|
|
57
|
+
return self.abconf_data
|
|
58
|
+
|
|
59
|
+
def _load_all_configs(self):
|
|
60
|
+
"""Load all configurations from the shared preferences."""
|
|
61
|
+
abconf_data = self._get_abconf_data()
|
|
62
|
+
self.abconf_data = abconf_data
|
|
63
|
+
for uuid_, meta in abconf_data.items():
|
|
64
|
+
filename = meta["path"]
|
|
65
|
+
conf_path = os.path.join(get_astrbot_config_path(), filename)
|
|
66
|
+
if os.path.exists(conf_path):
|
|
67
|
+
conf = AstrBotConfig(config_path=conf_path)
|
|
68
|
+
self.confs[uuid_] = conf
|
|
69
|
+
else:
|
|
70
|
+
logger.warning(
|
|
71
|
+
f"Config file {conf_path} for UUID {uuid_} does not exist, skipping.",
|
|
72
|
+
)
|
|
73
|
+
continue
|
|
74
|
+
|
|
75
|
+
def _load_conf_mapping(self, umo: str | MessageSession) -> ConfInfo:
|
|
76
|
+
"""获取指定 umo 的配置文件 uuid, 如果不存在则返回默认配置(返回 "default")
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
ConfInfo: 包含配置文件的 uuid, 路径和名称等信息, 是一个 dict 类型
|
|
80
|
+
|
|
81
|
+
"""
|
|
82
|
+
# uuid -> { "path": str, "name": str }
|
|
83
|
+
abconf_data = self._get_abconf_data()
|
|
84
|
+
|
|
85
|
+
if isinstance(umo, MessageSession):
|
|
86
|
+
umo = str(umo)
|
|
87
|
+
else:
|
|
88
|
+
try:
|
|
89
|
+
umo = str(MessageSession.from_str(umo)) # validate
|
|
90
|
+
except Exception:
|
|
91
|
+
return DEFAULT_CONFIG_CONF_INFO
|
|
92
|
+
|
|
93
|
+
conf_id = self.ucr.get_conf_id_for_umop(umo)
|
|
94
|
+
if conf_id:
|
|
95
|
+
meta = abconf_data.get(conf_id)
|
|
96
|
+
if meta and isinstance(meta, dict):
|
|
97
|
+
# the bind relation between umo and conf is defined in ucr now, so we remove "umop" here
|
|
98
|
+
meta.pop("umop", None)
|
|
99
|
+
return ConfInfo(**meta, id=conf_id)
|
|
100
|
+
|
|
101
|
+
return DEFAULT_CONFIG_CONF_INFO
|
|
102
|
+
|
|
103
|
+
def _save_conf_mapping(
|
|
104
|
+
self,
|
|
105
|
+
abconf_path: str,
|
|
106
|
+
abconf_id: str,
|
|
107
|
+
abconf_name: str | None = None,
|
|
108
|
+
) -> None:
|
|
109
|
+
"""保存配置文件的映射关系"""
|
|
110
|
+
abconf_data = self.sp.get(
|
|
111
|
+
"abconf_mapping",
|
|
112
|
+
{},
|
|
113
|
+
scope="global",
|
|
114
|
+
scope_id="global",
|
|
115
|
+
)
|
|
116
|
+
random_word = abconf_name or uuid.uuid4().hex[:8]
|
|
117
|
+
abconf_data[abconf_id] = {
|
|
118
|
+
"path": abconf_path,
|
|
119
|
+
"name": random_word,
|
|
120
|
+
}
|
|
121
|
+
self.sp.put("abconf_mapping", abconf_data, scope="global", scope_id="global")
|
|
122
|
+
self.abconf_data = abconf_data
|
|
123
|
+
|
|
124
|
+
def get_conf(self, umo: str | MessageSession | None) -> AstrBotConfig:
|
|
125
|
+
"""获取指定 umo 的配置文件。如果不存在,则 fallback 到默认配置文件。"""
|
|
126
|
+
if not umo:
|
|
127
|
+
return self.confs["default"]
|
|
128
|
+
if isinstance(umo, MessageSession):
|
|
129
|
+
umo = f"{umo.platform_id}:{umo.message_type}:{umo.session_id}"
|
|
130
|
+
|
|
131
|
+
uuid_ = self._load_conf_mapping(umo)["id"]
|
|
132
|
+
|
|
133
|
+
conf = self.confs.get(uuid_)
|
|
134
|
+
if not conf:
|
|
135
|
+
conf = self.confs["default"] # default MUST exists
|
|
136
|
+
|
|
137
|
+
return conf
|
|
138
|
+
|
|
139
|
+
@property
|
|
140
|
+
def default_conf(self) -> AstrBotConfig:
|
|
141
|
+
"""获取默认配置文件"""
|
|
142
|
+
return self.confs["default"]
|
|
143
|
+
|
|
144
|
+
def get_conf_info(self, umo: str | MessageSession) -> ConfInfo:
|
|
145
|
+
"""获取指定 umo 的配置文件元数据"""
|
|
146
|
+
if isinstance(umo, MessageSession):
|
|
147
|
+
umo = f"{umo.platform_id}:{umo.message_type}:{umo.session_id}"
|
|
148
|
+
|
|
149
|
+
return self._load_conf_mapping(umo)
|
|
150
|
+
|
|
151
|
+
def get_conf_list(self) -> list[ConfInfo]:
|
|
152
|
+
"""获取所有配置文件的元数据列表"""
|
|
153
|
+
conf_list = []
|
|
154
|
+
abconf_mapping = self._get_abconf_data()
|
|
155
|
+
for uuid_, meta in abconf_mapping.items():
|
|
156
|
+
if not isinstance(meta, dict):
|
|
157
|
+
continue
|
|
158
|
+
meta.pop("umop", None)
|
|
159
|
+
conf_list.append(ConfInfo(**meta, id=uuid_))
|
|
160
|
+
conf_list.append(DEFAULT_CONFIG_CONF_INFO)
|
|
161
|
+
return conf_list
|
|
162
|
+
|
|
163
|
+
def create_conf(
|
|
164
|
+
self,
|
|
165
|
+
config: dict = DEFAULT_CONFIG,
|
|
166
|
+
name: str | None = None,
|
|
167
|
+
) -> str:
|
|
168
|
+
conf_uuid = str(uuid.uuid4())
|
|
169
|
+
conf_file_name = f"abconf_{conf_uuid}.json"
|
|
170
|
+
conf_path = os.path.join(get_astrbot_config_path(), conf_file_name)
|
|
171
|
+
conf = AstrBotConfig(config_path=conf_path, default_config=config)
|
|
172
|
+
conf.save_config()
|
|
173
|
+
self._save_conf_mapping(conf_file_name, conf_uuid, abconf_name=name)
|
|
174
|
+
self.confs[conf_uuid] = conf
|
|
175
|
+
return conf_uuid
|
|
176
|
+
|
|
177
|
+
def delete_conf(self, conf_id: str) -> bool:
|
|
178
|
+
"""删除指定配置文件
|
|
179
|
+
|
|
180
|
+
Args:
|
|
181
|
+
conf_id: 配置文件的 UUID
|
|
182
|
+
|
|
183
|
+
Returns:
|
|
184
|
+
bool: 删除是否成功
|
|
185
|
+
|
|
186
|
+
Raises:
|
|
187
|
+
ValueError: 如果试图删除默认配置文件
|
|
188
|
+
|
|
189
|
+
"""
|
|
190
|
+
if conf_id == "default":
|
|
191
|
+
raise ValueError("不能删除默认配置文件")
|
|
192
|
+
|
|
193
|
+
# 从映射中移除
|
|
194
|
+
abconf_data = self.sp.get(
|
|
195
|
+
"abconf_mapping",
|
|
196
|
+
{},
|
|
197
|
+
scope="global",
|
|
198
|
+
scope_id="global",
|
|
199
|
+
)
|
|
200
|
+
if conf_id not in abconf_data:
|
|
201
|
+
logger.warning(f"配置文件 {conf_id} 不存在于映射中")
|
|
202
|
+
return False
|
|
203
|
+
|
|
204
|
+
# 获取配置文件路径
|
|
205
|
+
conf_path = os.path.join(
|
|
206
|
+
get_astrbot_config_path(),
|
|
207
|
+
abconf_data[conf_id]["path"],
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
# 删除配置文件
|
|
211
|
+
try:
|
|
212
|
+
if os.path.exists(conf_path):
|
|
213
|
+
os.remove(conf_path)
|
|
214
|
+
logger.info(f"已删除配置文件: {conf_path}")
|
|
215
|
+
except Exception as e:
|
|
216
|
+
logger.error(f"删除配置文件 {conf_path} 失败: {e}")
|
|
217
|
+
return False
|
|
218
|
+
|
|
219
|
+
# 从内存中移除
|
|
220
|
+
if conf_id in self.confs:
|
|
221
|
+
del self.confs[conf_id]
|
|
222
|
+
|
|
223
|
+
# 从映射中移除
|
|
224
|
+
del abconf_data[conf_id]
|
|
225
|
+
self.sp.put("abconf_mapping", abconf_data, scope="global", scope_id="global")
|
|
226
|
+
self.abconf_data = abconf_data
|
|
227
|
+
|
|
228
|
+
logger.info(f"成功删除配置文件 {conf_id}")
|
|
229
|
+
return True
|
|
230
|
+
|
|
231
|
+
def update_conf_info(self, conf_id: str, name: str | None = None) -> bool:
|
|
232
|
+
"""更新配置文件信息
|
|
233
|
+
|
|
234
|
+
Args:
|
|
235
|
+
conf_id: 配置文件的 UUID
|
|
236
|
+
name: 新的配置文件名称 (可选)
|
|
237
|
+
|
|
238
|
+
Returns:
|
|
239
|
+
bool: 更新是否成功
|
|
240
|
+
|
|
241
|
+
"""
|
|
242
|
+
if conf_id == "default":
|
|
243
|
+
raise ValueError("不能更新默认配置文件的信息")
|
|
244
|
+
|
|
245
|
+
abconf_data = self.sp.get(
|
|
246
|
+
"abconf_mapping",
|
|
247
|
+
{},
|
|
248
|
+
scope="global",
|
|
249
|
+
scope_id="global",
|
|
250
|
+
)
|
|
251
|
+
if conf_id not in abconf_data:
|
|
252
|
+
logger.warning(f"配置文件 {conf_id} 不存在于映射中")
|
|
253
|
+
return False
|
|
254
|
+
|
|
255
|
+
# 更新名称
|
|
256
|
+
if name is not None:
|
|
257
|
+
abconf_data[conf_id]["name"] = name
|
|
258
|
+
|
|
259
|
+
# 保存更新
|
|
260
|
+
self.sp.put("abconf_mapping", abconf_data, scope="global", scope_id="global")
|
|
261
|
+
self.abconf_data = abconf_data
|
|
262
|
+
logger.info(f"成功更新配置文件 {conf_id} 的信息")
|
|
263
|
+
return True
|
|
264
|
+
|
|
265
|
+
def g(
|
|
266
|
+
self,
|
|
267
|
+
umo: str | None = None,
|
|
268
|
+
key: str | None = None,
|
|
269
|
+
default: _VT = None,
|
|
270
|
+
) -> _VT:
|
|
271
|
+
"""获取配置项。umo 为 None 时使用默认配置"""
|
|
272
|
+
if umo is None:
|
|
273
|
+
return self.confs["default"].get(key, default)
|
|
274
|
+
conf = self.get_conf(umo)
|
|
275
|
+
return conf.get(key, default)
|
astrbot/core/config/__init__.py
CHANGED