AstrBot 4.3.5__py3-none-any.whl → 4.5.1__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.
Files changed (72) hide show
  1. astrbot/core/agent/runners/tool_loop_agent_runner.py +31 -2
  2. astrbot/core/astrbot_config_mgr.py +23 -51
  3. astrbot/core/config/default.py +132 -12
  4. astrbot/core/conversation_mgr.py +36 -1
  5. astrbot/core/core_lifecycle.py +24 -5
  6. astrbot/core/db/migration/helper.py +6 -3
  7. astrbot/core/db/migration/migra_45_to_46.py +44 -0
  8. astrbot/core/db/vec_db/base.py +33 -2
  9. astrbot/core/db/vec_db/faiss_impl/document_storage.py +310 -52
  10. astrbot/core/db/vec_db/faiss_impl/embedding_storage.py +31 -3
  11. astrbot/core/db/vec_db/faiss_impl/vec_db.py +81 -23
  12. astrbot/core/file_token_service.py +6 -1
  13. astrbot/core/initial_loader.py +6 -3
  14. astrbot/core/knowledge_base/chunking/__init__.py +11 -0
  15. astrbot/core/knowledge_base/chunking/base.py +24 -0
  16. astrbot/core/knowledge_base/chunking/fixed_size.py +57 -0
  17. astrbot/core/knowledge_base/chunking/recursive.py +155 -0
  18. astrbot/core/knowledge_base/kb_db_sqlite.py +299 -0
  19. astrbot/core/knowledge_base/kb_helper.py +348 -0
  20. astrbot/core/knowledge_base/kb_mgr.py +287 -0
  21. astrbot/core/knowledge_base/models.py +114 -0
  22. astrbot/core/knowledge_base/parsers/__init__.py +15 -0
  23. astrbot/core/knowledge_base/parsers/base.py +50 -0
  24. astrbot/core/knowledge_base/parsers/markitdown_parser.py +25 -0
  25. astrbot/core/knowledge_base/parsers/pdf_parser.py +100 -0
  26. astrbot/core/knowledge_base/parsers/text_parser.py +41 -0
  27. astrbot/core/knowledge_base/parsers/util.py +13 -0
  28. astrbot/core/knowledge_base/retrieval/__init__.py +16 -0
  29. astrbot/core/knowledge_base/retrieval/hit_stopwords.txt +767 -0
  30. astrbot/core/knowledge_base/retrieval/manager.py +273 -0
  31. astrbot/core/knowledge_base/retrieval/rank_fusion.py +138 -0
  32. astrbot/core/knowledge_base/retrieval/sparse_retriever.py +130 -0
  33. astrbot/core/pipeline/process_stage/method/llm_request.py +29 -7
  34. astrbot/core/pipeline/process_stage/utils.py +80 -0
  35. astrbot/core/platform/astr_message_event.py +8 -7
  36. astrbot/core/platform/sources/dingtalk/dingtalk_adapter.py +5 -2
  37. astrbot/core/platform/sources/misskey/misskey_adapter.py +380 -44
  38. astrbot/core/platform/sources/misskey/misskey_api.py +581 -45
  39. astrbot/core/platform/sources/misskey/misskey_event.py +76 -41
  40. astrbot/core/platform/sources/misskey/misskey_utils.py +254 -43
  41. astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_server.py +2 -1
  42. astrbot/core/platform/sources/satori/satori_adapter.py +27 -1
  43. astrbot/core/platform/sources/satori/satori_event.py +270 -99
  44. astrbot/core/provider/manager.py +22 -9
  45. astrbot/core/provider/provider.py +67 -0
  46. astrbot/core/provider/sources/anthropic_source.py +4 -4
  47. astrbot/core/provider/sources/dashscope_source.py +10 -9
  48. astrbot/core/provider/sources/dify_source.py +6 -8
  49. astrbot/core/provider/sources/gemini_embedding_source.py +1 -2
  50. astrbot/core/provider/sources/openai_embedding_source.py +1 -2
  51. astrbot/core/provider/sources/openai_source.py +43 -15
  52. astrbot/core/provider/sources/openai_tts_api_source.py +1 -1
  53. astrbot/core/provider/sources/xinference_rerank_source.py +108 -0
  54. astrbot/core/provider/sources/xinference_stt_provider.py +187 -0
  55. astrbot/core/star/context.py +19 -13
  56. astrbot/core/star/star.py +6 -0
  57. astrbot/core/star/star_manager.py +13 -7
  58. astrbot/core/umop_config_router.py +81 -0
  59. astrbot/core/updator.py +1 -1
  60. astrbot/core/utils/io.py +23 -12
  61. astrbot/dashboard/routes/__init__.py +2 -0
  62. astrbot/dashboard/routes/config.py +137 -9
  63. astrbot/dashboard/routes/knowledge_base.py +1065 -0
  64. astrbot/dashboard/routes/plugin.py +24 -5
  65. astrbot/dashboard/routes/update.py +1 -1
  66. astrbot/dashboard/server.py +6 -0
  67. astrbot/dashboard/utils.py +161 -0
  68. {astrbot-4.3.5.dist-info → astrbot-4.5.1.dist-info}/METADATA +30 -13
  69. {astrbot-4.3.5.dist-info → astrbot-4.5.1.dist-info}/RECORD +72 -46
  70. {astrbot-4.3.5.dist-info → astrbot-4.5.1.dist-info}/WHEEL +0 -0
  71. {astrbot-4.3.5.dist-info → astrbot-4.5.1.dist-info}/entry_points.txt +0 -0
  72. {astrbot-4.3.5.dist-info → astrbot-4.5.1.dist-info}/licenses/LICENSE +0 -0
