AstrBot 4.6.1__py3-none-any.whl → 4.7.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 (37) hide show
  1. astrbot/core/agent/mcp_client.py +3 -3
  2. astrbot/core/agent/runners/base.py +7 -4
  3. astrbot/core/agent/runners/coze/coze_agent_runner.py +367 -0
  4. astrbot/core/agent/runners/dashscope/dashscope_agent_runner.py +403 -0
  5. astrbot/core/agent/runners/dify/dify_agent_runner.py +336 -0
  6. astrbot/core/{utils → agent/runners/dify}/dify_api_client.py +51 -13
  7. astrbot/core/agent/runners/tool_loop_agent_runner.py +0 -6
  8. astrbot/core/config/default.py +141 -26
  9. astrbot/core/config/i18n_utils.py +110 -0
  10. astrbot/core/core_lifecycle.py +11 -13
  11. astrbot/core/db/po.py +1 -1
  12. astrbot/core/db/sqlite.py +2 -2
  13. astrbot/core/pipeline/process_stage/method/agent_request.py +48 -0
  14. astrbot/core/pipeline/process_stage/method/{llm_request.py → agent_sub_stages/internal.py} +13 -34
  15. astrbot/core/pipeline/process_stage/method/agent_sub_stages/third_party.py +202 -0
  16. astrbot/core/pipeline/process_stage/method/star_request.py +1 -1
  17. astrbot/core/pipeline/process_stage/stage.py +8 -5
  18. astrbot/core/pipeline/result_decorate/stage.py +15 -5
  19. astrbot/core/provider/manager.py +43 -41
  20. astrbot/core/star/session_llm_manager.py +0 -107
  21. astrbot/core/star/session_plugin_manager.py +0 -81
  22. astrbot/core/umop_config_router.py +19 -0
  23. astrbot/core/utils/migra_helper.py +73 -0
  24. astrbot/core/utils/shared_preferences.py +1 -28
  25. astrbot/dashboard/routes/chat.py +13 -1
  26. astrbot/dashboard/routes/config.py +20 -16
  27. astrbot/dashboard/routes/knowledge_base.py +0 -156
  28. astrbot/dashboard/routes/session_management.py +311 -606
  29. {astrbot-4.6.1.dist-info → astrbot-4.7.1.dist-info}/METADATA +1 -1
  30. {astrbot-4.6.1.dist-info → astrbot-4.7.1.dist-info}/RECORD +34 -30
  31. {astrbot-4.6.1.dist-info → astrbot-4.7.1.dist-info}/WHEEL +1 -1
  32. astrbot/core/provider/sources/coze_source.py +0 -650
  33. astrbot/core/provider/sources/dashscope_source.py +0 -207
  34. astrbot/core/provider/sources/dify_source.py +0 -285
  35. /astrbot/core/{provider/sources → agent/runners/coze}/coze_api_client.py +0 -0
  36. {astrbot-4.6.1.dist-info → astrbot-4.7.1.dist-info}/entry_points.txt +0 -0
  37. {astrbot-4.6.1.dist-info → astrbot-4.7.1.dist-info}/licenses/LICENSE +0 -0
@@ -42,87 +42,6 @@ class SessionPluginManager:
42
42
  # 如果都没有配置,默认为启用(兼容性考虑)
43
43
  return True
44
44
 
