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
@@ -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.list_sessions),
26
- "/session/update_persona": ("POST", self.update_session_persona),
27
- "/session/update_provider": ("POST", self.update_session_provider),
28
- "/session/plugins": ("GET", self.get_session_plugins),
29
- "/session/update_plugin": ("POST", self.update_session_plugin),
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 list_sessions(self):
41
- """获取所有会话的列表,包括 persona provider 信息"""
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
- page = int(request.args.get("page", 1))
44
- page_size = int(request.args.get("page_size", 20))
45
- search_query = request.args.get("search", "")
46
- platform = request.args.get("platform", "")
47
-
48
- # 获取活跃的会话数据(处于对话内的会话)
49
- sessions_data, total = await self.db_helper.get_session_conversations(
50
- page,
51
- page_size,
52
- search_query,
53
- platform,
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", "")} for p in personas
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
- for provider in provider_manager.provider_insts:
141
- meta = provider.meta()
142
- available_chat_providers.append(
143
- {
144
- "id": meta.id,
145
- "name": meta.id,
146
- "model": meta.model,
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
- except Exception as e:
194
- error_msg = f"获取会话列表失败: {e!s}\n{traceback.format_exc()}"
195
- logger.error(error_msg)
196
- return Response().error(f"获取会话列表失败: {e!s}").__dict__
197
-
198
- async def _update_single_session_persona(self, session_id: str, persona_name: str):
199
- """更新单个会话的 persona 的内部方法"""
200
- conversation_manager = self.core_lifecycle.star_context.conversation_manager
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
- # 更新 persona
215
- await conversation_manager.update_conversation_persona_id(
216
- session_id,
217
- persona_name,
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
- async def _handle_batch_operation(
221
- self,
222
- session_ids: list,
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
- "message": f"成功批量{operation_name} {success_count} 个会话",
257
- "success_count": success_count,
258
- },
259
- )
260
- .__dict__
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
- async def update_session_persona(self):
264
- """更新指定会话的 persona,支持批量操作"""
265
- try:
266
- data = await request.get_json()
267
- is_batch = data.get("is_batch", False)
268
- persona_name = data.get("persona_name")
269
-
270
- if persona_name is None:
271
- return Response().error("缺少必要参数: persona_name").__dict__
272
-
273
- if is_batch:
274
- session_ids = data.get("session_ids", [])
275
- if not session_ids:
276
- return Response().error("缺少必要参数: session_ids").__dict__
277
-
278
- return await self._handle_batch_operation(
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
- "message": f"成功更新会话 {session_id} 的人格为 {persona_name}",
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
- error_msg = f"更新会话人格失败: {e!s}\n{traceback.format_exc()}"
301
- logger.error(error_msg)
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 _update_single_session_provider(
305
- self,
306
- session_id: str,
307
- provider_id: str,
308
- provider_type_enum,
309
- ):
310
- """更新单个会话的 provider 的内部方法"""
311
- provider_manager = self.core_lifecycle.star_context.provider_manager
312
- await provider_manager.set_provider(
313
- provider_id=provider_id,
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
- is_batch = data.get("is_batch", False)
323
- provider_id = data.get("provider_id")
324
- provider_type = data.get("provider_type")
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
- if not provider_id or not provider_type:
327
- return (
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
- error_msg = f"更新会话提供商失败: {e!s}\n{traceback.format_exc()}"
380
- logger.error(error_msg)
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 get_session_plugins(self):
384
- """获取指定会话的插件配置信息"""
385
- try:
386
- session_id = request.args.get("session_id")
283
+ async def delete_session_rule(self):
284
+ """删除某个 umo 的自定义规则
387
285
 
388
- if not session_id:
389
- return Response().error("缺少必要参数: session_id").__dict__
390
-
391
- # 获取所有已激活的插件
392
- all_plugins = []
393
- plugin_manager = self.core_lifecycle.plugin_manager
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
- session_id = data.get("session_id")
434
- plugin_name = data.get("plugin_name")
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 plugin:
451
- return Response().error(f"插件 {plugin_name} 不存在").__dict__
297
+ if not umo:
298
+ return Response().error("缺少必要参数: umo").__dict__
452
299
 
453
- if not plugin.activated:
454
- return Response().error(f"插件 {plugin_name} 未激活").__dict__
455
-
456
- if plugin.reserved:
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
- .error(f"插件 {plugin_name} 是系统保留插件,无法管理")
307
+ .ok({"message": f"规则 {rule_key} 已删除", "umo": umo})
460
308
  .__dict__
461
309
  )
462
-
463
- # 使用 SessionPluginManager 更新插件状态
464
- SessionPluginManager.set_plugin_status_for_session(
465
- session_id,
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
- error_msg = f"更新会话LLM状态失败: {e!s}\n{traceback.format_exc()}"
533
- logger.error(error_msg)
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 _update_single_session_tts(self, session_id: str, enabled: bool):
537
- """更新单个会话的TTS状态的内部方法"""
538
- SessionServiceManager.set_tts_status_for_session(session_id, enabled)
318
+ async def batch_delete_session_rule(self):
319
+ """批量删除多个 umo 的自定义规则
539
320
 
540
- async def update_session_tts(self):
541
- """更新指定会话的TTS启停状态,支持批量操作"""
321
+ 请求体:
322
+ {
323
+ "umos": ["平台:消息类型:会话ID", ...] // umo 列表
324
+ }
325
+ """
542
326
  try:
543
327
  data = await request.get_json()
544
- is_batch = data.get("is_batch", False)
545
- enabled = data.get("enabled")
546
-
547
- if enabled is None:
548
- return Response().error("缺少必要参数: enabled").__dict__
549
-
550
- if is_batch:
551
- session_ids = data.get("session_ids", [])
552
- if not session_ids:
553
- return Response().error("缺少必要参数: session_ids").__dict__
554
-
555
- result = await self._handle_batch_operation(
556
- session_ids,
557
- self._update_single_session_tts,
558
- f"{'启用' if enabled else '禁用'}TTS",
559
- enabled=enabled,
560
- )
561
- return result
562
- session_id = data.get("session_id")
563
- if not session_id:
564
- return Response().error("缺少必要参数: session_id").__dict__
565
-
566
- await self._update_single_session_tts(session_id, enabled)
567
- return (
568
- Response()
569
- .ok(
570
- {
571
- "message": f"TTS已{'启用' if enabled else '禁用'}",
572
- "session_id": session_id,
573
- "tts_enabled": enabled,
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
- .__dict__
577
- )
578
-
579
- except Exception as e:
580
- error_msg = f"更新会话TTS状态失败: {e!s}\n{traceback.format_exc()}"
581
- logger.error(error_msg)
582
- return Response().error(f"更新会话TTS状态失败: {e!s}").__dict__
583
-
584
- async def update_session_name(self):
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
- error_msg = f"更新会话名称失败: {e!s}\n{traceback.format_exc()}"
614
- logger.error(error_msg)
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
- # 使用 SessionServiceManager 更新会话整体状态
631
- SessionServiceManager.set_session_status(session_id, session_enabled)
374
+ async def list_umos(self):
375
+ """列出所有有对话记录的 umo,从 Conversations 表中找
632
376
 
633
- return (
634
- Response()
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
- data = await request.get_json()
654
- session_id = data.get("session_id")
655
-
656
- if not session_id:
657
- return Response().error("缺少必要参数: session_id").__dict__
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
- .__dict__
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
- error_msg = f"删除会话失败: {e!s}\n{traceback.format_exc()}"
687
- logger.error(error_msg)
688
- return Response().error(f"删除会话失败: {e!s}").__dict__
392
+ logger.error(f"获取 UMO 列表失败: {e!s}")
393
+ return Response().error(f"获取 UMO 列表失败: {e!s}").__dict__