AstrBot 4.10.6__py3-none-any.whl → 4.11.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 (39) hide show
  1. astrbot/api/event/filter/__init__.py +4 -0
  2. astrbot/builtin_stars/builtin_commands/commands/tts.py +2 -2
  3. astrbot/cli/__init__.py +1 -1
  4. astrbot/core/agent/context/compressor.py +243 -0
  5. astrbot/core/agent/context/config.py +35 -0
  6. astrbot/core/agent/context/manager.py +120 -0
  7. astrbot/core/agent/context/token_counter.py +64 -0
  8. astrbot/core/agent/context/truncator.py +141 -0
  9. astrbot/core/agent/runners/tool_loop_agent_runner.py +48 -1
  10. astrbot/core/config/default.py +89 -28
  11. astrbot/core/conversation_mgr.py +4 -0
  12. astrbot/core/core_lifecycle.py +1 -0
  13. astrbot/core/db/__init__.py +1 -0
  14. astrbot/core/db/migration/migra_token_usage.py +61 -0
  15. astrbot/core/db/po.py +7 -0
  16. astrbot/core/db/sqlite.py +5 -1
  17. astrbot/core/pipeline/process_stage/method/agent_request.py +1 -1
  18. astrbot/core/pipeline/process_stage/method/agent_sub_stages/internal.py +70 -57
  19. astrbot/core/pipeline/result_decorate/stage.py +1 -1
  20. astrbot/core/pipeline/session_status_check/stage.py +1 -1
  21. astrbot/core/pipeline/waking_check/stage.py +1 -1
  22. astrbot/core/platform/sources/weixin_official_account/weixin_offacc_adapter.py +1 -1
  23. astrbot/core/provider/entities.py +5 -0
  24. astrbot/core/provider/manager.py +27 -12
  25. astrbot/core/star/context.py +14 -1
  26. astrbot/core/star/register/__init__.py +2 -0
  27. astrbot/core/star/register/star_handler.py +24 -0
  28. astrbot/core/star/session_llm_manager.py +38 -26
  29. astrbot/core/star/session_plugin_manager.py +23 -11
  30. astrbot/core/star/star_handler.py +1 -0
  31. astrbot/core/umop_config_router.py +6 -5
  32. astrbot/core/utils/migra_helper.py +8 -0
  33. astrbot/dashboard/routes/backup.py +1 -0
  34. astrbot/dashboard/routes/config.py +2 -2
  35. {astrbot-4.10.6.dist-info → astrbot-4.11.1.dist-info}/METADATA +3 -1
  36. {astrbot-4.10.6.dist-info → astrbot-4.11.1.dist-info}/RECORD +39 -33
  37. {astrbot-4.10.6.dist-info → astrbot-4.11.1.dist-info}/WHEEL +0 -0
  38. {astrbot-4.10.6.dist-info → astrbot-4.11.1.dist-info}/entry_points.txt +0 -0
  39. {astrbot-4.10.6.dist-info → astrbot-4.11.1.dist-info}/licenses/LICENSE +0 -0
@@ -227,7 +227,7 @@ class WakingCheckStage(Stage):
227
227
  event._extras.pop("parsed_params", None)
228
228
 
229
229
  # 根据会话配置过滤插件处理器
230
- activated_handlers = SessionPluginManager.filter_handlers_by_session(
230
+ activated_handlers = await SessionPluginManager.filter_handlers_by_session(
231
231
  event,
232
232
  activated_handlers,
233
233
  )
@@ -191,7 +191,7 @@ class WeixinOfficialAccountPlatformAdapter(Platform):
191
191
  if self.active_send_mode:
192
192
  await self.convert_message(msg, None)
193
193
  else:
194
- if msg.id in self.wexin_event_workers:
194
+ if str(msg.id) in self.wexin_event_workers:
195
195
  future = self.wexin_event_workers[str(cast(str | int, msg.id))]
196
196
  logger.debug(f"duplicate message id checked: {msg.id}")
197
197
  else:
@@ -344,6 +344,11 @@ class LLMResponse:
344
344
  self.raw_completion = raw_completion
345
345
  self.is_chunk = is_chunk
346
346
 
347
+ if id is not None:
348
+ self.id = id
349
+ if usage is not None:
350
+ self.usage = usage
351
+
347
352
  @property
348
353
  def completion_text(self):
349
354
  if self.result_chain:
@@ -119,19 +119,34 @@ class ProviderManager:
119
119
  TTSProvider,
120
120
  ):