45
- @staticmethod
46
- def set_plugin_status_for_session(
47
- session_id: str,
48
- plugin_name: str,
49
- enabled: bool,
50
- ) -> None:
51
- """设置插件在指定会话中的启停状态
52
-
53
- Args:
54
- session_id: 会话ID (unified_msg_origin)
55
- plugin_name: 插件名称
56
- enabled: True表示启用,False表示禁用
57
-
58
- """
59
- # 获取当前配置
60
- session_plugin_config = sp.get(
61
- "session_plugin_config",
62
- {},
63
- scope="umo",
64
- scope_id=session_id,
65
- )
66
- if session_id not in session_plugin_config:
67
- session_plugin_config[session_id] = {
68
- "enabled_plugins": [],
69
- "disabled_plugins": [],
70
- }
71
-
72
- session_config = session_plugin_config[session_id]
73
- enabled_plugins = session_config.get("enabled_plugins", [])
74
- disabled_plugins = session_config.get("disabled_plugins", [])
75
-
76
- if enabled:
77
- # 启用插件
78
- if plugin_name in disabled_plugins:
79
- disabled_plugins.remove(plugin_name)
80
- if plugin_name not in enabled_plugins:
81
- enabled_plugins.append(plugin_name)
82
- else:
83
- # 禁用插件
84
- if plugin_name in enabled_plugins:
85
- enabled_plugins.remove(plugin_name)
86
- if plugin_name not in disabled_plugins:
87
- disabled_plugins.append(plugin_name)
88
-
89
- # 保存配置
90
- session_config["enabled_plugins"] = enabled_plugins
91
- session_config["disabled_plugins"] = disabled_plugins
92
- session_plugin_config[session_id] = session_config
93
- sp.put(
94
- "session_plugin_config",
95
- session_plugin_config,
96
- scope="umo",
97
- scope_id=session_id,
98
- )
99
-
100
- logger.info(
101
- f"会话 {session_id} 的插件 {plugin_name} 状态已更新为: {'启用' if enabled else '禁用'}",
102
- )
103
-
104
- @staticmethod
105
- def get_session_plugin_config(session_id: str) -> dict[str, list[str]]:
106
- """获取指定会话的插件配置
107
-
108
- Args:
109
- session_id: 会话ID (unified_msg_origin)
110
-
111
- Returns:
112
- Dict[str, List[str]]: 包含enabled_plugins和disabled_plugins的字典
113
-
114
- """
115
- session_plugin_config = sp.get(
116
- "session_plugin_config",
117
- {},
118
- scope="umo",
119
- scope_id=session_id,
120
- )
121
- return session_plugin_config.get(
122
- session_id,
123
- {"enabled_plugins": [], "disabled_plugins": []},
124
- )
125
-
126
45
  @staticmethod
127
46
  def filter_handlers_by_session(event: AstrMessageEvent, handlers: list) -> list:
128
47
  """根据会话配置过滤处理器列表
@@ -85,3 +85,22 @@ class UmopConfigRouter:
85
85
 
86
86
  self.umop_to_conf_id[umo] = conf_id
87
87
  await self.sp.global_put("umop_config_routing", self.umop_to_conf_id)
88
+
89
+ async def delete_route(self, umo: str):
90
+ """删除一条路由
91
+
92
+ Args:
93
+ umo (str): 需要删除的 UMO 字符串
94
+
95
+ Raises:
96
+ ValueError: 当 umo 格式不正确时抛出
97
+ """
98
+
99
+ if not isinstance(umo, str) or len(umo.split(":")) != 3:
100
+ raise ValueError(
101
+ "umop must be a string in the format [platform_id]:[message_type]:[session_id], with optional wildcards * or empty for all",
102
+ )
103
+
104
+ if umo in self.umop_to_conf_id:
105
+ del self.umop_to_conf_id[umo]
106
+ await self.sp.global_put("umop_config_routing", self.umop_to_conf_id)
@@ -0,0 +1,73 @@
1
+ import traceback
2
+
3
+ from astrbot.core import astrbot_config, logger
4
+ from astrbot.core.astrbot_config_mgr import AstrBotConfig, AstrBotConfigManager
5
+ from astrbot.core.db.migration.migra_45_to_46 import migrate_45_to_46
6
+ from astrbot.core.db.migration.migra_webchat_session import migrate_webchat_session
7
+
8
+
9
+ def _migra_agent_runner_configs(conf: AstrBotConfig, ids_map: dict) -> None:
10
+ """
11
+ Migra agent runner configs from provider configs.
12
+ """
13
+ try:
14
+ default_prov_id = conf["provider_settings"]["default_provider_id"]
15
+ if default_prov_id in ids_map:
16
+ conf["provider_settings"]["default_provider_id"] = ""
17
+ p = ids_map[default_prov_id]
18
+ if p["type"] == "dify":
19
+ conf["provider_settings"]["dify_agent_runner_provider_id"] = p["id"]
20
+ conf["provider_settings"]["agent_runner_type"] = "dify"
21
+ elif p["type"] == "coze":
22
+ conf["provider_settings"]["coze_agent_runner_provider_id"] = p["id"]
23
+ conf["provider_settings"]["agent_runner_type"] = "coze"
24
+ elif p["type"] == "dashscope":
25
+ conf["provider_settings"]["dashscope_agent_runner_provider_id"] = p[
26
+ "id"
27
+ ]
28
+ conf["provider_settings"]["agent_runner_type"] = "dashscope"
29
+ conf.save_config()
30
+ except Exception as e:
31
+ logger.error(f"Migration for third party agent runner configs failed: {e!s}")
32
+ logger.error(traceback.format_exc())
33
+
34
+
35
+ async def migra(
36
+ db, astrbot_config_mgr, umop_config_router, acm: AstrBotConfigManager
37
+ ) -> None:
38
+ """
39
+ Stores the migration logic here.
40
+ btw, i really don't like migration :(
41
+ """
42
+ # 4.5 to 4.6 migration for umop_config_router
43
+ try:
44
+ await migrate_45_to_46(astrbot_config_mgr, umop_config_router)
45
+ except Exception as e:
46
+ logger.error(f"Migration from version 4.5 to 4.6 failed: {e!s}")
47
+ logger.error(traceback.format_exc())
48
+
49
+ # migration for webchat session
50
+ try:
51
+ await migrate_webchat_session(db)
52
+ except Exception as e:
53
+ logger.error(f"Migration for webchat session failed: {e!s}")
54
+ logger.error(traceback.format_exc())
55
+
56
+ # migra third party agent runner configs
57
+ _c = False
58
+ providers = astrbot_config["provider"]
59
+ ids_map = {}
60
+ for prov in providers:
61
+ type_ = prov.get("type")
62
+ if type_ in ["dify", "coze", "dashscope"]:
63
+ prov["provider_type"] = "agent_runner"
64
+ ids_map[prov["id"]] = {
65
+ "type": type_,
66
+ "id": prov["id"],
67
+ }
68
+ _c = True
69
+ if _c:
70
+ astrbot_config.save_config()
71
+
72
+ for conf in acm.confs.values():
73
+ _migra_agent_runner_configs(conf, ids_map)
@@ -40,9 +40,6 @@ class SharedPreferences:
40
40
  else:
41
41
  ret = default
42
42
  return ret
43
- raise ValueError(
44
- "scope_id and key cannot be None when getting a specific preference.",
45
- )
46
43
 
47
44
  async def range_get_async(
48
45
  self,
@@ -56,30 +53,6 @@ class SharedPreferences:
56
53
  ret = await self.db_helper.get_preferences(scope, scope_id, key)
57
54
  return ret
58
55
 
59
- @overload
60
- async def session_get(
61
- self,
62
- umo: None,
63
- key: str,
64
- default: Any = None,
65
- ) -> list[Preference]: ...
66
-
67
- @overload
68
- async def session_get(
69
- self,
70
- umo: str,
71
- key: None,
72
- default: Any = None,
73
- ) -> list[Preference]: ...
74
-
75
- @overload
76
- async def session_get(
77
- self,
78
- umo: None,
79
- key: None,
80
- default: Any = None,
81
- ) -> list[Preference]: ...
82
-
83
56
  async def session_get(
84
57
  self,
85
58
  umo: str | None,
@@ -88,7 +61,7 @@ class SharedPreferences:
88
61
  ) -> _VT | list[Preference]:
89
62
  """获取会话范围的偏好设置
90
63
 
