AstrBot 4.3.3__py3-none-any.whl → 4.5.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/core/agent/mcp_client.py +18 -4
- astrbot/core/agent/runners/tool_loop_agent_runner.py +31 -2
- astrbot/core/astr_agent_context.py +1 -0
- astrbot/core/astrbot_config_mgr.py +23 -51
- astrbot/core/config/default.py +139 -14
- astrbot/core/conversation_mgr.py +36 -1
- astrbot/core/core_lifecycle.py +24 -5
- astrbot/core/db/migration/migra_45_to_46.py +44 -0
- astrbot/core/db/vec_db/base.py +33 -2
- astrbot/core/db/vec_db/faiss_impl/document_storage.py +310 -52
- astrbot/core/db/vec_db/faiss_impl/embedding_storage.py +31 -3
- astrbot/core/db/vec_db/faiss_impl/vec_db.py +81 -23
- astrbot/core/file_token_service.py +6 -1
- astrbot/core/initial_loader.py +6 -3
- astrbot/core/knowledge_base/chunking/__init__.py +11 -0
- astrbot/core/knowledge_base/chunking/base.py +24 -0
- astrbot/core/knowledge_base/chunking/fixed_size.py +57 -0
- astrbot/core/knowledge_base/chunking/recursive.py +155 -0
- astrbot/core/knowledge_base/kb_db_sqlite.py +299 -0
- astrbot/core/knowledge_base/kb_helper.py +348 -0
- astrbot/core/knowledge_base/kb_mgr.py +287 -0
- astrbot/core/knowledge_base/models.py +114 -0
- astrbot/core/knowledge_base/parsers/__init__.py +15 -0
- astrbot/core/knowledge_base/parsers/base.py +50 -0
- astrbot/core/knowledge_base/parsers/markitdown_parser.py +25 -0
- astrbot/core/knowledge_base/parsers/pdf_parser.py +100 -0
- astrbot/core/knowledge_base/parsers/text_parser.py +41 -0
- astrbot/core/knowledge_base/parsers/util.py +13 -0
- astrbot/core/knowledge_base/retrieval/__init__.py +16 -0
- astrbot/core/knowledge_base/retrieval/hit_stopwords.txt +767 -0
- astrbot/core/knowledge_base/retrieval/manager.py +273 -0
- astrbot/core/knowledge_base/retrieval/rank_fusion.py +138 -0
- astrbot/core/knowledge_base/retrieval/sparse_retriever.py +130 -0
- astrbot/core/pipeline/process_stage/method/llm_request.py +61 -21
- astrbot/core/pipeline/process_stage/utils.py +80 -0
- astrbot/core/pipeline/scheduler.py +1 -1
- astrbot/core/platform/astr_message_event.py +8 -7
- astrbot/core/platform/manager.py +4 -0
- astrbot/core/platform/sources/misskey/misskey_adapter.py +380 -44
- astrbot/core/platform/sources/misskey/misskey_api.py +581 -45
- astrbot/core/platform/sources/misskey/misskey_event.py +76 -41
- astrbot/core/platform/sources/misskey/misskey_utils.py +254 -43
- astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_server.py +2 -1
- astrbot/core/platform/sources/satori/satori_adapter.py +27 -1
- astrbot/core/platform/sources/satori/satori_event.py +270 -77
- astrbot/core/platform/sources/webchat/webchat_adapter.py +0 -1
- astrbot/core/platform/sources/wecom_ai_bot/WXBizJsonMsgCrypt.py +289 -0
- astrbot/core/platform/sources/wecom_ai_bot/__init__.py +17 -0
- astrbot/core/platform/sources/wecom_ai_bot/ierror.py +20 -0
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_adapter.py +445 -0
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_api.py +378 -0
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_event.py +149 -0
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_queue_mgr.py +148 -0
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_server.py +166 -0
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_utils.py +199 -0
- astrbot/core/provider/manager.py +14 -9
- astrbot/core/provider/provider.py +67 -0
- astrbot/core/provider/sources/anthropic_source.py +4 -4
- astrbot/core/provider/sources/dashscope_source.py +10 -9
- astrbot/core/provider/sources/dify_source.py +6 -8
- astrbot/core/provider/sources/gemini_embedding_source.py +1 -2
- astrbot/core/provider/sources/openai_embedding_source.py +1 -2
- astrbot/core/provider/sources/openai_source.py +18 -15
- astrbot/core/provider/sources/openai_tts_api_source.py +1 -1
- astrbot/core/star/context.py +3 -0
- astrbot/core/star/star.py +6 -0
- astrbot/core/star/star_manager.py +13 -7
- astrbot/core/umop_config_router.py +81 -0
- astrbot/core/updator.py +1 -1
- astrbot/core/utils/io.py +23 -12
- astrbot/dashboard/routes/__init__.py +2 -0
- astrbot/dashboard/routes/config.py +137 -9
- astrbot/dashboard/routes/knowledge_base.py +1065 -0
- astrbot/dashboard/routes/plugin.py +24 -5
- astrbot/dashboard/routes/tools.py +14 -0
- astrbot/dashboard/routes/update.py +1 -1
- astrbot/dashboard/server.py +6 -0
- astrbot/dashboard/utils.py +161 -0
- {astrbot-4.3.3.dist-info → astrbot-4.5.0.dist-info}/METADATA +91 -55
- {astrbot-4.3.3.dist-info → astrbot-4.5.0.dist-info}/RECORD +83 -50
- {astrbot-4.3.3.dist-info → astrbot-4.5.0.dist-info}/WHEEL +0 -0
- {astrbot-4.3.3.dist-info → astrbot-4.5.0.dist-info}/entry_points.txt +0 -0
- {astrbot-4.3.3.dist-info → astrbot-4.5.0.dist-info}/licenses/LICENSE +0 -0
astrbot/core/agent/mcp_client.py
CHANGED
|
@@ -40,8 +40,15 @@ async def _quick_test_mcp_connection(config: dict) -> tuple[bool, str]:
|
|
|
40
40
|
timeout = cfg.get("timeout", 10)
|
|
41
41
|
|
|
42
42
|
try:
|
|
43
|
+
if "transport" in cfg:
|
|
44
|
+
transport_type = cfg["transport"]
|
|
45
|
+
elif "type" in cfg:
|
|
46
|
+
transport_type = cfg["type"]
|
|
47
|
+
else:
|
|
48
|
+
raise Exception("MCP 连接配置缺少 transport 或 type 字段")
|
|
49
|
+
|
|
43
50
|
async with aiohttp.ClientSession() as session:
|
|
44
|
-
if
|
|
51
|
+
if transport_type == "streamable_http":
|
|
45
52
|
test_payload = {
|
|
46
53
|
"jsonrpc": "2.0",
|
|
47
54
|
"method": "initialize",
|
|
@@ -121,7 +128,14 @@ class MCPClient:
|
|
|
121
128
|
if not success:
|
|
122
129
|
raise Exception(error_msg)
|
|
123
130
|
|
|
124
|
-
if
|
|
131
|
+
if "transport" in cfg:
|
|
132
|
+
transport_type = cfg["transport"]
|
|
133
|
+
elif "type" in cfg:
|
|
134
|
+
transport_type = cfg["type"]
|
|
135
|
+
else:
|
|
136
|
+
raise Exception("MCP 连接配置缺少 transport 或 type 字段")
|
|
137
|
+
|
|
138
|
+
if transport_type != "streamable_http":
|
|
125
139
|
# SSE transport method
|
|
126
140
|
self._streams_context = sse_client(
|
|
127
141
|
url=cfg["url"],
|
|
@@ -134,7 +148,7 @@ class MCPClient:
|
|
|
134
148
|
)
|
|
135
149
|
|
|
136
150
|
# Create a new client session
|
|
137
|
-
read_timeout = timedelta(seconds=cfg.get("session_read_timeout",
|
|
151
|
+
read_timeout = timedelta(seconds=cfg.get("session_read_timeout", 60))
|
|
138
152
|
self.session = await self.exit_stack.enter_async_context(
|
|
139
153
|
mcp.ClientSession(
|
|
140
154
|
*streams,
|
|
@@ -159,7 +173,7 @@ class MCPClient:
|
|
|
159
173
|
)
|
|
160
174
|
|
|
161
175
|
# Create a new client session
|
|
162
|
-
read_timeout = timedelta(seconds=cfg.get("session_read_timeout",
|
|
176
|
+
read_timeout = timedelta(seconds=cfg.get("session_read_timeout", 60))
|
|
163
177
|
self.session = await self.exit_stack.enter_async_context(
|
|
164
178
|
mcp.ClientSession(
|
|
165
179
|
read_stream=read_s,
|
|
@@ -209,9 +209,38 @@ class ToolLoopAgentRunner(BaseAgentRunner[TContext]):
|
|
|
209
209
|
)
|
|
210
210
|
continue
|
|
211
211
|
|
|
212
|
+
valid_params = {} # 参数过滤:只传递函数实际需要的参数
|
|
213
|
+
|
|
214
|
+
# 获取实际的 handler 函数
|
|
215
|
+
if func_tool.handler:
|
|
216
|
+
logger.debug(
|
|
217
|
+
f"工具 {func_tool_name} 期望的参数: {func_tool.parameters}"
|
|
218
|
+
)
|
|
219
|
+
if func_tool.parameters and func_tool.parameters.get("properties"):
|
|
220
|
+
expected_params = set(func_tool.parameters["properties"].keys())
|
|
221
|
+
|
|
222
|
+
valid_params = {
|
|
223
|
+
k: v
|
|
224
|
+
for k, v in func_tool_args.items()
|
|
225
|
+
if k in expected_params
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
# 记录被忽略的参数
|
|
229
|
+
ignored_params = set(func_tool_args.keys()) - set(
|
|
230
|
+
valid_params.keys()
|
|
231
|
+
)
|
|
232
|
+
if ignored_params:
|
|
233
|
+
logger.warning(
|
|
234
|
+
f"工具 {func_tool_name} 忽略非期望参数: {ignored_params}"
|
|
235
|
+
)
|
|
236
|
+
else:
|
|
237
|
+
# 如果没有 handler(如 MCP 工具),使用所有参数
|
|
238
|
+
valid_params = func_tool_args
|
|
239
|
+
logger.warning(f"工具 {func_tool_name} 没有 handler,使用所有参数")
|
|
240
|
+
|
|
212
241
|
try:
|
|
213
242
|
await self.agent_hooks.on_tool_start(
|
|
214
|
-
self.run_context, func_tool,
|
|
243
|
+
self.run_context, func_tool, valid_params
|
|
215
244
|
)
|
|
216
245
|
except Exception as e:
|
|
217
246
|
logger.error(f"Error in on_tool_start hook: {e}", exc_info=True)
|
|
@@ -219,7 +248,7 @@ class ToolLoopAgentRunner(BaseAgentRunner[TContext]):
|
|
|
219
248
|
executor = self.tool_executor.execute(
|
|
220
249
|
tool=func_tool,
|
|
221
250
|
run_context=self.run_context,
|
|
222
|
-
**
|
|
251
|
+
**valid_params, # 只传递有效的参数
|
|
223
252
|
)
|
|
224
253
|
|
|
225
254
|
_final_resp: CallToolResult | None = None
|
|
@@ -5,6 +5,7 @@ from astrbot.core.utils.shared_preferences import SharedPreferences
|
|
|
5
5
|
from astrbot.core.config.astrbot_config import ASTRBOT_CONFIG_PATH
|
|
6
6
|
from astrbot.core.config.default import DEFAULT_CONFIG
|
|
7
7
|
from astrbot.core.platform.message_session import MessageSession
|
|
8
|
+
from astrbot.core.umop_config_router import UmopConfigRouter
|
|
8
9
|
from astrbot.core.utils.astrbot_path import get_astrbot_config_path
|
|
9
10
|
from typing import TypeVar, TypedDict
|
|
10
11
|
|
|
@@ -15,14 +16,12 @@ class ConfInfo(TypedDict):
|
|
|
15
16
|
"""Configuration information for a specific session or platform."""
|
|
16
17
|
|
|
17
18
|
id: str # UUID of the configuration or "default"
|
|
18
|
-
umop: list[str] # Unified Message Origin Pattern
|
|
19
19
|
name: str
|
|
20
20
|
path: str # File name to the configuration file
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
DEFAULT_CONFIG_CONF_INFO = ConfInfo(
|
|
24
24
|
id="default",
|
|
25
|
-
umop=["::"],
|
|
26
25
|
name="default",
|
|
27
26
|
path=ASTRBOT_CONFIG_PATH,
|
|
28
27
|
)
|
|
@@ -31,8 +30,14 @@ DEFAULT_CONFIG_CONF_INFO = ConfInfo(
|
|
|
31
30
|
class AstrBotConfigManager:
|
|
32
31
|
"""A class to manage the system configuration of AstrBot, aka ACM"""
|
|
33
32
|
|
|
34
|
-
def __init__(
|
|
33
|
+
def __init__(
|
|
34
|
+
self,
|
|
35
|
+
default_config: AstrBotConfig,
|
|
36
|
+
ucr: UmopConfigRouter,
|
|
37
|
+
sp: SharedPreferences,
|
|
38
|
+
):
|
|
35
39
|
self.sp = sp
|
|
40
|
+
self.ucr = ucr
|
|
36
41
|
self.confs: dict[str, AstrBotConfig] = {}
|
|
37
42
|
"""uuid / "default" -> AstrBotConfig"""
|
|
38
43
|
self.confs["default"] = default_config
|
|
@@ -63,24 +68,15 @@ class AstrBotConfigManager:
|
|
|
63
68
|
)
|
|
64
69
|
continue
|
|
65
70
|
|
|
66
|
-
def _is_umo_match(self, p1: str, p2: str) -> bool:
|
|
67
|
-
"""判断 p2 umo 是否逻辑包含于 p1 umo"""
|
|
68
|
-
p1_ls = p1.split(":")
|
|
69
|
-
p2_ls = p2.split(":")
|
|
70
|
-
|
|
71
|
-
if len(p1_ls) != 3 or len(p2_ls) != 3:
|
|
72
|
-
return False # 非法格式
|
|
73
|
-
|
|
74
|
-
return all(p == "" or p == "*" or p == t for p, t in zip(p1_ls, p2_ls))
|
|
75
|
-
|
|
76
71
|
def _load_conf_mapping(self, umo: str | MessageSession) -> ConfInfo:
|
|
77
72
|
"""获取指定 umo 的配置文件 uuid, 如果不存在则返回默认配置(返回 "default")
|
|
78
73
|
|
|
79
74
|
Returns:
|
|
80
75
|
ConfInfo: 包含配置文件的 uuid, 路径和名称等信息, 是一个 dict 类型
|
|
81
76
|
"""
|
|
82
|
-
# uuid -> { "
|
|
77
|
+
# uuid -> { "path": str, "name": str }
|
|
83
78
|
abconf_data = self._get_abconf_data()
|
|
79
|
+
|
|
84
80
|
if isinstance(umo, MessageSession):
|
|
85
81
|
umo = str(umo)
|
|
86
82
|
else:
|
|
@@ -89,10 +85,13 @@ class AstrBotConfigManager:
|
|
|
89
85
|
except Exception:
|
|
90
86
|
return DEFAULT_CONFIG_CONF_INFO
|
|
91
87
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
88
|
+
conf_id = self.ucr.get_conf_id_for_umop(umo)
|
|
89
|
+
if conf_id:
|
|
90
|
+
meta = abconf_data.get(conf_id)
|
|
91
|
+
if meta and isinstance(meta, dict):
|
|
92
|
+
# the bind relation between umo and conf is defined in ucr now, so we remove "umop" here
|
|
93
|
+
meta.pop("umop", None)
|
|
94
|
+
return ConfInfo(**meta, id=conf_id)
|
|
96
95
|
|
|
97
96
|
return DEFAULT_CONFIG_CONF_INFO
|
|
98
97
|
|
|
@@ -100,23 +99,14 @@ class AstrBotConfigManager:
|
|
|
100
99
|
self,
|
|
101
100
|
abconf_path: str,
|
|
102
101
|
abconf_id: str,
|
|
103
|
-
umo_parts: list[str] | list[MessageSession],
|
|
104
102
|
abconf_name: str | None = None,
|
|
105
103
|
) -> None:
|
|
106
104
|
"""保存配置文件的映射关系"""
|
|
107
|
-
for part in umo_parts:
|
|
108
|
-
if isinstance(part, MessageSession):
|
|
109
|
-
part = str(part)
|
|
110
|
-
elif not isinstance(part, str):
|
|
111
|
-
raise ValueError(
|
|
112
|
-
"umo_parts must be a list of strings or MessageSession instances"
|
|
113
|
-
)
|
|
114
105
|
abconf_data = self.sp.get(
|
|
115
106
|
"abconf_mapping", {}, scope="global", scope_id="global"
|
|
116
107
|
)
|
|
117
108
|
random_word = abconf_name or uuid.uuid4().hex[:8]
|
|
118
109
|
abconf_data[abconf_id] = {
|
|
119
|
-
"umop": umo_parts,
|
|
120
110
|
"path": abconf_path,
|
|
121
111
|
"name": random_word,
|
|
122
112
|
}
|
|
@@ -153,29 +143,26 @@ class AstrBotConfigManager:
|
|
|
153
143
|
def get_conf_list(self) -> list[ConfInfo]:
|
|
154
144
|
"""获取所有配置文件的元数据列表"""
|
|
155
145
|
conf_list = []
|
|
156
|
-
conf_list.append(DEFAULT_CONFIG_CONF_INFO)
|
|
157
146
|
abconf_mapping = self._get_abconf_data()
|
|
158
147
|
for uuid_, meta in abconf_mapping.items():
|
|
148
|
+
if not isinstance(meta, dict):
|
|
149
|
+
continue
|
|
150
|
+
meta.pop("umop", None)
|
|
159
151
|
conf_list.append(ConfInfo(**meta, id=uuid_))
|
|
152
|
+
conf_list.append(DEFAULT_CONFIG_CONF_INFO)
|
|
160
153
|
return conf_list
|
|
161
154
|
|
|
162
155
|
def create_conf(
|
|
163
156
|
self,
|
|
164
|
-
umo_parts: list[str] | list[MessageSession],
|
|
165
157
|
config: dict = DEFAULT_CONFIG,
|
|
166
158
|
name: str | None = None,
|
|
167
159
|
) -> str:
|
|
168
|
-
"""
|
|
169
|
-
umo 由三个部分组成 [platform_id]:[message_type]:[session_id]。
|
|
170
|
-
|
|
171
|
-
umo_parts 可以是 "::" (代表所有), 可以是 "[platform_id]::" (代表指定平台下的所有类型消息和会话)。
|
|
172
|
-
"""
|
|
173
160
|
conf_uuid = str(uuid.uuid4())
|
|
174
161
|
conf_file_name = f"abconf_{conf_uuid}.json"
|
|
175
162
|
conf_path = os.path.join(get_astrbot_config_path(), conf_file_name)
|
|
176
163
|
conf = AstrBotConfig(config_path=conf_path, default_config=config)
|
|
177
164
|
conf.save_config()
|
|
178
|
-
self._save_conf_mapping(conf_file_name, conf_uuid,
|
|
165
|
+
self._save_conf_mapping(conf_file_name, conf_uuid, abconf_name=name)
|
|
179
166
|
self.confs[conf_uuid] = conf
|
|
180
167
|
return conf_uuid
|
|
181
168
|
|
|
@@ -228,15 +215,12 @@ class AstrBotConfigManager:
|
|
|
228
215
|
logger.info(f"成功删除配置文件 {conf_id}")
|
|
229
216
|
return True
|
|
230
217
|
|
|
231
|
-
def update_conf_info(
|
|
232
|
-
self, conf_id: str, name: str | None = None, umo_parts: list[str] | None = None
|
|
233
|
-
) -> bool:
|
|
218
|
+
def update_conf_info(self, conf_id: str, name: str | None = None) -> bool:
|
|
234
219
|
"""更新配置文件信息
|
|
235
220
|
|
|
236
221
|
Args:
|
|
237
222
|
conf_id: 配置文件的 UUID
|
|
238
223
|
name: 新的配置文件名称 (可选)
|
|
239
|
-
umo_parts: 新的 UMO 部分列表 (可选)
|
|
240
224
|
|
|
241
225
|
Returns:
|
|
242
226
|
bool: 更新是否成功
|
|
@@ -255,18 +239,6 @@ class AstrBotConfigManager:
|
|
|
255
239
|
if name is not None:
|
|
256
240
|
abconf_data[conf_id]["name"] = name
|
|
257
241
|
|
|
258
|
-
# 更新 UMO 部分
|
|
259
|
-
if umo_parts is not None:
|
|
260
|
-
# 验证 UMO 部分格式
|
|
261
|
-
for part in umo_parts:
|
|
262
|
-
if isinstance(part, MessageSession):
|
|
263
|
-
part = str(part)
|
|
264
|
-
elif not isinstance(part, str):
|
|
265
|
-
raise ValueError(
|
|
266
|
-
"umo_parts must be a list of strings or MessageSession instances"
|
|
267
|
-
)
|
|
268
|
-
abconf_data[conf_id]["umop"] = umo_parts
|
|
269
|
-
|
|
270
242
|
# 保存更新
|
|
271
243
|
self.sp.put("abconf_mapping", abconf_data, scope="global", scope_id="global")
|
|
272
244
|
self.abconf_data = abconf_data
|
astrbot/core/config/default.py
CHANGED
|
@@ -6,7 +6,7 @@ import os
|
|
|
6
6
|
|
|
7
7
|
from astrbot.core.utils.astrbot_path import get_astrbot_data_path
|
|
8
8
|
|
|
9
|
-
VERSION = "4.
|
|
9
|
+
VERSION = "4.5.0"
|
|
10
10
|
DB_PATH = os.path.join(get_astrbot_data_path(), "data_v4.db")
|
|
11
11
|
|
|
12
12
|
# 默认配置
|
|
@@ -57,6 +57,7 @@ DEFAULT_CONFIG = {
|
|
|
57
57
|
"web_search": False,
|
|
58
58
|
"websearch_provider": "default",
|
|
59
59
|
"websearch_tavily_key": [],
|
|
60
|
+
"websearch_baidu_app_builder_key": "",
|
|
60
61
|
"web_search_link": False,
|
|
61
62
|
"display_reasoning_text": False,
|
|
62
63
|
"identifier": False,
|
|
@@ -71,6 +72,7 @@ DEFAULT_CONFIG = {
|
|
|
71
72
|
"show_tool_use_status": False,
|
|
72
73
|
"streaming_segmented": False,
|
|
73
74
|
"max_agent_step": 30,
|
|
75
|
+
"tool_call_timeout": 60,
|
|
74
76
|
},
|
|
75
77
|
"provider_stt_settings": {
|
|
76
78
|
"enable": False,
|
|
@@ -132,8 +134,11 @@ DEFAULT_CONFIG = {
|
|
|
132
134
|
"persona": [], # deprecated
|
|
133
135
|
"timezone": "Asia/Shanghai",
|
|
134
136
|
"callback_api_base": "",
|
|
135
|
-
"default_kb_collection": "", #
|
|
137
|
+
"default_kb_collection": "", # 默认知识库名称, 已经过时
|
|
136
138
|
"plugin_set": ["*"], # "*" 表示使用所有可用的插件, 空列表表示不使用任何插件
|
|
139
|
+
"kb_names": [], # 默认知识库名称列表
|
|
140
|
+
"kb_fusion_top_k": 20, # 知识库检索融合阶段返回结果数量
|
|
141
|
+
"kb_final_top_k": 5, # 知识库检索最终返回结果数量
|
|
137
142
|
}
|
|
138
143
|
|
|
139
144
|
|
|
@@ -160,10 +165,11 @@ CONFIG_METADATA_2 = {
|
|
|
160
165
|
"enable": False,
|
|
161
166
|
"appid": "",
|
|
162
167
|
"secret": "",
|
|
168
|
+
"is_sandbox": False,
|
|
163
169
|
"callback_server_host": "0.0.0.0",
|
|
164
170
|
"port": 6196,
|
|
165
171
|
},
|
|
166
|
-
"QQ 个人号(
|
|
172
|
+
"QQ 个人号(OneBot v11)": {
|
|
167
173
|
"id": "default",
|
|
168
174
|
"type": "aiocqhttp",
|
|
169
175
|
"enable": False,
|
|
@@ -171,7 +177,7 @@ CONFIG_METADATA_2 = {
|
|
|
171
177
|
"ws_reverse_port": 6199,
|
|
172
178
|
"ws_reverse_token": "",
|
|
173
179
|
},
|
|
174
|
-
"
|
|
180
|
+
"WeChatPadPro": {
|
|
175
181
|
"id": "wechatpadpro",
|
|
176
182
|
"type": "wechatpadpro",
|
|
177
183
|
"enable": False,
|
|
@@ -207,6 +213,18 @@ CONFIG_METADATA_2 = {
|
|
|
207
213
|
"callback_server_host": "0.0.0.0",
|
|
208
214
|
"port": 6195,
|
|
209
215
|
},
|
|
216
|
+
"企业微信智能机器人": {
|
|
217
|
+
"id": "wecom_ai_bot",
|
|
218
|
+
"type": "wecom_ai_bot",
|
|
219
|
+
"enable": True,
|
|
220
|
+
"wecomaibot_init_respond_text": "💭 思考中...",
|
|
221
|
+
"wecomaibot_friend_message_welcome_text": "",
|
|
222
|
+
"wecom_ai_bot_name": "",
|
|
223
|
+
"token": "",
|
|
224
|
+
"encoding_aes_key": "",
|
|
225
|
+
"callback_server_host": "0.0.0.0",
|
|
226
|
+
"port": 6198,
|
|
227
|
+
},
|
|
210
228
|
"飞书(Lark)": {
|
|
211
229
|
"id": "lark",
|
|
212
230
|
"type": "lark",
|
|
@@ -254,6 +272,14 @@ CONFIG_METADATA_2 = {
|
|
|
254
272
|
"misskey_default_visibility": "public",
|
|
255
273
|
"misskey_local_only": False,
|
|
256
274
|
"misskey_enable_chat": True,
|
|
275
|
+
# download / security options
|
|
276
|
+
"misskey_allow_insecure_downloads": False,
|
|
277
|
+
"misskey_download_timeout": 15,
|
|
278
|
+
"misskey_download_chunk_size": 65536,
|
|
279
|
+
"misskey_max_download_bytes": None,
|
|
280
|
+
"misskey_enable_file_upload": True,
|
|
281
|
+
"misskey_upload_concurrency": 3,
|
|
282
|
+
"misskey_upload_folder": "",
|
|
257
283
|
},
|
|
258
284
|
"Slack": {
|
|
259
285
|
"id": "slack",
|
|
@@ -278,8 +304,26 @@ CONFIG_METADATA_2 = {
|
|
|
278
304
|
"satori_heartbeat_interval": 10,
|
|
279
305
|
"satori_reconnect_delay": 5,
|
|
280
306
|
},
|
|
307
|
+
# "WebChat": {
|
|
308
|
+
# "id": "webchat",
|
|
309
|
+
# "type": "webchat",
|
|
310
|
+
# "enable": False,
|
|
311
|
+
# "webchat_link_path": "",
|
|
312
|
+
# "webchat_present_type": "fullscreen",
|
|
313
|
+
# },
|
|
281
314
|
},
|
|
282
315
|
"items": {
|
|
316
|
+
# "webchat_link_path": {
|
|
317
|
+
# "description": "链接路径",
|
|
318
|
+
# "_special": "webchat_link_path",
|
|
319
|
+
# "type": "string",
|
|
320
|
+
# },
|
|
321
|
+
# "webchat_present_type": {
|
|
322
|
+
# "_special": "webchat_present_type",
|
|
323
|
+
# "description": "展现形式",
|
|
324
|
+
# "type": "string",
|
|
325
|
+
# "options": ["fullscreen", "embedded"],
|
|
326
|
+
# },
|
|
283
327
|
"satori_api_base_url": {
|
|
284
328
|
"description": "Satori API 终结点",
|
|
285
329
|
"type": "string",
|
|
@@ -382,6 +426,41 @@ CONFIG_METADATA_2 = {
|
|
|
382
426
|
"type": "bool",
|
|
383
427
|
"hint": "启用后,机器人将会监听和响应私信聊天消息",
|
|
384
428
|
},
|
|
429
|
+
"misskey_enable_file_upload": {
|
|
430
|
+
"description": "启用文件上传到 Misskey",
|
|
431
|
+
"type": "bool",
|
|
432
|
+
"hint": "启用后,适配器会尝试将消息链中的文件上传到 Misskey。URL 文件会先尝试服务器端上传,异步上传失败时会回退到下载后本地上传。",
|
|
433
|
+
},
|
|
434
|
+
"misskey_allow_insecure_downloads": {
|
|
435
|
+
"description": "允许不安全下载(禁用 SSL 验证)",
|
|
436
|
+
"type": "bool",
|
|
437
|
+
"hint": "当远端服务器存在证书问题导致无法正常下载时,自动禁用 SSL 验证作为回退方案。适用于某些图床的证书配置问题。启用有安全风险,仅在必要时使用。",
|
|
438
|
+
},
|
|
439
|
+
"misskey_download_timeout": {
|
|
440
|
+
"description": "远端下载超时时间(秒)",
|
|
441
|
+
"type": "int",
|
|
442
|
+
"hint": "下载远程文件时的超时时间(秒),用于异步上传回退到本地上传的场景。",
|
|
443
|
+
},
|
|
444
|
+
"misskey_download_chunk_size": {
|
|
445
|
+
"description": "流式下载分块大小(字节)",
|
|
446
|
+
"type": "int",
|
|
447
|
+
"hint": "流式下载和计算 MD5 时使用的每次读取字节数,过小会增加开销,过大会占用内存。",
|
|
448
|
+
},
|
|
449
|
+
"misskey_max_download_bytes": {
|
|
450
|
+
"description": "最大允许下载字节数(超出则中止)",
|
|
451
|
+
"type": "int",
|
|
452
|
+
"hint": "如果希望限制下载文件的最大大小以防止 OOM,请填写最大字节数;留空或 null 表示不限制。",
|
|
453
|
+
},
|
|
454
|
+
"misskey_upload_concurrency": {
|
|
455
|
+
"description": "并发上传限制",
|
|
456
|
+
"type": "int",
|
|
457
|
+
"hint": "同时进行的文件上传任务上限(整数,默认 3)。",
|
|
458
|
+
},
|
|
459
|
+
"misskey_upload_folder": {
|
|
460
|
+
"description": "上传到网盘的目标文件夹 ID",
|
|
461
|
+
"type": "string",
|
|
462
|
+
"hint": "可选:填写 Misskey 网盘中目标文件夹的 ID,上传的文件将放置到该文件夹内。留空则使用账号网盘根目录。",
|
|
463
|
+
},
|
|
385
464
|
"telegram_command_register": {
|
|
386
465
|
"description": "Telegram 命令注册",
|
|
387
466
|
"type": "bool",
|
|
@@ -433,24 +512,38 @@ CONFIG_METADATA_2 = {
|
|
|
433
512
|
"hint": "启用后,机器人可以接收到频道的私聊消息。",
|
|
434
513
|
},
|
|
435
514
|
"ws_reverse_host": {
|
|
436
|
-
"description": "反向 Websocket
|
|
515
|
+
"description": "反向 Websocket 主机",
|
|
437
516
|
"type": "string",
|
|
438
|
-
"hint": "
|
|
517
|
+
"hint": "AstrBot 将作为服务器端。",
|
|
439
518
|
},
|
|
440
519
|
"ws_reverse_port": {
|
|
441
520
|
"description": "反向 Websocket 端口",
|
|
442
521
|
"type": "int",
|
|
443
|
-
"hint": "aiocqhttp 适配器的反向 Websocket 端口。",
|
|
444
522
|
},
|
|
445
523
|
"ws_reverse_token": {
|
|
446
524
|
"description": "反向 Websocket Token",
|
|
447
525
|
"type": "string",
|
|
448
|
-
"hint": "
|
|
526
|
+
"hint": "反向 Websocket Token。未设置则不启用 Token 验证。",
|
|
527
|
+
},
|
|
528
|
+
"wecom_ai_bot_name": {
|
|
529
|
+
"description": "企业微信智能机器人的名字",
|
|
530
|
+
"type": "string",
|
|
531
|
+
"hint": "请务必填写正确,否则无法使用一些指令。",
|
|
532
|
+
},
|
|
533
|
+
"wecomaibot_init_respond_text": {
|
|
534
|
+
"description": "企业微信智能机器人初始响应文本",
|
|
535
|
+
"type": "string",
|
|
536
|
+
"hint": "当机器人收到消息时,首先回复的文本内容。留空则使用默认值。",
|
|
537
|
+
},
|
|
538
|
+
"wecomaibot_friend_message_welcome_text": {
|
|
539
|
+
"description": "企业微信智能机器人私聊欢迎语",
|
|
540
|
+
"type": "string",
|
|
541
|
+
"hint": "当用户当天进入智能机器人单聊会话,回复欢迎语,留空则不回复。",
|
|
449
542
|
},
|
|
450
543
|
"lark_bot_name": {
|
|
451
544
|
"description": "飞书机器人的名字",
|
|
452
545
|
"type": "string",
|
|
453
|
-
"hint": "
|
|
546
|
+
"hint": "请务必填写正确,否则 @ 机器人将无法唤醒,只能通过前缀唤醒。",
|
|
454
547
|
},
|
|
455
548
|
"discord_token": {
|
|
456
549
|
"description": "Discord Bot Token",
|
|
@@ -1324,6 +1417,7 @@ CONFIG_METADATA_2 = {
|
|
|
1324
1417
|
"description": "嵌入维度",
|
|
1325
1418
|
"type": "int",
|
|
1326
1419
|
"hint": "嵌入向量的维度。根据模型不同,可能需要调整,请参考具体模型的文档。此配置项请务必填写正确,否则将导致向量数据库无法正常工作。",
|
|
1420
|
+
"_special": "get_embedding_dim",
|
|
1327
1421
|
},
|
|
1328
1422
|
"embedding_model": {
|
|
1329
1423
|
"description": "嵌入模型",
|
|
@@ -1845,6 +1939,10 @@ CONFIG_METADATA_2 = {
|
|
|
1845
1939
|
"description": "工具调用轮数上限",
|
|
1846
1940
|
"type": "int",
|
|
1847
1941
|
},
|
|
1942
|
+
"tool_call_timeout": {
|
|
1943
|
+
"description": "工具调用超时时间(秒)",
|
|
1944
|
+
"type": "int",
|
|
1945
|
+
},
|
|
1848
1946
|
},
|
|
1849
1947
|
},
|
|
1850
1948
|
"provider_stt_settings": {
|
|
@@ -1967,6 +2065,9 @@ CONFIG_METADATA_2 = {
|
|
|
1967
2065
|
"default_kb_collection": {
|
|
1968
2066
|
"type": "string",
|
|
1969
2067
|
},
|
|
2068
|
+
"kb_names": {"type": "list", "items": {"type": "string"}},
|
|
2069
|
+
"kb_fusion_top_k": {"type": "int", "default": 20},
|
|
2070
|
+
"kb_final_top_k": {"type": "int", "default": 5},
|
|
1970
2071
|
},
|
|
1971
2072
|
},
|
|
1972
2073
|
}
|
|
@@ -2045,10 +2146,22 @@ CONFIG_METADATA_3 = {
|
|
|
2045
2146
|
"description": "知识库",
|
|
2046
2147
|
"type": "object",
|
|
2047
2148
|
"items": {
|
|
2048
|
-
"
|
|
2049
|
-
"description": "
|
|
2050
|
-
"type": "
|
|
2149
|
+
"kb_names": {
|
|
2150
|
+
"description": "知识库列表",
|
|
2151
|
+
"type": "list",
|
|
2152
|
+
"items": {"type": "string"},
|
|
2051
2153
|
"_special": "select_knowledgebase",
|
|
2154
|
+
"hint": "支持多选",
|
|
2155
|
+
},
|
|
2156
|
+
"kb_fusion_top_k": {
|
|
2157
|
+
"description": "融合检索结果数",
|
|
2158
|
+
"type": "int",
|
|
2159
|
+
"hint": "多个知识库检索结果融合后的返回结果数量",
|
|
2160
|
+
},
|
|
2161
|
+
"kb_final_top_k": {
|
|
2162
|
+
"description": "最终返回结果数",
|
|
2163
|
+
"type": "int",
|
|
2164
|
+
"hint": "从知识库中检索到的结果数量,越大可能获得越多相关信息,但也可能引入噪音。建议根据实际需求调整",
|
|
2052
2165
|
},
|
|
2053
2166
|
},
|
|
2054
2167
|
},
|
|
@@ -2063,7 +2176,7 @@ CONFIG_METADATA_3 = {
|
|
|
2063
2176
|
"provider_settings.websearch_provider": {
|
|
2064
2177
|
"description": "网页搜索提供商",
|
|
2065
2178
|
"type": "string",
|
|
2066
|
-
"options": ["default", "tavily"],
|
|
2179
|
+
"options": ["default", "tavily", "baidu_ai_search"],
|
|
2067
2180
|
},
|
|
2068
2181
|
"provider_settings.websearch_tavily_key": {
|
|
2069
2182
|
"description": "Tavily API Key",
|
|
@@ -2074,6 +2187,14 @@ CONFIG_METADATA_3 = {
|
|
|
2074
2187
|
"provider_settings.websearch_provider": "tavily",
|
|
2075
2188
|
},
|
|
2076
2189
|
},
|
|
2190
|
+
"provider_settings.websearch_baidu_app_builder_key": {
|
|
2191
|
+
"description": "百度千帆智能云 APP Builder API Key",
|
|
2192
|
+
"type": "string",
|
|
2193
|
+
"hint": "参考:https://console.bce.baidu.com/iam/#/iam/apikey/list",
|
|
2194
|
+
"condition": {
|
|
2195
|
+
"provider_settings.websearch_provider": "baidu_ai_search",
|
|
2196
|
+
},
|
|
2197
|
+
},
|
|
2077
2198
|
"provider_settings.web_search_link": {
|
|
2078
2199
|
"description": "显示来源引用",
|
|
2079
2200
|
"type": "bool",
|
|
@@ -2109,6 +2230,10 @@ CONFIG_METADATA_3 = {
|
|
|
2109
2230
|
"description": "工具调用轮数上限",
|
|
2110
2231
|
"type": "int",
|
|
2111
2232
|
},
|
|
2233
|
+
"provider_settings.tool_call_timeout": {
|
|
2234
|
+
"description": "工具调用超时时间(秒)",
|
|
2235
|
+
"type": "int",
|
|
2236
|
+
},
|
|
2112
2237
|
"provider_settings.streaming_response": {
|
|
2113
2238
|
"description": "流式回复",
|
|
2114
2239
|
"type": "bool",
|
|
@@ -2130,7 +2255,7 @@ CONFIG_METADATA_3 = {
|
|
|
2130
2255
|
"provider_settings.wake_prefix": {
|
|
2131
2256
|
"description": "LLM 聊天额外唤醒前缀 ",
|
|
2132
2257
|
"type": "string",
|
|
2133
|
-
"hint": "
|
|
2258
|
+
"hint": "如果唤醒前缀为 `/`, 额外聊天唤醒前缀为 `chat`,则需要 `/chat` 才会触发 LLM 请求。默认为空。",
|
|
2134
2259
|
},
|
|
2135
2260
|
"provider_settings.prompt_prefix": {
|
|
2136
2261
|
"description": "用户提示词",
|
astrbot/core/conversation_mgr.py
CHANGED
|
@@ -7,7 +7,7 @@ AstrBot 会话-对话管理器, 维护两个本地存储, 其中一个是 json
|
|
|
7
7
|
|
|
8
8
|
import json
|
|
9
9
|
from astrbot.core import sp
|
|
10
|
-
from typing import Dict, List
|
|
10
|
+
from typing import Dict, List, Callable, Awaitable
|
|
11
11
|
from astrbot.core.db import BaseDatabase
|
|
12
12
|
from astrbot.core.db.po import Conversation, ConversationV2
|
|
13
13
|
|
|
@@ -20,6 +20,38 @@ class ConversationManager:
|
|
|
20
20
|
self.db = db_helper
|
|
21
21
|
self.save_interval = 60 # 每 60 秒保存一次
|
|
22
22
|
|
|
23
|
+
# 会话删除回调函数列表(用于级联清理,如知识库配置)
|
|
24
|
+
self._on_session_deleted_callbacks: List[Callable[[str], Awaitable[None]]] = []
|
|
25
|
+
|
|
26
|
+
def register_on_session_deleted(
|
|
27
|
+
self, callback: Callable[[str], Awaitable[None]]
|
|
28
|
+
) -> None:
|
|
29
|
+
"""注册会话删除回调函数
|
|
30
|
+
|
|
31
|
+
其他模块可以注册回调来响应会话删除事件,实现级联清理。
|
|
32
|
+
例如:知识库模块可以注册回调来清理会话的知识库配置。
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
callback: 回调函数,接收会话ID (unified_msg_origin) 作为参数
|
|
36
|
+
"""
|
|
37
|
+
self._on_session_deleted_callbacks.append(callback)
|
|
38
|
+
|
|
39
|
+
async def _trigger_session_deleted(self, unified_msg_origin: str) -> None:
|
|
40
|
+
"""触发会话删除回调
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
unified_msg_origin: 会话ID
|
|
44
|
+
"""
|
|
45
|
+
for callback in self._on_session_deleted_callbacks:
|
|
46
|
+
try:
|
|
47
|
+
await callback(unified_msg_origin)
|
|
48
|
+
except Exception as e:
|
|
49
|
+
from astrbot.core import logger
|
|
50
|
+
|
|
51
|
+
logger.error(
|
|
52
|
+
f"会话删除回调执行失败 (session: {unified_msg_origin}): {e}"
|
|
53
|
+
)
|
|
54
|
+
|
|
23
55
|
def _convert_conv_from_v2_to_v1(self, conv_v2: ConversationV2) -> Conversation:
|
|
24
56
|
"""将 ConversationV2 对象转换为 Conversation 对象"""
|
|
25
57
|
created_at = int(conv_v2.created_at.timestamp())
|
|
@@ -106,6 +138,9 @@ class ConversationManager:
|
|
|
106
138
|
self.session_conversations.pop(unified_msg_origin, None)
|
|
107
139
|
await sp.session_remove(unified_msg_origin, "sel_conv_id")
|
|
108
140
|
|
|
141
|
+
# 触发会话删除回调(级联清理)
|
|
142
|
+
await self._trigger_session_deleted(unified_msg_origin)
|
|
143
|
+
|
|
109
144
|
async def get_curr_conversation_id(self, unified_msg_origin: str) -> str | None:
|
|
110
145
|
"""获取会话当前的对话 ID
|
|
111
146
|
|