121
121
  self.curr_tts_provider_inst = prov
122
- sp.put("curr_provider_tts", provider_id, scope="global", scope_id="global")
122
+ await sp.put_async(
123
+ key="curr_provider_tts",
124
+ value=provider_id,
125
+ scope="global",
126
+ scope_id="global",
127
+ )
123
128
  elif provider_type == ProviderType.SPEECH_TO_TEXT and isinstance(
124
129
  prov,
125
130
  STTProvider,
126
131
  ):
127
132
  self.curr_stt_provider_inst = prov
128
- sp.put("curr_provider_stt", provider_id, scope="global", scope_id="global")
133
+ await sp.put_async(
134
+ key="curr_provider_stt",
135
+ value=provider_id,
136
+ scope="global",
137
+ scope_id="global",
138
+ )
129
139
  elif provider_type == ProviderType.CHAT_COMPLETION and isinstance(
130
140
  prov,
131
141
  Provider,
132
142
  ):
133
143
  self.curr_provider_inst = prov
134
- sp.put("curr_provider", provider_id, scope="global", scope_id="global")
144
+ await sp.put_async(
145
+ key="curr_provider",
146
+ value=provider_id,
147
+ scope="global",
148
+ scope_id="global",
149
+ )
135
150
 
136
151
  async def get_provider_by_id(self, provider_id: str) -> Providers | None:
137
152
  """根据提供商 ID 获取提供商实例"""
@@ -206,21 +221,21 @@ class ProviderManager:
206
221
  logger.error(traceback.format_exc())
207
222
  logger.error(e)
208
223
 
