AstrBot 4.5.1__py3-none-any.whl → 4.5.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- astrbot/api/__init__.py +10 -11
- astrbot/api/event/__init__.py +5 -6
- astrbot/api/event/filter/__init__.py +37 -36
- astrbot/api/platform/__init__.py +7 -8
- astrbot/api/provider/__init__.py +7 -7
- astrbot/api/star/__init__.py +3 -4
- astrbot/api/util/__init__.py +2 -2
- astrbot/cli/__main__.py +5 -5
- astrbot/cli/commands/__init__.py +3 -3
- astrbot/cli/commands/cmd_conf.py +19 -16
- astrbot/cli/commands/cmd_init.py +3 -2
- astrbot/cli/commands/cmd_plug.py +8 -10
- astrbot/cli/commands/cmd_run.py +5 -6
- astrbot/cli/utils/__init__.py +6 -6
- astrbot/cli/utils/basic.py +14 -14
- astrbot/cli/utils/plugin.py +24 -15
- astrbot/cli/utils/version_comparator.py +10 -12
- astrbot/core/__init__.py +8 -6
- astrbot/core/agent/agent.py +3 -2
- astrbot/core/agent/handoff.py +6 -2
- astrbot/core/agent/hooks.py +9 -6
- astrbot/core/agent/mcp_client.py +50 -15
- astrbot/core/agent/message.py +168 -0
- astrbot/core/agent/response.py +2 -1
- astrbot/core/agent/run_context.py +2 -3
- astrbot/core/agent/runners/base.py +10 -13
- astrbot/core/agent/runners/tool_loop_agent_runner.py +52 -51
- astrbot/core/agent/tool.py +60 -41
- astrbot/core/agent/tool_executor.py +9 -3
- astrbot/core/astr_agent_context.py +3 -1
- astrbot/core/astrbot_config_mgr.py +29 -9
- astrbot/core/config/__init__.py +2 -2
- astrbot/core/config/astrbot_config.py +28 -26
- astrbot/core/config/default.py +4 -6
- astrbot/core/conversation_mgr.py +105 -36
- astrbot/core/core_lifecycle.py +68 -54
- astrbot/core/db/__init__.py +33 -18
- astrbot/core/db/migration/helper.py +12 -10
- astrbot/core/db/migration/migra_3_to_4.py +53 -34
- astrbot/core/db/migration/migra_45_to_46.py +1 -1
- astrbot/core/db/migration/shared_preferences_v3.py +2 -1
- astrbot/core/db/migration/sqlite_v3.py +26 -23
- astrbot/core/db/po.py +27 -18
- astrbot/core/db/sqlite.py +74 -45
- astrbot/core/db/vec_db/base.py +10 -14
- astrbot/core/db/vec_db/faiss_impl/document_storage.py +90 -77
- astrbot/core/db/vec_db/faiss_impl/embedding_storage.py +9 -3
- astrbot/core/db/vec_db/faiss_impl/vec_db.py +36 -31
- astrbot/core/event_bus.py +8 -6
- astrbot/core/file_token_service.py +6 -5
- astrbot/core/initial_loader.py +7 -5
- astrbot/core/knowledge_base/chunking/__init__.py +1 -3
- astrbot/core/knowledge_base/chunking/base.py +1 -0
- astrbot/core/knowledge_base/chunking/fixed_size.py +2 -0
- astrbot/core/knowledge_base/chunking/recursive.py +16 -10
- astrbot/core/knowledge_base/kb_db_sqlite.py +50 -48
- astrbot/core/knowledge_base/kb_helper.py +30 -17
- astrbot/core/knowledge_base/kb_mgr.py +6 -7
- astrbot/core/knowledge_base/models.py +10 -4
- astrbot/core/knowledge_base/parsers/__init__.py +3 -5
- astrbot/core/knowledge_base/parsers/base.py +1 -0
- astrbot/core/knowledge_base/parsers/markitdown_parser.py +2 -1
- astrbot/core/knowledge_base/parsers/pdf_parser.py +2 -1
- astrbot/core/knowledge_base/parsers/text_parser.py +1 -0
- astrbot/core/knowledge_base/parsers/util.py +1 -1
- astrbot/core/knowledge_base/retrieval/__init__.py +6 -8
- astrbot/core/knowledge_base/retrieval/manager.py +17 -14
- astrbot/core/knowledge_base/retrieval/rank_fusion.py +7 -3
- astrbot/core/knowledge_base/retrieval/sparse_retriever.py +11 -5
- astrbot/core/log.py +21 -13
- astrbot/core/message/components.py +123 -217
- astrbot/core/message/message_event_result.py +24 -24
- astrbot/core/persona_mgr.py +20 -11
- astrbot/core/pipeline/__init__.py +7 -7
- astrbot/core/pipeline/content_safety_check/stage.py +13 -9
- astrbot/core/pipeline/content_safety_check/strategies/__init__.py +1 -2
- astrbot/core/pipeline/content_safety_check/strategies/baidu_aip.py +12 -13
- astrbot/core/pipeline/content_safety_check/strategies/keywords.py +1 -0
- astrbot/core/pipeline/content_safety_check/strategies/strategy.py +6 -6
- astrbot/core/pipeline/context.py +4 -1
- astrbot/core/pipeline/context_utils.py +77 -7
- astrbot/core/pipeline/preprocess_stage/stage.py +12 -9
- astrbot/core/pipeline/process_stage/method/llm_request.py +125 -72
- astrbot/core/pipeline/process_stage/method/star_request.py +19 -17
- astrbot/core/pipeline/process_stage/stage.py +13 -10
- astrbot/core/pipeline/process_stage/utils.py +6 -5
- astrbot/core/pipeline/rate_limit_check/stage.py +37 -36
- astrbot/core/pipeline/respond/stage.py +23 -20
- astrbot/core/pipeline/result_decorate/stage.py +31 -23
- astrbot/core/pipeline/scheduler.py +12 -8
- astrbot/core/pipeline/session_status_check/stage.py +12 -8
- astrbot/core/pipeline/stage.py +10 -4
- astrbot/core/pipeline/waking_check/stage.py +24 -18
- astrbot/core/pipeline/whitelist_check/stage.py +10 -7
- astrbot/core/platform/__init__.py +6 -6
- astrbot/core/platform/astr_message_event.py +76 -110
- astrbot/core/platform/astrbot_message.py +11 -13
- astrbot/core/platform/manager.py +16 -15
- astrbot/core/platform/message_session.py +5 -3
- astrbot/core/platform/platform.py +16 -24
- astrbot/core/platform/platform_metadata.py +4 -4
- astrbot/core/platform/register.py +8 -8
- astrbot/core/platform/sources/aiocqhttp/aiocqhttp_message_event.py +23 -15
- astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py +51 -33
- astrbot/core/platform/sources/dingtalk/dingtalk_adapter.py +42 -27
- astrbot/core/platform/sources/dingtalk/dingtalk_event.py +7 -3
- astrbot/core/platform/sources/discord/client.py +9 -6
- astrbot/core/platform/sources/discord/components.py +18 -14
- astrbot/core/platform/sources/discord/discord_platform_adapter.py +45 -30
- astrbot/core/platform/sources/discord/discord_platform_event.py +38 -30
- astrbot/core/platform/sources/lark/lark_adapter.py +23 -17
- astrbot/core/platform/sources/lark/lark_event.py +21 -14
- astrbot/core/platform/sources/misskey/misskey_adapter.py +107 -67
- astrbot/core/platform/sources/misskey/misskey_api.py +153 -129
- astrbot/core/platform/sources/misskey/misskey_event.py +20 -15
- astrbot/core/platform/sources/misskey/misskey_utils.py +74 -62
- astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py +63 -44
- astrbot/core/platform/sources/qqofficial/qqofficial_platform_adapter.py +41 -26
- astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_adapter.py +36 -17
- astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_event.py +3 -1
- astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_server.py +12 -7
- astrbot/core/platform/sources/satori/satori_adapter.py +56 -38
- astrbot/core/platform/sources/satori/satori_event.py +34 -25
- astrbot/core/platform/sources/slack/client.py +11 -9
- astrbot/core/platform/sources/slack/slack_adapter.py +52 -36
- astrbot/core/platform/sources/slack/slack_event.py +34 -24
- astrbot/core/platform/sources/telegram/tg_adapter.py +38 -18
- astrbot/core/platform/sources/telegram/tg_event.py +32 -18
- astrbot/core/platform/sources/webchat/webchat_adapter.py +27 -17
- astrbot/core/platform/sources/webchat/webchat_event.py +14 -10
- astrbot/core/platform/sources/wechatpadpro/wechatpadpro_adapter.py +115 -120
- astrbot/core/platform/sources/wechatpadpro/wechatpadpro_message_event.py +9 -8
- astrbot/core/platform/sources/wechatpadpro/xml_data_parser.py +15 -16
- astrbot/core/platform/sources/wecom/wecom_adapter.py +35 -18
- astrbot/core/platform/sources/wecom/wecom_event.py +55 -48
- astrbot/core/platform/sources/wecom/wecom_kf.py +34 -44
- astrbot/core/platform/sources/wecom/wecom_kf_message.py +26 -10
- astrbot/core/platform/sources/wecom_ai_bot/WXBizJsonMsgCrypt.py +18 -10
- astrbot/core/platform/sources/wecom_ai_bot/__init__.py +3 -5
- astrbot/core/platform/sources/wecom_ai_bot/ierror.py +0 -1
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_adapter.py +61 -37
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_api.py +67 -28
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_event.py +8 -9
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_queue_mgr.py +18 -9
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_server.py +14 -12
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_utils.py +22 -12
- astrbot/core/platform/sources/weixin_official_account/weixin_offacc_adapter.py +40 -26
- astrbot/core/platform/sources/weixin_official_account/weixin_offacc_event.py +47 -45
- astrbot/core/platform_message_history_mgr.py +5 -3
- astrbot/core/provider/__init__.py +2 -3
- astrbot/core/provider/entites.py +8 -8
- astrbot/core/provider/entities.py +61 -75
- astrbot/core/provider/func_tool_manager.py +59 -55
- astrbot/core/provider/manager.py +32 -22
- astrbot/core/provider/provider.py +72 -46
- astrbot/core/provider/register.py +7 -7
- astrbot/core/provider/sources/anthropic_source.py +48 -30
- astrbot/core/provider/sources/azure_tts_source.py +17 -13
- astrbot/core/provider/sources/coze_api_client.py +27 -17
- astrbot/core/provider/sources/coze_source.py +104 -87
- astrbot/core/provider/sources/dashscope_source.py +18 -11
- astrbot/core/provider/sources/dashscope_tts.py +36 -23
- astrbot/core/provider/sources/dify_source.py +25 -20
- astrbot/core/provider/sources/edge_tts_source.py +21 -17
- astrbot/core/provider/sources/fishaudio_tts_api_source.py +22 -14
- astrbot/core/provider/sources/gemini_embedding_source.py +12 -13
- astrbot/core/provider/sources/gemini_source.py +72 -58
- astrbot/core/provider/sources/gemini_tts_source.py +8 -6
- astrbot/core/provider/sources/gsv_selfhosted_source.py +17 -14
- astrbot/core/provider/sources/gsvi_tts_source.py +11 -7
- astrbot/core/provider/sources/minimax_tts_api_source.py +50 -40
- astrbot/core/provider/sources/openai_embedding_source.py +6 -8
- astrbot/core/provider/sources/openai_source.py +77 -69
- astrbot/core/provider/sources/openai_tts_api_source.py +14 -6
- astrbot/core/provider/sources/sensevoice_selfhosted_source.py +13 -11
- astrbot/core/provider/sources/vllm_rerank_source.py +10 -4
- astrbot/core/provider/sources/volcengine_tts.py +38 -31
- astrbot/core/provider/sources/whisper_api_source.py +14 -12
- astrbot/core/provider/sources/whisper_selfhosted_source.py +15 -11
- astrbot/core/provider/sources/xinference_rerank_source.py +16 -8
- astrbot/core/provider/sources/xinference_stt_provider.py +35 -25
- astrbot/core/star/__init__.py +16 -11
- astrbot/core/star/config.py +10 -15
- astrbot/core/star/context.py +97 -75
- astrbot/core/star/filter/__init__.py +4 -3
- astrbot/core/star/filter/command.py +30 -28
- astrbot/core/star/filter/command_group.py +27 -24
- astrbot/core/star/filter/custom_filter.py +6 -5
- astrbot/core/star/filter/event_message_type.py +4 -2
- astrbot/core/star/filter/permission.py +4 -2
- astrbot/core/star/filter/platform_adapter_type.py +4 -2
- astrbot/core/star/filter/regex.py +4 -2
- astrbot/core/star/register/__init__.py +19 -19
- astrbot/core/star/register/star.py +6 -2
- astrbot/core/star/register/star_handler.py +96 -73
- astrbot/core/star/session_llm_manager.py +48 -14
- astrbot/core/star/session_plugin_manager.py +29 -15
- astrbot/core/star/star.py +1 -2
- astrbot/core/star/star_handler.py +13 -8
- astrbot/core/star/star_manager.py +151 -59
- astrbot/core/star/star_tools.py +44 -37
- astrbot/core/star/updator.py +10 -10
- astrbot/core/umop_config_router.py +10 -4
- astrbot/core/updator.py +13 -5
- astrbot/core/utils/astrbot_path.py +3 -5
- astrbot/core/utils/dify_api_client.py +33 -15
- astrbot/core/utils/io.py +66 -42
- astrbot/core/utils/log_pipe.py +1 -1
- astrbot/core/utils/metrics.py +7 -7
- astrbot/core/utils/path_util.py +15 -16
- astrbot/core/utils/pip_installer.py +5 -5
- astrbot/core/utils/session_waiter.py +19 -20
- astrbot/core/utils/shared_preferences.py +45 -20
- astrbot/core/utils/t2i/__init__.py +4 -1
- astrbot/core/utils/t2i/network_strategy.py +35 -26
- astrbot/core/utils/t2i/renderer.py +11 -5
- astrbot/core/utils/t2i/template_manager.py +14 -15
- astrbot/core/utils/tencent_record_helper.py +19 -13
- astrbot/core/utils/version_comparator.py +10 -13
- astrbot/core/zip_updator.py +43 -40
- astrbot/dashboard/routes/__init__.py +18 -18
- astrbot/dashboard/routes/auth.py +10 -8
- astrbot/dashboard/routes/chat.py +30 -21
- astrbot/dashboard/routes/config.py +92 -75
- astrbot/dashboard/routes/conversation.py +46 -39
- astrbot/dashboard/routes/file.py +4 -2
- astrbot/dashboard/routes/knowledge_base.py +47 -40
- astrbot/dashboard/routes/log.py +9 -4
- astrbot/dashboard/routes/persona.py +19 -16
- astrbot/dashboard/routes/plugin.py +69 -55
- astrbot/dashboard/routes/route.py +3 -1
- astrbot/dashboard/routes/session_management.py +130 -116
- astrbot/dashboard/routes/stat.py +34 -34
- astrbot/dashboard/routes/t2i.py +15 -12
- astrbot/dashboard/routes/tools.py +47 -52
- astrbot/dashboard/routes/update.py +32 -28
- astrbot/dashboard/server.py +30 -26
- astrbot/dashboard/utils.py +8 -4
- {astrbot-4.5.1.dist-info → astrbot-4.5.2.dist-info}/METADATA +2 -1
- astrbot-4.5.2.dist-info/RECORD +261 -0
- astrbot-4.5.1.dist-info/RECORD +0 -260
- {astrbot-4.5.1.dist-info → astrbot-4.5.2.dist-info}/WHEEL +0 -0
- {astrbot-4.5.1.dist-info → astrbot-4.5.2.dist-info}/entry_points.txt +0 -0
- {astrbot-4.5.1.dist-info → astrbot-4.5.2.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,31 +1,33 @@
|
|
|
1
1
|
import sys
|
|
2
2
|
import traceback
|
|
3
3
|
import typing as T
|
|
4
|
-
|
|
5
|
-
from
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
4
|
+
|
|
5
|
+
from mcp.types import (
|
|
6
|
+
BlobResourceContents,
|
|
7
|
+
CallToolResult,
|
|
8
|
+
EmbeddedResource,
|
|
9
|
+
ImageContent,
|
|
10
|
+
TextContent,
|
|
11
|
+
TextResourceContents,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
from astrbot import logger
|
|
10
15
|
from astrbot.core.message.message_event_result import (
|
|
11
16
|
MessageChain,
|
|
12
17
|
)
|
|
13
18
|
from astrbot.core.provider.entities import (
|
|
14
|
-
ProviderRequest,
|
|
15
19
|
LLMResponse,
|
|
16
|
-
|
|
17
|
-
AssistantMessageSegment,
|
|
20
|
+
ProviderRequest,
|
|
18
21
|
ToolCallsResult,
|
|
19
22
|
)
|
|
20
|
-
from
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
from astrbot import logger
|
|
23
|
+
from astrbot.core.provider.provider import Provider
|
|
24
|
+
|
|
25
|
+
from ..hooks import BaseAgentRunHooks
|
|
26
|
+
from ..message import AssistantMessageSegment, ToolCallMessageSegment
|
|
27
|
+
from ..response import AgentResponseData
|
|
28
|
+
from ..run_context import ContextWrapper, TContext
|
|
29
|
+
from ..tool_executor import BaseFunctionToolExecutor
|
|
30
|
+
from .base import AgentResponse, AgentState, BaseAgentRunner
|
|
29
31
|
|
|
30
32
|
if sys.version_info >= (3, 12):
|
|
31
33
|
from typing import override
|
|
@@ -70,8 +72,7 @@ class ToolLoopAgentRunner(BaseAgentRunner[TContext]):
|
|
|
70
72
|
|
|
71
73
|
@override
|
|
72
74
|
async def step(self):
|
|
73
|
-
"""
|
|
74
|
-
Process a single step of the agent.
|
|
75
|
+
"""Process a single step of the agent.
|
|
75
76
|
This method should return the result of the step.
|
|
76
77
|
"""
|
|
77
78
|
if not self.req:
|
|
@@ -99,7 +100,7 @@ class ToolLoopAgentRunner(BaseAgentRunner[TContext]):
|
|
|
99
100
|
yield AgentResponse(
|
|
100
101
|
type="streaming_delta",
|
|
101
102
|
data=AgentResponseData(
|
|
102
|
-
chain=MessageChain().message(llm_response.completion_text)
|
|
103
|
+
chain=MessageChain().message(llm_response.completion_text),
|
|
103
104
|
),
|
|
104
105
|
)
|
|
105
106
|
continue
|
|
@@ -120,8 +121,8 @@ class ToolLoopAgentRunner(BaseAgentRunner[TContext]):
|
|
|
120
121
|
type="err",
|
|
121
122
|
data=AgentResponseData(
|
|
122
123
|
chain=MessageChain().message(
|
|
123
|
-
f"LLM 响应错误: {llm_resp.completion_text or '未知错误'}"
|
|
124
|
-
)
|
|
124
|
+
f"LLM 响应错误: {llm_resp.completion_text or '未知错误'}",
|
|
125
|
+
),
|
|
125
126
|
),
|
|
126
127
|
)
|
|
127
128
|
|
|
@@ -144,7 +145,7 @@ class ToolLoopAgentRunner(BaseAgentRunner[TContext]):
|
|
|
144
145
|
yield AgentResponse(
|
|
145
146
|
type="llm_result",
|
|
146
147
|
data=AgentResponseData(
|
|
147
|
-
chain=MessageChain().message(llm_resp.completion_text)
|
|
148
|
+
chain=MessageChain().message(llm_resp.completion_text),
|
|
148
149
|
),
|
|
149
150
|
)
|
|
150
151
|
|
|
@@ -155,7 +156,7 @@ class ToolLoopAgentRunner(BaseAgentRunner[TContext]):
|
|
|
155
156
|
yield AgentResponse(
|
|
156
157
|
type="tool_call",
|
|
157
158
|
data=AgentResponseData(
|
|
158
|
-
chain=MessageChain().message(f"🔨 调用工具: {tool_call_name}")
|
|
159
|
+
chain=MessageChain().message(f"🔨 调用工具: {tool_call_name}"),
|
|
159
160
|
),
|
|
160
161
|
)
|
|
161
162
|
async for result in self._handle_function_tools(self.req, llm_resp):
|
|
@@ -169,8 +170,7 @@ class ToolLoopAgentRunner(BaseAgentRunner[TContext]):
|
|
|
169
170
|
# 将结果添加到上下文中
|
|
170
171
|
tool_calls_result = ToolCallsResult(
|
|
171
172
|
tool_calls_info=AssistantMessageSegment(
|
|
172
|
-
|
|
173
|
-
tool_calls=llm_resp.to_openai_tool_calls(),
|
|
173
|
+
tool_calls=llm_resp.to_openai_to_calls_model(),
|
|
174
174
|
content=llm_resp.completion_text,
|
|
175
175
|
),
|
|
176
176
|
tool_calls_result=tool_call_result_blocks,
|
|
@@ -205,7 +205,7 @@ class ToolLoopAgentRunner(BaseAgentRunner[TContext]):
|
|
|
205
205
|
role="tool",
|
|
206
206
|
tool_call_id=func_tool_id,
|
|
207
207
|
content=f"error: 未找到工具 {func_tool_name}",
|
|
208
|
-
)
|
|
208
|
+
),
|
|
209
209
|
)
|
|
210
210
|
continue
|
|
211
211
|
|
|
@@ -214,7 +214,7 @@ class ToolLoopAgentRunner(BaseAgentRunner[TContext]):
|
|
|
214
214
|
# 获取实际的 handler 函数
|
|
215
215
|
if func_tool.handler:
|
|
216
216
|
logger.debug(
|
|
217
|
-
f"工具 {func_tool_name} 期望的参数: {func_tool.parameters}"
|
|
217
|
+
f"工具 {func_tool_name} 期望的参数: {func_tool.parameters}",
|
|
218
218
|
)
|
|
219
219
|
if func_tool.parameters and func_tool.parameters.get("properties"):
|
|
220
220
|
expected_params = set(func_tool.parameters["properties"].keys())
|
|
@@ -227,20 +227,21 @@ class ToolLoopAgentRunner(BaseAgentRunner[TContext]):
|
|
|
227
227
|
|
|
228
228
|
# 记录被忽略的参数
|
|
229
229
|
ignored_params = set(func_tool_args.keys()) - set(
|
|
230
|
-
valid_params.keys()
|
|
230
|
+
valid_params.keys(),
|
|
231
231
|
)
|
|
232
232
|
if ignored_params:
|
|
233
233
|
logger.warning(
|
|
234
|
-
f"工具 {func_tool_name} 忽略非期望参数: {ignored_params}"
|
|
234
|
+
f"工具 {func_tool_name} 忽略非期望参数: {ignored_params}",
|
|
235
235
|
)
|
|
236
236
|
else:
|
|
237
237
|
# 如果没有 handler(如 MCP 工具),使用所有参数
|
|
238
238
|
valid_params = func_tool_args
|
|
239
|
-
logger.warning(f"工具 {func_tool_name} 没有 handler,使用所有参数")
|
|
240
239
|
|
|
241
240
|
try:
|
|
242
241
|
await self.agent_hooks.on_tool_start(
|
|
243
|
-
self.run_context,
|
|
242
|
+
self.run_context,
|
|
243
|
+
func_tool,
|
|
244
|
+
valid_params,
|
|
244
245
|
)
|
|
245
246
|
except Exception as e:
|
|
246
247
|
logger.error(f"Error in on_tool_start hook: {e}", exc_info=True)
|
|
@@ -262,7 +263,7 @@ class ToolLoopAgentRunner(BaseAgentRunner[TContext]):
|
|
|
262
263
|
role="tool",
|
|
263
264
|
tool_call_id=func_tool_id,
|
|
264
265
|
content=res.content[0].text,
|
|
265
|
-
)
|
|
266
|
+
),
|
|
266
267
|
)
|
|
267
268
|
yield MessageChain().message(res.content[0].text)
|
|
268
269
|
elif isinstance(res.content[0], ImageContent):
|
|
@@ -271,10 +272,10 @@ class ToolLoopAgentRunner(BaseAgentRunner[TContext]):
|
|
|
271
272
|
role="tool",
|
|
272
273
|
tool_call_id=func_tool_id,
|
|
273
274
|
content="返回了图片(已直接发送给用户)",
|
|
274
|
-
)
|
|
275
|
+
),
|
|
275
276
|
)
|
|
276
277
|
yield MessageChain(type="tool_direct_result").base64_image(
|
|
277
|
-
res.content[0].data
|
|
278
|
+
res.content[0].data,
|
|
278
279
|
)
|
|
279
280
|
elif isinstance(res.content[0], EmbeddedResource):
|
|
280
281
|
resource = res.content[0].resource
|
|
@@ -284,7 +285,7 @@ class ToolLoopAgentRunner(BaseAgentRunner[TContext]):
|
|
|
284
285
|
role="tool",
|
|
285
286
|
tool_call_id=func_tool_id,
|
|
286
287
|
content=resource.text,
|
|
287
|
-
)
|
|
288
|
+
),
|
|
288
289
|
)
|
|
289
290
|
yield MessageChain().message(resource.text)
|
|
290
291
|
elif (
|
|
@@ -297,10 +298,10 @@ class ToolLoopAgentRunner(BaseAgentRunner[TContext]):
|
|
|
297
298
|
role="tool",
|
|
298
299
|
tool_call_id=func_tool_id,
|
|
299
300
|
content="返回了图片(已直接发送给用户)",
|
|
300
|
-
)
|
|
301
|
+
),
|
|
301
302
|
)
|
|
302
303
|
yield MessageChain(
|
|
303
|
-
type="tool_direct_result"
|
|
304
|
+
type="tool_direct_result",
|
|
304
305
|
).base64_image(resource.blob)
|
|
305
306
|
else:
|
|
306
307
|
tool_call_result_blocks.append(
|
|
@@ -308,41 +309,41 @@ class ToolLoopAgentRunner(BaseAgentRunner[TContext]):
|
|
|
308
309
|
role="tool",
|
|
309
310
|
tool_call_id=func_tool_id,
|
|
310
311
|
content="返回的数据类型不受支持",
|
|
311
|
-
)
|
|
312
|
+
),
|
|
312
313
|
)
|
|
313
314
|
yield MessageChain().message("返回的数据类型不受支持。")
|
|
314
315
|
|
|
315
316
|
elif resp is None:
|
|
316
317
|
# Tool 直接请求发送消息给用户
|
|
317
318
|
# 这里我们将直接结束 Agent Loop。
|
|
319
|
+
# 发送消息逻辑在 ToolExecutor 中处理了。
|
|
320
|
+
logger.warning(
|
|
321
|
+
f"{func_tool_name} 没有没有返回值或者将结果直接发送给用户,此工具调用不会被记录到历史中。"
|
|
322
|
+
)
|
|
318
323
|
self._transition_state(AgentState.DONE)
|
|
319
|
-
if res := self.run_context.event.get_result():
|
|
320
|
-
if res.chain:
|
|
321
|
-
yield MessageChain(
|
|
322
|
-
chain=res.chain, type="tool_direct_result"
|
|
323
|
-
)
|
|
324
324
|
else:
|
|
325
325
|
# 不应该出现其他类型
|
|
326
326
|
logger.warning(
|
|
327
|
-
f"Tool 返回了不支持的类型: {type(resp)},将忽略。"
|
|
327
|
+
f"Tool 返回了不支持的类型: {type(resp)},将忽略。",
|
|
328
328
|
)
|
|
329
329
|
|
|
330
330
|
try:
|
|
331
331
|
await self.agent_hooks.on_tool_end(
|
|
332
|
-
self.run_context,
|
|
332
|
+
self.run_context,
|
|
333
|
+
func_tool,
|
|
334
|
+
func_tool_args,
|
|
335
|
+
_final_resp,
|
|
333
336
|
)
|
|
334
337
|
except Exception as e:
|
|
335
338
|
logger.error(f"Error in on_tool_end hook: {e}", exc_info=True)
|
|
336
|
-
|
|
337
|
-
self.run_context.event.clear_result()
|
|
338
339
|
except Exception as e:
|
|
339
340
|
logger.warning(traceback.format_exc())
|
|
340
341
|
tool_call_result_blocks.append(
|
|
341
342
|
ToolCallMessageSegment(
|
|
342
343
|
role="tool",
|
|
343
344
|
tool_call_id=func_tool_id,
|
|
344
|
-
content=f"error: {
|
|
345
|
-
)
|
|
345
|
+
content=f"error: {e!s}",
|
|
346
|
+
),
|
|
346
347
|
)
|
|
347
348
|
|
|
348
349
|
# 处理函数调用响应
|
astrbot/core/agent/tool.py
CHANGED
|
@@ -1,55 +1,75 @@
|
|
|
1
|
-
from
|
|
1
|
+
from collections.abc import Awaitable, Callable
|
|
2
|
+
from typing import Any, Generic
|
|
3
|
+
|
|
4
|
+
import jsonschema
|
|
5
|
+
import mcp
|
|
2
6
|
from deprecated import deprecated
|
|
3
|
-
from
|
|
4
|
-
from .
|
|
7
|
+
from pydantic import model_validator
|
|
8
|
+
from pydantic.dataclasses import dataclass
|
|
9
|
+
|
|
10
|
+
from .run_context import ContextWrapper, TContext
|
|
11
|
+
|
|
12
|
+
ParametersType = dict[str, Any]
|
|
5
13
|
|
|
6
14
|
|
|
7
15
|
@dataclass
|
|
8
|
-
class
|
|
9
|
-
"""A class representing
|
|
16
|
+
class ToolSchema:
|
|
17
|
+
"""A class representing the schema of a tool for function calling."""
|
|
10
18
|
|
|
11
19
|
name: str
|
|
12
|
-
|
|
13
|
-
|
|
20
|
+
"""The name of the tool."""
|
|
21
|
+
|
|
22
|
+
description: str
|
|
23
|
+
"""The description of the tool."""
|
|
24
|
+
|
|
25
|
+
parameters: ParametersType
|
|
26
|
+
"""The parameters of the tool, in JSON Schema format."""
|
|
27
|
+
|
|
28
|
+
@model_validator(mode="after")
|
|
29
|
+
def validate_parameters(self) -> "ToolSchema":
|
|
30
|
+
jsonschema.validate(
|
|
31
|
+
self.parameters, jsonschema.Draft202012Validator.META_SCHEMA
|
|
32
|
+
)
|
|
33
|
+
return self
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass
|
|
37
|
+
class FunctionTool(ToolSchema, Generic[TContext]):
|
|
38
|
+
"""A callable tool, for function calling."""
|
|
39
|
+
|
|
14
40
|
handler: Callable[..., Awaitable[Any]] | None = None
|
|
15
|
-
"""
|
|
16
|
-
handler_module_path: str | None = None
|
|
17
|
-
"""处理函数的模块路径,当 origin 为 mcp 时,这个为空
|
|
41
|
+
"""a callable that implements the tool's functionality. It should be an async function."""
|
|
18
42
|
|
|
19
|
-
|
|
43
|
+
handler_module_path: str | None = None
|
|
44
|
+
"""
|
|
45
|
+
The module path of the handler function. This is empty when the origin is mcp.
|
|
46
|
+
This field must be retained, as the handler will be wrapped in functools.partial during initialization,
|
|
47
|
+
causing the handler's __module__ to be functools
|
|
20
48
|
"""
|
|
21
49
|
active: bool = True
|
|
22
|
-
"""
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
"""
|
|
26
|
-
|
|
27
|
-
# MCP 相关字段
|
|
28
|
-
mcp_server_name: str | None = None
|
|
29
|
-
"""MCP 服务名称,当 origin 为 mcp 时有效"""
|
|
30
|
-
mcp_client: MCPClient | None = None
|
|
31
|
-
"""MCP 客户端,当 origin 为 mcp 时有效"""
|
|
50
|
+
"""
|
|
51
|
+
Whether the tool is active. This field is a special field for AstrBot.
|
|
52
|
+
You can ignore it when integrating with other frameworks.
|
|
53
|
+
"""
|
|
32
54
|
|
|
33
55
|
def __repr__(self):
|
|
34
|
-
return f"FuncTool(name={self.name}, parameters={self.parameters}, description={self.description}
|
|
35
|
-
|
|
36
|
-
def
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
"
|
|
42
|
-
|
|
43
|
-
"origin": self.origin,
|
|
44
|
-
"mcp_server_name": self.mcp_server_name,
|
|
45
|
-
}
|
|
56
|
+
return f"FuncTool(name={self.name}, parameters={self.parameters}, description={self.description})"
|
|
57
|
+
|
|
58
|
+
async def call(
|
|
59
|
+
self, context: ContextWrapper[TContext], **kwargs
|
|
60
|
+
) -> str | mcp.types.CallToolResult:
|
|
61
|
+
"""Run the tool with the given arguments. The handler field has priority."""
|
|
62
|
+
raise NotImplementedError(
|
|
63
|
+
"FunctionTool.call() must be implemented by subclasses or set a handler."
|
|
64
|
+
)
|
|
46
65
|
|
|
47
66
|
|
|
48
67
|
class ToolSet:
|
|
49
68
|
"""A set of function tools that can be used in function calling.
|
|
50
69
|
|
|
51
70
|
This class provides methods to add, remove, and retrieve tools, as well as
|
|
52
|
-
convert the tools to different API formats (OpenAI, Anthropic, Google GenAI).
|
|
71
|
+
convert the tools to different API formats (OpenAI, Anthropic, Google GenAI).
|
|
72
|
+
"""
|
|
53
73
|
|
|
54
74
|
def __init__(self, tools: list[FunctionTool] | None = None):
|
|
55
75
|
self.tools: list[FunctionTool] = tools or []
|
|
@@ -71,7 +91,7 @@ class ToolSet:
|
|
|
71
91
|
"""Remove a tool by its name."""
|
|
72
92
|
self.tools = [tool for tool in self.tools if tool.name != name]
|
|
73
93
|
|
|
74
|
-
def get_tool(self, name: str) ->
|
|
94
|
+
def get_tool(self, name: str) -> FunctionTool | None:
|
|
75
95
|
"""Get a tool by its name."""
|
|
76
96
|
for tool in self.tools:
|
|
77
97
|
if tool.name == name:
|
|
@@ -132,10 +152,8 @@ class ToolSet:
|
|
|
132
152
|
}
|
|
133
153
|
|
|
134
154
|
if (
|
|
135
|
-
tool.parameters
|
|
136
|
-
|
|
137
|
-
or not omit_empty_parameter_field
|
|
138
|
-
):
|
|
155
|
+
tool.parameters and tool.parameters.get("properties")
|
|
156
|
+
) or not omit_empty_parameter_field:
|
|
139
157
|
func_def["function"]["parameters"] = tool.parameters
|
|
140
158
|
|
|
141
159
|
result.append(func_def)
|
|
@@ -185,7 +203,8 @@ class ToolSet:
|
|
|
185
203
|
if "type" in schema and schema["type"] in supported_types:
|
|
186
204
|
result["type"] = schema["type"]
|
|
187
205
|
if "format" in schema and schema["format"] in supported_formats.get(
|
|
188
|
-
result["type"],
|
|
206
|
+
result["type"],
|
|
207
|
+
set(),
|
|
189
208
|
):
|
|
190
209
|
result["format"] = schema["format"]
|
|
191
210
|
else:
|
|
@@ -222,7 +241,7 @@ class ToolSet:
|
|
|
222
241
|
|
|
223
242
|
tools = []
|
|
224
243
|
for tool in self.tools:
|
|
225
|
-
d = {
|
|
244
|
+
d: dict[str, Any] = {
|
|
226
245
|
"name": tool.name,
|
|
227
246
|
"description": tool.description,
|
|
228
247
|
}
|
|
@@ -1,11 +1,17 @@
|
|
|
1
|
+
from collections.abc import AsyncGenerator
|
|
2
|
+
from typing import Any, Generic
|
|
3
|
+
|
|
1
4
|
import mcp
|
|
2
|
-
|
|
3
|
-
from .run_context import
|
|
5
|
+
|
|
6
|
+
from .run_context import ContextWrapper, TContext
|
|
4
7
|
from .tool import FunctionTool
|
|
5
8
|
|
|
6
9
|
|
|
7
10
|
class BaseFunctionToolExecutor(Generic[TContext]):
|
|
8
11
|
@classmethod
|
|
9
12
|
async def execute(
|
|
10
|
-
cls,
|
|
13
|
+
cls,
|
|
14
|
+
tool: FunctionTool,
|
|
15
|
+
run_context: ContextWrapper[TContext],
|
|
16
|
+
**tool_args,
|
|
11
17
|
) -> AsyncGenerator[Any | mcp.types.CallToolResult, None]: ...
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
from dataclasses import dataclass
|
|
2
|
+
|
|
3
|
+
from astrbot.core.platform.astr_message_event import AstrMessageEvent
|
|
2
4
|
from astrbot.core.provider import Provider
|
|
3
5
|
from astrbot.core.provider.entities import ProviderRequest
|
|
4
6
|
|
|
@@ -9,4 +11,4 @@ class AstrAgentContext:
|
|
|
9
11
|
first_provider_request: ProviderRequest
|
|
10
12
|
curr_provider_request: ProviderRequest
|
|
11
13
|
streaming: bool
|
|
12
|
-
|
|
14
|
+
event: AstrMessageEvent
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import uuid
|
|
3
|
+
from typing import TypedDict, TypeVar
|
|
4
|
+
|
|
3
5
|
from astrbot.core import AstrBotConfig, logger
|
|
4
|
-
from astrbot.core.utils.shared_preferences import SharedPreferences
|
|
5
6
|
from astrbot.core.config.astrbot_config import ASTRBOT_CONFIG_PATH
|
|
6
7
|
from astrbot.core.config.default import DEFAULT_CONFIG
|
|
7
8
|
from astrbot.core.platform.message_session import MessageSession
|
|
8
9
|
from astrbot.core.umop_config_router import UmopConfigRouter
|
|
9
10
|
from astrbot.core.utils.astrbot_path import get_astrbot_config_path
|
|
10
|
-
from
|
|
11
|
+
from astrbot.core.utils.shared_preferences import SharedPreferences
|
|
11
12
|
|
|
12
13
|
_VT = TypeVar("_VT")
|
|
13
14
|
|
|
@@ -48,7 +49,10 @@ class AstrBotConfigManager:
|
|
|
48
49
|
"""获取所有的 abconf 数据"""
|
|
49
50
|
if self.abconf_data is None:
|
|
50
51
|
self.abconf_data = self.sp.get(
|
|
51
|
-
"abconf_mapping",
|
|
52
|
+
"abconf_mapping",
|
|
53
|
+
{},
|
|
54
|
+
scope="global",
|
|
55
|
+
scope_id="global",
|
|
52
56
|
)
|
|
53
57
|
return self.abconf_data
|
|
54
58
|
|
|
@@ -64,7 +68,7 @@ class AstrBotConfigManager:
|
|
|
64
68
|
self.confs[uuid_] = conf
|
|
65
69
|
else:
|
|
66
70
|
logger.warning(
|
|
67
|
-
f"Config file {conf_path} for UUID {uuid_} does not exist, skipping."
|
|
71
|
+
f"Config file {conf_path} for UUID {uuid_} does not exist, skipping.",
|
|
68
72
|
)
|
|
69
73
|
continue
|
|
70
74
|
|
|
@@ -73,6 +77,7 @@ class AstrBotConfigManager:
|
|
|
73
77
|
|
|
74
78
|
Returns:
|
|
75
79
|
ConfInfo: 包含配置文件的 uuid, 路径和名称等信息, 是一个 dict 类型
|
|
80
|
+
|
|
76
81
|
"""
|
|
77
82
|
# uuid -> { "path": str, "name": str }
|
|
78
83
|
abconf_data = self._get_abconf_data()
|
|
@@ -103,7 +108,10 @@ class AstrBotConfigManager:
|
|
|
103
108
|
) -> None:
|
|
104
109
|
"""保存配置文件的映射关系"""
|
|
105
110
|
abconf_data = self.sp.get(
|
|
106
|
-
"abconf_mapping",
|
|
111
|
+
"abconf_mapping",
|
|
112
|
+
{},
|
|
113
|
+
scope="global",
|
|
114
|
+
scope_id="global",
|
|
107
115
|
)
|
|
108
116
|
random_word = abconf_name or uuid.uuid4().hex[:8]
|
|
109
117
|
abconf_data[abconf_id] = {
|
|
@@ -177,13 +185,17 @@ class AstrBotConfigManager:
|
|
|
177
185
|
|
|
178
186
|
Raises:
|
|
179
187
|
ValueError: 如果试图删除默认配置文件
|
|
188
|
+
|
|
180
189
|
"""
|
|
181
190
|
if conf_id == "default":
|
|
182
191
|
raise ValueError("不能删除默认配置文件")
|
|
183
192
|
|
|
184
193
|
# 从映射中移除
|
|
185
194
|
abconf_data = self.sp.get(
|
|
186
|
-
"abconf_mapping",
|
|
195
|
+
"abconf_mapping",
|
|
196
|
+
{},
|
|
197
|
+
scope="global",
|
|
198
|
+
scope_id="global",
|
|
187
199
|
)
|
|
188
200
|
if conf_id not in abconf_data:
|
|
189
201
|
logger.warning(f"配置文件 {conf_id} 不存在于映射中")
|
|
@@ -191,7 +203,8 @@ class AstrBotConfigManager:
|
|
|
191
203
|
|
|
192
204
|
# 获取配置文件路径
|
|
193
205
|
conf_path = os.path.join(
|
|
194
|
-
get_astrbot_config_path(),
|
|
206
|
+
get_astrbot_config_path(),
|
|
207
|
+
abconf_data[conf_id]["path"],
|
|
195
208
|
)
|
|
196
209
|
|
|
197
210
|
# 删除配置文件
|
|
@@ -224,12 +237,16 @@ class AstrBotConfigManager:
|
|
|
224
237
|
|
|
225
238
|
Returns:
|
|
226
239
|
bool: 更新是否成功
|
|
240
|
+
|
|
227
241
|
"""
|
|
228
242
|
if conf_id == "default":
|
|
229
243
|
raise ValueError("不能更新默认配置文件的信息")
|
|
230
244
|
|
|
231
245
|
abconf_data = self.sp.get(
|
|
232
|
-
"abconf_mapping",
|
|
246
|
+
"abconf_mapping",
|
|
247
|
+
{},
|
|
248
|
+
scope="global",
|
|
249
|
+
scope_id="global",
|
|
233
250
|
)
|
|
234
251
|
if conf_id not in abconf_data:
|
|
235
252
|
logger.warning(f"配置文件 {conf_id} 不存在于映射中")
|
|
@@ -246,7 +263,10 @@ class AstrBotConfigManager:
|
|
|
246
263
|
return True
|
|
247
264
|
|
|
248
265
|
def g(
|
|
249
|
-
self,
|
|
266
|
+
self,
|
|
267
|
+
umo: str | None = None,
|
|
268
|
+
key: str | None = None,
|
|
269
|
+
default: _VT = None,
|
|
250
270
|
) -> _VT:
|
|
251
271
|
"""获取配置项。umo 为 None 时使用默认配置"""
|
|
252
272
|
if umo is None:
|
astrbot/core/config/__init__.py
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import
|
|
1
|
+
import enum
|
|
2
2
|
import json
|
|
3
3
|
import logging
|
|
4
|
-
import
|
|
5
|
-
|
|
6
|
-
from typing import Dict
|
|
4
|
+
import os
|
|
5
|
+
|
|
7
6
|
from astrbot.core.utils.astrbot_path import get_astrbot_data_path
|
|
8
7
|
|
|
8
|
+
from .default import DEFAULT_CONFIG, DEFAULT_VALUE_MAP
|
|
9
|
+
|
|
9
10
|
ASTRBOT_CONFIG_PATH = os.path.join(get_astrbot_data_path(), "cmd_config.json")
|
|
10
11
|
logger = logging.getLogger("astrbot")
|
|
11
12
|
|
|
@@ -27,7 +28,7 @@ class AstrBotConfig(dict):
|
|
|
27
28
|
self,
|
|
28
29
|
config_path: str = ASTRBOT_CONFIG_PATH,
|
|
29
30
|
default_config: dict = DEFAULT_CONFIG,
|
|
30
|
-
schema: dict = None,
|
|
31
|
+
schema: dict | None = None,
|
|
31
32
|
):
|
|
32
33
|
super().__init__()
|
|
33
34
|
|
|
@@ -45,7 +46,7 @@ class AstrBotConfig(dict):
|
|
|
45
46
|
json.dump(default_config, f, indent=4, ensure_ascii=False)
|
|
46
47
|
object.__setattr__(self, "first_deploy", True) # 标记第一次部署
|
|
47
48
|
|
|
48
|
-
with open(config_path,
|
|
49
|
+
with open(config_path, encoding="utf-8-sig") as f:
|
|
49
50
|
conf_str = f.read()
|
|
50
51
|
conf = json.loads(conf_str)
|
|
51
52
|
|
|
@@ -65,7 +66,7 @@ class AstrBotConfig(dict):
|
|
|
65
66
|
for k, v in schema.items():
|
|
66
67
|
if v["type"] not in DEFAULT_VALUE_MAP:
|
|
67
68
|
raise TypeError(
|
|
68
|
-
f"不受支持的配置类型 {v['type']}。支持的类型有:{DEFAULT_VALUE_MAP.keys()}"
|
|
69
|
+
f"不受支持的配置类型 {v['type']}。支持的类型有:{DEFAULT_VALUE_MAP.keys()}",
|
|
69
70
|
)
|
|
70
71
|
if "default" in v:
|
|
71
72
|
default = v["default"]
|
|
@@ -82,7 +83,7 @@ class AstrBotConfig(dict):
|
|
|
82
83
|
|
|
83
84
|
return conf
|
|
84
85
|
|
|
85
|
-
def check_config_integrity(self, refer_conf:
|
|
86
|
+
def check_config_integrity(self, refer_conf: dict, conf: dict, path=""):
|
|
86
87
|
"""检查配置完整性,如果有新的配置项或顺序不一致则返回 True"""
|
|
87
88
|
has_new = False
|
|
88
89
|
|
|
@@ -97,27 +98,28 @@ class AstrBotConfig(dict):
|
|
|
97
98
|
logger.info(f"检查到配置项 {path_} 不存在,已插入默认值 {value}")
|
|
98
99
|
new_conf[key] = value
|
|
99
100
|
has_new = True
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
101
|
+
elif conf[key] is None:
|
|
102
|
+
# 配置项为 None,使用默认值
|
|
103
|
+
new_conf[key] = value
|
|
104
|
+
has_new = True
|
|
105
|
+
elif isinstance(value, dict):
|
|
106
|
+
# 递归检查子配置项
|
|
107
|
+
if not isinstance(conf[key], dict):
|
|
108
|
+
# 类型不匹配,使用默认值
|
|
103
109
|
new_conf[key] = value
|
|
104
110
|
has_new = True
|
|
105
|
-
elif isinstance(value, dict):
|
|
106
|
-
# 递归检查子配置项
|
|
107
|
-
if not isinstance(conf[key], dict):
|
|
108
|
-
# 类型不匹配,使用默认值
|
|
109
|
-
new_conf[key] = value
|
|
110
|
-
has_new = True
|
|
111
|
-
else:
|
|
112
|
-
# 递归检查并同步顺序
|
|
113
|
-
child_has_new = self.check_config_integrity(
|
|
114
|
-
value, conf[key], path + "." + key if path else key
|
|
115
|
-
)
|
|
116
|
-
new_conf[key] = conf[key]
|
|
117
|
-
has_new |= child_has_new
|
|
118
111
|
else:
|
|
119
|
-
#
|
|
112
|
+
# 递归检查并同步顺序
|
|
113
|
+
child_has_new = self.check_config_integrity(
|
|
114
|
+
value,
|
|
115
|
+
conf[key],
|
|
116
|
+
path + "." + key if path else key,
|
|
117
|
+
)
|
|
120
118
|
new_conf[key] = conf[key]
|
|
119
|
+
has_new |= child_has_new
|
|
120
|
+
else:
|
|
121
|
+
# 直接使用现有配置
|
|
122
|
+
new_conf[key] = conf[key]
|
|
121
123
|
|
|
122
124
|
# 检查是否存在参考配置中没有的配置项
|
|
123
125
|
for key in list(conf.keys()):
|
|
@@ -140,7 +142,7 @@ class AstrBotConfig(dict):
|
|
|
140
142
|
|
|
141
143
|
return has_new
|
|
142
144
|
|
|
143
|
-
def save_config(self, replace_config:
|
|
145
|
+
def save_config(self, replace_config: dict | None = None):
|
|
144
146
|
"""将配置写入文件
|
|
145
147
|
|
|
146
148
|
如果传入 replace_config,则将配置替换为 replace_config
|