@@ -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, func_tool_args
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
- **func_tool_args,
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__(self, default_config: AstrBotConfig, sp: SharedPreferences):
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 -> { "umop": list, "path": str, "name": str }
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
- for uuid_, meta in abconf_data.items():
93
- for pattern in meta["umop"]:
94
- if self._is_umo_match(pattern, umo):
95
- return ConfInfo(**meta, id=uuid_)
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, umo_parts, abconf_name=name)
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
@@ -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.3.5"
9
+ VERSION = "4.5.1"
10
10
  DB_PATH = os.path.join(get_astrbot_data_path(), "data_v4.db")
11
11
 
12
12
  # 默认配置
@@ -134,8 +134,11 @@ DEFAULT_CONFIG = {
134
134
  "persona": [], # deprecated
135
135
  "timezone": "Asia/Shanghai",
136
136
  "callback_api_base": "",
137
- "default_kb_collection": "", # 默认知识库名称
137
+ "default_kb_collection": "", # 默认知识库名称, 已经过时
138
138
  "plugin_set": ["*"], # "*" 表示使用所有可用的插件, 空列表表示不使用任何插件
139
+ "kb_names": [], # 默认知识库名称列表
140
+ "kb_fusion_top_k": 20, # 知识库检索融合阶段返回结果数量
141
+ "kb_final_top_k": 5, # 知识库检索最终返回结果数量
139
142
  }
140
143
 
141
144
 
@@ -162,10 +165,11 @@ CONFIG_METADATA_2 = {
162
165
  "enable": False,
163
166
  "appid": "",
164
167
  "secret": "",
168
+ "is_sandbox": False,
165
169
  "callback_server_host": "0.0.0.0",
166
170
  "port": 6196,
167
171
  },
