Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion astrbot/builtin_stars/astrbot/group_chat_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ async def get_image_caption(
image_caption_prompt: str,
) -> str:
if not image_caption_provider_id:
provider = self.context.get_using_provider()
provider = await self.context.get_using_provider_async()
else:
provider = self.context.get_provider_by_id(image_caption_provider_id)
if not provider:
Expand Down
4 changes: 3 additions & 1 deletion astrbot/builtin_stars/astrbot/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,9 @@ async def on_message(self, event: AstrMessageEvent):
logger.error(e)

if need_active:
provider = self.context.get_using_provider(event.unified_msg_origin)
provider = await self.context.get_using_provider_async(
event.unified_msg_origin
)
if not provider:
logger.error("未找到任何 LLM 提供商。请先配置。无法主动回复")
return
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ async def reset(self, message: AstrMessageEvent) -> None:
)
return

if not self.context.get_using_provider(umo):
if not await self.context.get_using_provider_async(umo):
message.set_result(
MessageEventResult().message(
"😕 Cannot find any LLM provider. Configure one first."
Expand Down
6 changes: 3 additions & 3 deletions astrbot/builtin_stars/builtin_commands/commands/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ async def provider(
),
)

provider_using = self.context.get_using_provider(umo=umo)
provider_using = await self.context.get_using_provider_async(umo=umo)
for i, d in enumerate(llm_data):
line = f"{i + 1}. {d['info']}{d['mark']}"
if (
Expand All @@ -161,7 +161,7 @@ async def provider(

if tts_data:
parts.append("\n## TTS Providers\n")
tts_using = self.context.get_using_tts_provider(umo=umo)
tts_using = await self.context.get_using_tts_provider_async(umo=umo)
for i, d in enumerate(tts_data):
line = f"{i + 1}. {d['info']}{d['mark']}"
if tts_using and tts_using.meta().id == d["provider"].meta().id:
Expand All @@ -170,7 +170,7 @@ async def provider(

if stt_data:
parts.append("\n## STT Providers\n")
stt_using = self.context.get_using_stt_provider(umo=umo)
stt_using = await self.context.get_using_stt_provider_async(umo=umo)
for i, d in enumerate(stt_data):
line = f"{i + 1}. {d['info']}{d['mark']}"
if stt_using and stt_using.meta().id == d["provider"].meta().id:
Expand Down
44 changes: 36 additions & 8 deletions astrbot/core/astr_main_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -227,10 +227,18 @@ def _set_llm_error_message(event: AstrMessageEvent, message: str) -> None:
event.set_extra(LLM_ERROR_MESSAGE_EXTRA_KEY, message)


def _select_provider(
async def _select_provider(
event: AstrMessageEvent, plugin_context: Context
) -> Provider | None:
"""Select chat provider for the event."""
"""Select the chat provider for an event.

Args:
event: Message event that may contain an explicit provider selection.
plugin_context: Plugin context used to resolve configured providers.

Returns:
Selected chat provider, or None if selection fails.
"""
sel_provider = event.get_extra("selected_provider")
if sel_provider and isinstance(sel_provider, str):
provider = plugin_context.get_provider_by_id(sel_provider)
Expand All @@ -252,7 +260,9 @@ def _select_provider(
return None
return provider
try:
return plugin_context.get_using_provider(umo=event.unified_msg_origin)
return await plugin_context.get_using_provider_async(
umo=event.unified_msg_origin
)
except ValueError as exc:
logger.error("Error occurred while selecting provider: %s", exc)
_set_llm_error_message(event, f"LLM 请求失败:{exc}")
Expand Down Expand Up @@ -916,7 +926,9 @@ async def _process_quote_message(
compress_path = None
prov = plugin_context.get_provider_by_id(img_cap_prov_id)
if prov is None:
prov = plugin_context.get_using_provider(event.unified_msg_origin)
prov = await plugin_context.get_using_provider_async(
event.unified_msg_origin
)

if prov and isinstance(prov, Provider):
path = await image_seg.convert_to_file_path()
Expand Down Expand Up @@ -1292,11 +1304,21 @@ def _apply_web_search_citation_prompt(
req.system_prompt = f"{system_prompt}\n{WEB_SEARCH_CITATION_PROMPT}\n"


def _get_compress_provider(
async def _get_compress_provider(
config: MainAgentBuildConfig,
plugin_context: Context,
event: AstrMessageEvent | None = None,
) -> Provider | None:
"""Resolve the provider used for context compression.

Args:
config: Main agent build configuration.
plugin_context: Plugin context used to resolve providers.
event: Optional event used for session-specific fallback selection.

Returns:
Compression provider, or None if compression is disabled or unavailable.
"""
if config.context_limit_reached_strategy != "llm_compress":
return None
if config.llm_compress_provider_id:
Expand All @@ -1310,7 +1332,9 @@ def _get_compress_provider(
# fallback: use current chat provider for this session
if event:
try:
return plugin_context.get_using_provider(umo=event.unified_msg_origin)
return await plugin_context.get_using_provider_async(
umo=event.unified_msg_origin
)
except ValueError:
pass
return None
Expand Down Expand Up @@ -1398,7 +1422,7 @@ async def build_main_agent(

If apply_reset is False, will not call reset on the agent runner.
"""
provider = provider or _select_provider(event, plugin_context)
provider = provider or await _select_provider(event, plugin_context)
if provider is None:
logger.info("未找到任何对话模型(提供商),跳过 LLM 请求处理。")
if not event.get_extra(LLM_ERROR_MESSAGE_EXTRA_KEY):
Expand Down Expand Up @@ -1699,7 +1723,11 @@ async def build_main_agent(
streaming=config.streaming_response,
llm_compress_instruction=config.llm_compress_instruction,
llm_compress_keep_recent_ratio=config.llm_compress_keep_recent_ratio,
llm_compress_provider=_get_compress_provider(config, plugin_context, event),
llm_compress_provider=await _get_compress_provider(
config,
plugin_context,
event,
),
truncate_turns=config.dequeue_context_length,
enforce_max_turns=config.max_context_length,
tool_schema_mode=config.tool_schema_mode,
Expand Down
6 changes: 6 additions & 0 deletions astrbot/core/core_lifecycle.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,8 @@ async def initialize(self) -> None:
LogManager.configure_trace_logger(self.astrbot_config)

await self.db.initialize()
if sp.db_helper is self.db:
await sp.initialize()

await html_renderer.initialize()

Expand Down Expand Up @@ -404,6 +406,8 @@ async def stop(self) -> None:
await self.provider_manager.terminate()
await self.platform_manager.terminate()
await self.kb_manager.terminate()
if sp.db_helper is self.db:
await sp.close()
self.dashboard_shutdown_event.set()

# 再次遍历curr_tasks等待每个任务真正结束
Expand All @@ -427,6 +431,8 @@ async def restart(self) -> None:
await self.provider_manager.terminate()
await self.platform_manager.terminate()
await self.kb_manager.terminate()
if sp.db_helper is self.db:
await sp.close()
self.dashboard_shutdown_event.set()
threading.Thread(
target=restart_process,
Expand Down
4 changes: 2 additions & 2 deletions astrbot/core/db/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -577,11 +577,11 @@ async def get_preference(self, scope: str, scope_id: str, key: str) -> Preferenc
@abc.abstractmethod
async def get_preferences(
self,
scope: str,
scope: str | None = None,
scope_id: str | None = None,
key: str | None = None,
) -> list[Preference]:
"""Get all preferences for a specific scope ID or key."""
"""Get preferences, optionally filtered by scope, scope ID, or key."""
...

@abc.abstractmethod
Expand Down
8 changes: 5 additions & 3 deletions astrbot/core/db/sqlite.py
Original file line number Diff line number Diff line change
Expand Up @@ -1379,11 +1379,13 @@ async def get_preference(self, scope, scope_id, key):
result = await session.execute(query)
return result.scalar_one_or_none()

async def get_preferences(self, scope, scope_id=None, key=None):
"""Get all preferences for a specific scope ID or key."""
async def get_preferences(self, scope=None, scope_id=None, key=None):
"""Get preferences, optionally filtered by scope, scope ID, or key."""
async with self.get_db() as session:
session: AsyncSession
query = select(Preference).where(Preference.scope == scope)
query = select(Preference)
if scope is not None:
query = query.where(Preference.scope == scope)
if scope_id is not None:
query = query.where(Preference.scope_id == scope_id)
if key is not None:
Expand Down
4 changes: 3 additions & 1 deletion astrbot/core/pipeline/preprocess_stage/stage.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,9 @@ async def process(
if self.stt_settings.get("enable", False):
# TODO: 独立
ctx = self.plugin_manager.context
stt_provider = ctx.get_using_stt_provider(event.unified_msg_origin)
stt_provider = await ctx.get_using_stt_provider_async(
event.unified_msg_origin
)
if not stt_provider:
logger.warning(
f"Session {event.unified_msg_origin} has no speech-to-text "
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -298,10 +298,8 @@ async def process(
)

# 获取 TTS Provider
tts_provider = (
self.ctx.plugin_manager.context.get_using_tts_provider(
event.unified_msg_origin
)
tts_provider = await self.ctx.plugin_manager.context.get_using_tts_provider_async(
event.unified_msg_origin
)

if not tts_provider:
Expand Down
6 changes: 4 additions & 2 deletions astrbot/core/pipeline/result_decorate/stage.py
Original file line number Diff line number Diff line change
Expand Up @@ -267,8 +267,10 @@ async def process(
result.chain = new_chain

# TTS
tts_provider = self.ctx.plugin_manager.context.get_using_tts_provider(
event.unified_msg_origin,
tts_provider = (
await self.ctx.plugin_manager.context.get_using_tts_provider_async(
event.unified_msg_origin,
)
)

should_tts = (
Expand Down
74 changes: 74 additions & 0 deletions astrbot/core/provider/func_tool_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -983,6 +983,7 @@ def get_func_desc_google_genai_style(self) -> dict:
toolset = ToolSet(tools)
return toolset.google_schema()

@deprecated(reason="Use deactivate_llm_tool_async() instead.")
def deactivate_llm_tool(self, name: str) -> bool:
"""停用一个已经注册的函数调用工具。

Expand Down Expand Up @@ -1012,7 +1013,39 @@ def deactivate_llm_tool(self, name: str) -> bool:
return True
return False

async def deactivate_llm_tool_async(self, name: str) -> bool:
"""Asynchronously deactivate a registered function-calling tool.

Args:
name: Tool name.

Returns:
True when the tool was deactivated, or False when it was not found.
"""
func_tool = self.get_func(name)
if func_tool is not None:
func_tool.active = False

inactivated_llm_tools: list = await sp.get_async(
"global",
"global",
"inactivated_llm_tools",
[],
)
if name not in inactivated_llm_tools:
inactivated_llm_tools.append(name)
await sp.put_async(
"global",
"global",
"inactivated_llm_tools",
inactivated_llm_tools,
)

return True
return False

# 因为不想解决循环引用,所以这里直接传入 star_map 先了...
@deprecated(reason="Use activate_llm_tool_async() instead.")
def activate_llm_tool(self, name: str, star_map: dict) -> bool:
func_tool = self.get_func(name)
if func_tool is not None:
Expand Down Expand Up @@ -1042,6 +1075,47 @@ def activate_llm_tool(self, name: str, star_map: dict) -> bool:
return True
return False

async def activate_llm_tool_async(self, name: str, star_map: dict) -> bool:
"""Asynchronously activate a registered function-calling tool.

Args:
name: Tool name.
star_map: Loaded plugins indexed by module path.

Returns:
True when the tool was activated, or False when it was not found.

Raises:
ValueError: If the plugin that owns the tool is disabled.
"""
func_tool = self.get_func(name)
if func_tool is not None:
if func_tool.handler_module_path in star_map:
if not star_map[func_tool.handler_module_path].activated:
raise ValueError(
f"此函数调用工具所属的插件 {star_map[func_tool.handler_module_path].name} 已被禁用,请先在管理面板启用再激活此工具。",
)

func_tool.active = True

inactivated_llm_tools: list = await sp.get_async(
"global",
"global",
"inactivated_llm_tools",
[],
)
if name in inactivated_llm_tools:
inactivated_llm_tools.remove(name)
await sp.put_async(
"global",
"global",
"inactivated_llm_tools",
inactivated_llm_tools,
)

return True
return False

@property
def mcp_config_path(self):
data_dir = get_astrbot_data_path()
Expand Down
Loading
Loading