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.
- astrbot/core/agent/mcp_client.py +3 -3
- astrbot/core/agent/runners/base.py +7 -4
- astrbot/core/agent/runners/coze/coze_agent_runner.py +367 -0
- astrbot/core/agent/runners/dashscope/dashscope_agent_runner.py +403 -0
- astrbot/core/agent/runners/dify/dify_agent_runner.py +336 -0
- astrbot/core/{utils → agent/runners/dify}/dify_api_client.py +51 -13
- astrbot/core/agent/runners/tool_loop_agent_runner.py +0 -6
- astrbot/core/config/default.py +141 -26
- astrbot/core/config/i18n_utils.py +110 -0
- astrbot/core/core_lifecycle.py +11 -13
- astrbot/core/db/po.py +1 -1
- astrbot/core/db/sqlite.py +2 -2
- astrbot/core/pipeline/process_stage/method/agent_request.py +48 -0
- astrbot/core/pipeline/process_stage/method/{llm_request.py → agent_sub_stages/internal.py} +13 -34
- astrbot/core/pipeline/process_stage/method/agent_sub_stages/third_party.py +202 -0
- astrbot/core/pipeline/process_stage/method/star_request.py +1 -1
- astrbot/core/pipeline/process_stage/stage.py +8 -5
- astrbot/core/pipeline/result_decorate/stage.py +15 -5
- astrbot/core/provider/manager.py +43 -41
- astrbot/core/star/session_llm_manager.py +0 -107
- astrbot/core/star/session_plugin_manager.py +0 -81
- astrbot/core/umop_config_router.py +19 -0
- astrbot/core/utils/migra_helper.py +73 -0
- astrbot/core/utils/shared_preferences.py +1 -28
- astrbot/dashboard/routes/chat.py +13 -1
- astrbot/dashboard/routes/config.py +20 -16
- astrbot/dashboard/routes/knowledge_base.py +0 -156
- astrbot/dashboard/routes/session_management.py +311 -606
- {astrbot-4.6.1.dist-info → astrbot-4.7.1.dist-info}/METADATA +1 -1
- {astrbot-4.6.1.dist-info → astrbot-4.7.1.dist-info}/RECORD +34 -30
- {astrbot-4.6.1.dist-info → astrbot-4.7.1.dist-info}/WHEEL +1 -1
- astrbot/core/provider/sources/coze_source.py +0 -650
- astrbot/core/provider/sources/dashscope_source.py +0 -207
- astrbot/core/provider/sources/dify_source.py +0 -285
- /astrbot/core/{provider/sources → agent/runners/coze}/coze_api_client.py +0 -0
- {astrbot-4.6.1.dist-info → astrbot-4.7.1.dist-info}/entry_points.txt +0 -0
- {astrbot-4.6.1.dist-info → astrbot-4.7.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,16 +1,24 @@
|
|
|
1
|
-
import traceback
|
|
2
|
-
|
|
3
1
|
from quart import request
|
|
2
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
|
3
|
+
from sqlmodel import col, select
|
|
4
4
|
|
|
5
5
|
from astrbot.core import logger, sp
|
|
6
6
|
from astrbot.core.core_lifecycle import AstrBotCoreLifecycle
|
|
7
7
|
from astrbot.core.db import BaseDatabase
|
|
8
|
+
from astrbot.core.db.po import ConversationV2, Preference
|
|
8
9
|
from astrbot.core.provider.entities import ProviderType
|
|
9
|
-
from astrbot.core.star.session_llm_manager import SessionServiceManager
|
|
10
|
-
from astrbot.core.star.session_plugin_manager import SessionPluginManager
|
|
11
10
|
|
|
12
11
|
from .route import Response, Route, RouteContext
|
|
13
12
|
|
|
13
|
+
AVAILABLE_SESSION_RULE_KEYS = [
|
|
14
|
+
"session_service_config",
|
|
15
|
+
"session_plugin_config",
|
|
16
|
+
"kb_config",
|
|
17
|
+
f"provider_perf_{ProviderType.CHAT_COMPLETION.value}",
|
|
18
|
+
f"provider_perf_{ProviderType.SPEECH_TO_TEXT.value}",
|
|
19
|
+
f"provider_perf_{ProviderType.TEXT_TO_SPEECH.value}",
|
|
20
|
+
]
|
|
21
|
+
|
|
14
22
|
|
|
15
23
|
class SessionManagementRoute(Route):
|
|
16
24
|
def __init__(
|
|
@@ -22,667 +30,364 @@ class SessionManagementRoute(Route):
|
|
|
22
30
|
super().__init__(context)
|
|
23
31
|
self.db_helper = db_helper
|
|
24
32
|
self.routes = {
|
|
25
|
-
"/session/list": ("GET", self.
|
|
26
|
-
"/session/
|
|
27
|
-
"/session/
|
|
28
|
-
"/session/
|
|
29
|
-
"/session/
|
|
30
|
-
"/session/update_llm": ("POST", self.update_session_llm),
|
|
31
|
-
"/session/update_tts": ("POST", self.update_session_tts),
|
|
32
|
-
"/session/update_name": ("POST", self.update_session_name),
|
|
33
|
-
"/session/update_status": ("POST", self.update_session_status),
|
|
34
|
-
"/session/delete": ("POST", self.delete_session),
|
|
33
|
+
"/session/list-rule": ("GET", self.list_session_rule),
|
|
34
|
+
"/session/update-rule": ("POST", self.update_session_rule),
|
|
35
|
+
"/session/delete-rule": ("POST", self.delete_session_rule),
|
|
36
|
+
"/session/batch-delete-rule": ("POST", self.batch_delete_session_rule),
|
|
37
|
+
"/session/active-umos": ("GET", self.list_umos),
|
|
35
38
|
}
|
|
36
39
|
self.conv_mgr = core_lifecycle.conversation_manager
|
|
37
40
|
self.core_lifecycle = core_lifecycle
|
|
38
41
|
self.register_routes()
|
|
39
42
|
|
|
40
|
-
async def
|
|
41
|
-
|
|
43
|
+
async def _get_umo_rules(
|
|
44
|
+
self, page: int = 1, page_size: int = 10, search: str = ""
|
|
45
|
+
) -> tuple[dict, int]:
|
|
46
|
+
"""获取所有带有自定义规则的 umo 及其规则内容(支持分页和搜索)。
|
|
47
|
+
|
|
48
|
+
如果某个 umo 在 preference 中有以下字段,则表示有自定义规则:
|
|
49
|
+
|
|
50
|
+
1. session_service_config (包含了 是否启用这个umo, 这个umo是否启用 llm, 这个umo是否启用tts, umo自定义名称。)
|
|
51
|
+
2. session_plugin_config (包含了 这个 umo 的 plugin set)
|
|
52
|
+
3. provider_perf_{ProviderType.value} (包含了这个 umo 所选择使用的 provider 信息)
|
|
53
|
+
4. kb_config (包含了这个 umo 的知识库相关配置)
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
page: 页码,从 1 开始
|
|
57
|
+
page_size: 每页数量
|
|
58
|
+
search: 搜索关键词,匹配 umo 或 custom_name
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
tuple[dict, int]: (umo_rules, total) - 分页后的 umo 规则和总数
|
|
62
|
+
"""
|
|
63
|
+
umo_rules = {}
|
|
64
|
+
async with self.db_helper.get_db() as session:
|
|
65
|
+
session: AsyncSession
|
|
66
|
+
result = await session.execute(
|
|
67
|
+
select(Preference).where(
|
|
68
|
+
col(Preference.scope) == "umo",
|
|
69
|
+
col(Preference.key).in_(AVAILABLE_SESSION_RULE_KEYS),
|
|
70
|
+
)
|
|
71
|
+
)
|
|
72
|
+
prefs = result.scalars().all()
|
|
73
|
+
for pref in prefs:
|
|
74
|
+
umo_id = pref.scope_id
|
|
75
|
+
if umo_id not in umo_rules:
|
|
76
|
+
umo_rules[umo_id] = {}
|
|
77
|
+
if pref.key == "session_plugin_config" and umo_id in pref.value["val"]:
|
|
78
|
+
umo_rules[umo_id][pref.key] = pref.value["val"][umo_id]
|
|
79
|
+
else:
|
|
80
|
+
umo_rules[umo_id][pref.key] = pref.value["val"]
|
|
81
|
+
|
|
82
|
+
# 搜索过滤
|
|
83
|
+
if search:
|
|
84
|
+
search_lower = search.lower()
|
|
85
|
+
filtered_rules = {}
|
|
86
|
+
for umo_id, rules in umo_rules.items():
|
|
87
|
+
# 匹配 umo
|
|
88
|
+
if search_lower in umo_id.lower():
|
|
89
|
+
filtered_rules[umo_id] = rules
|
|
90
|
+
continue
|
|
91
|
+
# 匹配 custom_name
|
|
92
|
+
svc_config = rules.get("session_service_config", {})
|
|
93
|
+
custom_name = svc_config.get("custom_name", "") if svc_config else ""
|
|
94
|
+
if custom_name and search_lower in custom_name.lower():
|
|
95
|
+
filtered_rules[umo_id] = rules
|
|
96
|
+
umo_rules = filtered_rules
|
|
97
|
+
|
|
98
|
+
# 获取总数
|
|
99
|
+
total = len(umo_rules)
|
|
100
|
+
|
|
101
|
+
# 分页处理
|
|
102
|
+
all_umo_ids = list(umo_rules.keys())
|
|
103
|
+
start_idx = (page - 1) * page_size
|
|
104
|
+
end_idx = start_idx + page_size
|
|
105
|
+
paginated_umo_ids = all_umo_ids[start_idx:end_idx]
|
|
106
|
+
|
|
107
|
+
# 只返回分页后的数据
|
|
108
|
+
paginated_rules = {umo_id: umo_rules[umo_id] for umo_id in paginated_umo_ids}
|
|
109
|
+
|
|
110
|
+
return paginated_rules, total
|
|
111
|
+
|
|
112
|
+
async def list_session_rule(self):
|
|
113
|
+
"""获取所有自定义的规则(支持分页和搜索)
|
|
114
|
+
|
|
115
|
+
返回已配置规则的 umo 列表及其规则内容,以及可用的 personas 和 providers
|
|
116
|
+
|
|
117
|
+
Query 参数:
|
|
118
|
+
page: 页码,默认为 1
|
|
119
|
+
page_size: 每页数量,默认为 10
|
|
120
|
+
search: 搜索关键词,匹配 umo 或 custom_name
|
|
121
|
+
"""
|
|
42
122
|
try:
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
#
|
|
49
|
-
|
|
50
|
-
page
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
123
|
+
# 获取分页和搜索参数
|
|
124
|
+
page = request.args.get("page", 1, type=int)
|
|
125
|
+
page_size = request.args.get("page_size", 10, type=int)
|
|
126
|
+
search = request.args.get("search", "", type=str).strip()
|
|
127
|
+
|
|
128
|
+
# 参数校验
|
|
129
|
+
if page < 1:
|
|
130
|
+
page = 1
|
|
131
|
+
if page_size < 1:
|
|
132
|
+
page_size = 10
|
|
133
|
+
if page_size > 100:
|
|
134
|
+
page_size = 100
|
|
135
|
+
|
|
136
|
+
umo_rules, total = await self._get_umo_rules(
|
|
137
|
+
page=page, page_size=page_size, search=search
|
|
54
138
|
)
|
|
55
139
|
|
|
140
|
+
# 构建规则列表
|
|
141
|
+
rules_list = []
|
|
142
|
+
for umo, rules in umo_rules.items():
|
|
143
|
+
rule_info = {
|
|
144
|
+
"umo": umo,
|
|
145
|
+
"rules": rules,
|
|
146
|
+
}
|
|
147
|
+
# 解析 umo 格式: 平台:消息类型:会话ID
|
|
148
|
+
parts = umo.split(":")
|
|
149
|
+
if len(parts) >= 3:
|
|
150
|
+
rule_info["platform"] = parts[0]
|
|
151
|
+
rule_info["message_type"] = parts[1]
|
|
152
|
+
rule_info["session_id"] = parts[2]
|
|
153
|
+
rules_list.append(rule_info)
|
|
154
|
+
|
|
155
|
+
# 获取可用的 providers 和 personas
|
|
56
156
|
provider_manager = self.core_lifecycle.provider_manager
|
|
57
157
|
persona_mgr = self.core_lifecycle.persona_mgr
|
|
58
|
-
personas = persona_mgr.personas_v3
|
|
59
|
-
|
|
60
|
-
sessions = []
|
|
61
|
-
|
|
62
|
-
# 循环补充非数据库信息,如 provider 和 session 状态
|
|
63
|
-
for data in sessions_data:
|
|
64
|
-
session_id = data["session_id"]
|
|
65
|
-
conversation_id = data["conversation_id"]
|
|
66
|
-
conv_persona_id = data["persona_id"]
|
|
67
|
-
title = data["title"]
|
|
68
|
-
persona_name = data["persona_name"]
|
|
69
|
-
|
|
70
|
-
# 处理 persona 显示
|
|
71
|
-
if persona_name is None:
|
|
72
|
-
if conv_persona_id is None:
|
|
73
|
-
if default_persona := persona_mgr.selected_default_persona_v3:
|
|
74
|
-
persona_name = default_persona["name"]
|
|
75
|
-
else:
|
|
76
|
-
persona_name = "[%None]"
|
|
77
|
-
|
|
78
|
-
session_info = {
|
|
79
|
-
"session_id": session_id,
|
|
80
|
-
"conversation_id": conversation_id,
|
|
81
|
-
"persona_id": persona_name,
|
|
82
|
-
"chat_provider_id": None,
|
|
83
|
-
"stt_provider_id": None,
|
|
84
|
-
"tts_provider_id": None,
|
|
85
|
-
"session_enabled": SessionServiceManager.is_session_enabled(
|
|
86
|
-
session_id,
|
|
87
|
-
),
|
|
88
|
-
"llm_enabled": SessionServiceManager.is_llm_enabled_for_session(
|
|
89
|
-
session_id,
|
|
90
|
-
),
|
|
91
|
-
"tts_enabled": SessionServiceManager.is_tts_enabled_for_session(
|
|
92
|
-
session_id,
|
|
93
|
-
),
|
|
94
|
-
"platform": session_id.split(":")[0]
|
|
95
|
-
if ":" in session_id
|
|
96
|
-
else "unknown",
|
|
97
|
-
"message_type": session_id.split(":")[1]
|
|
98
|
-
if session_id.count(":") >= 1
|
|
99
|
-
else "unknown",
|
|
100
|
-
"session_name": SessionServiceManager.get_session_display_name(
|
|
101
|
-
session_id,
|
|
102
|
-
),
|
|
103
|
-
"session_raw_name": session_id.split(":")[2]
|
|
104
|
-
if session_id.count(":") >= 2
|
|
105
|
-
else session_id,
|
|
106
|
-
"title": title,
|
|
107
|
-
}
|
|
108
158
|
|
|
109
|
-
# 获取 provider 信息
|
|
110
|
-
chat_provider = provider_manager.get_using_provider(
|
|
111
|
-
provider_type=ProviderType.CHAT_COMPLETION,
|
|
112
|
-
umo=session_id,
|
|
113
|
-
)
|
|
114
|
-
tts_provider = provider_manager.get_using_provider(
|
|
115
|
-
provider_type=ProviderType.TEXT_TO_SPEECH,
|
|
116
|
-
umo=session_id,
|
|
117
|
-
)
|
|
118
|
-
stt_provider = provider_manager.get_using_provider(
|
|
119
|
-
provider_type=ProviderType.SPEECH_TO_TEXT,
|
|
120
|
-
umo=session_id,
|
|
121
|
-
)
|
|
122
|
-
if chat_provider:
|
|
123
|
-
meta = chat_provider.meta()
|
|
124
|
-
session_info["chat_provider_id"] = meta.id
|
|
125
|
-
if tts_provider:
|
|
126
|
-
meta = tts_provider.meta()
|
|
127
|
-
session_info["tts_provider_id"] = meta.id
|
|
128
|
-
if stt_provider:
|
|
129
|
-
meta = stt_provider.meta()
|
|
130
|
-
session_info["stt_provider_id"] = meta.id
|
|
131
|
-
|
|
132
|
-
sessions.append(session_info)
|
|
133
|
-
|
|
134
|
-
# 获取可用的 personas 和 providers 列表
|
|
135
159
|
available_personas = [
|
|
136
|
-
{"name": p["name"], "prompt": p.get("prompt", "")}
|
|
160
|
+
{"name": p["name"], "prompt": p.get("prompt", "")}
|
|
161
|
+
for p in persona_mgr.personas_v3
|
|
137
162
|
]
|
|
138
163
|
|
|
139
|
-
available_chat_providers = [
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
"type": meta.type,
|
|
148
|
-
},
|
|
149
|
-
)
|
|
150
|
-
|
|
151
|
-
available_stt_providers = []
|
|
152
|
-
for provider in provider_manager.stt_provider_insts:
|
|
153
|
-
meta = provider.meta()
|
|
154
|
-
available_stt_providers.append(
|
|
155
|
-
{
|
|
156
|
-
"id": meta.id,
|
|
157
|
-
"name": meta.id,
|
|
158
|
-
"model": meta.model,
|
|
159
|
-
"type": meta.type,
|
|
160
|
-
},
|
|
161
|
-
)
|
|
162
|
-
|
|
163
|
-
available_tts_providers = []
|
|
164
|
-
for provider in provider_manager.tts_provider_insts:
|
|
165
|
-
meta = provider.meta()
|
|
166
|
-
available_tts_providers.append(
|
|
167
|
-
{
|
|
168
|
-
"id": meta.id,
|
|
169
|
-
"name": meta.id,
|
|
170
|
-
"model": meta.model,
|
|
171
|
-
"type": meta.type,
|
|
172
|
-
},
|
|
173
|
-
)
|
|
174
|
-
|
|
175
|
-
result = {
|
|
176
|
-
"sessions": sessions,
|
|
177
|
-
"available_personas": available_personas,
|
|
178
|
-
"available_chat_providers": available_chat_providers,
|
|
179
|
-
"available_stt_providers": available_stt_providers,
|
|
180
|
-
"available_tts_providers": available_tts_providers,
|
|
181
|
-
"pagination": {
|
|
182
|
-
"page": page,
|
|
183
|
-
"page_size": page_size,
|
|
184
|
-
"total": total,
|
|
185
|
-
"total_pages": (total + page_size - 1) // page_size
|
|
186
|
-
if page_size > 0
|
|
187
|
-
else 0,
|
|
188
|
-
},
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
return Response().ok(result).__dict__
|
|
164
|
+
available_chat_providers = [
|
|
165
|
+
{
|
|
166
|
+
"id": p.meta().id,
|
|
167
|
+
"name": p.meta().id,
|
|
168
|
+
"model": p.meta().model,
|
|
169
|
+
}
|
|
170
|
+
for p in provider_manager.provider_insts
|
|
171
|
+
]
|
|
192
172
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
conversation_id = await conversation_manager.get_curr_conversation_id(
|
|
202
|
-
session_id,
|
|
203
|
-
)
|
|
204
|
-
|
|
205
|
-
conv = None
|
|
206
|
-
if conversation_id:
|
|
207
|
-
conv = await conversation_manager.get_conversation(
|
|
208
|
-
unified_msg_origin=session_id,
|
|
209
|
-
conversation_id=conversation_id,
|
|
210
|
-
)
|
|
211
|
-
if not conv or not conversation_id:
|
|
212
|
-
conversation_id = await conversation_manager.new_conversation(session_id)
|
|
173
|
+
available_stt_providers = [
|
|
174
|
+
{
|
|
175
|
+
"id": p.meta().id,
|
|
176
|
+
"name": p.meta().id,
|
|
177
|
+
"model": p.meta().model,
|
|
178
|
+
}
|
|
179
|
+
for p in provider_manager.stt_provider_insts
|
|
180
|
+
]
|
|
213
181
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
182
|
+
available_tts_providers = [
|
|
183
|
+
{
|
|
184
|
+
"id": p.meta().id,
|
|
185
|
+
"name": p.meta().id,
|
|
186
|
+
"model": p.meta().model,
|
|
187
|
+
}
|
|
188
|
+
for p in provider_manager.tts_provider_insts
|
|
189
|
+
]
|
|
219
190
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
operation_func,
|
|
224
|
-
operation_name: str,
|
|
225
|
-
**kwargs,
|
|
226
|
-
):
|
|
227
|
-
"""通用的批量操作处理方法"""
|
|
228
|
-
success_count = 0
|
|
229
|
-
error_sessions = []
|
|
230
|
-
|
|
231
|
-
for session_id in session_ids:
|
|
232
|
-
try:
|
|
233
|
-
await operation_func(session_id, **kwargs)
|
|
234
|
-
success_count += 1
|
|
235
|
-
except Exception as e:
|
|
236
|
-
logger.error(f"批量{operation_name} 会话 {session_id} 失败: {e!s}")
|
|
237
|
-
error_sessions.append(session_id)
|
|
238
|
-
|
|
239
|
-
if error_sessions:
|
|
240
|
-
return (
|
|
241
|
-
Response()
|
|
242
|
-
.ok(
|
|
243
|
-
{
|
|
244
|
-
"message": f"批量更新完成,成功: {success_count},失败: {len(error_sessions)}",
|
|
245
|
-
"success_count": success_count,
|
|
246
|
-
"error_count": len(error_sessions),
|
|
247
|
-
"error_sessions": error_sessions,
|
|
248
|
-
},
|
|
249
|
-
)
|
|
250
|
-
.__dict__
|
|
251
|
-
)
|
|
252
|
-
return (
|
|
253
|
-
Response()
|
|
254
|
-
.ok(
|
|
191
|
+
# 获取可用的插件列表(排除 reserved 的系统插件)
|
|
192
|
+
plugin_manager = self.core_lifecycle.plugin_manager
|
|
193
|
+
available_plugins = [
|
|
255
194
|
{
|
|
256
|
-
"
|
|
257
|
-
"
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
195
|
+
"name": p.name,
|
|
196
|
+
"display_name": p.display_name or p.name,
|
|
197
|
+
"desc": p.desc,
|
|
198
|
+
}
|
|
199
|
+
for p in plugin_manager.context.get_all_stars()
|
|
200
|
+
if not p.reserved and p.name
|
|
201
|
+
]
|
|
262
202
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
session_ids,
|
|
280
|
-
self._update_single_session_persona,
|
|
281
|
-
"更新人格",
|
|
282
|
-
persona_name=persona_name,
|
|
283
|
-
)
|
|
284
|
-
session_id = data.get("session_id")
|
|
285
|
-
if not session_id:
|
|
286
|
-
return Response().error("缺少必要参数: session_id").__dict__
|
|
203
|
+
# 获取可用的知识库列表
|
|
204
|
+
available_kbs = []
|
|
205
|
+
kb_manager = self.core_lifecycle.kb_manager
|
|
206
|
+
if kb_manager:
|
|
207
|
+
try:
|
|
208
|
+
kbs = await kb_manager.list_kbs()
|
|
209
|
+
available_kbs = [
|
|
210
|
+
{
|
|
211
|
+
"kb_id": kb.kb_id,
|
|
212
|
+
"kb_name": kb.kb_name,
|
|
213
|
+
"emoji": kb.emoji,
|
|
214
|
+
}
|
|
215
|
+
for kb in kbs
|
|
216
|
+
]
|
|
217
|
+
except Exception as e:
|
|
218
|
+
logger.warning(f"获取知识库列表失败: {e!s}")
|
|
287
219
|
|
|
288
|
-
await self._update_single_session_persona(session_id, persona_name)
|
|
289
220
|
return (
|
|
290
221
|
Response()
|
|
291
222
|
.ok(
|
|
292
223
|
{
|
|
293
|
-
"
|
|
294
|
-
|
|
224
|
+
"rules": rules_list,
|
|
225
|
+
"total": total,
|
|
226
|
+
"page": page,
|
|
227
|
+
"page_size": page_size,
|
|
228
|
+
"available_personas": available_personas,
|
|
229
|
+
"available_chat_providers": available_chat_providers,
|
|
230
|
+
"available_stt_providers": available_stt_providers,
|
|
231
|
+
"available_tts_providers": available_tts_providers,
|
|
232
|
+
"available_plugins": available_plugins,
|
|
233
|
+
"available_kbs": available_kbs,
|
|
234
|
+
"available_rule_keys": AVAILABLE_SESSION_RULE_KEYS,
|
|
235
|
+
}
|
|
295
236
|
)
|
|
296
237
|
.__dict__
|
|
297
238
|
)
|
|
298
|
-
|
|
299
239
|
except Exception as e:
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
return Response().error(f"更新会话人格失败: {e!s}").__dict__
|
|
240
|
+
logger.error(f"获取规则列表失败: {e!s}")
|
|
241
|
+
return Response().error(f"获取规则列表失败: {e!s}").__dict__
|
|
303
242
|
|
|
304
|
-
async def
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
provider_type=provider_type_enum,
|
|
315
|
-
umo=session_id,
|
|
316
|
-
)
|
|
317
|
-
|
|
318
|
-
async def update_session_provider(self):
|
|
319
|
-
"""更新指定会话的 provider,支持批量操作"""
|
|
243
|
+
async def update_session_rule(self):
|
|
244
|
+
"""更新某个 umo 的自定义规则
|
|
245
|
+
|
|
246
|
+
请求体:
|
|
247
|
+
{
|
|
248
|
+
"umo": "平台:消息类型:会话ID",
|
|
249
|
+
"rule_key": "session_service_config" | "session_plugin_config" | "kb_config" | "provider_perf_xxx",
|
|
250
|
+
"rule_value": {...} // 规则值,具体结构根据 rule_key 不同而不同
|
|
251
|
+
}
|
|
252
|
+
"""
|
|
320
253
|
try:
|
|
321
254
|
data = await request.get_json()
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
255
|
+
umo = data.get("umo")
|
|
256
|
+
rule_key = data.get("rule_key")
|
|
257
|
+
rule_value = data.get("rule_value")
|
|
258
|
+
|
|
259
|
+
if not umo:
|
|
260
|
+
return Response().error("缺少必要参数: umo").__dict__
|
|
261
|
+
if not rule_key:
|
|
262
|
+
return Response().error("缺少必要参数: rule_key").__dict__
|
|
263
|
+
if rule_key not in AVAILABLE_SESSION_RULE_KEYS:
|
|
264
|
+
return Response().error(f"不支持的规则键: {rule_key}").__dict__
|
|
265
|
+
|
|
266
|
+
if rule_key == "session_plugin_config":
|
|
267
|
+
rule_value = {
|
|
268
|
+
umo: rule_value,
|
|
269
|
+
}
|
|
325
270
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
Response()
|
|
329
|
-
.error("缺少必要参数: provider_id, provider_type")
|
|
330
|
-
.__dict__
|
|
331
|
-
)
|
|
271
|
+
# 使用 shared preferences 更新规则
|
|
272
|
+
await sp.session_put(umo, rule_key, rule_value)
|
|
332
273
|
|
|
333
|
-
# 转换 provider_type 字符串为枚举
|
|
334
|
-
if provider_type == "chat_completion":
|
|
335
|
-
provider_type_enum = ProviderType.CHAT_COMPLETION
|
|
336
|
-
elif provider_type == "speech_to_text":
|
|
337
|
-
provider_type_enum = ProviderType.SPEECH_TO_TEXT
|
|
338
|
-
elif provider_type == "text_to_speech":
|
|
339
|
-
provider_type_enum = ProviderType.TEXT_TO_SPEECH
|
|
340
|
-
else:
|
|
341
|
-
return (
|
|
342
|
-
Response()
|
|
343
|
-
.error(f"不支持的 provider_type: {provider_type}")
|
|
344
|
-
.__dict__
|
|
345
|
-
)
|
|
346
|
-
|
|
347
|
-
if is_batch:
|
|
348
|
-
session_ids = data.get("session_ids", [])
|
|
349
|
-
if not session_ids:
|
|
350
|
-
return Response().error("缺少必要参数: session_ids").__dict__
|
|
351
|
-
|
|
352
|
-
return await self._handle_batch_operation(
|
|
353
|
-
session_ids,
|
|
354
|
-
self._update_single_session_provider,
|
|
355
|
-
f"更新 {provider_type} 提供商",
|
|
356
|
-
provider_id=provider_id,
|
|
357
|
-
provider_type_enum=provider_type_enum,
|
|
358
|
-
)
|
|
359
|
-
session_id = data.get("session_id")
|
|
360
|
-
if not session_id:
|
|
361
|
-
return Response().error("缺少必要参数: session_id").__dict__
|
|
362
|
-
|
|
363
|
-
await self._update_single_session_provider(
|
|
364
|
-
session_id,
|
|
365
|
-
provider_id,
|
|
366
|
-
provider_type_enum,
|
|
367
|
-
)
|
|
368
274
|
return (
|
|
369
275
|
Response()
|
|
370
|
-
.ok(
|
|
371
|
-
{
|
|
372
|
-
"message": f"成功更新会话 {session_id} 的 {provider_type} 提供商为 {provider_id}",
|
|
373
|
-
},
|
|
374
|
-
)
|
|
276
|
+
.ok({"message": f"规则 {rule_key} 已更新", "umo": umo})
|
|
375
277
|
.__dict__
|
|
376
278
|
)
|
|
377
|
-
|
|
378
279
|
except Exception as e:
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
return Response().error(f"更新会话提供商失败: {e!s}").__dict__
|
|
280
|
+
logger.error(f"更新会话规则失败: {e!s}")
|
|
281
|
+
return Response().error(f"更新会话规则失败: {e!s}").__dict__
|
|
382
282
|
|
|
383
|
-
async def
|
|
384
|
-
"""
|
|
385
|
-
try:
|
|
386
|
-
session_id = request.args.get("session_id")
|
|
283
|
+
async def delete_session_rule(self):
|
|
284
|
+
"""删除某个 umo 的自定义规则
|
|
387
285
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
for plugin in plugin_manager.context.get_all_stars():
|
|
396
|
-
# 只显示已激活的插件,不包括保留插件
|
|
397
|
-
if plugin.activated and not plugin.reserved:
|
|
398
|
-
plugin_name = plugin.name or ""
|
|
399
|
-
plugin_enabled = SessionPluginManager.is_plugin_enabled_for_session(
|
|
400
|
-
session_id,
|
|
401
|
-
plugin_name,
|
|
402
|
-
)
|
|
403
|
-
|
|
404
|
-
all_plugins.append(
|
|
405
|
-
{
|
|
406
|
-
"name": plugin_name,
|
|
407
|
-
"author": plugin.author,
|
|
408
|
-
"desc": plugin.desc,
|
|
409
|
-
"enabled": plugin_enabled,
|
|
410
|
-
},
|
|
411
|
-
)
|
|
412
|
-
|
|
413
|
-
return (
|
|
414
|
-
Response()
|
|
415
|
-
.ok(
|
|
416
|
-
{
|
|
417
|
-
"session_id": session_id,
|
|
418
|
-
"plugins": all_plugins,
|
|
419
|
-
},
|
|
420
|
-
)
|
|
421
|
-
.__dict__
|
|
422
|
-
)
|
|
423
|
-
|
|
424
|
-
except Exception as e:
|
|
425
|
-
error_msg = f"获取会话插件配置失败: {e!s}\n{traceback.format_exc()}"
|
|
426
|
-
logger.error(error_msg)
|
|
427
|
-
return Response().error(f"获取会话插件配置失败: {e!s}").__dict__
|
|
428
|
-
|
|
429
|
-
async def update_session_plugin(self):
|
|
430
|
-
"""更新指定会话的插件启停状态"""
|
|
286
|
+
请求体:
|
|
287
|
+
{
|
|
288
|
+
"umo": "平台:消息类型:会话ID",
|
|
289
|
+
"rule_key": "session_service_config" | "session_plugin_config" | ... (可选,不传则删除所有规则)
|
|
290
|
+
}
|
|
291
|
+
"""
|
|
431
292
|
try:
|
|
432
293
|
data = await request.get_json()
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
enabled = data.get("enabled")
|
|
436
|
-
|
|
437
|
-
if not session_id:
|
|
438
|
-
return Response().error("缺少必要参数: session_id").__dict__
|
|
439
|
-
|
|
440
|
-
if not plugin_name:
|
|
441
|
-
return Response().error("缺少必要参数: plugin_name").__dict__
|
|
442
|
-
|
|
443
|
-
if enabled is None:
|
|
444
|
-
return Response().error("缺少必要参数: enabled").__dict__
|
|
445
|
-
|
|
446
|
-
# 验证插件是否存在且已激活
|
|
447
|
-
plugin_manager = self.core_lifecycle.plugin_manager
|
|
448
|
-
plugin = plugin_manager.context.get_registered_star(plugin_name)
|
|
294
|
+
umo = data.get("umo")
|
|
295
|
+
rule_key = data.get("rule_key")
|
|
449
296
|
|
|
450
|
-
if not
|
|
451
|
-
return Response().error(
|
|
297
|
+
if not umo:
|
|
298
|
+
return Response().error("缺少必要参数: umo").__dict__
|
|
452
299
|
|
|
453
|
-
if
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
300
|
+
if rule_key:
|
|
301
|
+
# 删除单个规则
|
|
302
|
+
if rule_key not in AVAILABLE_SESSION_RULE_KEYS:
|
|
303
|
+
return Response().error(f"不支持的规则键: {rule_key}").__dict__
|
|
304
|
+
await sp.session_remove(umo, rule_key)
|
|
457
305
|
return (
|
|
458
306
|
Response()
|
|
459
|
-
.
|
|
307
|
+
.ok({"message": f"规则 {rule_key} 已删除", "umo": umo})
|
|
460
308
|
.__dict__
|
|
461
309
|
)
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
plugin_name,
|
|
467
|
-
enabled,
|
|
468
|
-
)
|
|
469
|
-
|
|
470
|
-
return (
|
|
471
|
-
Response()
|
|
472
|
-
.ok(
|
|
473
|
-
{
|
|
474
|
-
"message": f"插件 {plugin_name} 已{'启用' if enabled else '禁用'}",
|
|
475
|
-
"session_id": session_id,
|
|
476
|
-
"plugin_name": plugin_name,
|
|
477
|
-
"enabled": enabled,
|
|
478
|
-
},
|
|
479
|
-
)
|
|
480
|
-
.__dict__
|
|
481
|
-
)
|
|
482
|
-
|
|
483
|
-
except Exception as e:
|
|
484
|
-
error_msg = f"更新会话插件状态失败: {e!s}\n{traceback.format_exc()}"
|
|
485
|
-
logger.error(error_msg)
|
|
486
|
-
return Response().error(f"更新会话插件状态失败: {e!s}").__dict__
|
|
487
|
-
|
|
488
|
-
async def _update_single_session_llm(self, session_id: str, enabled: bool):
|
|
489
|
-
"""更新单个会话的LLM状态的内部方法"""
|
|
490
|
-
SessionServiceManager.set_llm_status_for_session(session_id, enabled)
|
|
491
|
-
|
|
492
|
-
async def update_session_llm(self):
|
|
493
|
-
"""更新指定会话的LLM启停状态,支持批量操作"""
|
|
494
|
-
try:
|
|
495
|
-
data = await request.get_json()
|
|
496
|
-
is_batch = data.get("is_batch", False)
|
|
497
|
-
enabled = data.get("enabled")
|
|
498
|
-
|
|
499
|
-
if enabled is None:
|
|
500
|
-
return Response().error("缺少必要参数: enabled").__dict__
|
|
501
|
-
|
|
502
|
-
if is_batch:
|
|
503
|
-
session_ids = data.get("session_ids", [])
|
|
504
|
-
if not session_ids:
|
|
505
|
-
return Response().error("缺少必要参数: session_ids").__dict__
|
|
506
|
-
|
|
507
|
-
result = await self._handle_batch_operation(
|
|
508
|
-
session_ids,
|
|
509
|
-
self._update_single_session_llm,
|
|
510
|
-
f"{'启用' if enabled else '禁用'}LLM",
|
|
511
|
-
enabled=enabled,
|
|
512
|
-
)
|
|
513
|
-
return result
|
|
514
|
-
session_id = data.get("session_id")
|
|
515
|
-
if not session_id:
|
|
516
|
-
return Response().error("缺少必要参数: session_id").__dict__
|
|
517
|
-
|
|
518
|
-
await self._update_single_session_llm(session_id, enabled)
|
|
519
|
-
return (
|
|
520
|
-
Response()
|
|
521
|
-
.ok(
|
|
522
|
-
{
|
|
523
|
-
"message": f"LLM已{'启用' if enabled else '禁用'}",
|
|
524
|
-
"session_id": session_id,
|
|
525
|
-
"llm_enabled": enabled,
|
|
526
|
-
},
|
|
527
|
-
)
|
|
528
|
-
.__dict__
|
|
529
|
-
)
|
|
530
|
-
|
|
310
|
+
else:
|
|
311
|
+
# 删除该 umo 的所有规则
|
|
312
|
+
await sp.clear_async("umo", umo)
|
|
313
|
+
return Response().ok({"message": "所有规则已删除", "umo": umo}).__dict__
|
|
531
314
|
except Exception as e:
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
return Response().error(f"更新会话LLM状态失败: {e!s}").__dict__
|
|
315
|
+
logger.error(f"删除会话规则失败: {e!s}")
|
|
316
|
+
return Response().error(f"删除会话规则失败: {e!s}").__dict__
|
|
535
317
|
|
|
536
|
-
async def
|
|
537
|
-
"""
|
|
538
|
-
SessionServiceManager.set_tts_status_for_session(session_id, enabled)
|
|
318
|
+
async def batch_delete_session_rule(self):
|
|
319
|
+
"""批量删除多个 umo 的自定义规则
|
|
539
320
|
|
|
540
|
-
|
|
541
|
-
|
|
321
|
+
请求体:
|
|
322
|
+
{
|
|
323
|
+
"umos": ["平台:消息类型:会话ID", ...] // umo 列表
|
|
324
|
+
}
|
|
325
|
+
"""
|
|
542
326
|
try:
|
|
543
327
|
data = await request.get_json()
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
if
|
|
564
|
-
return
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
},
|
|
328
|
+
umos = data.get("umos", [])
|
|
329
|
+
|
|
330
|
+
if not umos:
|
|
331
|
+
return Response().error("缺少必要参数: umos").__dict__
|
|
332
|
+
|
|
333
|
+
if not isinstance(umos, list):
|
|
334
|
+
return Response().error("参数 umos 必须是数组").__dict__
|
|
335
|
+
|
|
336
|
+
# 批量删除
|
|
337
|
+
deleted_count = 0
|
|
338
|
+
failed_umos = []
|
|
339
|
+
for umo in umos:
|
|
340
|
+
try:
|
|
341
|
+
await sp.clear_async("umo", umo)
|
|
342
|
+
deleted_count += 1
|
|
343
|
+
except Exception as e:
|
|
344
|
+
logger.error(f"删除 umo {umo} 的规则失败: {e!s}")
|
|
345
|
+
failed_umos.append(umo)
|
|
346
|
+
|
|
347
|
+
if failed_umos:
|
|
348
|
+
return (
|
|
349
|
+
Response()
|
|
350
|
+
.ok(
|
|
351
|
+
{
|
|
352
|
+
"message": f"已删除 {deleted_count} 条规则,{len(failed_umos)} 条删除失败",
|
|
353
|
+
"deleted_count": deleted_count,
|
|
354
|
+
"failed_umos": failed_umos,
|
|
355
|
+
}
|
|
356
|
+
)
|
|
357
|
+
.__dict__
|
|
575
358
|
)
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
try:
|
|
587
|
-
data = await request.get_json()
|
|
588
|
-
session_id = data.get("session_id")
|
|
589
|
-
custom_name = data.get("custom_name", "")
|
|
590
|
-
|
|
591
|
-
if not session_id:
|
|
592
|
-
return Response().error("缺少必要参数: session_id").__dict__
|
|
593
|
-
|
|
594
|
-
# 使用 SessionServiceManager 更新会话名称
|
|
595
|
-
SessionServiceManager.set_session_custom_name(session_id, custom_name)
|
|
596
|
-
|
|
597
|
-
return (
|
|
598
|
-
Response()
|
|
599
|
-
.ok(
|
|
600
|
-
{
|
|
601
|
-
"message": f"会话名称已更新为: {custom_name if custom_name.strip() else '已清除自定义名称'}",
|
|
602
|
-
"session_id": session_id,
|
|
603
|
-
"custom_name": custom_name,
|
|
604
|
-
"display_name": SessionServiceManager.get_session_display_name(
|
|
605
|
-
session_id,
|
|
606
|
-
),
|
|
607
|
-
},
|
|
359
|
+
else:
|
|
360
|
+
return (
|
|
361
|
+
Response()
|
|
362
|
+
.ok(
|
|
363
|
+
{
|
|
364
|
+
"message": f"已删除 {deleted_count} 条规则",
|
|
365
|
+
"deleted_count": deleted_count,
|
|
366
|
+
}
|
|
367
|
+
)
|
|
368
|
+
.__dict__
|
|
608
369
|
)
|
|
609
|
-
.__dict__
|
|
610
|
-
)
|
|
611
|
-
|
|
612
370
|
except Exception as e:
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
return Response().error(f"更新会话名称失败: {e!s}").__dict__
|
|
616
|
-
|
|
617
|
-
async def update_session_status(self):
|
|
618
|
-
"""更新指定会话的整体启停状态"""
|
|
619
|
-
try:
|
|
620
|
-
data = await request.get_json()
|
|
621
|
-
session_id = data.get("session_id")
|
|
622
|
-
session_enabled = data.get("session_enabled")
|
|
623
|
-
|
|
624
|
-
if not session_id:
|
|
625
|
-
return Response().error("缺少必要参数: session_id").__dict__
|
|
626
|
-
|
|
627
|
-
if session_enabled is None:
|
|
628
|
-
return Response().error("缺少必要参数: session_enabled").__dict__
|
|
371
|
+
logger.error(f"批量删除会话规则失败: {e!s}")
|
|
372
|
+
return Response().error(f"批量删除会话规则失败: {e!s}").__dict__
|
|
629
373
|
|
|
630
|
-
|
|
631
|
-
|
|
374
|
+
async def list_umos(self):
|
|
375
|
+
"""列出所有有对话记录的 umo,从 Conversations 表中找
|
|
632
376
|
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
.ok(
|
|
636
|
-
{
|
|
637
|
-
"message": f"会话整体状态已更新为: {'启用' if session_enabled else '禁用'}",
|
|
638
|
-
"session_id": session_id,
|
|
639
|
-
"session_enabled": session_enabled,
|
|
640
|
-
},
|
|
641
|
-
)
|
|
642
|
-
.__dict__
|
|
643
|
-
)
|
|
644
|
-
|
|
645
|
-
except Exception as e:
|
|
646
|
-
error_msg = f"更新会话整体状态失败: {e!s}\n{traceback.format_exc()}"
|
|
647
|
-
logger.error(error_msg)
|
|
648
|
-
return Response().error(f"更新会话整体状态失败: {e!s}").__dict__
|
|
649
|
-
|
|
650
|
-
async def delete_session(self):
|
|
651
|
-
"""删除指定会话及其所有相关数据"""
|
|
377
|
+
仅返回 umo 字符串列表,用于用户在创建规则时选择 umo
|
|
378
|
+
"""
|
|
652
379
|
try:
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
conversation_manager = self.core_lifecycle.conversation_manager
|
|
661
|
-
|
|
662
|
-
# 1. 删除会话的所有对话
|
|
663
|
-
try:
|
|
664
|
-
await conversation_manager.delete_conversations_by_user_id(session_id)
|
|
665
|
-
except Exception as e:
|
|
666
|
-
logger.warning(f"删除会话 {session_id} 的对话失败: {e!s}")
|
|
667
|
-
|
|
668
|
-
# 2. 清除会话的偏好设置数据(清空该会话的所有配置)
|
|
669
|
-
try:
|
|
670
|
-
await sp.clear_async("umo", session_id)
|
|
671
|
-
except Exception as e:
|
|
672
|
-
logger.warning(f"清除会话 {session_id} 的偏好设置失败: {e!s}")
|
|
673
|
-
|
|
674
|
-
return (
|
|
675
|
-
Response()
|
|
676
|
-
.ok(
|
|
677
|
-
{
|
|
678
|
-
"message": f"会话 {session_id} 及其相关所有对话数据已成功删除",
|
|
679
|
-
"session_id": session_id,
|
|
680
|
-
},
|
|
380
|
+
# 从 Conversation 表获取所有 distinct user_id (即 umo)
|
|
381
|
+
async with self.db_helper.get_db() as session:
|
|
382
|
+
session: AsyncSession
|
|
383
|
+
result = await session.execute(
|
|
384
|
+
select(ConversationV2.user_id)
|
|
385
|
+
.distinct()
|
|
386
|
+
.order_by(ConversationV2.user_id)
|
|
681
387
|
)
|
|
682
|
-
.
|
|
683
|
-
)
|
|
388
|
+
umos = [row[0] for row in result.fetchall()]
|
|
684
389
|
|
|
390
|
+
return Response().ok({"umos": umos}).__dict__
|
|
685
391
|
except Exception as e:
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
return Response().error(f"删除会话失败: {e!s}").__dict__
|
|
392
|
+
logger.error(f"获取 UMO 列表失败: {e!s}")
|
|
393
|
+
return Response().error(f"获取 UMO 列表失败: {e!s}").__dict__
|