168
- "QQ 个人号(aiocqhttp)": {
172
+ "QQ 个人号(OneBot v11)": {
169
173
  "id": "default",
170
174
  "type": "aiocqhttp",
171
175
  "enable": False,
@@ -173,7 +177,7 @@ CONFIG_METADATA_2 = {
173
177
  "ws_reverse_port": 6199,
174
178
  "ws_reverse_token": "",
175
179
  },
176
- "微信个人号(WeChatPadPro)": {
180
+ "WeChatPadPro": {
177
181
  "id": "wechatpadpro",
178
182
  "type": "wechatpadpro",
179
183
  "enable": False,
@@ -268,6 +272,14 @@ CONFIG_METADATA_2 = {
268
272
  "misskey_default_visibility": "public",
269
273
  "misskey_local_only": False,
270
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": "",
271
283
  },
272
284
  "Slack": {
273
285
  "id": "slack",
@@ -292,8 +304,30 @@ CONFIG_METADATA_2 = {
292
304
  "satori_heartbeat_interval": 10,
293
305
  "satori_reconnect_delay": 5,
294
306
  },
307
+ # "WebChat": {
308
+ # "id": "webchat",
309
+ # "type": "webchat",
310
+ # "enable": False,
311
+ # "webchat_link_path": "",
312
+ # "webchat_present_type": "fullscreen",
313
+ # },
295
314
  },
296
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
+ # },
327
+ "is_sandbox": {
328
+ "description": "沙箱模式",
329
+ "type": "bool",
330
+ },
297
331
  "satori_api_base_url": {
298
332
  "description": "Satori API 终结点",
299
333
  "type": "string",
@@ -396,6 +430,41 @@ CONFIG_METADATA_2 = {
396
430
  "type": "bool",
397
431
  "hint": "启用后,机器人将会监听和响应私信聊天消息",
398
432
  },
433
+ "misskey_enable_file_upload": {
434
+ "description": "启用文件上传到 Misskey",
435
+ "type": "bool",
436
+ "hint": "启用后,适配器会尝试将消息链中的文件上传到 Misskey。URL 文件会先尝试服务器端上传,异步上传失败时会回退到下载后本地上传。",
437
+ },
438
+ "misskey_allow_insecure_downloads": {
439
+ "description": "允许不安全下载(禁用 SSL 验证)",
440
+ "type": "bool",
441
+ "hint": "当远端服务器存在证书问题导致无法正常下载时,自动禁用 SSL 验证作为回退方案。适用于某些图床的证书配置问题。启用有安全风险,仅在必要时使用。",
442
+ },
443
+ "misskey_download_timeout": {
444
+ "description": "远端下载超时时间(秒)",
445
+ "type": "int",
446
+ "hint": "下载远程文件时的超时时间(秒),用于异步上传回退到本地上传的场景。",
447
+ },
448
+ "misskey_download_chunk_size": {
449
+ "description": "流式下载分块大小(字节)",
450
+ "type": "int",
451
+ "hint": "流式下载和计算 MD5 时使用的每次读取字节数,过小会增加开销,过大会占用内存。",
452
+ },
453
+ "misskey_max_download_bytes": {
454
+ "description": "最大允许下载字节数(超出则中止)",
455
+ "type": "int",
456
+ "hint": "如果希望限制下载文件的最大大小以防止 OOM,请填写最大字节数;留空或 null 表示不限制。",
457
+ },
458
+ "misskey_upload_concurrency": {
459
+ "description": "并发上传限制",
460
+ "type": "int",
461
+ "hint": "同时进行的文件上传任务上限(整数,默认 3)。",
462
+ },
463
+ "misskey_upload_folder": {
464
+ "description": "上传到网盘的目标文件夹 ID",
465
+ "type": "string",
466
+ "hint": "可选:填写 Misskey 网盘中目标文件夹的 ID,上传的文件将放置到该文件夹内。留空则使用账号网盘根目录。",
467
+ },
399
468
  "telegram_command_register": {
400
469
  "description": "Telegram 命令注册",
401
470
  "type": "bool",
@@ -447,19 +516,18 @@ CONFIG_METADATA_2 = {
447
516
  "hint": "启用后,机器人可以接收到频道的私聊消息。",
448
517
  },
449
518
  "ws_reverse_host": {
450
- "description": "反向 Websocket 主机地址(AstrBot 为服务器端)",
519
+ "description": "反向 Websocket 主机",
451
520
  "type": "string",
452
- "hint": "aiocqhttp 适配器的反向 Websocket 服务器 IP 地址,不包含端口号。",
521
+ "hint": "AstrBot 将作为服务器端。",
453
522
  },
454
523
  "ws_reverse_port": {
455
524
  "description": "反向 Websocket 端口",
456
525
  "type": "int",
457
- "hint": "aiocqhttp 适配器的反向 Websocket 端口。",
458
526
  },
459
527
  "ws_reverse_token": {
460
528
  "description": "反向 Websocket Token",
461
529
  "type": "string",
462
- "hint": "aiocqhttp 适配器的反向 Websocket Token。未设置则不启用 Token 验证。",
530
+ "hint": "反向 Websocket Token。未设置则不启用 Token 验证。",
463
531
  },
464
532
  "wecom_ai_bot_name": {
465
533
  "description": "企业微信智能机器人的名字",
@@ -703,6 +771,7 @@ CONFIG_METADATA_2 = {
703
771
  "timeout": 120,
704
772
  "model_config": {"model": "grok-2-latest", "temperature": 0.4},
705
773
  "custom_extra_body": {},
774
+ "xai_native_search": False,
706
775
  "modalities": ["text", "image", "tool_use"],
707
776
  },
708
777
  "Anthropic": {
@@ -1194,8 +1263,38 @@ CONFIG_METADATA_2 = {
1194
1263
  "rerank_model": "BAAI/bge-reranker-base",
1195
1264
  "timeout": 20,
1196
1265
  },
1266
+ "Xinference Rerank": {
1267
+ "id": "xinference_rerank",
1268
+ "type": "xinference_rerank",
1269
+ "provider": "xinference",
1270
+ "provider_type": "rerank",
1271
+ "enable": True,
1272
+ "rerank_api_key": "",
1273
+ "rerank_api_base": "http://127.0.0.1:9997",
1274
+ "rerank_model": "BAAI/bge-reranker-base",
1275
+ "timeout": 20,
1276
+ "launch_model_if_not_running": False,
1277
+ },
1278
+ "Xinference STT": {
1279
+ "id": "xinference_stt",
1280
+ "type": "xinference_stt",
1281
+ "provider": "xinference",
1282
+ "provider_type": "speech_to_text",
1283
+ "enable": False,
1284
+ "api_key": "",
1285
+ "api_base": "http://127.0.0.1:9997",
1286
+ "model": "whisper-large-v3",
1287
+ "timeout": 180,
1288
+ "launch_model_if_not_running": False,
1289
+ },
1197
1290
  },
1198
1291
  "items": {
1292
+ "xai_native_search": {
1293
+ "description": "启用原生搜索功能",
1294
+ "type": "bool",
1295
+ "hint": "启用后,将通过 xAI 的 Chat Completions 原生 Live Search 进行联网检索(按需计费)。仅对 xAI 提供商生效。",
1296
+ "condition": {"provider": "xai"},
1297
+ },
1199
1298
  "rerank_api_base": {
1200
1299
  "description": "重排序模型 API Base URL",
1201
1300
  "type": "string",
@@ -1210,6 +1309,11 @@ CONFIG_METADATA_2 = {
1210
1309
  "description": "重排序模型名称",
1211
1310
  "type": "string",
1212
1311
  },
1312
+ "launch_model_if_not_running": {
1313
+ "description": "模型未运行时自动启动",
1314
+ "type": "bool",
1315
+ "hint": "如果模型当前未在 Xinference 服务中运行,是否尝试自动启动它。在生产环境中建议关闭。",
1316
+ },
1213
1317
  "modalities": {
1214
1318
  "description": "模型能力",
1215
1319
  "type": "list",
@@ -1353,6 +1457,7 @@ CONFIG_METADATA_2 = {
1353
1457
  "description": "嵌入维度",
1354
1458
  "type": "int",
1355
1459
  "hint": "嵌入向量的维度。根据模型不同,可能需要调整,请参考具体模型的文档。此配置项请务必填写正确,否则将导致向量数据库无法正常工作。",
1460
+ "_special": "get_embedding_dim",
1356
1461
  },
1357
1462
  "embedding_model": {
1358
1463
  "description": "嵌入模型",
@@ -2000,6 +2105,9 @@ CONFIG_METADATA_2 = {
2000
2105
  "default_kb_collection": {
2001
2106
  "type": "string",
2002
2107
  },
2108
+ "kb_names": {"type": "list", "items": {"type": "string"}},
2109
+ "kb_fusion_top_k": {"type": "int", "default": 20},
2110
+ "kb_final_top_k": {"type": "int", "default": 5},
2003
2111
  },
2004
2112
  },
2005
2113
  }
@@ -2078,10 +2186,22 @@ CONFIG_METADATA_3 = {
2078
2186
  "description": "知识库",
2079
2187
  "type": "object",
2080
2188
  "items": {
2081
- "default_kb_collection": {
2082
- "description": "默认使用的知识库",
2083
- "type": "string",
2189
+ "kb_names": {
2190
+ "description": "知识库列表",
2191
+ "type": "list",
2192
+ "items": {"type": "string"},
2084
2193
  "_special": "select_knowledgebase",
2194
+ "hint": "支持多选",
2195
+ },
2196
+ "kb_fusion_top_k": {
2197
+ "description": "融合检索结果数",
2198
+ "type": "int",
2199
+ "hint": "多个知识库检索结果融合后的返回结果数量",
2200
+ },
2201
+ "kb_final_top_k": {
2202
+ "description": "最终返回结果数",
2203
+ "type": "int",
2204
+ "hint": "从知识库中检索到的结果数量,越大可能获得越多相关信息,但也可能引入噪音。建议根据实际需求调整",
2085
2205
  },
2086
2206
  },
2087
2207
  },
@@ -2175,7 +2295,7 @@ CONFIG_METADATA_3 = {
2175
2295
  "provider_settings.wake_prefix": {
2176
2296
  "description": "LLM 聊天额外唤醒前缀 ",
2177
2297
  "type": "string",
2178
- "hint": "例子: 如果唤醒前缀为 `/`, 额外聊天唤醒前缀为 `chat`,则需要 `/chat` 才会触发 LLM 请求。默认为空。",
2298
+ "hint": "如果唤醒前缀为 `/`, 额外聊天唤醒前缀为 `chat`,则需要 `/chat` 才会触发 LLM 请求。默认为空。",
2179
2299
  },
2180
2300
  "provider_settings.prompt_prefix": {
2181
2301
  "description": "用户提示词",
@@ -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
 
@@ -17,7 +17,6 @@ import os
17
17
  from .event_bus import EventBus
18
18
  from . import astrbot_config, html_renderer
19
19
  from asyncio import Queue
20
- from typing import List
21
20
  from astrbot.core.pipeline.scheduler import PipelineScheduler, PipelineContext
22
21
  from astrbot.core.star import PluginManager
23
22
  from astrbot.core.platform.manager import PlatformManager
@@ -26,14 +25,17 @@ from astrbot.core.persona_mgr import PersonaManager
26
25
  from astrbot.core.provider.manager import ProviderManager
27
26
  from astrbot.core import LogBroker
28
27
  from astrbot.core.db import BaseDatabase
28
+ from astrbot.core.db.migration.migra_45_to_46 import migrate_45_to_46
29
29
  from astrbot.core.updator import AstrBotUpdator
30
30
  from astrbot.core import logger, sp
31
31
  from astrbot.core.config.default import VERSION
32
32
  from astrbot.core.conversation_mgr import ConversationManager
33
33
  from astrbot.core.platform_message_history_mgr import PlatformMessageHistoryManager
34
+ from astrbot.core.umop_config_router import UmopConfigRouter
34
35
  from astrbot.core.astrbot_config_mgr import AstrBotConfigManager
35
36
  from astrbot.core.star.star_handler import star_handlers_registry, EventType
36
37
  from astrbot.core.star.star_handler import star_map
38
+ from astrbot.core.knowledge_base.kb_mgr import KnowledgeBaseManager
37
39
 
38
40
 
39
41
  class AstrBotCoreLifecycle:
@@ -84,11 +86,21 @@ class AstrBotCoreLifecycle:
84
86
 
85
87
  await html_renderer.initialize()
86
88
 
89
+ # 初始化 UMOP 配置路由器
90
+ self.umop_config_router = UmopConfigRouter(sp=sp)
91
+
87
92
  # 初始化 AstrBot 配置管理器
88
93
  self.astrbot_config_mgr = AstrBotConfigManager(
89
- default_config=self.astrbot_config, sp=sp
94
+ default_config=self.astrbot_config, ucr=self.umop_config_router, sp=sp
90
95
  )
91
96
 
97
+ # 4.5 to 4.6 migration for umop_config_router
98
+ try:
99
+ await migrate_45_to_46(self.astrbot_config_mgr, self.umop_config_router)
100
+ except Exception as e:
101
+ logger.error(f"Migration from version 4.5 to 4.6 failed: {e!s}")
102
+ logger.error(traceback.format_exc())
103
+
92
104
  # 初始化事件队列
93
105
  self.event_queue = Queue()
94
106
 
@@ -110,6 +122,9 @@ class AstrBotCoreLifecycle:
110
122
  # 初始化平台消息历史管理器
111
123
  self.platform_message_history_manager = PlatformMessageHistoryManager(self.db)
112
124
 
125
+ # 初始化知识库管理器
126
+ self.kb_manager = KnowledgeBaseManager(self.provider_manager)
127
+
113
128
  # 初始化提供给插件的上下文
114
129
  self.star_context = Context(
115
130
  self.event_queue,
@@ -121,6 +136,7 @@ class AstrBotCoreLifecycle:
121
136
  self.platform_message_history_manager,
122
137
  self.persona_mgr,
123
138
  self.astrbot_config_mgr,
139
+ self.kb_manager,
124
140
  )
125
141
 
126
142
  # 初始化插件管理器
@@ -132,8 +148,9 @@ class AstrBotCoreLifecycle:
132
148
  # 根据配置实例化各个 Provider
133
149
  await self.provider_manager.initialize()
134
150
 
135
- # 初始化消息事件流水线调度器
151
+ await self.kb_manager.initialize()
136
152
 
153
+ # 初始化消息事件流水线调度器
137
154
  self.pipeline_scheduler_mapping = await self.load_pipeline_scheduler()
138
155
 
139
156
  # 初始化更新器
@@ -148,7 +165,7 @@ class AstrBotCoreLifecycle:
148
165
  self.start_time = int(time.time())
149
166
 
150
167
  # 初始化当前任务列表
151
- self.curr_tasks: List[asyncio.Task] = []
168
+ self.curr_tasks: list[asyncio.Task] = []
152
169
 
153
170
  # 根据配置实例化各个平台适配器
154
171
  await self.platform_manager.initialize()
@@ -233,6 +250,7 @@ class AstrBotCoreLifecycle:
233
250
 
234
251
  await self.provider_manager.terminate()
235
252
  await self.platform_manager.terminate()
253
+ await self.kb_manager.terminate()
236
254
  self.dashboard_shutdown_event.set()
237
255
 
238
256
  # 再次遍历curr_tasks等待每个任务真正结束
@@ -248,12 +266,13 @@ class AstrBotCoreLifecycle:
248
266
  """重启 AstrBot 核心生命周期管理类, 终止各个管理器并重新加载平台实例"""
249
267
  await self.provider_manager.terminate()
250
268
  await self.platform_manager.terminate()
269
+ await self.kb_manager.terminate()
251
270
  self.dashboard_shutdown_event.set()
252
271
  threading.Thread(
253
272
  target=self.astrbot_updator._reboot, name="restart", daemon=True
254
273
  ).start()
255
274
 
256
- def load_platform(self) -> List[asyncio.Task]:
275
+ def load_platform(self) -> list[asyncio.Task]:
257
276
  """加载平台实例并返回所有平台实例的异步任务列表"""
258
277
  tasks = []
259
278
  platform_insts = self.platform_manager.get_insts()