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
@@ -57,6 +57,7 @@ class PluginManager:
57
57
  )
58
58
  """保留插件的路径。在 packages 目录下"""
59
59
  self.conf_schema_fname = "_conf_schema.json"
60
+ self.logo_fname = "logo.png"
60
61
  """插件配置 Schema 文件名"""
61
62
  self._pm_lock = asyncio.Lock()
62
63
  """StarManager操作互斥锁"""
@@ -200,7 +201,7 @@ class PluginManager:
200
201
 
201
202
  if os.path.exists(os.path.join(plugin_path, "metadata.yaml")):
202
203
  with open(
203
- os.path.join(plugin_path, "metadata.yaml"), "r", encoding="utf-8"
204
+ os.path.join(plugin_path, "metadata.yaml"), encoding="utf-8"
204
205
  ) as f:
205
206
  metadata = yaml.safe_load(f)
206
207
  elif plugin_obj and hasattr(plugin_obj, "info"):
@@ -226,6 +227,7 @@ class PluginManager:
226
227
  desc=metadata["desc"],
227
228
  version=metadata["version"],
228
229
  repo=metadata["repo"] if "repo" in metadata else None,
230
+ display_name=metadata.get("display_name", None),
229
231
  )
230
232
 
231
233
  return metadata
@@ -407,13 +409,14 @@ class PluginManager:
407
409
  )
408
410
  if os.path.exists(plugin_schema_path):
409
411
  # 加载插件配置
410
- with open(plugin_schema_path, "r", encoding="utf-8") as f:
412
+ with open(plugin_schema_path, encoding="utf-8") as f:
411
413
  plugin_config = AstrBotConfig(
412
414
  config_path=os.path.join(
413
415
  self.plugin_config_path, f"{root_dir_name}_config.json"
414
416
  ),
415
417
  schema=json.loads(f.read()),
416
418
  )
419
+ logo_path = os.path.join(plugin_dir_path, self.logo_fname)
417
420
 
418
421
  if path in star_map:
419
422
  # 通过 __init__subclass__ 注册插件
@@ -430,6 +433,7 @@ class PluginManager:
430
433
  metadata.desc = metadata_yaml.desc
431
434
  metadata.version = metadata_yaml.version
432
435
  metadata.repo = metadata_yaml.repo
436
+ metadata.display_name = metadata_yaml.display_name
433
437
  except Exception as e:
434
438
  logger.warning(
435
439
  f"插件 {root_dir_name} 元数据载入失败: {str(e)}。使用默认元数据。"
@@ -540,9 +544,11 @@ class PluginManager:
540
544
  if metadata.module_path in inactivated_plugins:
541
545
  metadata.activated = False
542
546
 
543
- assert metadata.module_path is not None, (
544
- f"插件 {metadata.name} 的模块路径为空。"
545
- )
547
+ # Plugin logo path
548
+ if os.path.exists(logo_path):
549
+ metadata.logo_path = logo_path
550
+
551
+ assert metadata.module_path, f"插件 {metadata.name} 模块路径为空"
546
552
 
547
553
  full_names = []
548
554
  for handler in star_handlers_registry.get_handlers_by_module_name(
@@ -642,7 +648,7 @@ class PluginManager:
642
648
 
643
649
  if os.path.exists(readme_path):
644
650
  try:
645
- with open(readme_path, "r", encoding="utf-8") as f:
651
+ with open(readme_path, encoding="utf-8") as f:
646
652
  readme_content = f.read()
647
653
  except Exception as e:
648
654
  logger.warning(
@@ -857,7 +863,7 @@ class PluginManager:
857
863
 
858
864
  if os.path.exists(readme_path):
859
865
  try:
860
- with open(readme_path, "r", encoding="utf-8") as f:
866
+ with open(readme_path, encoding="utf-8") as f:
861
867
  readme_content = f.read()
862
868
  except Exception as e:
863
869
  logger.warning(f"读取插件 {dir_name} 的 README.md 文件失败: {str(e)}")
@@ -0,0 +1,81 @@
1
+ from astrbot.core.utils.shared_preferences import SharedPreferences
2
+
3
+
4
+ class UmopConfigRouter:
5
+ """UMOP 配置路由器"""
6
+
7
+ def __init__(self, sp: SharedPreferences):
8
+ self.umop_to_conf_id: dict[str, str] = {}
9
+ """UMOP 到配置文件 ID 的映射"""
10
+ self.sp = sp
11
+
12
+ self._load_routing_table()
13
+
14
+ def _load_routing_table(self):
15
+ """加载路由表"""
16
+ # 从 SharedPreferences 中加载 umop_to_conf_id 映射
17
+ sp_data = self.sp.get(
18
+ "umop_config_routing", {}, scope="global", scope_id="global"
19
+ )
20
+ self.umop_to_conf_id = sp_data
21
+
22
+ def _is_umo_match(self, p1: str, p2: str) -> bool:
23
+ """判断 p2 umo 是否逻辑包含于 p1 umo"""
24
+ p1_ls = p1.split(":")
25
+ p2_ls = p2.split(":")
26
+
27
+ if len(p1_ls) != 3 or len(p2_ls) != 3:
28
+ return False # 非法格式
29
+
30
+ return all(p == "" or p == "*" or p == t for p, t in zip(p1_ls, p2_ls))
31
+
32
+ def get_conf_id_for_umop(self, umo: str) -> str | None:
33
+ """根据 UMO 获取对应的配置文件 ID
34
+
35
+ Args:
36
+ umo (str): UMO 字符串
37
+
38
+ Returns:
39
+ str | None: 配置文件 ID,如果没有找到则返回 None
40
+ """
41
+ for pattern, conf_id in self.umop_to_conf_id.items():
42
+ if self._is_umo_match(pattern, umo):
43
+ return conf_id
44
+ return None
45
+
46
+ async def update_routing_data(self, new_routing: dict[str, str]):
47
+ """更新路由表
48
+
49
+ Args:
50
+ new_routing (dict[str, str]): 新的 UMOP 到配置文件 ID 的映射。umo 由三个部分组成 [platform_id]:[message_type]:[session_id]。
51
+ umop 可以是 "::" (代表所有), 可以是 "[platform_id]::" (代表指定平台下的所有类型消息和会话)。
52
+
53
+ Raises:
54
+ ValueError: 如果 new_routing 中的 key 格式不正确
55
+ """
56
+ for part in new_routing.keys():
57
+ if not isinstance(part, str) or len(part.split(":")) != 3:
58
+ raise ValueError(
59
+ "umop keys must be strings in the format [platform_id]:[message_type]:[session_id], with optional wildcards * or empty for all"
60
+ )
61
+
62
+ self.umop_to_conf_id = new_routing
63
+ await self.sp.global_put("umop_config_routing", self.umop_to_conf_id)
64
+
65
+ async def update_route(self, umo: str, conf_id: str):
66
+ """更新一条路由
67
+
68
+ Args:
69
+ umo (str): UMO 字符串
70
+ conf_id (str): 配置文件 ID
71
+
72
+ Raises:
73
+ ValueError: 如果 umo 格式不正确
74
+ """
75
+ if not isinstance(umo, str) or len(umo.split(":")) != 3:
76
+ raise ValueError(
77
+ "umop must be a string in the format [platform_id]:[message_type]:[session_id], with optional wildcards * or empty for all"
78
+ )
79
+
80
+ self.umop_to_conf_id[umo] = conf_id
81
+ await self.sp.global_put("umop_config_routing", self.umop_to_conf_id)
astrbot/core/updator.py CHANGED
@@ -99,7 +99,7 @@ class AstrBotUpdator(RepoZipUpdator):
99
99
  else:
100
100
  if len(str(version)) != 40:
101
101
  raise Exception("commit hash 长度不正确,应为 40")
102
- file_url = f"https://github.com/Soulter/AstrBot/archive/{version}.zip"
102
+ file_url = f"https://github.com/AstrBotDevs/AstrBot/archive/{version}.zip"
103
103
  logger.info(f"准备更新至指定版本的 AstrBot Core: {version}")
104
104
 
105
105
  if proxy:
astrbot/core/utils/io.py CHANGED
@@ -1,4 +1,5 @@
1
1
  import os
2
+ from pathlib import Path
2
3
  import ssl
3
4
  import shutil
4
5
  import socket
@@ -12,7 +13,6 @@ import logging
12
13
 
13
14
  import certifi
14
15
 
15
- from typing import Union
16
16
 
17
17
  from PIL import Image
18
18
  from .astrbot_path import get_astrbot_data_path
@@ -52,7 +52,7 @@ def port_checker(port: int, host: str = "localhost"):
52
52
  return False
53
53
 
54
54
 
55
- def save_temp_img(img: Union[Image.Image, str]) -> str:
55
+ def save_temp_img(img: Image.Image | str) -> str:
56
56
  temp_dir = os.path.join(get_astrbot_data_path(), "temp")
57
57
  # 获得文件创建时间,清除超过 12 小时的
58
58
  try:
@@ -150,7 +150,11 @@ async def download_file(url: str, path: str, show_progress: bool = False):
150
150
  f.write(chunk)
151
151
  downloaded_size += len(chunk)
152
152
  if show_progress:
153
- elapsed_time = time.time() - start_time
153
+ elapsed_time = (
154
+ time.time() - start_time
155
+ if time.time() - start_time > 0
156
+ else 1
157
+ )
154
158
  speed = downloaded_size / 1024 / elapsed_time # KB/s
155
159
  print(
156
160
  f"\r下载进度: {downloaded_size / total_size:.2%} 速度: {speed:.2f} KB/s",
@@ -209,7 +213,7 @@ async def get_dashboard_version():
209
213
  if os.path.exists(dist_dir):
210
214
  version_file = os.path.join(dist_dir, "assets", "version")
211
215
  if os.path.exists(version_file):
212
- with open(version_file, "r") as f:
216
+ with open(version_file, encoding="utf-8") as f:
213
217
  v = f.read().strip()
214
218
  return v
215
219
  return None
@@ -221,10 +225,13 @@ async def download_dashboard(
221
225
  latest: bool = True,
222
226
  version: str | None = None,
223
227
  proxy: str | None = None,
224
- ):
228
+ ) -> None:
225
229
  """下载管理面板文件"""
230
+
226
231
  if path is None:
227
- path = os.path.join(get_astrbot_data_path(), "dashboard.zip")
232
+ zip_path = Path(get_astrbot_data_path()).absolute() / "dashboard.zip"
233
+ else:
234
+ zip_path = Path(path).absolute()
228
235
 
229
236
  if latest or len(str(version)) != 40:
230
237
  ver_name = "latest" if latest else version
@@ -233,20 +240,24 @@ async def download_dashboard(
233
240
  f"准备下载指定发行版本的 AstrBot WebUI 文件: {dashboard_release_url}"
234
241
  )
235
242
  try:
236
- await download_file(dashboard_release_url, path, show_progress=True)
243
+ await download_file(
244
+ dashboard_release_url, str(zip_path), show_progress=True
245
+ )
237
246
  except BaseException as _:
238
247
  if latest:
239
- dashboard_release_url = "https://github.com/Soulter/AstrBot/releases/latest/download/dist.zip"
248
+ dashboard_release_url = "https://github.com/AstrBotDevs/AstrBot/releases/latest/download/dist.zip"
240
249
  else:
241
- dashboard_release_url = f"https://github.com/Soulter/AstrBot/releases/download/{version}/dist.zip"
250
+ dashboard_release_url = f"https://github.com/AstrBotDevs/AstrBot/releases/download/{version}/dist.zip"
242
251
  if proxy:
243
252
  dashboard_release_url = f"{proxy}/{dashboard_release_url}"
244
- await download_file(dashboard_release_url, path, show_progress=True)
253
+ await download_file(
254
+ dashboard_release_url, str(zip_path), show_progress=True
255
+ )
245
256
  else:
246
257
  url = f"https://github.com/AstrBotDevs/astrbot-release-harbour/releases/download/release-{version}/dist.zip"
247
258
  logger.info(f"准备下载指定版本的 AstrBot WebUI: {url}")
248
259
  if proxy:
249
260
  url = f"{proxy}/{url}"
250
- await download_file(url, path, show_progress=True)
251
- with zipfile.ZipFile(path, "r") as z:
261
+ await download_file(url, str(zip_path), show_progress=True)
262
+ with zipfile.ZipFile(zip_path, "r") as z:
252
263
  z.extractall(extract_path)
@@ -11,6 +11,7 @@ from .conversation import ConversationRoute
11
11
  from .file import FileRoute
12
12
  from .session_management import SessionManagementRoute
13
13
  from .persona import PersonaRoute
14
+ from .knowledge_base import KnowledgeBaseRoute
14
15
 
15
16
  __all__ = [
16
17
  "AuthRoute",
@@ -26,4 +27,5 @@ __all__ = [
26
27
  "FileRoute",
27
28
  "SessionManagementRoute",
28
29
  "PersonaRoute",
30
+ "KnowledgeBaseRoute",
29
31
  ]
@@ -1,4 +1,3 @@
1
- import typing
2
1
  import traceback
3
2
  import os
4
3
  import inspect
@@ -6,6 +5,7 @@ from .route import Route, Response, RouteContext
6
5
  from astrbot.core.provider.entities import ProviderType
7
6
  from quart import request
8
7
  from astrbot.core.config.default import (
8
+ DEFAULT_CONFIG,
9
9
  CONFIG_METADATA_2,
10
10
  DEFAULT_VALUE_MAP,
11
11
  CONFIG_METADATA_3,
@@ -44,9 +44,7 @@ def try_cast(value: str, type_: str):
44
44
  return None
45
45
 
46
46
 
47
- def validate_config(
48
- data, schema: dict, is_core: bool
49
- ) -> typing.Tuple[typing.List[str], typing.Dict]:
47
+ def validate_config(data, schema: dict, is_core: bool) -> tuple[list[str], dict]:
50
48
  errors = []
51
49
 
52
50
  def validate(data: dict, metadata: dict = schema, path=""):
@@ -152,13 +150,19 @@ class ConfigRoute(Route):
152
150
  self.config: AstrBotConfig = core_lifecycle.astrbot_config
153
151
  self._logo_token_cache = {} # 缓存logo token,避免重复注册
154
152
  self.acm = core_lifecycle.astrbot_config_mgr
153
+ self.ucr = core_lifecycle.umop_config_router
155
154
  self.routes = {
156
155
  "/config/abconf/new": ("POST", self.create_abconf),
157
156
  "/config/abconf": ("GET", self.get_abconf),
158
157
  "/config/abconfs": ("GET", self.get_abconf_list),
159
158
  "/config/abconf/delete": ("POST", self.delete_abconf),
160
159
  "/config/abconf/update": ("POST", self.update_abconf),
160
+ "/config/umo_abconf_routes": ("GET", self.get_uc_table),
161
+ "/config/umo_abconf_route/update_all": ("POST", self.update_ucr_all),
162
+ "/config/umo_abconf_route/update": ("POST", self.update_ucr),
163
+ "/config/umo_abconf_route/delete": ("POST", self.delete_ucr),
161
164
  "/config/get": ("GET", self.get_configs),
165
+ "/config/default": ("GET", self.get_default_config),
162
166
  "/config/astrbot/update": ("POST", self.post_astrbot_configs),
163
167
  "/config/plugin/update": ("POST", self.post_plugin_configs),
164
168
  "/config/platform/new": ("POST", self.post_new_platform),
@@ -171,9 +175,79 @@ class ConfigRoute(Route):
171
175
  "/config/provider/check_one": ("GET", self.check_one_provider_status),
172
176
  "/config/provider/list": ("GET", self.get_provider_config_list),
173
177
  "/config/provider/model_list": ("GET", self.get_provider_model_list),
178
+ "/config/provider/get_embedding_dim": ("POST", self.get_embedding_dim),
174
179
  }
175
180
  self.register_routes()
176
181
 
182
+ async def get_uc_table(self):
183
+ """获取 UMOP 配置路由表"""
184
+ return Response().ok({"routing": self.ucr.umop_to_conf_id}).__dict__
185
+
186
+ async def update_ucr_all(self):
187
+ """更新 UMOP 配置路由表的全部内容"""
188
+ post_data = await request.json
189
+ if not post_data:
190
+ return Response().error("缺少配置数据").__dict__
191
+
192
+ new_routing = post_data.get("routing", None)
193
+
194
+ if not new_routing or not isinstance(new_routing, dict):
195
+ return Response().error("缺少或错误的路由表数据").__dict__
196
+
197
+ try:
198
+ await self.ucr.update_routing_data(new_routing)
199
+ return Response().ok(message="更新成功").__dict__
200
+ except Exception as e:
201
+ logger.error(traceback.format_exc())
202
+ return Response().error(f"更新路由表失败: {str(e)}").__dict__
203
+
204
+ async def update_ucr(self):
205
+ """更新 UMOP 配置路由表"""
206
+ post_data = await request.json
207
+ if not post_data:
208
+ return Response().error("缺少配置数据").__dict__
209
+
210
+ umo = post_data.get("umo", None)
211
+ conf_id = post_data.get("conf_id", None)
212
+
213
+ if not umo or not conf_id:
214
+ return Response().error("缺少 UMO 或配置文件 ID").__dict__
215
+
216
+ try:
217
+ await self.ucr.update_route(umo, conf_id)
218
+ return Response().ok(message="更新成功").__dict__
219
+ except Exception as e:
220
+ logger.error(traceback.format_exc())
221
+ return Response().error(f"更新路由表失败: {str(e)}").__dict__
222
+
223
+ async def delete_ucr(self):
224
+ """删除 UMOP 配置路由表中的一项"""
225
+ post_data = await request.json
226
+ if not post_data:
227
+ return Response().error("缺少配置数据").__dict__
228
+
229
+ umo = post_data.get("umo", None)
230
+
231
+ if not umo:
232
+ return Response().error("缺少 UMO").__dict__
233
+
234
+ try:
235
+ if umo in self.ucr.umop_to_conf_id:
236
+ del self.ucr.umop_to_conf_id[umo]
237
+ await self.ucr.update_routing_data(self.ucr.umop_to_conf_id)
238
+ return Response().ok(message="删除成功").__dict__
239
+ except Exception as e:
240
+ logger.error(traceback.format_exc())
241
+ return Response().error(f"删除路由表项失败: {str(e)}").__dict__
242
+
243
+ async def get_default_config(self):
244
+ """获取默认配置文件"""
245
+ return (
246
+ Response()
247
+ .ok({"config": DEFAULT_CONFIG, "metadata": CONFIG_METADATA_3})
248
+ .__dict__
249
+ )
250
+
177
251
  async def get_abconf_list(self):
178
252
  """获取所有 AstrBot 配置文件的列表"""
179
253
  abconf_list = self.acm.get_conf_list()
@@ -184,11 +258,11 @@ class ConfigRoute(Route):
184
258
  post_data = await request.json
185
259
  if not post_data:
186
260
  return Response().error("缺少配置数据").__dict__
187
- umo_parts = post_data["umo_parts"]
188
261
  name = post_data.get("name", None)
262
+ config = post_data.get("config", DEFAULT_CONFIG)
189
263
 
190
264
  try:
191
- conf_id = self.acm.create_conf(umo_parts=umo_parts, name=name)
265
+ conf_id = self.acm.create_conf(name=name, config=config)
192
266
  return Response().ok(message="创建成功", data={"conf_id": conf_id}).__dict__
193
267
  except ValueError as e:
194
268
  return Response().error(str(e)).__dict__
@@ -250,10 +324,9 @@ class ConfigRoute(Route):
250
324
  return Response().error("缺少配置文件 ID").__dict__
251
325
 
252
326
  name = post_data.get("name")
253
- umo_parts = post_data.get("umo_parts")
254
327
 
255
328
  try:
256
- success = self.acm.update_conf_info(conf_id, name=name, umo_parts=umo_parts)
329
+ success = self.acm.update_conf_info(conf_id, name=name)
257
330
  if success:
258
331
  return Response().ok(message="更新成功").__dict__
259
332
  else:
@@ -526,6 +599,61 @@ class ConfigRoute(Route):
526
599
  logger.error(traceback.format_exc())
527
600
  return Response().error(str(e)).__dict__
528
601
 
602
+ async def get_embedding_dim(self):
603
+ """获取嵌入模型的维度"""
604
+ post_data = await request.json
605
+ provider_config = post_data.get("provider_config", None)
606
+ if not provider_config:
607
+ return Response().error("缺少参数 provider_config").__dict__
608
+
609
+ try:
610
+ # 动态导入 EmbeddingProvider
611
+ from astrbot.core.provider.provider import EmbeddingProvider
612
+ from astrbot.core.provider.register import provider_cls_map
613
+
614
+ # 获取 provider 类型
615
+ provider_type = provider_config.get("type", None)
616
+ if not provider_type:
617
+ return Response().error("provider_config 缺少 type 字段").__dict__
618
+
619
+ # 获取对应的 provider 类
620
+ if provider_type not in provider_cls_map:
621
+ return (
622
+ Response()
623
+ .error(f"未找到适用于 {provider_type} 的提供商适配器")
624
+ .__dict__
625
+ )
626
+
627
+ provider_metadata = provider_cls_map[provider_type]
628
+ cls_type = provider_metadata.cls_type
629
+
630
+ if not cls_type:
631
+ return Response().error(f"无法找到 {provider_type} 的类").__dict__
632
+
633
+ # 实例化 provider
634
+ inst = cls_type(provider_config, {})
635
+
636
+ # 检查是否是 EmbeddingProvider
637
+ if not isinstance(inst, EmbeddingProvider):
638
+ return Response().error("提供商不是 EmbeddingProvider 类型").__dict__
639
+
640
+ # 初始化
641
+ if getattr(inst, "initialize", None):
642
+ await inst.initialize()
643
+
644
+ # 获取嵌入向量维度
645
+ vec = await inst.get_embedding("echo")
646
+ dim = len(vec)
647
+
648
+ logger.info(
649
+ f"检测到 {provider_config.get('id', 'unknown')} 的嵌入向量维度为 {dim}"
650
+ )
651
+
652
+ return Response().ok({"embedding_dimensions": dim}).__dict__
653
+ except Exception as e:
654
+ logger.error(traceback.format_exc())
655
+ return Response().error(f"获取嵌入维度失败: {str(e)}").__dict__
656
+
529
657
  async def get_platform_list(self):
530
658
  """获取所有平台的列表"""
531
659
  platform_list = []
@@ -722,7 +850,7 @@ class ConfigRoute(Route):
722
850
  logger.warning(
723
851
  f"Failed to import required modules for platform {platform.name}: {e}"
724
852
  )
725
- except (OSError, IOError) as e:
853
+ except OSError as e:
726
854
  logger.warning(f"File system error for platform {platform.name} logo: {e}")
727
855
  except Exception as e:
728
856
  logger.warning(