91
- Note: 当 scope_id 或者 key 为 None,时,返回 Preference 列表,其中的 value 属性是一个 dict,value["val"] 为值。
64
+ Note: 当 umo 或者 key 为 None,时,返回 Preference 列表,其中的 value 属性是一个 dict,value["val"] 为值。
92
65
  """
93
66
  if umo is None or key is None:
94
67
  return await self.range_get_async("umo", umo, key)
@@ -56,6 +56,7 @@ class ChatRoute(Route):
56
56
  self.conv_mgr = core_lifecycle.conversation_manager
57
57
  self.platform_history_mgr = core_lifecycle.platform_message_history_manager
58
58
  self.db = db
59
+ self.umop_config_router = core_lifecycle.umop_config_router
59
60
 
60
61
  self.running_convs: dict[str, bool] = {}
61
62
 
@@ -266,7 +267,8 @@ class ChatRoute(Route):
266
267
  return Response().error("Permission denied").__dict__
267
268
 
268
269
  # 删除该会话下的所有对话
269
- unified_msg_origin = f"{session.platform_id}:FriendMessage:{session.platform_id}!{username}!{session_id}"
270
+ message_type = "GroupMessage" if session.is_group else "FriendMessage"
271
+ unified_msg_origin = f"{session.platform_id}:{message_type}:{session.platform_id}!{username}!{session_id}"
270
272
  await self.conv_mgr.delete_conversations_by_user_id(unified_msg_origin)
271
273
 
272
274
  # 删除消息历史
@@ -276,6 +278,16 @@ class ChatRoute(Route):
276
278
  offset_sec=99999999,
277
279
  )
278
280
 
281
+ # 删除与会话关联的配置路由
282
+ try:
283
+ await self.umop_config_router.delete_route(unified_msg_origin)
284
+ except ValueError as exc:
285
+ logger.warning(
286
+ "Failed to delete UMO route %s during session cleanup: %s",
287
+ unified_msg_origin,
288
+ exc,
289
+ )
290
+
279
291
  # 清理队列(仅对 webchat)
280
292
  if session.platform_id == "webchat":
281
293
  webchat_queue_mgr.remove_queues(session_id)
@@ -14,6 +14,7 @@ from astrbot.core.config.default import (
14
14
  DEFAULT_CONFIG,
15
15
  DEFAULT_VALUE_MAP,
16
16
  )
17
+ from astrbot.core.config.i18n_utils import ConfigMetadataI18n
17
18
  from astrbot.core.core_lifecycle import AstrBotCoreLifecycle
18
19
  from astrbot.core.platform.register import platform_cls_map, platform_registry
19
20
  from astrbot.core.provider import Provider
@@ -133,7 +134,9 @@ def save_config(post_config: dict, config: AstrBotConfig, is_core: bool = False)
133
134
  is_core,
134
135
  )
135
136
  else:
136
- errors, post_config = validate_config(post_config, config.schema, is_core)
137
+ errors, post_config = validate_config(
138
+ post_config, getattr(config, "schema", {}), is_core
139
+ )
137
140
  except BaseException as e:
138
141
  logger.error(traceback.format_exc())
139
142
  logger.warning(f"验证配置时出现异常: {e}")
@@ -247,11 +250,8 @@ class ConfigRoute(Route):
247
250
 
248
251
  async def get_default_config(self):
249
252
  """获取默认配置文件"""
250
- return (
251
- Response()
252
- .ok({"config": DEFAULT_CONFIG, "metadata": CONFIG_METADATA_3})
253
- .__dict__
254
- )
253
+ metadata = ConfigMetadataI18n.convert_to_i18n_keys(CONFIG_METADATA_3)
254
+ return Response().ok({"config": DEFAULT_CONFIG, "metadata": metadata}).__dict__
255
255
 
256
256
  async def get_abconf_list(self):
257
257
  """获取所有 AstrBot 配置文件的列表"""
@@ -282,17 +282,15 @@ class ConfigRoute(Route):
282
282
  try:
283
283
  if system_config:
284
284
  abconf = self.acm.confs["default"]
285
- return (
286
- Response()
287
- .ok({"config": abconf, "metadata": CONFIG_METADATA_3_SYSTEM})
288
- .__dict__
285
+ metadata = ConfigMetadataI18n.convert_to_i18n_keys(
286
+ CONFIG_METADATA_3_SYSTEM
289
287
  )
288
+ return Response().ok({"config": abconf, "metadata": metadata}).__dict__
289
+ if abconf_id is None:
290
+ raise ValueError("abconf_id cannot be None")
290
291
  abconf = self.acm.confs[abconf_id]
291
- return (
292
- Response()
293
- .ok({"config": abconf, "metadata": CONFIG_METADATA_3})
294
- .__dict__
295
- )
292
+ metadata = ConfigMetadataI18n.convert_to_i18n_keys(CONFIG_METADATA_3)
293
+ return Response().ok({"config": abconf, "metadata": metadata}).__dict__
296
294
  except ValueError as e:
297
295
  return Response().error(str(e)).__dict__
298
296
 
@@ -598,9 +596,15 @@ class ConfigRoute(Route):
598
596
  return Response().error("缺少参数 provider_id").__dict__
599
597
 
600
598
  prov_mgr = self.core_lifecycle.provider_manager
601
- provider: Provider | None = prov_mgr.inst_map.get(provider_id, None)
599
+ provider = prov_mgr.inst_map.get(provider_id, None)
602
600
  if not provider:
603
601
  return Response().error(f"未找到 ID 为 {provider_id} 的提供商").__dict__
602
+ if not isinstance(provider, Provider):
603
+ return (
604
+ Response()
605
+ .error(f"提供商 {provider_id} 类型不支持获取模型列表")
606
+ .__dict__
607
+ )
604
608
 
605
609
  try:
606
610
  models = await provider.get_models()
@@ -60,10 +60,6 @@ class KnowledgeBaseRoute(Route):
60
60
  # "/kb/media/delete": ("POST", self.delete_media),
61
61
  # 检索
62
62
  "/kb/retrieve": ("POST", self.retrieve),
63
- # 会话知识库配置
64
- "/kb/session/config/get": ("GET", self.get_session_kb_config),
65
- "/kb/session/config/set": ("POST", self.set_session_kb_config),
66
- "/kb/session/config/delete": ("POST", self.delete_session_kb_config),
67
63
  }
68
64
  self.register_routes()
69
65
 
@@ -920,158 +916,6 @@ class KnowledgeBaseRoute(Route):
920
916
  logger.error(traceback.format_exc())
921
917
  return Response().error(f"检索失败: {e!s}").__dict__
922
918
 
923
- # ===== 会话知识库配置 API =====
924
-
925
- async def get_session_kb_config(self):
926
- """获取会话的知识库配置
927
-
928
- Query 参数:
929
- - session_id: 会话 ID (必填)
930
-
931
- 返回:
932
- - kb_ids: 知识库 ID 列表
933
- - top_k: 返回结果数量
934
- - enable_rerank: 是否启用重排序
935
- """
936
- try:
937
- from astrbot.core import sp
938
-
939
- session_id = request.args.get("session_id")
940
-
941
- if not session_id:
942
- return Response().error("缺少参数 session_id").__dict__
943
-
944
- # 从 SharedPreferences 获取配置
945
- config = await sp.session_get(session_id, "kb_config", default={})
946
-
947
- logger.debug(f"[KB配置] 读取到配置: session_id={session_id}")
948
-
949
- # 如果没有配置,返回默认值
950
- if not config:
951
- config = {"kb_ids": [], "top_k": 5, "enable_rerank": True}
952
-
953
- return Response().ok(config).__dict__
954
-
955
- except Exception as e:
956
- logger.error(f"[KB配置] 获取配置时出错: {e}", exc_info=True)
957
- return Response().error(f"获取会话知识库配置失败: {e!s}").__dict__
958
-
959
- async def set_session_kb_config(self):
960
- """设置会话的知识库配置
961
-
962
- Body:
963
- - scope: 配置范围 (目前只支持 "session")
964
- - scope_id: 会话 ID (必填)
965
- - kb_ids: 知识库 ID 列表 (必填)
966
- - top_k: 返回结果数量 (可选, 默认 5)
967
- - enable_rerank: 是否启用重排序 (可选, 默认 true)
968
- """
969
- try:
970
- from astrbot.core import sp
971
-
972
- data = await request.json
973
-
974
- scope = data.get("scope")
975
- scope_id = data.get("scope_id")
976
- kb_ids = data.get("kb_ids", [])
977
- top_k = data.get("top_k", 5)
978
- enable_rerank = data.get("enable_rerank", True)
979
-
980
- # 验证参数
981
- if scope != "session":
982
- return Response().error("目前仅支持 session 范围的配置").__dict__
983
-
984
- if not scope_id:
985
- return Response().error("缺少参数 scope_id").__dict__
986
-
987
- if not isinstance(kb_ids, list):
988
- return Response().error("kb_ids 必须是列表").__dict__
989
-
990
- # 验证知识库是否存在
991
- kb_mgr = self._get_kb_manager()
992
- invalid_ids = []
993
- valid_ids = []
994
- for kb_id in kb_ids:
995
- kb_helper = await kb_mgr.get_kb(kb_id)
996
- if kb_helper:
997
- valid_ids.append(kb_id)
998
- else:
999
- invalid_ids.append(kb_id)
1000
- logger.warning(f"[KB配置] 知识库不存在: {kb_id}")
1001
-
1002
- if invalid_ids:
1003
- logger.warning(f"[KB配置] 以下知识库ID无效: {invalid_ids}")
1004
-
1005
- # 允许保存空列表,表示明确不使用任何知识库
1006
- if kb_ids and not valid_ids:
1007
- # 只有当用户提供了 kb_ids 但全部无效时才报错
1008
- return Response().error(f"所有提供的知识库ID都无效: {kb_ids}").__dict__
1009
-
1010
- # 如果 kb_ids 为空列表,表示用户想清空配置
1011
- if not kb_ids:
1012
- valid_ids = []
1013
-
1014
- # 构建配置对象(只保存有效的ID)
1015
- config = {
1016
- "kb_ids": valid_ids,
1017
- "top_k": top_k,
1018
- "enable_rerank": enable_rerank,
1019
- }
1020
-
1021
- # 保存到 SharedPreferences
1022
- await sp.session_put(scope_id, "kb_config", config)
1023
-
1024
- # 立即验证是否保存成功
1025
- verify_config = await sp.session_get(scope_id, "kb_config", default={})
1026
-
1027
- if verify_config == config:
1028
- return (
1029
- Response()
1030
- .ok(
1031
- {"valid_ids": valid_ids, "invalid_ids": invalid_ids},
1032
- "保存知识库配置成功",
1033
- )
1034
- .__dict__
1035
- )
1036
- logger.error("[KB配置] 配置保存失败,验证不匹配")
1037
- return Response().error("配置保存失败").__dict__
1038
-
1039
- except Exception as e:
1040
- logger.error(f"[KB配置] 设置配置时出错: {e}", exc_info=True)
1041
- return Response().error(f"设置会话知识库配置失败: {e!s}").__dict__
1042
-
1043
- async def delete_session_kb_config(self):
1044
- """删除会话的知识库配置
1045
-
1046
- Body:
1047
- - scope: 配置范围 (目前只支持 "session")
1048
- - scope_id: 会话 ID (必填)
1049
- """
1050
- try:
1051
- from astrbot.core import sp
1052
-
1053
- data = await request.json
1054
-
1055
- scope = data.get("scope")
1056
- scope_id = data.get("scope_id")
1057
-
1058
- # 验证参数
1059
- if scope != "session":
1060
- return Response().error("目前仅支持 session 范围的配置").__dict__
1061
-
1062
- if not scope_id:
1063
- return Response().error("缺少参数 scope_id").__dict__
1064
-
1065
- # 从 SharedPreferences 删除配置
1066
- await sp.session_remove(scope_id, "kb_config")
1067
-
1068
- return Response().ok(message="删除知识库配置成功").__dict__
1069
-
1070
- except Exception as e:
1071
- logger.error(f"删除会话知识库配置失败: {e}")
1072
- logger.error(traceback.format_exc())
1073
- return Response().error(f"删除会话知识库配置失败: {e!s}").__dict__
1074
-
1075
919
  async def upload_document_from_url(self):
1076
920
  """从 URL 上传文档
1077
921