209
- selected_provider_id = sp.get(
210
- "curr_provider",
211
- self.provider_settings.get("default_provider_id"),
224
+ selected_provider_id = await sp.get_async(
225
+ key="curr_provider",
226
+ default=self.provider_settings.get("default_provider_id"),
212
227
  scope="global",
213
228
  scope_id="global",
214
229
  )
215
- selected_stt_provider_id = sp.get(
216
- "curr_provider_stt",
217
- self.provider_stt_settings.get("provider_id"),
230
+ selected_stt_provider_id = await sp.get_async(
231
+ key="curr_provider_stt",
232
+ default=self.provider_stt_settings.get("provider_id"),
218
233
  scope="global",
219
234
  scope_id="global",
220
235
  )
221
- selected_tts_provider_id = sp.get(
222
- "curr_provider_tts",
223
- self.provider_tts_settings.get("provider_id"),
236
+ selected_tts_provider_id = await sp.get_async(
237
+ key="curr_provider_tts",
238
+ default=self.provider_tts_settings.get("provider_id"),
224
239
  scope="global",
225
240
  scope_id="global",
226
241
  )
@@ -149,9 +149,12 @@ class Context:
149
149
  contexts: context messages for the LLM
150
150
  max_steps: Maximum number of tool calls before stopping the loop
151
151
  **kwargs: Additional keyword arguments. The kwargs will not be passed to the LLM directly for now, but can include:
152
+ stream: bool - whether to stream the LLM response
152
153
  agent_hooks: BaseAgentRunHooks[AstrAgentContext] - hooks to run during agent execution
153
154
  agent_context: AstrAgentContext - context to use for the agent
154
155
 
156
+ other kwargs will be DIRECTLY passed to the runner.reset() method
157
+
155
158
  Returns:
156
159
  The final LLMResponse after tool calls are completed.
157
160
 
@@ -194,6 +197,15 @@ class Context:
194
197
  )
195
198
  agent_runner = ToolLoopAgentRunner()
196
199
  tool_executor = FunctionToolExecutor()
200
+
201
+ streaming = kwargs.get("stream", False)
202
+
203
+ other_kwargs = {
204
+ k: v
205
+ for k, v in kwargs.items()
206
+ if k not in ["stream", "agent_hooks", "agent_context"]
207
+ }
208
+
197
209
  await agent_runner.reset(
198
210
  provider=prov,
199
211
  request=request,
@@ -203,7 +215,8 @@ class Context:
203
215
  ),
204
216
  tool_executor=tool_executor,
205
217
  agent_hooks=agent_hooks,
206
- streaming=kwargs.get("stream", False),
218
+ streaming=streaming,
219
+ **other_kwargs,
207
220
  )
208
221
  async for _ in agent_runner.step_until_done(max_steps):
209
222
  pass
@@ -12,6 +12,7 @@ from .star_handler import (
12
12
  register_on_llm_request,
13
13
  register_on_llm_response,
14
14
  register_on_platform_loaded,
15
+ register_on_waiting_llm_request,
15
16
  register_permission_type,
16
17
  register_platform_adapter_type,
17
18
  register_regex,
@@ -30,6 +31,7 @@ __all__ = [
30
31
  "register_on_llm_request",
31
32
  "register_on_llm_response",
32
33
  "register_on_platform_loaded",
34
+ "register_on_waiting_llm_request",
33
35
  "register_permission_type",
34
36
  "register_platform_adapter_type",
35
37
  "register_regex",
@@ -339,6 +339,30 @@ def register_on_platform_loaded(**kwargs):
339
339
  return decorator
340
340
 
341
341
 
342
+ def register_on_waiting_llm_request(**kwargs):
343
+ """当等待调用 LLM 时的通知事件(在获取锁之前)
344
+
345
+ 此钩子在消息确定要调用 LLM 但还未开始排队等锁时触发,
346
+ 适合用于发送"正在思考中..."等用户反馈提示。
347
+
348
+ Examples:
349
+ ```py
350
+ @on_waiting_llm_request()
351
+ async def on_waiting_llm(self, event: AstrMessageEvent) -> None:
352
+ await event.send("🤔 正在思考中...")
353
+ ```
354
+
355
+ """
356
+
357
+ def decorator(awaitable):
358
+ _ = get_handler_or_create(
359
+ awaitable, EventType.OnWaitingLLMRequestEvent, **kwargs
360
+ )
361
+ return awaitable
362
+
363
+ return decorator
364
+
365
+
342
366
  def register_on_llm_request(**kwargs):
343
367
  """当有 LLM 请求时的事件
344
368
 
@@ -12,7 +12,7 @@ class SessionServiceManager:
12
12
  # =============================================================================
13
13
 
14
14
  @staticmethod
15
- def is_llm_enabled_for_session(session_id: str) -> bool:
15
+ async def is_llm_enabled_for_session(session_id: str) -> bool:
16
16
  """检查LLM是否在指定会话中启用
17
17
 
18
18
  Args:
@@ -23,11 +23,11 @@ class SessionServiceManager:
23
23
 
24
24
  """
25
25
  # 获取会话服务配置
26
- session_services = sp.get(
27
- "session_service_config",
28
- {},
26
+ session_services = await sp.get_async(
29
27
  scope="umo",
30
28
  scope_id=session_id,
29
+ key="session_service_config",
30
+ default={},
31
31
  )
32
32
 
33
33
  # 如果配置了该会话的LLM状态,返回该状态
@@ -39,7 +39,7 @@ class SessionServiceManager:
39
39
  return True
40
40
 
41
41
  @staticmethod
42
- def set_llm_status_for_session(session_id: str, enabled: bool) -> None:
42
+ async def set_llm_status_for_session(session_id: str, enabled: bool) -> None:
43
43
  """设置LLM在指定会话中的启停状态
44
44
 
45
45
  Args:
@@ -48,18 +48,24 @@ class SessionServiceManager:
48
48
 
49
49
  """
50
50
  session_config = (
51
- sp.get("session_service_config", {}, scope="umo", scope_id=session_id) or {}
51
+ await sp.get_async(
52
+ scope="umo",
53
+ scope_id=session_id,
54
+ key="session_service_config",
55
+ default={},
56
+ )
57
+ or {}
52
58
  )
53
59
  session_config["llm_enabled"] = enabled
54
- sp.put(
55
- "session_service_config",
56
- session_config,
60
+ await sp.put_async(
57
61
  scope="umo",
58
62
  scope_id=session_id,
63
+ key="session_service_config",
64
+ value=session_config,
59
65
  )
60
66
 
61
67
  @staticmethod
62
- def should_process_llm_request(event: AstrMessageEvent) -> bool:
68
+ async def should_process_llm_request(event: AstrMessageEvent) -> bool:
63
69
  """检查是否应该处理LLM请求
64
70
 
65
71
  Args:
@@ -70,14 +76,14 @@ class SessionServiceManager:
70
76
 
71
77
  """
72
78
  session_id = event.unified_msg_origin
73
- return SessionServiceManager.is_llm_enabled_for_session(session_id)
79
+ return await SessionServiceManager.is_llm_enabled_for_session(session_id)
74
80
 
75
81
  # =============================================================================
76
82
  # TTS 相关方法
77
83
  # =============================================================================
78
84
 
79
85
  @staticmethod
80
- def is_tts_enabled_for_session(session_id: str) -> bool:
86
+ async def is_tts_enabled_for_session(session_id: str) -> bool:
81
87
  """检查TTS是否在指定会话中启用
82
88
 
83
89
  Args:
@@ -88,11 +94,11 @@ class SessionServiceManager:
88
94
 
89
95
  """
90
96
  # 获取会话服务配置
91
- session_services = sp.get(
92
- "session_service_config",
93
- {},
97
+ session_services = await sp.get_async(
94
98
  scope="umo",
95
99
  scope_id=session_id,
100
+ key="session_service_config",
101
+ default={},
96
102
  )
97
103
 
98
104
  # 如果配置了该会话的TTS状态,返回该状态
@@ -104,7 +110,7 @@ class SessionServiceManager:
104
110
  return True
105
111
 
106
112
  @staticmethod
107
- def set_tts_status_for_session(session_id: str, enabled: bool) -> None:
113
+ async def set_tts_status_for_session(session_id: str, enabled: bool) -> None:
108
114
  """设置TTS在指定会话中的启停状态
109
115
 
110
116
  Args:
@@ -113,14 +119,20 @@ class SessionServiceManager:
113
119
 
114
120
  """
115
121
  session_config = (
116
- sp.get("session_service_config", {}, scope="umo", scope_id=session_id) or {}
122
+ await sp.get_async(
123
+ scope="umo",
124
+ scope_id=session_id,
125
+ key="session_service_config",
126
+ default={},
127
+ )
128
+ or {}
117
129
  )
118
130
  session_config["tts_enabled"] = enabled
119
- sp.put(
120
- "session_service_config",
121
- session_config,
131
+ await sp.put_async(
122
132
  scope="umo",
123
133
  scope_id=session_id,
134
+ key="session_service_config",
135
+ value=session_config,
124
136
  )
125
137
 
126
138
  logger.info(
@@ -128,7 +140,7 @@ class SessionServiceManager:
128
140
  )
129
141
 
130
142
  @staticmethod
131
- def should_process_tts_request(event: AstrMessageEvent) -> bool:
143
+ async def should_process_tts_request(event: AstrMessageEvent) -> bool:
132
144
  """检查是否应该处理TTS请求
133
145
 
134
146
  Args:
@@ -139,14 +151,14 @@ class SessionServiceManager:
139
151
 
140
152
  """
141
153
  session_id = event.unified_msg_origin
142
- return SessionServiceManager.is_tts_enabled_for_session(session_id)
154
+ return await SessionServiceManager.is_tts_enabled_for_session(session_id)
143
155
 
144
156
  # =============================================================================
145
157
  # 会话整体启停相关方法
146
158
  # =============================================================================
147
159
 
148
160
  @staticmethod
149
- def is_session_enabled(session_id: str) -> bool:
161
+ async def is_session_enabled(session_id: str) -> bool:
150
162
  """检查会话是否整体启用
151
163
 
152
164
  Args:
@@ -157,11 +169,11 @@ class SessionServiceManager:
157
169
 
158
170
  """
159
171
  # 获取会话服务配置
160
- session_services = sp.get(
161
- "session_service_config",
162
- {},
172
+ session_services = await sp.get_async(
163
173
  scope="umo",
164
174
  scope_id=session_id,
175
+ key="session_service_config",
176
+ default={},
165
177
  )
166
178
 
167
179
  # 如果配置了该会话的整体状态,返回该状态
@@ -8,7 +8,10 @@ class SessionPluginManager:
8
8
  """管理会话级别的插件启停状态"""
9
9
 
10
10
  @staticmethod
11
- def is_plugin_enabled_for_session(session_id: str, plugin_name: str) -> bool:
11
+ async def is_plugin_enabled_for_session(
12
+ session_id: str,
13
+ plugin_name: str,
14
+ ) -> bool:
12
15
  """检查插件是否在指定会话中启用
13
16
 
14
17
  Args:
@@ -20,11 +23,11 @@ class SessionPluginManager:
20
23
 
21
24
  """
22
25
  # 获取会话插件配置
23
- session_plugin_config = sp.get(
24
- "session_plugin_config",
25
- {},
26
+ session_plugin_config = await sp.get_async(
26
27
  scope="umo",
27
28
  scope_id=session_id,
29
+ key="session_plugin_config",
30
+ default={},
28
31
  )
29
32
  session_config = session_plugin_config.get(session_id, {})
30
33
 
@@ -43,7 +46,10 @@ class SessionPluginManager:
43
46
  return True
44
47
 
45
48
  @staticmethod
46
- def filter_handlers_by_session(event: AstrMessageEvent, handlers: list) -> list:
49
+ async def filter_handlers_by_session(
50
+ event: AstrMessageEvent,
51
+ handlers: list,
52
+ ) -> list:
47
53
  """根据会话配置过滤处理器列表
48
54
 
49
55
  Args:
@@ -59,6 +65,15 @@ class SessionPluginManager:
59
65
  session_id = event.unified_msg_origin
60
66
  filtered_handlers = []
61
67
 
68
+ session_plugin_config = await sp.get_async(
69
+ scope="umo",
70
+ scope_id=session_id,
71
+ key="session_plugin_config",
72
+ default={},
73
+ )
74
+ session_config = session_plugin_config.get(session_id, {})
75
+ disabled_plugins = session_config.get("disabled_plugins", [])
76
+
62
77
  for handler in handlers:
63
78
  # 获取处理器对应的插件
64
79
  plugin = star_map.get(handler.handler_module_path)
@@ -76,14 +91,11 @@ class SessionPluginManager:
76
91
  continue
77
92
 
78
93
  # 检查插件是否在当前会话中启用
79
- if SessionPluginManager.is_plugin_enabled_for_session(
80
- session_id,
81
- plugin.name,
82
- ):
83
- filtered_handlers.append(handler)
84
- else:
94
+ if plugin.name in disabled_plugins:
85
95
  logger.debug(
86
96
  f"插件 {plugin.name} 在会话 {session_id} 中被禁用,跳过处理器 {handler.handler_name}",
87
97
  )
98
+ else:
99
+ filtered_handlers.append(handler)
88
100
 
89
101
  return filtered_handlers
@@ -184,6 +184,7 @@ class EventType(enum.Enum):
184
184
  OnPlatformLoadedEvent = enum.auto() # 平台加载完成
185
185
 
186
186
  AdapterMessageEvent = enum.auto() # 收到适配器发来的消息
187
+ OnWaitingLLMRequestEvent = enum.auto() # 等待调用 LLM(在获取锁之前,仅通知)
187
188
  OnLLMRequestEvent = enum.auto() # 收到 LLM 请求(可以是用户也可以是插件)
188
189
  OnLLMResponseEvent = enum.auto() # LLM 响应后
189
190
  OnDecoratingResultEvent = enum.auto() # 发送消息前
@@ -11,14 +11,15 @@ class UmopConfigRouter:
11
11
  """UMOP 到配置文件 ID 的映射"""
12
12
  self.sp = sp
13
13
 
14
- self._load_routing_table()
14
+ async def initialize(self):
15
+ await self._load_routing_table()
15
16
 
16
- def _load_routing_table(self):
17
+ async def _load_routing_table(self):
17
18
  """加载路由表"""
18
19
  # 从 SharedPreferences 中加载 umop_to_conf_id 映射
19
- sp_data = self.sp.get(
20
- "umop_config_routing",
21
- {},
20
+ sp_data = await self.sp.get_async(
21
+ key="umop_config_routing",
22
+ default={},
22
23
  scope="global",
23
24
  scope_id="global",
24
25
  )
@@ -3,6 +3,7 @@ import traceback
3
3
  from astrbot.core import astrbot_config, logger
4
4
  from astrbot.core.astrbot_config_mgr import AstrBotConfig, AstrBotConfigManager
5
5
  from astrbot.core.db.migration.migra_45_to_46 import migrate_45_to_46
6
+ from astrbot.core.db.migration.migra_token_usage import migrate_token_usage
6
7
  from astrbot.core.db.migration.migra_webchat_session import migrate_webchat_session
7
8
 
8
9
 
@@ -139,6 +140,13 @@ async def migra(
139
140
  logger.error(f"Migration for webchat session failed: {e!s}")
140
141
  logger.error(traceback.format_exc())
141
142
 
143
+ # migration for token_usage column
144
+ try:
145
+ await migrate_token_usage(db)
146
+ except Exception as e:
147
+ logger.error(f"Migration for token_usage column failed: {e!s}")
148
+ logger.error(traceback.format_exc())
149
+
142
150
  # migra third party agent runner configs
143
151
  _c = False
144
152
  providers = astrbot_config["provider"]
@@ -993,6 +993,7 @@ class BackupRoute(Route):
993
993
  file_path,
994
994
  as_attachment=True,
995
995
  attachment_filename=filename,
996
+ conditional=True, # 启用 Range 请求支持(断点续传)
996
997
  )
997
998
  except Exception as e:
998
999
  logger.error(f"下载备份失败: {e}")
@@ -625,7 +625,7 @@ class ConfigRoute(Route):
625
625
  provider_list = []
626
626
  ps = self.core_lifecycle.provider_manager.providers_config
627
627
  p_source_pt = {
628
- psrc["id"]: psrc["provider_type"]
628
+ psrc["id"]: psrc.get("provider_type", "chat_completion")
629
629
  for psrc in self.core_lifecycle.provider_manager.provider_sources_config
630
630
  }
631
631
  for provider in ps:
@@ -640,7 +640,7 @@ class ConfigRoute(Route):
640
640
  provider
641
641
  )
642
642
  provider_list.append(prov)
643
- elif not ps_id and provider.get("provider_type", None) in provider_type_ls:
643
+ elif not ps_id and provider.get("provider_type", "") in provider_type_ls:
644
644
  # agent runner, embedding, etc
645
645
  provider_list.append(provider)
646
646
  return Response().ok(provider_list).__dict__
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: AstrBot
3
- Version: 4.10.6
3
+ Version: 4.11.1
4
4
  Summary: Easy-to-use multi-platform LLM chatbot and development framework
5
5
  License-File: LICENSE
6
6
  Keywords: Astrbot,Astrbot Module,Astrbot Plugin
@@ -193,6 +193,7 @@ uv run main.py
193
193
 
194
194
  **社区维护**
195
195
 
196
+ - [Matrix](https://github.com/stevessr/astrbot_plugin_matrix_adapter)
196
197
  - [KOOK](https://github.com/wuyan1003/astrbot_plugin_kook_adapter)
197
198
  - [VoceChat](https://github.com/HikariFroya/astrbot_plugin_vocechat)
198
199
  - [Bilibili 私信](https://github.com/Hina-Chat/astrbot_plugin_bilibili_adapter)
@@ -269,6 +270,7 @@ pre-commit install
269
270
  - 5 群:822130018
270
271
  - 6 群:753075035
271
272
  - 7 群:743746109
273
+ - 8 群:1030353265
272
274
  - 开发者群:975206796
273
275
 
274
276
  ### Telegram 群组