diff --git a/astrbot/api/event/filter/__init__.py b/astrbot/api/event/filter/__init__.py
index f5ab15ed0..71b21a445 100644
--- a/astrbot/api/event/filter/__init__.py
+++ b/astrbot/api/event/filter/__init__.py
@@ -55,14 +55,14 @@ __all__ = [
"on_decorating_result",
"on_llm_request",
"on_llm_response",
+ "on_llm_tool_respond",
+ "on_platform_loaded",
"on_plugin_error",
"on_plugin_loaded",
"on_plugin_unloaded",
- "on_platform_loaded",
+ "on_using_llm_tool",
"on_waiting_llm_request",
"permission_type",
"platform_adapter_type",
"regex",
- "on_using_llm_tool",
- "on_llm_tool_respond",
]
diff --git a/astrbot/builtin_stars/astrbot/long_term_memory.py b/astrbot/builtin_stars/astrbot/long_term_memory.py
index e08cdc515..ed97e408a 100644
--- a/astrbot/builtin_stars/astrbot/long_term_memory.py
+++ b/astrbot/builtin_stars/astrbot/long_term_memory.py
@@ -32,10 +32,10 @@ class LongTermMemory:
max_cnt = 300
image_caption_prompt = cfg["provider_settings"]["image_caption_prompt"]
image_caption_provider_id = cfg["provider_ltm_settings"].get(
- "image_caption_provider_id"
+ "image_caption_provider_id",
)
image_caption = cfg["provider_ltm_settings"]["image_caption"] and bool(
- image_caption_provider_id
+ image_caption_provider_id,
)
active_reply = cfg["provider_ltm_settings"]["active_reply"]
enable_active_reply = active_reply.get("enable", False)
@@ -126,7 +126,7 @@ class LongTermMemory:
elif isinstance(comp, Image):
if cfg["image_caption"]:
try:
- url = comp.url if comp.url else comp.file
+ url = comp.url or comp.file
if not url:
raise Exception("图片 URL 为空")
caption = await self.get_image_caption(
@@ -172,7 +172,7 @@ class LongTermMemory:
req.system_prompt += chats_str
async def after_req_llm(
- self, event: AstrMessageEvent, llm_resp: LLMResponse
+ self, event: AstrMessageEvent, llm_resp: LLMResponse,
) -> None:
if event.unified_msg_origin not in self.session_chats:
return
@@ -180,7 +180,7 @@ class LongTermMemory:
if llm_resp.completion_text:
final_message = f"[You/{datetime.datetime.now().strftime('%H:%M:%S')}]: {llm_resp.completion_text}"
logger.debug(
- f"Recorded AI response: {event.unified_msg_origin} | {final_message}"
+ f"Recorded AI response: {event.unified_msg_origin} | {final_message}",
)
self.session_chats[event.unified_msg_origin].append(final_message)
cfg = self.cfg(event)
diff --git a/astrbot/builtin_stars/astrbot/main.py b/astrbot/builtin_stars/astrbot/main.py
index da2a00835..b5b793ca4 100644
--- a/astrbot/builtin_stars/astrbot/main.py
+++ b/astrbot/builtin_stars/astrbot/main.py
@@ -86,7 +86,7 @@ class Main(star.Star):
@filter.on_llm_request()
async def decorate_llm_req(
- self, event: AstrMessageEvent, req: ProviderRequest
+ self, event: AstrMessageEvent, req: ProviderRequest,
) -> None:
"""在请求 LLM 前注入人格信息、Identifier、时间、回复内容等 System Prompt"""
if self.ltm and self.ltm_enabled(event):
@@ -97,7 +97,7 @@ class Main(star.Star):
@filter.on_llm_response()
async def record_llm_resp_to_ltm(
- self, event: AstrMessageEvent, resp: LLMResponse
+ self, event: AstrMessageEvent, resp: LLMResponse,
) -> None:
"""在 LLM 响应后记录对话"""
if self.ltm and self.ltm_enabled(event):
diff --git a/astrbot/builtin_stars/builtin_commands/commands/conversation.py b/astrbot/builtin_stars/builtin_commands/commands/conversation.py
index 5190a363e..cc771840c 100644
--- a/astrbot/builtin_stars/builtin_commands/commands/conversation.py
+++ b/astrbot/builtin_stars/builtin_commands/commands/conversation.py
@@ -124,8 +124,8 @@ class ConversationCommands:
if stopped_count > 0:
message.set_result(
MessageEventResult().message(
- f"已请求停止 {stopped_count} 个运行中的任务。"
- )
+ f"已请求停止 {stopped_count} 个运行中的任务。",
+ ),
)
return
@@ -207,7 +207,7 @@ class ConversationCommands:
"""生成所有对话的标题字典"""
_titles = {}
for conv in conversations_all:
- title = conv.title if conv.title else "新对话"
+ title = conv.title or "新对话"
_titles[conv.cid] = title
"""遍历分页后的对话生成列表显示"""
@@ -237,7 +237,7 @@ class ConversationCommands:
title = _titles.get(conv.cid, "新对话")
parts.append(
- f"{global_index}. {title}({conv.cid[:4]})\n 人格情景: {persona_name}\n 上次更新: {datetime.datetime.fromtimestamp(conv.updated_at).strftime('%m-%d %H:%M')}\n"
+ f"{global_index}. {title}({conv.cid[:4]})\n 人格情景: {persona_name}\n 上次更新: {datetime.datetime.fromtimestamp(conv.updated_at).strftime('%m-%d %H:%M')}\n",
)
global_index += 1
@@ -349,7 +349,7 @@ class ConversationCommands:
)
else:
conversation = conversations[index - 1]
- title = conversation.title if conversation.title else "新对话"
+ title = conversation.title or "新对话"
await self.context.conversation_manager.switch_conversation(
message.unified_msg_origin,
conversation.cid,
diff --git a/astrbot/builtin_stars/builtin_commands/commands/help.py b/astrbot/builtin_stars/builtin_commands/commands/help.py
index ae2f4c787..f147af160 100644
--- a/astrbot/builtin_stars/builtin_commands/commands/help.py
+++ b/astrbot/builtin_stars/builtin_commands/commands/help.py
@@ -23,8 +23,7 @@ class HelpCommand:
return ""
async def _build_reserved_command_lines(self) -> list[str]:
- """
- 使用实时指令配置生成内置指令清单,确保重命名/禁用后与实际生效状态保持一致。
+ """使用实时指令配置生成内置指令清单,确保重命名/禁用后与实际生效状态保持一致。
"""
try:
commands = await command_management.list_commands()
diff --git a/astrbot/builtin_stars/builtin_commands/commands/persona.py b/astrbot/builtin_stars/builtin_commands/commands/persona.py
index 7a7416bba..d1b8be24e 100644
--- a/astrbot/builtin_stars/builtin_commands/commands/persona.py
+++ b/astrbot/builtin_stars/builtin_commands/commands/persona.py
@@ -45,7 +45,7 @@ class PersonaCommands:
children,
all_personas,
depth + 1,
- )
+ ),
)
return lines
@@ -100,7 +100,7 @@ class PersonaCommands:
if force_applied_persona_id:
curr_persona_name = f"{curr_persona_name} (自定义规则)"
- curr_cid_title = conv.title if conv.title else "新对话"
+ curr_cid_title = conv.title or "新对话"
curr_cid_title += f"({cid[:4]})"
if len(l) == 1:
diff --git a/astrbot/builtin_stars/builtin_commands/commands/provider.py b/astrbot/builtin_stars/builtin_commands/commands/provider.py
index b5ee75ca2..166bf6907 100644
--- a/astrbot/builtin_stars/builtin_commands/commands/provider.py
+++ b/astrbot/builtin_stars/builtin_commands/commands/provider.py
@@ -48,7 +48,7 @@ class _ModelCache:
return models
def set(
- self, provider_id: str, umo: str | None, models: list[str], ttl: float
+ self, provider_id: str, umo: str | None, models: list[str], ttl: float,
) -> None:
if ttl <= 0:
return
@@ -67,7 +67,7 @@ class _ModelCache:
self._store.pop(key, None)
def invalidate(
- self, provider_id: str | None = None, *, umo: str | None = None
+ self, provider_id: str | None = None, *, umo: str | None = None,
) -> None:
if provider_id is None:
self._store.clear()
@@ -106,7 +106,7 @@ class ProviderCommands:
register_change_hook(self._on_provider_manager_changed)
def invalidate_provider_models_cache(
- self, provider_id: str | None = None, *, umo: str | None = None
+ self, provider_id: str | None = None, *, umo: str | None = None,
) -> None:
"""Public hook for cache invalidation on external provider config changes."""
self._model_cache.invalidate(provider_id, umo=umo)
@@ -198,14 +198,14 @@ class ProviderCommands:
for candidate in models:
cand_norm = candidate.casefold()
if cand_norm.endswith(f"/{requested_norm}") or cand_norm.endswith(
- f":{requested_norm}"
+ f":{requested_norm}",
):
return candidate
return None
def _apply_model(
- self, prov: Provider, model_name: str, *, umo: str | None = None
+ self, prov: Provider, model_name: str, *, umo: str | None = None,
) -> str:
prov.set_model(model_name)
self.invalidate_provider_models_cache(prov.meta().id, umo=umo)
@@ -287,7 +287,7 @@ class ProviderCommands:
err_code = "TEST_FAILED"
err_reason = safe_error("", e)
self._log_reachability_failure(
- provider, provider_capability_type, err_code, err_reason
+ provider, provider_capability_type, err_code, err_reason,
)
return False, err_code, err_reason
@@ -339,7 +339,7 @@ class ProviderCommands:
return provider, None, err
results = await asyncio.gather(
- *(fetch_models(provider) for provider in all_providers)
+ *(fetch_models(provider) for provider in all_providers),
)
failed_provider_errors: list[tuple[str, str]] = []
for provider, models, err in results:
@@ -405,8 +405,8 @@ class ProviderCommands:
if all_providers:
await event.send(
MessageEventResult().message(
- "正在进行提供商可达性测试,请稍候..."
- )
+ "正在进行提供商可达性测试,请稍候...",
+ ),
)
check_results = await asyncio.gather(
*[self._test_provider_capability(p) for p, _ in all_providers],
@@ -463,7 +463,7 @@ class ProviderCommands:
"info": info,
"mark": mark,
"provider": p,
- }
+ },
)
# 分组输出
@@ -558,7 +558,7 @@ class ProviderCommands:
event.set_result(MessageEventResult().message("无效的参数。"))
async def _switch_model_by_name(
- self, message: AstrMessageEvent, model_name: str, prov: Provider
+ self, message: AstrMessageEvent, model_name: str, prov: Provider,
) -> None:
model_name = model_name.strip()
if not model_name:
@@ -583,7 +583,7 @@ class ProviderCommands:
if matched_model_name is not None:
message.set_result(
MessageEventResult().message(
- self._apply_model(prov, matched_model_name, umo=umo)
+ self._apply_model(prov, matched_model_name, umo=umo),
),
)
return
@@ -620,7 +620,7 @@ class ProviderCommands:
except Exception as e:
message.set_result(
MessageEventResult().message(
- safe_error("跨提供商切换并设置模型失败: ", e)
+ safe_error("跨提供商切换并设置模型失败: ", e),
),
)
@@ -655,7 +655,7 @@ class ProviderCommands:
curr_model = prov.get_model() or "无"
parts.append(f"\n当前模型: [{curr_model}]")
parts.append(
- "\nTips: 使用 /model <模型名/编号> 切换模型。输入模型名时可自动跨提供商查找并切换;跨提供商也可使用 /provider 切换。"
+ "\nTips: 使用 /model <模型名/编号> 切换模型。输入模型名时可自动跨提供商查找并切换;跨提供商也可使用 /provider 切换。",
)
ret = "".join(parts)
@@ -680,13 +680,13 @@ class ProviderCommands:
prov,
new_model,
umo=message.unified_msg_origin,
- )
+ ),
),
)
except Exception as e:
message.set_result(
MessageEventResult().message(
- safe_error("切换模型未知错误: ", e)
+ safe_error("切换模型未知错误: ", e),
),
)
return
@@ -730,7 +730,7 @@ class ProviderCommands:
except Exception as e:
message.set_result(
MessageEventResult().message(
- safe_error("切换 Key 未知错误: ", e)
+ safe_error("切换 Key 未知错误: ", e),
),
)
return
diff --git a/astrbot/builtin_stars/builtin_commands/main.py b/astrbot/builtin_stars/builtin_commands/main.py
index fb4a83403..3e4b911b3 100644
--- a/astrbot/builtin_stars/builtin_commands/main.py
+++ b/astrbot/builtin_stars/builtin_commands/main.py
@@ -170,7 +170,7 @@ class Main(star.Star):
@filter.command("switch")
async def switch_conv(
- self, message: AstrMessageEvent, index: int | None = None
+ self, message: AstrMessageEvent, index: int | None = None,
) -> None:
"""通过 /ls 前面的序号切换对话"""
await self.conversation_c.switch_conv(message, index)
diff --git a/astrbot/cli/commands/cmd_conf.py b/astrbot/cli/commands/cmd_conf.py
index 5a39cb2f7..fea1a2316 100644
--- a/astrbot/cli/commands/cmd_conf.py
+++ b/astrbot/cli/commands/cmd_conf.py
@@ -50,7 +50,7 @@ def _validate_timezone(value: str) -> str:
zoneinfo.ZoneInfo(value)
except Exception:
raise click.ClickException(
- f"Invalid timezone: {value}. Please use a valid IANA timezone name"
+ f"Invalid timezone: {value}. Please use a valid IANA timezone name",
)
return value
@@ -59,7 +59,7 @@ def _validate_callback_api_base(value: str) -> str:
"""Validate callback API base URL"""
if not value.startswith("http://") and not value.startswith("https://"):
raise click.ClickException(
- "Callback API base must start with http:// or https://"
+ "Callback API base must start with http:// or https://",
)
return value
diff --git a/astrbot/cli/commands/cmd_init.py b/astrbot/cli/commands/cmd_init.py
index e7e047cca..44a68a5fe 100644
--- a/astrbot/cli/commands/cmd_init.py
+++ b/astrbot/cli/commands/cmd_init.py
@@ -48,7 +48,7 @@ def init() -> None:
click.echo("Done! You can now run 'astrbot run' to start AstrBot")
except Timeout:
raise click.ClickException(
- "Cannot acquire lock file. Please check if another instance is running"
+ "Cannot acquire lock file. Please check if another instance is running",
)
except Exception as e:
diff --git a/astrbot/cli/commands/cmd_plug.py b/astrbot/cli/commands/cmd_plug.py
index 46057fc6b..9b7a69f06 100644
--- a/astrbot/cli/commands/cmd_plug.py
+++ b/astrbot/cli/commands/cmd_plug.py
@@ -33,7 +33,7 @@ def display_plugins(plugins, title=None, color=None) -> None:
click.echo(click.style(title, fg=color, bold=True))
click.echo(
- f"{'Name':<20} {'Version':<10} {'Status':<10} {'Author':<15} {'Description':<30}"
+ f"{'Name':<20} {'Version':<10} {'Status':<10} {'Author':<15} {'Description':<30}",
)
click.echo("-" * 85)
@@ -84,7 +84,7 @@ def new(name: str) -> None:
# Rewrite README.md
with open(plug_path / "README.md", "w", encoding="utf-8") as f:
f.write(
- f"# {name}\n\n{desc}\n\n# Support\n\n[Documentation](https://astrbot.app)\n"
+ f"# {name}\n\n{desc}\n\n# Support\n\n[Documentation](https://astrbot.app)\n",
)
# Rewrite main.py
@@ -180,7 +180,7 @@ def remove(name: str) -> None:
plugin_path = plugin["local_path"]
click.confirm(
- f"Are you sure you want to uninstall plugin {name}?", default=False, abort=True
+ f"Are you sure you want to uninstall plugin {name}?", default=False, abort=True,
)
try:
@@ -211,7 +211,7 @@ def update(name: str, proxy: str | None) -> None:
if not plugin:
raise click.ClickException(
- f"Plugin {name} does not need updating or cannot be updated"
+ f"Plugin {name} does not need updating or cannot be updated",
)
manage_plugin(plugin, plug_path, is_update=True, proxy=proxy)
diff --git a/astrbot/cli/commands/cmd_run.py b/astrbot/cli/commands/cmd_run.py
index de09e5852..b250f5b11 100644
--- a/astrbot/cli/commands/cmd_run.py
+++ b/astrbot/cli/commands/cmd_run.py
@@ -58,7 +58,7 @@ def run(reload: bool, port: str) -> None:
click.echo("AstrBot has been shut down.")
except Timeout:
raise click.ClickException(
- "Cannot acquire lock file. Please check if another instance is running"
+ "Cannot acquire lock file. Please check if another instance is running",
)
except Exception as e:
raise click.ClickException(f"Runtime error: {e}\n{traceback.format_exc()}")
diff --git a/astrbot/cli/utils/plugin.py b/astrbot/cli/utils/plugin.py
index c06dda350..6eb71331d 100644
--- a/astrbot/cli/utils/plugin.py
+++ b/astrbot/cli/utils/plugin.py
@@ -32,7 +32,7 @@ def get_git_repo(url: str, target_path: Path, proxy: str | None = None) -> None:
release_url = f"https://api.github.com/repos/{author}/{repo}/releases"
try:
with httpx.Client(
- proxy=proxy if proxy else None,
+ proxy=proxy or None,
follow_redirects=True,
) as client:
resp = client.get(release_url)
@@ -56,7 +56,7 @@ def get_git_repo(url: str, target_path: Path, proxy: str | None = None) -> None:
# Download and extract
with httpx.Client(
- proxy=proxy if proxy else None,
+ proxy=proxy or None,
follow_redirects=True,
) as client:
resp = client.get(download_url)
@@ -219,7 +219,7 @@ def manage_plugin(
# Check if plugin exists
if is_update and not target_path.exists():
raise click.ClickException(
- f"Plugin {plugin_name} is not installed and cannot be updated"
+ f"Plugin {plugin_name} is not installed and cannot be updated",
)
# Backup existing plugin
@@ -238,7 +238,7 @@ def manage_plugin(
if is_update and backup_path is not None and backup_path.exists():
shutil.rmtree(backup_path)
click.echo(
- f"Plugin {plugin_name} {'updated' if is_update else 'installed'} successfully"
+ f"Plugin {plugin_name} {'updated' if is_update else 'installed'} successfully",
)
except Exception as e:
if target_path.exists():
diff --git a/astrbot/cli/utils/version_comparator.py b/astrbot/cli/utils/version_comparator.py
index 1f236946c..2bac3a824 100644
--- a/astrbot/cli/utils/version_comparator.py
+++ b/astrbot/cli/utils/version_comparator.py
@@ -62,12 +62,7 @@ class VersionComparator:
return -1
if isinstance(p1, str) and isinstance(p2, int):
return 1
- if isinstance(p1, int) and isinstance(p2, int):
- if p1 > p2:
- return 1
- if p1 < p2:
- return -1
- elif isinstance(p1, str) and isinstance(p2, str):
+ if (isinstance(p1, int) and isinstance(p2, int)) or (isinstance(p1, str) and isinstance(p2, str)):
if p1 > p2:
return 1
if p1 < p2:
diff --git a/astrbot/core/agent/context/compressor.py b/astrbot/core/agent/context/compressor.py
index 31a0b0b48..6f6e6e2d7 100644
--- a/astrbot/core/agent/context/compressor.py
+++ b/astrbot/core/agent/context/compressor.py
@@ -20,13 +20,12 @@ from ..context.truncator import ContextTruncator
@runtime_checkable
class ContextCompressor(Protocol):
- """
- Protocol for context compressors.
+ """Protocol for context compressors.
Provides an interface for compressing message lists.
"""
def should_compress(
- self, messages: list[Message], current_tokens: int, max_tokens: int
+ self, messages: list[Message], current_tokens: int, max_tokens: int,
) -> bool:
"""Check if compression is needed.
@@ -37,6 +36,7 @@ class ContextCompressor(Protocol):
Returns:
True if compression is needed, False otherwise.
+
"""
...
@@ -48,6 +48,7 @@ class ContextCompressor(Protocol):
Returns:
The compressed message list.
+
"""
...
@@ -58,19 +59,20 @@ class TruncateByTurnsCompressor:
"""
def __init__(
- self, truncate_turns: int = 1, compression_threshold: float = 0.82
+ self, truncate_turns: int = 1, compression_threshold: float = 0.82,
) -> None:
"""Initialize the truncate by turns compressor.
Args:
truncate_turns: The number of turns to remove when truncating (default: 1).
compression_threshold: The compression trigger threshold (default: 0.82).
+
"""
self.truncate_turns = truncate_turns
self.compression_threshold = compression_threshold
def should_compress(
- self, messages: list[Message], current_tokens: int, max_tokens: int
+ self, messages: list[Message], current_tokens: int, max_tokens: int,
) -> bool:
"""Check if compression is needed.
@@ -81,6 +83,7 @@ class TruncateByTurnsCompressor:
Returns:
True if compression is needed, False otherwise.
+
"""
if max_tokens <= 0 or current_tokens <= 0:
return False
@@ -97,7 +100,7 @@ class TruncateByTurnsCompressor:
def split_history(
- messages: list[Message], keep_recent: int
+ messages: list[Message], keep_recent: int,
) -> tuple[list[Message], list[Message], list[Message]]:
"""Split the message list into system messages, messages to summarize, and recent messages.
@@ -109,6 +112,7 @@ def split_history(
Returns:
tuple: (system_messages, messages_to_summarize, recent_messages)
+
"""
# keep the system messages
first_non_system = 0
@@ -162,6 +166,7 @@ class LLMSummaryCompressor:
keep_recent: The number of latest messages to keep (default: 4).
instruction_text: Custom instruction for summary generation.
compression_threshold: The compression trigger threshold (default: 0.82).
+
"""
self.provider = provider
self.keep_recent = keep_recent
@@ -176,7 +181,7 @@ class LLMSummaryCompressor:
)
def should_compress(
- self, messages: list[Message], current_tokens: int, max_tokens: int
+ self, messages: list[Message], current_tokens: int, max_tokens: int,
) -> bool:
"""Check if compression is needed.
@@ -187,6 +192,7 @@ class LLMSummaryCompressor:
Returns:
True if compression is needed, False otherwise.
+
"""
if max_tokens <= 0 or current_tokens <= 0:
return False
@@ -205,7 +211,7 @@ class LLMSummaryCompressor:
return messages
system_messages, messages_to_summarize, recent_messages = split_history(
- messages, self.keep_recent
+ messages, self.keep_recent,
)
if not messages_to_summarize:
@@ -231,13 +237,13 @@ class LLMSummaryCompressor:
Message(
role="user",
content=f"Our previous history conversation summary: {summary_content}",
- )
+ ),
)
result.append(
Message(
role="assistant",
content="Acknowledged the summary of our previous conversation history.",
- )
+ ),
)
result.extend(recent_messages)
diff --git a/astrbot/core/agent/context/manager.py b/astrbot/core/agent/context/manager.py
index 216a3e7e1..49550b423 100644
--- a/astrbot/core/agent/context/manager.py
+++ b/astrbot/core/agent/context/manager.py
@@ -22,6 +22,7 @@ class ContextManager:
Args:
config: The context configuration.
+
"""
self.config = config
@@ -38,11 +39,11 @@ class ContextManager:
)
else:
self.compressor = TruncateByTurnsCompressor(
- truncate_turns=config.truncate_turns
+ truncate_turns=config.truncate_turns,
)
async def process(
- self, messages: list[Message], trusted_token_usage: int = 0
+ self, messages: list[Message], trusted_token_usage: int = 0,
) -> list[Message]:
"""Process the messages.
@@ -51,6 +52,7 @@ class ContextManager:
Returns:
The processed message list.
+
"""
try:
result = messages
@@ -66,11 +68,11 @@ class ContextManager:
# 2. 基于 token 的压缩
if self.config.max_context_tokens > 0:
total_tokens = self.token_counter.count_tokens(
- result, trusted_token_usage
+ result, trusted_token_usage,
)
if self.compressor.should_compress(
- result, total_tokens, self.config.max_context_tokens
+ result, total_tokens, self.config.max_context_tokens,
):
result = await self._run_compression(result, total_tokens)
@@ -80,10 +82,9 @@ class ContextManager:
return messages
async def _run_compression(
- self, messages: list[Message], prev_tokens: int
+ self, messages: list[Message], prev_tokens: int,
) -> list[Message]:
- """
- Compress/truncate the messages.
+ """Compress/truncate the messages.
Args:
messages: The original message list.
@@ -91,6 +92,7 @@ class ContextManager:
Returns:
The compressed/truncated message list.
+
"""
logger.debug("Compress triggered, starting compression...")
@@ -109,10 +111,10 @@ class ContextManager:
# last check
if self.compressor.should_compress(
- messages, tokens_after_summary, self.config.max_context_tokens
+ messages, tokens_after_summary, self.config.max_context_tokens,
):
logger.info(
- "Context still exceeds max tokens after compression, applying halving truncation..."
+ "Context still exceeds max tokens after compression, applying halving truncation...",
)
# still need compress, truncate by half
messages = self.truncator.truncate_by_halving(messages)
diff --git a/astrbot/core/agent/context/token_counter.py b/astrbot/core/agent/context/token_counter.py
index 7c60cb23e..ec9e53da7 100644
--- a/astrbot/core/agent/context/token_counter.py
+++ b/astrbot/core/agent/context/token_counter.py
@@ -6,13 +6,12 @@ from ..message import AudioURLPart, ImageURLPart, Message, TextPart, ThinkPart
@runtime_checkable
class TokenCounter(Protocol):
- """
- Protocol for token counters.
+ """Protocol for token counters.
Provides an interface for counting tokens in message lists.
"""
def count_tokens(
- self, messages: list[Message], trusted_token_usage: int = 0
+ self, messages: list[Message], trusted_token_usage: int = 0,
) -> int:
"""Count the total tokens in the message list.
@@ -24,6 +23,7 @@ class TokenCounter(Protocol):
Returns:
The total token count.
+
"""
...
@@ -44,7 +44,7 @@ class EstimateTokenCounter:
"""
def count_tokens(
- self, messages: list[Message], trusted_token_usage: int = 0
+ self, messages: list[Message], trusted_token_usage: int = 0,
) -> int:
if trusted_token_usage > 0:
return trusted_token_usage
diff --git a/astrbot/core/agent/context/truncator.py b/astrbot/core/agent/context/truncator.py
index 9abf57433..bc1538216 100644
--- a/astrbot/core/agent/context/truncator.py
+++ b/astrbot/core/agent/context/truncator.py
@@ -20,6 +20,7 @@ class ContextTruncator:
Returns:
tuple: (system_messages, non_system_messages)
+
"""
first_non_system = 0
for i, msg in enumerate(messages):
@@ -103,8 +104,7 @@ class ContextTruncator:
keep_most_recent_turns: int,
drop_turns: int = 1,
) -> list[Message]:
- """
- Turn-based truncation strategy, which drops the oldest turns while keeping the most recent N turns.
+ """Turn-based truncation strategy, which drops the oldest turns while keeping the most recent N turns.
A turn consists of a user message and an assistant message.
This method ensures that the truncated context list conforms to OpenAI's context format.
@@ -115,6 +115,7 @@ class ContextTruncator:
Returns:
The truncated list of messages.
+
"""
if keep_most_recent_turns == -1:
return messages
@@ -139,7 +140,7 @@ class ContextTruncator:
truncated_contexts = truncated_contexts[index:]
result = self._ensure_user_message(
- system_messages, truncated_contexts, messages
+ system_messages, truncated_contexts, messages,
)
return self.fix_messages(result)
@@ -168,7 +169,7 @@ class ContextTruncator:
truncated_non_system = truncated_non_system[index:]
result = self._ensure_user_message(
- system_messages, truncated_non_system, messages
+ system_messages, truncated_non_system, messages,
)
return self.fix_messages(result)
@@ -197,6 +198,6 @@ class ContextTruncator:
truncated_non_system = truncated_non_system[index:]
result = self._ensure_user_message(
- system_messages, truncated_non_system, messages
+ system_messages, truncated_non_system, messages,
)
return self.fix_messages(result)
diff --git a/astrbot/core/agent/mcp_client.py b/astrbot/core/agent/mcp_client.py
index aae1aaa77..a5ef336f6 100644
--- a/astrbot/core/agent/mcp_client.py
+++ b/astrbot/core/agent/mcp_client.py
@@ -27,7 +27,7 @@ try:
from mcp.client.sse import sse_client
except (ModuleNotFoundError, ImportError):
logger.warning(
- "Warning: Missing 'mcp' dependency, MCP services will be unavailable."
+ "Warning: Missing 'mcp' dependency, MCP services will be unavailable.",
)
try:
@@ -179,7 +179,7 @@ class MCPClient:
# Handle MCP service error logs
if isinstance(msg, mcp.types.LoggingMessageNotificationParams):
if msg.level in ("warning", "error", "critical", "alert", "emergency"):
- log_msg = f"[{msg.level.upper()}] {str(msg.data)}"
+ log_msg = f"[{msg.level.upper()}] {msg.data!s}"
self.server_errlogs.append(log_msg)
if "url" in cfg:
@@ -258,7 +258,7 @@ class MCPClient:
"alert",
"emergency",
):
- log_msg = f"[{msg.level.upper()}] {str(msg.data)}"
+ log_msg = f"[{msg.level.upper()}] {msg.data!s}"
self.server_errlogs.append(log_msg)
stdio_transport = await self.exit_stack.enter_async_context(
@@ -294,12 +294,13 @@ class MCPClient:
Raises:
Exception: raised when reconnection fails
+
"""
async with self._reconnect_lock:
# Check if already reconnecting (useful for logging)
if self._reconnecting:
logger.debug(
- f"MCP Client {self._server_name} is already reconnecting, skipping"
+ f"MCP Client {self._server_name} is already reconnecting, skipping",
)
return
@@ -309,7 +310,7 @@ class MCPClient:
self._reconnecting = True
try:
logger.info(
- f"Attempting to reconnect to MCP server {self._server_name}..."
+ f"Attempting to reconnect to MCP server {self._server_name}...",
)
# Save old exit_stack for later cleanup (don't close it now to avoid cancel scope issues)
@@ -327,11 +328,11 @@ class MCPClient:
await self.list_tools_and_save()
logger.info(
- f"Successfully reconnected to MCP server {self._server_name}"
+ f"Successfully reconnected to MCP server {self._server_name}",
)
except Exception as e:
logger.error(
- f"Failed to reconnect to MCP server {self._server_name}: {e}"
+ f"Failed to reconnect to MCP server {self._server_name}: {e}",
)
raise
finally:
@@ -356,6 +357,7 @@ class MCPClient:
Raises:
ValueError: MCP session is not available
anyio.ClosedResourceError: raised after reconnection failure
+
"""
@retry(
@@ -377,7 +379,7 @@ class MCPClient:
)
except anyio.ClosedResourceError:
logger.warning(
- f"MCP tool {tool_name} call failed (ClosedResourceError), attempting to reconnect..."
+ f"MCP tool {tool_name} call failed (ClosedResourceError), attempting to reconnect...",
)
# Attempt to reconnect
await self._reconnect()
@@ -407,7 +409,7 @@ class MCPTool(FunctionTool, Generic[TContext]):
"""A function tool that calls an MCP service."""
def __init__(
- self, mcp_tool: mcp.Tool, mcp_client: MCPClient, mcp_server_name: str, **kwargs
+ self, mcp_tool: mcp.Tool, mcp_client: MCPClient, mcp_server_name: str, **kwargs,
) -> None:
super().__init__(
name=mcp_tool.name,
@@ -419,7 +421,7 @@ class MCPTool(FunctionTool, Generic[TContext]):
self.mcp_server_name = mcp_server_name
async def call(
- self, context: ContextWrapper[TContext], **kwargs
+ self, context: ContextWrapper[TContext], **kwargs,
) -> mcp.types.CallToolResult:
return await self.mcp_client.call_tool_with_reconnect(
tool_name=self.mcp_tool.name,
diff --git a/astrbot/core/agent/message.py b/astrbot/core/agent/message.py
index bde6353ff..e51ff96ab 100644
--- a/astrbot/core/agent/message.py
+++ b/astrbot/core/agent/message.py
@@ -33,7 +33,7 @@ class ContentPart(BaseModel):
@classmethod
def __get_pydantic_core_schema__(
- cls, source_type: Any, handler: GetCoreSchemaHandler
+ cls, source_type: Any, handler: GetCoreSchemaHandler,
) -> core_schema.CoreSchema:
# If we're dealing with the base ContentPart class, use custom validation
if cls.__name__ == "ContentPart":
@@ -45,7 +45,7 @@ class ContentPart(BaseModel):
# if it's a dict with a type field, dispatch to the appropriate subclass
if isinstance(value, dict) and "type" in value:
- type_value: Any | None = cast(dict[str, Any], value).get("type")
+ type_value: Any | None = cast("dict[str, Any]", value).get("type")
if not isinstance(type_value, str):
raise ValueError(f"Cannot validate {value} as ContentPart")
target_class = cls.__content_part_registry[type_value]
@@ -60,8 +60,7 @@ class ContentPart(BaseModel):
class TextPart(ContentPart):
- """
- >>> TextPart(text="Hello, world!").model_dump()
+ """>>> TextPart(text="Hello, world!").model_dump()
{'type': 'text', 'text': 'Hello, world!'}
"""
@@ -70,8 +69,7 @@ class TextPart(ContentPart):
class ThinkPart(ContentPart):
- """
- >>> ThinkPart(think="I think I need to think about this.").model_dump()
+ """>>> ThinkPart(think="I think I need to think about this.").model_dump()
{'type': 'think', 'think': 'I think I need to think about this.', 'encrypted': None}
"""
@@ -92,8 +90,7 @@ class ThinkPart(ContentPart):
class ImageURLPart(ContentPart):
- """
- >>> ImageURLPart(image_url="http://example.com/image.jpg").model_dump()
+ """>>> ImageURLPart(image_url="http://example.com/image.jpg").model_dump()
{'type': 'image_url', 'image_url': 'http://example.com/image.jpg'}
"""
@@ -108,8 +105,7 @@ class ImageURLPart(ContentPart):
class AudioURLPart(ContentPart):
- """
- >>> AudioURLPart(audio_url=AudioURLPart.AudioURL(url="https://example.com/audio.mp3")).model_dump()
+ """>>> AudioURLPart(audio_url=AudioURLPart.AudioURL(url="https://example.com/audio.mp3")).model_dump()
{'type': 'audio_url', 'audio_url': {'url': 'https://example.com/audio.mp3', 'id': None}}
"""
@@ -124,8 +120,7 @@ class AudioURLPart(ContentPart):
class ToolCall(BaseModel):
- """
- A tool call requested by the assistant.
+ """A tool call requested by the assistant.
>>> ToolCall(
... id="123",
@@ -195,7 +190,7 @@ class Message(BaseModel):
# other all cases: content is required
if self.content is None:
raise ValueError(
- "content is required unless role='assistant' and tool_calls is not None"
+ "content is required unless role='assistant' and tool_calls is not None",
)
return self
diff --git a/astrbot/core/agent/runners/base.py b/astrbot/core/agent/runners/base.py
index 21e796433..ac9eae8f1 100644
--- a/astrbot/core/agent/runners/base.py
+++ b/astrbot/core/agent/runners/base.py
@@ -39,7 +39,7 @@ class BaseAgentRunner(T.Generic[TContext]):
@abc.abstractmethod
async def step_until_done(
- self, max_step: int
+ self, max_step: int,
) -> T.AsyncGenerator[AgentResponse, None]:
"""Process steps until the agent is done."""
...
diff --git a/astrbot/core/agent/runners/coze/coze_agent_runner.py b/astrbot/core/agent/runners/coze/coze_agent_runner.py
index a8300bb71..4ab4c9267 100644
--- a/astrbot/core/agent/runners/coze/coze_agent_runner.py
+++ b/astrbot/core/agent/runners/coze/coze_agent_runner.py
@@ -71,8 +71,7 @@ class CozeAgentRunner(BaseAgentRunner[TContext]):
@override
async def step(self):
- """
- 执行 Coze Agent 的一个步骤
+ """执行 Coze Agent 的一个步骤
"""
if not self.req:
raise ValueError("Request is not set. Please call reset() first.")
@@ -91,15 +90,15 @@ class CozeAgentRunner(BaseAgentRunner[TContext]):
async for response in self._execute_coze_request():
yield response
except Exception as e:
- logger.error(f"Coze 请求失败:{str(e)}")
+ logger.error(f"Coze 请求失败:{e!s}")
self._transition_state(AgentState.ERROR)
self.final_llm_resp = LLMResponse(
- role="err", completion_text=f"Coze 请求失败:{str(e)}"
+ role="err", completion_text=f"Coze 请求失败:{e!s}",
)
yield AgentResponse(
type="err",
data=AgentResponseData(
- chain=MessageChain().message(f"Coze 请求失败:{str(e)}")
+ chain=MessageChain().message(f"Coze 请求失败:{e!s}"),
),
)
finally:
@@ -107,7 +106,7 @@ class CozeAgentRunner(BaseAgentRunner[TContext]):
@override
async def step_until_done(
- self, max_step: int = 30
+ self, max_step: int = 30,
) -> T.AsyncGenerator[AgentResponse, None]:
while not self.done():
async for resp in self.step():
@@ -166,7 +165,7 @@ class CozeAgentRunner(BaseAgentRunner[TContext]):
if url:
file_id = (
await self._download_and_upload_image(
- url, session_id
+ url, session_id,
)
)
processed_content.append(
@@ -174,7 +173,7 @@ class CozeAgentRunner(BaseAgentRunner[TContext]):
"type": "file",
"file_id": file_id,
"file_url": url,
- }
+ },
)
except Exception as e:
logger.warning(f"处理上下文图片失败: {e}")
@@ -186,7 +185,7 @@ class CozeAgentRunner(BaseAgentRunner[TContext]):
"role": ctx["role"],
"content": processed_content,
"content_type": "object_string",
- }
+ },
)
else:
# 纯文本内容
@@ -195,7 +194,7 @@ class CozeAgentRunner(BaseAgentRunner[TContext]):
"role": ctx["role"],
"content": content,
"content_type": "text",
- }
+ },
)
# 构建当前消息
@@ -215,7 +214,7 @@ class CozeAgentRunner(BaseAgentRunner[TContext]):
{
"type": "image",
"file_id": file_id,
- }
+ },
)
except Exception as e:
logger.warning(f"处理图片失败 {url}: {e}")
@@ -228,7 +227,7 @@ class CozeAgentRunner(BaseAgentRunner[TContext]):
"role": "user",
"content": content,
"content_type": "object_string",
- }
+ },
)
elif prompt:
# 纯文本
@@ -282,7 +281,7 @@ class CozeAgentRunner(BaseAgentRunner[TContext]):
yield AgentResponse(
type="streaming_delta",
data=AgentResponseData(
- chain=MessageChain().message(content)
+ chain=MessageChain().message(content),
),
)
diff --git a/astrbot/core/agent/runners/dashscope/dashscope_agent_runner.py b/astrbot/core/agent/runners/dashscope/dashscope_agent_runner.py
index 8169a678c..14b7201ab 100644
--- a/astrbot/core/agent/runners/dashscope/dashscope_agent_runner.py
+++ b/astrbot/core/agent/runners/dashscope/dashscope_agent_runner.py
@@ -83,8 +83,7 @@ class DashscopeAgentRunner(BaseAgentRunner[TContext]):
@override
async def step(self):
- """
- 执行 Dashscope Agent 的一个步骤
+ """执行 Dashscope Agent 的一个步骤
"""
if not self.req:
raise ValueError("Request is not set. Please call reset() first.")
@@ -103,28 +102,28 @@ class DashscopeAgentRunner(BaseAgentRunner[TContext]):
async for response in self._execute_dashscope_request():
yield response
except Exception as e:
- logger.error(f"阿里云百炼请求失败:{str(e)}")
+ logger.error(f"阿里云百炼请求失败:{e!s}")
self._transition_state(AgentState.ERROR)
self.final_llm_resp = LLMResponse(
- role="err", completion_text=f"阿里云百炼请求失败:{str(e)}"
+ role="err", completion_text=f"阿里云百炼请求失败:{e!s}",
)
yield AgentResponse(
type="err",
data=AgentResponseData(
- chain=MessageChain().message(f"阿里云百炼请求失败:{str(e)}")
+ chain=MessageChain().message(f"阿里云百炼请求失败:{e!s}"),
),
)
@override
async def step_until_done(
- self, max_step: int = 30
+ self, max_step: int = 30,
) -> T.AsyncGenerator[AgentResponse, None]:
while not self.done():
async for resp in self.step():
yield resp
def _consume_sync_generator(
- self, response: T.Any, response_queue: queue.Queue
+ self, response: T.Any, response_queue: queue.Queue,
) -> None:
"""在线程中消费同步generator,将结果放入队列
@@ -145,7 +144,7 @@ class DashscopeAgentRunner(BaseAgentRunner[TContext]):
response_queue.put(("done", None))
async def _process_stream_chunk(
- self, chunk: ApplicationResponse, output_text: str
+ self, chunk: ApplicationResponse, output_text: str,
) -> tuple[str, list | None, AgentResponse | None]:
"""处理流式响应的单个chunk
@@ -217,7 +216,7 @@ class DashscopeAgentRunner(BaseAgentRunner[TContext]):
return f"\n\n回答来源:\n{ref_str}"
async def _build_request_payload(
- self, prompt: str, session_id: str, contexts: list, system_prompt: str
+ self, prompt: str, session_id: str, contexts: list, system_prompt: str,
) -> dict:
"""构建请求payload
@@ -263,22 +262,21 @@ class DashscopeAgentRunner(BaseAgentRunner[TContext]):
if conversation_id:
p["session_id"] = conversation_id
return p
- else:
- # 不支持多轮对话的
- payload = {
- "app_id": self.app_id,
- "prompt": prompt,
- "api_key": self.api_key,
- "biz_params": payload_vars or None,
- "stream": self.streaming,
- "incremental_output": True,
- }
- if self.rag_options:
- payload["rag_options"] = self.rag_options
- return payload
+ # 不支持多轮对话的
+ payload = {
+ "app_id": self.app_id,
+ "prompt": prompt,
+ "api_key": self.api_key,
+ "biz_params": payload_vars or None,
+ "stream": self.streaming,
+ "incremental_output": True,
+ }
+ if self.rag_options:
+ payload["rag_options"] = self.rag_options
+ return payload
async def _handle_streaming_response(
- self, response: T.Any, session_id: str
+ self, response: T.Any, session_id: str,
) -> T.AsyncGenerator[AgentResponse, None]:
"""处理流式响应
@@ -303,7 +301,7 @@ class DashscopeAgentRunner(BaseAgentRunner[TContext]):
while True:
try:
item_type, item_data = await asyncio.get_running_loop().run_in_executor(
- None, response_queue.get, True, 1
+ None, response_queue.get, True, 1,
)
except queue.Empty:
continue
@@ -380,7 +378,7 @@ class DashscopeAgentRunner(BaseAgentRunner[TContext]):
# 构建请求payload
payload = await self._build_request_payload(
- prompt, session_id, contexts, system_prompt
+ prompt, session_id, contexts, system_prompt,
)
if not self.streaming:
diff --git a/astrbot/core/agent/runners/deerflow/deerflow_agent_runner.py b/astrbot/core/agent/runners/deerflow/deerflow_agent_runner.py
index 50ec7c826..58740fe2e 100644
--- a/astrbot/core/agent/runners/deerflow/deerflow_agent_runner.py
+++ b/astrbot/core/agent/runners/deerflow/deerflow_agent_runner.py
@@ -130,7 +130,7 @@ class DeerFlowAgentRunner(BaseAgentRunner[TContext]):
logger.error(f"Error in on_agent_done hook: {e}", exc_info=True)
async def _finish_with_result(
- self, chain: MessageChain, role: str
+ self, chain: MessageChain, role: str,
) -> AgentResponse:
self.final_llm_resp = LLMResponse(
role=role,
@@ -247,7 +247,7 @@ class DeerFlowAgentRunner(BaseAgentRunner[TContext]):
await old_client.close()
except Exception as e:
logger.warning(
- f"Failed to close previous DeerFlow API client cleanly: {e}"
+ f"Failed to close previous DeerFlow API client cleanly: {e}",
)
self.api_client = DeerFlowAPIClient(
@@ -304,7 +304,7 @@ class DeerFlowAgentRunner(BaseAgentRunner[TContext]):
@override
async def step_until_done(
- self, max_step: int = 30
+ self, max_step: int = 30,
) -> T.AsyncGenerator[AgentResponse, None]:
if max_step <= 0:
raise ValueError("max_step must be greater than 0")
@@ -317,7 +317,7 @@ class DeerFlowAgentRunner(BaseAgentRunner[TContext]):
if not self.done():
raise RuntimeError(
- f"DeerFlow agent reached max_step ({max_step}) without completion."
+ f"DeerFlow agent reached max_step ({max_step}) without completion.",
)
def _extract_new_messages_from_values(
@@ -382,7 +382,7 @@ class DeerFlowAgentRunner(BaseAgentRunner[TContext]):
thread_id = thread.get("thread_id", "")
if not thread_id:
raise Exception(
- f"DeerFlow create thread returned invalid payload: {thread}"
+ f"DeerFlow create thread returned invalid payload: {thread}",
)
await sp.put_async(
@@ -468,7 +468,7 @@ class DeerFlowAgentRunner(BaseAgentRunner[TContext]):
AgentResponse(
type="streaming_delta",
data=AgentResponseData(chain=MessageChain().message(delta)),
- )
+ ),
]
if delta_text:
@@ -478,9 +478,9 @@ class DeerFlowAgentRunner(BaseAgentRunner[TContext]):
AgentResponse(
type="streaming_delta",
data=AgentResponseData(
- chain=MessageChain().message(delta_text)
+ chain=MessageChain().message(delta_text),
),
- )
+ ),
]
return []
@@ -532,7 +532,7 @@ class DeerFlowAgentRunner(BaseAgentRunner[TContext]):
self._update_text_and_maybe_stream(
state=state,
new_full_text=latest_text or None,
- )
+ ),
)
return responses
@@ -549,7 +549,7 @@ class DeerFlowAgentRunner(BaseAgentRunner[TContext]):
self._update_text_and_maybe_stream(
state=state,
delta_text=delta,
- )
+ ),
)
maybe_clarification = extract_clarification_from_event_data(data)
diff --git a/astrbot/core/agent/runners/deerflow/deerflow_api_client.py b/astrbot/core/agent/runners/deerflow/deerflow_api_client.py
index 37a23f243..3d7a557ef 100644
--- a/astrbot/core/agent/runners/deerflow/deerflow_api_client.py
+++ b/astrbot/core/agent/runners/deerflow/deerflow_api_client.py
@@ -168,7 +168,7 @@ class DeerFlowAPIClient:
input_payload = payload.get("input")
message_count = 0
if isinstance(input_payload, dict) and isinstance(
- input_payload.get("messages"), list
+ input_payload.get("messages"), list,
):
message_count = len(input_payload["messages"])
# Log only a minimal summary to avoid exposing sensitive user content.
@@ -237,7 +237,7 @@ class DeerFlowAPIClient:
return
logger.warning(
"DeerFlowAPIClient garbage collected with unclosed session; "
- "explicit close() should be called by runner lifecycle (or `async with`)."
+ "explicit close() should be called by runner lifecycle (or `async with`).",
)
@property
diff --git a/astrbot/core/agent/runners/deerflow/deerflow_content_mapper.py b/astrbot/core/agent/runners/deerflow/deerflow_content_mapper.py
index 2477adbb9..87cb691f4 100644
--- a/astrbot/core/agent/runners/deerflow/deerflow_content_mapper.py
+++ b/astrbot/core/agent/runners/deerflow/deerflow_content_mapper.py
@@ -58,7 +58,7 @@ def build_user_content(prompt: str, image_urls: list[str]) -> Any:
if not is_likely_base64_image(url):
skipped_invalid_images += 1
logger.debug(
- "Skipped DeerFlow image input because it is neither URL/data URI nor valid base64."
+ "Skipped DeerFlow image input because it is neither URL/data URI nor valid base64.",
)
continue
compact_base64 = url.replace("\n", "").replace("\r", "")
@@ -164,14 +164,14 @@ def append_components_from_content(
if "content" in content:
append_components_from_content(
- content.get("content"), components, image_resolver
+ content.get("content"), components, image_resolver,
)
return
kwargs = content.get("kwargs")
if isinstance(kwargs, dict) and "content" in kwargs:
append_components_from_content(
- kwargs.get("content"), components, image_resolver
+ kwargs.get("content"), components, image_resolver,
)
diff --git a/astrbot/core/agent/runners/dify/dify_agent_runner.py b/astrbot/core/agent/runners/dify/dify_agent_runner.py
index 93f8d3570..b438c182c 100644
--- a/astrbot/core/agent/runners/dify/dify_agent_runner.py
+++ b/astrbot/core/agent/runners/dify/dify_agent_runner.py
@@ -64,8 +64,7 @@ class DifyAgentRunner(BaseAgentRunner[TContext]):
@override
async def step(self):
- """
- 执行 Dify Agent 的一个步骤
+ """执行 Dify Agent 的一个步骤
"""
if not self.req:
raise ValueError("Request is not set. Please call reset() first.")
@@ -84,15 +83,15 @@ class DifyAgentRunner(BaseAgentRunner[TContext]):
async for response in self._execute_dify_request():
yield response
except Exception as e:
- logger.error(f"Dify 请求失败:{str(e)}")
+ logger.error(f"Dify 请求失败:{e!s}")
self._transition_state(AgentState.ERROR)
self.final_llm_resp = LLMResponse(
- role="err", completion_text=f"Dify 请求失败:{str(e)}"
+ role="err", completion_text=f"Dify 请求失败:{e!s}",
)
yield AgentResponse(
type="err",
data=AgentResponseData(
- chain=MessageChain().message(f"Dify 请求失败:{str(e)}")
+ chain=MessageChain().message(f"Dify 请求失败:{e!s}"),
),
)
finally:
@@ -100,7 +99,7 @@ class DifyAgentRunner(BaseAgentRunner[TContext]):
@override
async def step_until_done(
- self, max_step: int = 30
+ self, max_step: int = 30,
) -> T.AsyncGenerator[AgentResponse, None]:
while not self.done():
async for resp in self.step():
@@ -136,7 +135,7 @@ class DifyAgentRunner(BaseAgentRunner[TContext]):
logger.debug(f"Dify 上传图片响应:{file_response}")
if "id" not in file_response:
logger.warning(
- f"上传图片后得到未知的 Dify 响应:{file_response},图片将忽略。"
+ f"上传图片后得到未知的 Dify 响应:{file_response},图片将忽略。",
)
continue
files_payload.append(
@@ -144,7 +143,7 @@ class DifyAgentRunner(BaseAgentRunner[TContext]):
"type": "image",
"transfer_method": "local_file",
"upload_file_id": file_response["id"],
- }
+ },
)
except Exception as e:
logger.warning(f"上传图片失败:{e}")
@@ -195,7 +194,7 @@ class DifyAgentRunner(BaseAgentRunner[TContext]):
yield AgentResponse(
type="streaming_delta",
data=AgentResponseData(
- chain=MessageChain().message(chunk["answer"])
+ chain=MessageChain().message(chunk["answer"]),
),
)
elif chunk["event"] == "message_end":
@@ -204,7 +203,7 @@ class DifyAgentRunner(BaseAgentRunner[TContext]):
elif chunk["event"] == "error":
logger.error(f"Dify 出现错误:{chunk}")
raise Exception(
- f"Dify 出现错误 status: {chunk['status']} message: {chunk['message']}"
+ f"Dify 出现错误 status: {chunk['status']} message: {chunk['message']}",
)
case "workflow":
@@ -222,11 +221,11 @@ class DifyAgentRunner(BaseAgentRunner[TContext]):
match chunk["event"]:
case "workflow_started":
logger.info(
- f"Dify 工作流(ID: {chunk['workflow_run_id']})开始运行。"
+ f"Dify 工作流(ID: {chunk['workflow_run_id']})开始运行。",
)
case "node_finished":
logger.debug(
- f"Dify 工作流节点(ID: {chunk['data']['node_id']} Title: {chunk['data'].get('title', '')})运行结束。"
+ f"Dify 工作流节点(ID: {chunk['data']['node_id']} Title: {chunk['data'].get('title', '')})运行结束。",
)
case "text_chunk":
if self.streaming and chunk["data"]["text"]:
@@ -234,25 +233,25 @@ class DifyAgentRunner(BaseAgentRunner[TContext]):
type="streaming_delta",
data=AgentResponseData(
chain=MessageChain().message(
- chunk["data"]["text"]
- )
+ chunk["data"]["text"],
+ ),
),
)
case "workflow_finished":
logger.info(
- f"Dify 工作流(ID: {chunk['workflow_run_id']})运行结束"
+ f"Dify 工作流(ID: {chunk['workflow_run_id']})运行结束",
)
logger.debug(f"Dify 工作流结果:{chunk}")
if chunk["data"]["error"]:
logger.error(
- f"Dify 工作流出现错误:{chunk['data']['error']}"
+ f"Dify 工作流出现错误:{chunk['data']['error']}",
)
raise Exception(
- f"Dify 工作流出现错误:{chunk['data']['error']}"
+ f"Dify 工作流出现错误:{chunk['data']['error']}",
)
if self.workflow_output_key not in chunk["data"]["outputs"]:
raise Exception(
- f"Dify 工作流的输出不包含指定的键名:{self.workflow_output_key}"
+ f"Dify 工作流的输出不包含指定的键名:{self.workflow_output_key}",
)
result = chunk
case _:
diff --git a/astrbot/core/agent/runners/dify/dify_api_client.py b/astrbot/core/agent/runners/dify/dify_api_client.py
index 26da6dfe9..9ef427172 100644
--- a/astrbot/core/agent/runners/dify/dify_api_client.py
+++ b/astrbot/core/agent/runners/dify/dify_api_client.py
@@ -114,8 +114,10 @@ class DifyAPIClient:
file_path: The path to the file to upload.
file_data: The file data in bytes.
file_name: Optional file name when using file_data.
+
Returns:
A dictionary containing the uploaded file information.
+
"""
url = f"{self.api_base}/files/upload"
diff --git a/astrbot/core/agent/runners/tool_loop_agent_runner.py b/astrbot/core/agent/runners/tool_loop_agent_runner.py
index 9d0b0ffce..a47f4ff4b 100644
--- a/astrbot/core/agent/runners/tool_loop_agent_runner.py
+++ b/astrbot/core/agent/runners/tool_loop_agent_runner.py
@@ -72,7 +72,7 @@ class _HandleFunctionToolsResult:
@classmethod
def from_tool_call_result_blocks(
- cls, blocks: list[ToolCallMessageSegment]
+ cls, blocks: list[ToolCallMessageSegment],
) -> "_HandleFunctionToolsResult":
return cls(kind="tool_call_result_blocks", tool_call_result_blocks=blocks)
@@ -169,7 +169,7 @@ class ToolLoopAgentRunner(BaseAgentRunner[TContext]):
ThinkPart(
think=llm_resp.reasoning_content,
encrypted=llm_resp.reasoning_signature,
- )
+ ),
)
if llm_resp.completion_text:
parts.append(TextPart(text=llm_resp.completion_text))
@@ -299,7 +299,7 @@ class ToolLoopAgentRunner(BaseAgentRunner[TContext]):
self.stats.start_time = time.time()
async def _iter_llm_responses(
- self, *, include_model: bool = True
+ self, *, include_model: bool = True,
) -> T.AsyncGenerator[LLMResponse, None]:
"""Yields chunks *and* a final LLMResponse."""
payload = {
@@ -355,7 +355,7 @@ class ToolLoopAgentRunner(BaseAgentRunner[TContext]):
with attempt:
try:
async for resp in self._iter_llm_responses(
- include_model=idx == 0
+ include_model=idx == 0,
):
if resp.is_chunk:
has_stream_output = True
@@ -522,7 +522,7 @@ class ToolLoopAgentRunner(BaseAgentRunner[TContext]):
token_usage = self.req.conversation.token_usage if self.req.conversation else 0
self._simple_print_message_role("[BefCompact]")
self.run_context.messages = await self.context_manager.process(
- self.run_context.messages, trusted_token_usage=token_usage
+ self.run_context.messages, trusted_token_usage=token_usage,
)
self._simple_print_message_role("[AftCompact]")
@@ -625,7 +625,7 @@ class ToolLoopAgentRunner(BaseAgentRunner[TContext]):
llm_resp, _ = await self._resolve_tool_exec(llm_resp)
if not llm_resp.tools_call_name:
logger.warning(
- "skills_like tool re-query returned no tool calls; fallback to assistant response."
+ "skills_like tool re-query returned no tool calls; fallback to assistant response.",
)
if llm_resp.result_chain:
yield AgentResponse(
@@ -677,7 +677,7 @@ class ToolLoopAgentRunner(BaseAgentRunner[TContext]):
ThinkPart(
think=llm_resp.reasoning_content,
encrypted=llm_resp.reasoning_signature,
- )
+ ),
)
if llm_resp.completion_text:
parts.append(TextPart(text=llm_resp.completion_text))
@@ -692,7 +692,7 @@ class ToolLoopAgentRunner(BaseAgentRunner[TContext]):
)
# record the assistant message with tool calls
self.run_context.messages.extend(
- tool_calls_result.to_openai_messages_model()
+ tool_calls_result.to_openai_messages_model(),
)
# If there are cached images and the model supports image input,
@@ -705,35 +705,35 @@ class ToolLoopAgentRunner(BaseAgentRunner[TContext]):
image_parts = []
for cached_img in cached_images:
img_data = tool_image_cache.get_image_base64_by_path(
- cached_img.file_path, cached_img.mime_type
+ cached_img.file_path, cached_img.mime_type,
)
if img_data:
base64_data, mime_type = img_data
image_parts.append(
TextPart(
- text=f"[Image from tool '{cached_img.tool_name}', path='{cached_img.file_path}']"
- )
+ text=f"[Image from tool '{cached_img.tool_name}', path='{cached_img.file_path}']",
+ ),
)
image_parts.append(
ImageURLPart(
image_url=ImageURLPart.ImageURL(
url=f"data:{mime_type};base64,{base64_data}",
id=cached_img.file_path,
- )
- )
+ ),
+ ),
)
if image_parts:
self.run_context.messages.append(
- Message(role="user", content=image_parts)
+ Message(role="user", content=image_parts),
)
logger.debug(
- f"Appended {len(cached_images)} cached image(s) to context for LLM review"
+ f"Appended {len(cached_images)} cached image(s) to context for LLM review",
)
self.req.append_tool_calls_result(tool_calls_result)
async def step_until_done(
- self, max_step: int
+ self, max_step: int,
) -> T.AsyncGenerator[AgentResponse, None]:
"""Process steps until the agent is done."""
step_count = 0
@@ -745,7 +745,7 @@ class ToolLoopAgentRunner(BaseAgentRunner[TContext]):
# 如果循环结束了但是 agent 还没有完成,说明是达到了 max_step
if not self.done():
logger.warning(
- f"Agent reached max steps ({max_step}), forcing a final response."
+ f"Agent reached max steps ({max_step}), forcing a final response.",
)
# 拔掉所有工具
if self.req:
@@ -755,7 +755,7 @@ class ToolLoopAgentRunner(BaseAgentRunner[TContext]):
Message(
role="user",
content=self.MAX_STEPS_REACHED_PROMPT,
- )
+ ),
)
# 再执行最后一步
async for resp in self.step():
@@ -796,10 +796,10 @@ class ToolLoopAgentRunner(BaseAgentRunner[TContext]):
"name": func_tool_name,
"args": func_tool_args,
"ts": time.time(),
- }
- )
+ },
+ ),
],
- )
+ ),
)
try:
if not req.func_tool:
@@ -896,11 +896,11 @@ class ToolLoopAgentRunner(BaseAgentRunner[TContext]):
result_parts.append(
f"Image returned and cached at path='{cached_img.file_path}'. "
f"Review the image below. Use send_message_to_user to send it to the user if satisfied, "
- f"with type='image' and path='{cached_img.file_path}'."
+ f"with type='image' and path='{cached_img.file_path}'.",
)
# Yield image info for LLM visibility (will be handled in step())
yield _HandleFunctionToolsResult.from_cached_image(
- cached_img
+ cached_img,
)
elif isinstance(content_item, EmbeddedResource):
resource = content_item.resource
@@ -922,22 +922,22 @@ class ToolLoopAgentRunner(BaseAgentRunner[TContext]):
result_parts.append(
f"Image returned and cached at path='{cached_img.file_path}'. "
f"Review the image below. Use send_message_to_user to send it to the user if satisfied, "
- f"with type='image' and path='{cached_img.file_path}'."
+ f"with type='image' and path='{cached_img.file_path}'.",
)
# Yield image info for LLM visibility
yield _HandleFunctionToolsResult.from_cached_image(
- cached_img
+ cached_img,
)
else:
result_parts.append(
- "The tool has returned a data type that is not supported."
+ "The tool has returned a data type that is not supported.",
)
if result_parts:
_append_tool_call_result(
func_tool_id,
"\n\n".join(result_parts)
+ self._build_repeated_tool_call_guidance(
- func_tool_name, tool_call_streak
+ func_tool_name, tool_call_streak,
),
)
@@ -946,7 +946,7 @@ class ToolLoopAgentRunner(BaseAgentRunner[TContext]):
# 这里我们将直接结束 Agent Loop
# 发送消息逻辑在 ToolExecutor 中处理了
logger.warning(
- f"{func_tool_name} 没有返回值,或者已将结果直接发送给用户。"
+ f"{func_tool_name} 没有返回值,或者已将结果直接发送给用户。",
)
self._transition_state(AgentState.DONE)
self.stats.end_time = time.time()
@@ -954,7 +954,7 @@ class ToolLoopAgentRunner(BaseAgentRunner[TContext]):
func_tool_id,
"The tool has no return value, or has sent the result directly to the user."
+ self._build_repeated_tool_call_guidance(
- func_tool_name, tool_call_streak
+ func_tool_name, tool_call_streak,
),
)
else:
@@ -966,7 +966,7 @@ class ToolLoopAgentRunner(BaseAgentRunner[TContext]):
func_tool_id,
"*The tool has returned an unsupported type. Please tell the user to check the definition and implementation of this tool.*"
+ self._build_repeated_tool_call_guidance(
- func_tool_name, tool_call_streak
+ func_tool_name, tool_call_streak,
),
)
@@ -987,7 +987,7 @@ class ToolLoopAgentRunner(BaseAgentRunner[TContext]):
func_tool_id,
f"error: {e!s}"
+ self._build_repeated_tool_call_guidance(
- func_tool_name, tool_call_streak
+ func_tool_name, tool_call_streak,
),
)
@@ -1003,17 +1003,17 @@ class ToolLoopAgentRunner(BaseAgentRunner[TContext]):
"id": func_tool_id,
"ts": time.time(),
"result": last_tcr_content,
- }
- )
+ },
+ ),
],
- )
+ ),
)
logger.info(f"Tool `{func_tool_name}` Result: {last_tcr_content}")
# 处理函数调用响应
if tool_call_result_blocks:
yield _HandleFunctionToolsResult.from_tool_call_result_blocks(
- tool_call_result_blocks
+ tool_call_result_blocks,
)
def _build_tool_requery_context(
@@ -1029,7 +1029,7 @@ class ToolLoopAgentRunner(BaseAgentRunner[TContext]):
elif isinstance(msg, dict):
contexts.append(copy.deepcopy(msg))
instruction = self.SKILLS_LIKE_REQUERY_INSTRUCTION_TEMPLATE.format(
- tool_names=", ".join(tool_names)
+ tool_names=", ".join(tool_names),
)
if extra_instruction:
instruction = f"{instruction}\n{extra_instruction}"
@@ -1072,7 +1072,7 @@ class ToolLoopAgentRunner(BaseAgentRunner[TContext]):
if isinstance(self._tool_schema_param_set, ToolSet):
param_subset = self._build_tool_subset(
- self._tool_schema_param_set, tool_names
+ self._tool_schema_param_set, tool_names,
)
if param_subset.tools and tool_names:
contexts = self._build_tool_requery_context(tool_names)
@@ -1096,7 +1096,7 @@ class ToolLoopAgentRunner(BaseAgentRunner[TContext]):
and not self._has_meaningful_assistant_reply(llm_resp)
):
logger.warning(
- "skills_like tool re-query returned no tool calls and no explanation; retrying with stronger instruction."
+ "skills_like tool re-query returned no tool calls and no explanation; retrying with stronger instruction.",
)
repair_contexts = self._build_tool_requery_context(
tool_names,
@@ -1155,7 +1155,7 @@ class ToolLoopAgentRunner(BaseAgentRunner[TContext]):
ThinkPart(
think=llm_resp.reasoning_content,
encrypted=llm_resp.reasoning_signature,
- )
+ ),
)
if llm_resp.completion_text:
parts.append(TextPart(text=llm_resp.completion_text))
@@ -1188,7 +1188,7 @@ class ToolLoopAgentRunner(BaseAgentRunner[TContext]):
if self._is_stop_requested():
await self._close_executor(executor)
raise _ToolExecutionInterrupted(
- "Tool execution interrupted before reading the next tool result."
+ "Tool execution interrupted before reading the next tool result.",
)
next_result_task = asyncio.create_task(anext(executor))
@@ -1208,7 +1208,7 @@ class ToolLoopAgentRunner(BaseAgentRunner[TContext]):
await self._close_executor(executor)
raise _ToolExecutionInterrupted(
- "Tool execution interrupted by a stop request."
+ "Tool execution interrupted by a stop request.",
)
try:
diff --git a/astrbot/core/agent/tool.py b/astrbot/core/agent/tool.py
index 4cee6ba6d..d5a8f6338 100644
--- a/astrbot/core/agent/tool.py
+++ b/astrbot/core/agent/tool.py
@@ -32,7 +32,7 @@ class ToolSchema:
@model_validator(mode="after")
def validate_parameters(self) -> "ToolSchema":
jsonschema.validate(
- self.parameters, jsonschema.Draft202012Validator.META_SCHEMA
+ self.parameters, jsonschema.Draft202012Validator.META_SCHEMA,
)
return self
@@ -70,7 +70,7 @@ class FunctionTool(ToolSchema, Generic[TContext]):
async def call(self, context: ContextWrapper[TContext], **kwargs) -> ToolExecResult:
"""Run the tool with the given arguments. The handler field has priority."""
raise NotImplementedError(
- "FunctionTool.call() must be implemented by subclasses or set a handler."
+ "FunctionTool.call() must be implemented by subclasses or set a handler.",
)
@@ -134,7 +134,7 @@ class ToolSet:
parameters=light_params,
description=tool.description,
handler=None,
- )
+ ),
)
return ToolSet(light_tools)
@@ -155,7 +155,7 @@ class ToolSet:
parameters=params,
description="",
handler=None,
- )
+ ),
)
return ToolSet(param_tools)
diff --git a/astrbot/core/agent/tool_image_cache.py b/astrbot/core/agent/tool_image_cache.py
index 72e22dd52..0bcf44791 100644
--- a/astrbot/core/agent/tool_image_cache.py
+++ b/astrbot/core/agent/tool_image_cache.py
@@ -86,6 +86,7 @@ class ToolImageCache:
Returns:
CachedImage object with file path.
+
"""
ext = self._get_file_extension(mime_type)
file_name = f"{tool_call_id}_{index}{ext}"
@@ -109,7 +110,7 @@ class ToolImageCache:
)
def get_image_base64_by_path(
- self, file_path: str, mime_type: str = "image/png"
+ self, file_path: str, mime_type: str = "image/png",
) -> tuple[str, str] | None:
"""Read an image file and return its base64 encoded data.
@@ -119,6 +120,7 @@ class ToolImageCache:
Returns:
Tuple of (base64_data, mime_type) if found, None otherwise.
+
"""
if not os.path.exists(file_path):
return None
@@ -137,6 +139,7 @@ class ToolImageCache:
Returns:
Number of images cleaned up.
+
"""
now = time.time()
cleaned = 0
diff --git a/astrbot/core/astr_agent_hooks.py b/astrbot/core/astr_agent_hooks.py
index a67d7b49d..852f8f6c1 100644
--- a/astrbot/core/astr_agent_hooks.py
+++ b/astrbot/core/astr_agent_hooks.py
@@ -17,7 +17,7 @@ class MainAgentHooks(BaseAgentRunHooks[AstrAgentContext]):
if llm_response and llm_response.reasoning_content:
# we will use this in result_decorate stage to inject reasoning content to chain
run_context.context.event.set_extra(
- "_llm_reasoning_content", llm_response.reasoning_content
+ "_llm_reasoning_content", llm_response.reasoning_content,
)
await call_event_hook(
diff --git a/astrbot/core/astr_agent_run_util.py b/astrbot/core/astr_agent_run_util.py
index eca24699a..02c29a925 100644
--- a/astrbot/core/astr_agent_run_util.py
+++ b/astrbot/core/astr_agent_run_util.py
@@ -47,7 +47,7 @@ def _extract_chain_json_data(msg_chain: MessageChain) -> dict | None:
def _record_tool_call_name(
- tool_info: dict | None, tool_name_by_call_id: dict[str, str]
+ tool_info: dict | None, tool_name_by_call_id: dict[str, str],
) -> None:
if not isinstance(tool_info, dict):
return
@@ -65,7 +65,7 @@ def _build_tool_call_status_message(tool_info: dict | None) -> str:
def _build_tool_result_status_message(
- msg_chain: MessageChain, tool_name_by_call_id: dict[str, str]
+ msg_chain: MessageChain, tool_name_by_call_id: dict[str, str],
) -> str:
tool_name = "unknown"
tool_result = ""
@@ -103,7 +103,7 @@ async def run_agent(
if step_idx == max_step + 1:
logger.warning(
- f"Agent reached max steps ({max_step}), forcing a final response."
+ f"Agent reached max steps ({max_step}), forcing a final response.",
)
if not agent_runner.done():
# 拔掉所有工具
@@ -114,7 +114,7 @@ async def run_agent(
Message(
role="user",
content="工具调用次数已达到上限,请停止使用工具,并根据已经收集到的信息,对你的任务和发现进行总结,然后直接回复用户。",
- )
+ ),
)
stop_watcher = asyncio.create_task(
@@ -145,7 +145,7 @@ async def run_agent(
astr_event.trace.record(
"agent_tool_result",
tool_result=msg_chain.get_plain_text(
- with_other_comps_mark=True
+ with_other_comps_mark=True,
),
)
@@ -157,10 +157,10 @@ async def run_agent(
await astr_event.send(msg_chain)
elif show_tool_use and show_tool_call_result:
status_msg = _build_tool_result_status_message(
- msg_chain, tool_name_by_call_id
+ msg_chain, tool_name_by_call_id,
)
await astr_event.send(
- MessageChain(type="tool_call").message(status_msg)
+ MessageChain(type="tool_call").message(status_msg),
)
# 对于其他情况,暂时先不处理
continue
@@ -177,7 +177,7 @@ async def run_agent(
tool_info = _extract_chain_json_data(resp.data["chain"])
astr_event.trace.record(
"agent_tool_call",
- tool_name=tool_info if tool_info else "unknown",
+ tool_name=tool_info or "unknown",
)
_record_tool_call_name(tool_info, tool_name_by_call_id)
@@ -188,7 +188,7 @@ async def run_agent(
# Delay tool status notification until tool_call_result.
continue
chain = MessageChain(type="tool_call").message(
- _build_tool_call_status_message(tool_info)
+ _build_tool_call_status_message(tool_info),
)
await astr_event.send(chain)
continue
@@ -229,7 +229,7 @@ async def run_agent(
MessageChain(
type="agent_stats",
chain=[Json(data=agent_runner.stats.to_dict())],
- )
+ ),
)
break
@@ -244,7 +244,7 @@ async def run_agent(
logger.error(traceback.format_exc())
custom_error_message = extract_persona_custom_error_message_from_event(
- astr_event
+ astr_event,
)
if custom_error_message:
err_msg = custom_error_message
@@ -252,7 +252,7 @@ async def run_agent(
err_msg = (
f"Error occurred during AI execution.\n"
f"Error Type: {type(e).__name__}\n"
- f"Error Message: {str(e)}"
+ f"Error Message: {e!s}"
)
error_llm_response = LLMResponse(
@@ -261,7 +261,7 @@ async def run_agent(
)
try:
await agent_runner.agent_hooks.on_agent_done(
- agent_runner.run_context, error_llm_response
+ agent_runner.run_context, error_llm_response,
)
except Exception:
logger.exception("Error in on_agent_done hook")
@@ -301,6 +301,7 @@ async def run_live_agent(
Yields:
MessageChain: 包含文本或音频数据的消息链
+
"""
# 如果没有 TTS Provider,直接发送文本
if not tts_provider:
@@ -321,7 +322,7 @@ async def run_live_agent(
else:
logger.info(
f"[Live Agent] 使用 TTS({tts_provider.meta().type} "
- "使用 get_audio,将按句子分块生成音频)"
+ "使用 get_audio,将按句子分块生成音频)",
)
# 统计数据初始化
@@ -343,17 +344,17 @@ async def run_live_agent(
show_tool_use,
show_tool_call_result,
show_reasoning,
- )
+ ),
)
# 2. 启动 TTS 任务:负责从 text_queue 读取文本并生成音频到 audio_queue
if support_stream:
tts_task = asyncio.create_task(
- _safe_tts_stream_wrapper(tts_provider, text_queue, audio_queue)
+ _safe_tts_stream_wrapper(tts_provider, text_queue, audio_queue),
)
else:
tts_task = asyncio.create_task(
- _simulated_stream_tts(tts_provider, text_queue, audio_queue)
+ _simulated_stream_tts(tts_provider, text_queue, audio_queue),
)
# 3. 主循环:从 audio_queue 读取音频并 yield
@@ -395,7 +396,6 @@ async def run_live_agent(
tts_task.cancel()
# 确保队列被消费
- pass
tts_end_time = time.time()
@@ -414,10 +414,10 @@ async def run_live_agent(
"tts_first_frame_time": tts_first_frame_time,
"tts": tts_provider.meta().type,
"chat_model": agent_runner.provider.get_model(),
- }
- )
+ },
+ ),
],
- )
+ ),
)
except Exception as e:
logger.error(f"发送 TTS 统计信息失败: {e}")
@@ -519,7 +519,7 @@ async def _simulated_stream_tts(
await audio_queue.put((text, audio_data))
except Exception as e:
logger.error(
- f"[Live TTS Simulated] Error processing text '{text[:20]}...': {e}"
+ f"[Live TTS Simulated] Error processing text '{text[:20]}...': {e}",
)
# 继续处理下一句
diff --git a/astrbot/core/astr_agent_tool_exec.py b/astrbot/core/astr_agent_tool_exec.py
index 18ac1a446..40becfa45 100644
--- a/astrbot/core/astr_agent_tool_exec.py
+++ b/astrbot/core/astr_agent_tool_exec.py
@@ -53,7 +53,7 @@ class FunctionToolExecutor(BaseFunctionToolExecutor[AstrAgentContext]):
return [image_urls_raw]
if isinstance(image_urls_raw, (Sequence, AbstractSet)) and not isinstance(
- image_urls_raw, (str, bytes, bytearray)
+ image_urls_raw, (str, bytes, bytearray),
):
return [item for item in image_urls_raw if isinstance(item, str)]
@@ -65,7 +65,7 @@ class FunctionToolExecutor(BaseFunctionToolExecutor[AstrAgentContext]):
@classmethod
async def _collect_image_urls_from_message(
- cls, run_context: ContextWrapper[AstrAgentContext]
+ cls, run_context: ContextWrapper[AstrAgentContext],
) -> list[str]:
urls: list[str] = []
event = getattr(run_context.context, "event", None)
@@ -133,7 +133,7 @@ class FunctionToolExecutor(BaseFunctionToolExecutor[AstrAgentContext]):
is_bg = tool_args.pop("background_task", False)
if is_bg:
async for r in cls._execute_handoff_background(
- tool, run_context, **tool_args
+ tool, run_context, **tool_args,
):
yield r
return
@@ -273,7 +273,7 @@ class FunctionToolExecutor(BaseFunctionToolExecutor[AstrAgentContext]):
# Use per-subagent provider override if configured; otherwise fall back
# to the current/default provider resolution.
prov_id = getattr(
- tool, "provider_id", None
+ tool, "provider_id", None,
) or await ctx.get_current_chat_provider_id(umo)
# prepare begin dialogs
@@ -286,7 +286,7 @@ class FunctionToolExecutor(BaseFunctionToolExecutor[AstrAgentContext]):
contexts.append(
dialog
if isinstance(dialog, Message)
- else Message.model_validate(dialog)
+ else Message.model_validate(dialog),
)
except Exception:
continue
@@ -307,7 +307,7 @@ class FunctionToolExecutor(BaseFunctionToolExecutor[AstrAgentContext]):
stream=stream,
)
yield mcp.types.CallToolResult(
- content=[mcp.types.TextContent(type="text", text=llm_resp.completion_text)]
+ content=[mcp.types.TextContent(type="text", text=llm_resp.completion_text)],
)
@classmethod
@@ -412,7 +412,7 @@ class FunctionToolExecutor(BaseFunctionToolExecutor[AstrAgentContext]):
result_text = ""
try:
async for r in cls._execute_local(
- tool, run_context, tool_call_timeout=3600, **tool_args
+ tool, run_context, tool_call_timeout=3600, **tool_args,
):
# collect results, currently we just collect the text results
if isinstance(r, mcp.types.CallToolResult):
@@ -503,7 +503,7 @@ class FunctionToolExecutor(BaseFunctionToolExecutor[AstrAgentContext]):
bg = json.dumps(extras["background_task_result"], ensure_ascii=False)
req.system_prompt += BACKGROUND_TASK_RESULT_WOKE_SYSTEM_PROMPT.format(
- background_task_result=bg
+ background_task_result=bg,
)
req.prompt = (
"Proceed according to your system instructions. "
@@ -516,11 +516,11 @@ class FunctionToolExecutor(BaseFunctionToolExecutor[AstrAgentContext]):
if not req.func_tool:
req.func_tool = ToolSet()
req.func_tool.add_tool(
- ctx.get_llm_tool_manager().get_builtin_tool(SendMessageToUserTool)
+ ctx.get_llm_tool_manager().get_builtin_tool(SendMessageToUserTool),
)
result = await build_main_agent(
- event=cron_event, plugin_context=ctx, config=config, req=req
+ event=cron_event, plugin_context=ctx, config=config, req=req,
)
if not result:
logger.error(f"Failed to build main agent for background task {tool_name}.")
@@ -583,7 +583,7 @@ class FunctionToolExecutor(BaseFunctionToolExecutor[AstrAgentContext]):
awaitable = tool.call
method_name = "call"
elif hasattr(tool, "run"):
- awaitable = getattr(tool, "run")
+ awaitable = tool.run
method_name = "run"
if awaitable is None:
raise ValueError("Tool must have a valid handler or override 'run' method.")
@@ -620,7 +620,7 @@ class FunctionToolExecutor(BaseFunctionToolExecutor[AstrAgentContext]):
MessageChain(
chain=res.chain,
type="tool_direct_result",
- )
+ ),
)
except Exception as e:
logger.error(
@@ -705,7 +705,7 @@ async def call_local_llm_tool(
handler_param_str = "(unable to inspect signature)"
raise Exception(
- f"Tool handler parameter mismatch, please check the handler definition. Handler parameters: {handler_param_str}"
+ f"Tool handler parameter mismatch, please check the handler definition. Handler parameters: {handler_param_str}",
) from e
except Exception as e:
trace_ = traceback.format_exc()
diff --git a/astrbot/core/astr_main_agent.py b/astrbot/core/astr_main_agent.py
index ebb95baa4..9126c0ded 100644
--- a/astrbot/core/astr_main_agent.py
+++ b/astrbot/core/astr_main_agent.py
@@ -96,7 +96,8 @@ from astrbot.core.utils.string_utils import normalize_and_dedupe_strings
@dataclass(slots=True)
class MainAgentBuildConfig:
"""The main agent build configuration.
- Most of the configs can be found in the cmd_config.json"""
+ Most of the configs can be found in the cmd_config.json
+ """
tool_call_timeout: int
"""The timeout (in seconds) for a tool call.
@@ -160,7 +161,7 @@ class MainAgentBuildResult:
def _select_provider(
- event: AstrMessageEvent, plugin_context: Context
+ event: AstrMessageEvent, plugin_context: Context,
) -> Provider | None:
"""Select chat provider for the event."""
sel_provider = event.get_extra("selected_provider")
@@ -170,7 +171,7 @@ def _select_provider(
logger.error("未找到指定的提供商: %s。", sel_provider)
if not isinstance(provider, Provider):
logger.error(
- "选择的提供商类型无效(%s),跳过 LLM 请求处理。", type(provider)
+ "选择的提供商类型无效(%s),跳过 LLM 请求处理。", type(provider),
)
return None
return provider
@@ -182,7 +183,7 @@ def _select_provider(
async def _get_session_conv(
- event: AstrMessageEvent, plugin_context: Context
+ event: AstrMessageEvent, plugin_context: Context,
) -> Conversation:
conv_mgr = plugin_context.conversation_manager
umo = event.unified_msg_origin
@@ -226,8 +227,8 @@ async def _apply_kb(
req.func_tool = ToolSet()
req.func_tool.add_tool(
plugin_context.get_llm_tool_manager().get_builtin_tool(
- KnowledgeBaseQueryTool
- )
+ KnowledgeBaseQueryTool,
+ ),
)
@@ -262,7 +263,7 @@ async def _apply_file_extract(
config.file_extract_msh_api_key,
)
for file_path in file_paths
- ]
+ ],
)
else:
logger.error("Unsupported file extract provider: %s", config.file_extract_prov)
@@ -336,7 +337,7 @@ async def _ensure_persona_and_skills(
)
set_persona_custom_error_message_on_event(
- event, extract_persona_custom_error_message_from_persona(persona)
+ event, extract_persona_custom_error_message_from_persona(persona),
)
if persona:
@@ -417,7 +418,7 @@ async def _ensure_persona_and_skills(
tool.name
for tool in tmgr.func_list
if not isinstance(tool, HandoffTool)
- ]
+ ],
)
continue
if not isinstance(tools, list):
@@ -509,7 +510,7 @@ async def _ensure_img_caption(
)
if caption:
req.extra_user_content_parts.append(
- TextPart(text=f"{caption}")
+ TextPart(text=f"{caption}"),
)
req.image_urls = []
except Exception as exc: # noqa: BLE001
@@ -521,19 +522,19 @@ async def _ensure_img_caption(
def _append_quoted_image_attachment(req: ProviderRequest, image_path: str) -> None:
req.extra_user_content_parts.append(
- TextPart(text=f"[Image Attachment in quoted message: path {image_path}]")
+ TextPart(text=f"[Image Attachment in quoted message: path {image_path}]"),
)
def _append_audio_attachment(req: ProviderRequest, audio_path: str) -> None:
req.extra_user_content_parts.append(
- TextPart(text=f"[Audio Attachment: path {audio_path}]")
+ TextPart(text=f"[Audio Attachment: path {audio_path}]"),
)
def _append_quoted_audio_attachment(req: ProviderRequest, audio_path: str) -> None:
req.extra_user_content_parts.append(
- TextPart(text=f"[Audio Attachment in quoted message: path {audio_path}]")
+ TextPart(text=f"[Audio Attachment in quoted message: path {audio_path}]"),
)
@@ -659,7 +660,7 @@ async def _process_quote_message(
)
if llm_resp.completion_text:
content_parts.append(
- f"[Image Caption in quoted message]: {llm_resp.completion_text}"
+ f"[Image Caption in quoted message]: {llm_resp.completion_text}",
)
else:
logger.warning("No provider found for image captioning in quote.")
@@ -732,7 +733,7 @@ async def _decorate_llm_request(
config: MainAgentBuildConfig,
) -> None:
cfg = config.provider_settings or plugin_context.get_config(
- umo=event.unified_msg_origin
+ umo=event.unified_msg_origin,
).get("provider_settings", {})
_apply_prompt_prefix(req, cfg)
@@ -772,7 +773,7 @@ def _modalities_fix(provider: Provider, req: ProviderRequest) -> None:
provider_cfg = provider.provider_config.get("modalities", ["image"])
if "image" not in provider_cfg:
logger.debug(
- "Provider %s does not support image, using placeholder.", provider
+ "Provider %s does not support image, using placeholder.", provider,
)
image_count = len(req.image_urls)
placeholder = " ".join(["[Image]"] * image_count)
@@ -785,7 +786,7 @@ def _modalities_fix(provider: Provider, req: ProviderRequest) -> None:
provider_cfg = provider.provider_config.get("modalities", ["audio"])
if "audio" not in provider_cfg:
logger.debug(
- "Provider %s does not support audio, using placeholder.", provider
+ "Provider %s does not support audio, using placeholder.", provider,
)
audio_count = len(req.audio_urls)
placeholder = " ".join(["[Audio]"] * audio_count)
@@ -798,7 +799,7 @@ def _modalities_fix(provider: Provider, req: ProviderRequest) -> None:
provider_cfg = provider.provider_config.get("modalities", ["tool_use"])
if "tool_use" not in provider_cfg:
logger.debug(
- "Provider %s does not support tool_use, clearing tools.", provider
+ "Provider %s does not support tool_use, clearing tools.", provider,
)
req.func_tool = None
@@ -927,7 +928,7 @@ def _plugin_tool_fix(event: AstrMessageEvent, req: ProviderRequest) -> None:
async def _handle_webchat(
- event: AstrMessageEvent, req: ProviderRequest, prov: Provider
+ event: AstrMessageEvent, req: ProviderRequest, prov: Provider,
) -> None:
from astrbot.core import db_helper
@@ -962,7 +963,7 @@ async def _handle_webchat(
if not title or "" in title:
return
logger.info(
- "Generated chatui title for session %s: %s", chatui_session_id, title
+ "Generated chatui title for session %s: %s", chatui_session_id, title,
)
await db_helper.update_platform_session(
session_id=chatui_session_id,
@@ -981,7 +982,7 @@ def _apply_llm_safety_mode(config: MainAgentBuildConfig, req: ProviderRequest) -
def _apply_sandbox_tools(
- config: MainAgentBuildConfig, req: ProviderRequest, session_id: str
+ config: MainAgentBuildConfig, req: ProviderRequest, session_id: str,
) -> None:
if req.func_tool is None:
req.func_tool = ToolSet()
@@ -1092,7 +1093,7 @@ async def _apply_web_search_tools(
def _get_compress_provider(
- config: MainAgentBuildConfig, plugin_context: Context
+ config: MainAgentBuildConfig, plugin_context: Context,
) -> Provider | None:
if not config.llm_compress_provider_id:
return None
@@ -1115,12 +1116,12 @@ def _get_compress_provider(
def _get_fallback_chat_providers(
- provider: Provider, plugin_context: Context, provider_settings: dict
+ provider: Provider, plugin_context: Context, provider_settings: dict,
) -> list[Provider]:
fallback_ids = provider_settings.get("fallback_chat_models", [])
if not isinstance(fallback_ids, list):
logger.warning(
- "fallback_chat_models setting is not a list, skip fallback providers."
+ "fallback_chat_models setting is not a list, skip fallback providers.",
)
return []
@@ -1183,7 +1184,7 @@ async def build_main_agent(
if sel_model := event.get_extra("selected_model"):
req.model = sel_model
if config.provider_wake_prefix and not event.message_str.startswith(
- config.provider_wake_prefix
+ config.provider_wake_prefix,
):
return None
@@ -1201,7 +1202,7 @@ async def build_main_agent(
event.track_temporary_local_file(image_path)
req.image_urls.append(image_path)
req.extra_user_content_parts.append(
- TextPart(text=f"[Image Attachment: path {image_path}]")
+ TextPart(text=f"[Image Attachment: path {image_path}]"),
)
elif isinstance(comp, Record):
audio_path = await comp.convert_to_file_path()
@@ -1212,15 +1213,15 @@ async def build_main_agent(
file_name = comp.name or os.path.basename(file_path)
req.extra_user_content_parts.append(
TextPart(
- text=f"[File Attachment: name {file_name}, path {file_path}]"
- )
+ text=f"[File Attachment: name {file_name}, path {file_path}]",
+ ),
)
# quoted message attachments
reply_comps = [
comp for comp in event.message_obj.message if isinstance(comp, Reply)
]
quoted_message_settings = _get_quoted_message_parser_settings(
- config.provider_settings
+ config.provider_settings,
)
fallback_quoted_image_count = 0
for comp in reply_comps:
@@ -1250,8 +1251,8 @@ async def build_main_agent(
text=(
f"[File Attachment in quoted message: "
f"name {file_name}, path {file_path}]"
- )
- )
+ ),
+ ),
)
# Fallback quoted image extraction for reply-id-only payloads, or when
@@ -1263,7 +1264,7 @@ async def build_main_agent(
event,
comp,
settings=quoted_message_settings,
- )
+ ),
)
remaining_limit = max(
config.max_quoted_fallback_images
@@ -1357,8 +1358,8 @@ async def build_main_agent(
req.func_tool = ToolSet()
req.func_tool.add_tool(
plugin_context.get_llm_tool_manager().get_builtin_tool(
- SendMessageToUserTool
- )
+ SendMessageToUserTool,
+ ),
)
if provider.provider_config.get("max_context_tokens", 0) <= 0:
@@ -1400,7 +1401,7 @@ async def build_main_agent(
enforce_max_turns=config.max_context_length,
tool_schema_mode=config.tool_schema_mode,
fallback_providers=_get_fallback_chat_providers(
- provider, plugin_context, config.provider_settings
+ provider, plugin_context, config.provider_settings,
),
)
diff --git a/astrbot/core/backup/__init__.py b/astrbot/core/backup/__init__.py
index 8e33ef970..ba96d9edf 100644
--- a/astrbot/core/backup/__init__.py
+++ b/astrbot/core/backup/__init__.py
@@ -16,11 +16,11 @@ from .exporter import AstrBotExporter
from .importer import AstrBotImporter, ImportPreCheckResult
__all__ = [
+ "BACKUP_MANIFEST_VERSION",
+ "KB_METADATA_MODELS",
+ "MAIN_DB_MODELS",
"AstrBotExporter",
"AstrBotImporter",
"ImportPreCheckResult",
- "MAIN_DB_MODELS",
- "KB_METADATA_MODELS",
"get_backup_directories",
- "BACKUP_MANIFEST_VERSION",
]
diff --git a/astrbot/core/backup/constants.py b/astrbot/core/backup/constants.py
index b832a1b72..6a26e0cdd 100644
--- a/astrbot/core/backup/constants.py
+++ b/astrbot/core/backup/constants.py
@@ -68,6 +68,7 @@ def get_backup_directories() -> dict[str, str]:
Returns:
dict: 键为备份文件中的目录名称,值为目录的绝对路径
+
"""
return {
"plugins": get_astrbot_plugin_path(), # 插件本体
diff --git a/astrbot/core/backup/exporter.py b/astrbot/core/backup/exporter.py
index a92237599..cbae0b8af 100644
--- a/astrbot/core/backup/exporter.py
+++ b/astrbot/core/backup/exporter.py
@@ -78,6 +78,7 @@ class AstrBotExporter:
Returns:
str: 生成的 ZIP 文件路径
+
"""
if output_dir is None:
output_dir = get_astrbot_backups_path()
@@ -98,7 +99,7 @@ class AstrBotExporter:
await progress_callback("main_db", 0, 100, "正在导出主数据库...")
main_data = await self._export_main_database()
main_db_json = json.dumps(
- main_data, ensure_ascii=False, indent=2, default=str
+ main_data, ensure_ascii=False, indent=2, default=str,
)
zf.writestr("databases/main_db.json", main_db_json)
self._add_checksum("databases/main_db.json", main_db_json)
@@ -114,17 +115,17 @@ class AstrBotExporter:
if self.kb_manager:
if progress_callback:
await progress_callback(
- "kb_metadata", 0, 100, "正在导出知识库元数据..."
+ "kb_metadata", 0, 100, "正在导出知识库元数据...",
)
kb_meta_data = await self._export_kb_metadata()
kb_meta_json = json.dumps(
- kb_meta_data, ensure_ascii=False, indent=2, default=str
+ kb_meta_data, ensure_ascii=False, indent=2, default=str,
)
zf.writestr("databases/kb_metadata.json", kb_meta_json)
self._add_checksum("databases/kb_metadata.json", kb_meta_json)
if progress_callback:
await progress_callback(
- "kb_metadata", 100, 100, "知识库元数据导出完成"
+ "kb_metadata", 100, 100, "知识库元数据导出完成",
)
# 导出每个知识库的文档数据
@@ -140,7 +141,7 @@ class AstrBotExporter:
)
doc_data = await self._export_kb_documents(kb_helper)
doc_json = json.dumps(
- doc_data, ensure_ascii=False, indent=2, default=str
+ doc_data, ensure_ascii=False, indent=2, default=str,
)
doc_path = f"databases/kb_{kb_id}/documents.json"
zf.writestr(doc_path, doc_json)
@@ -154,7 +155,7 @@ class AstrBotExporter:
if progress_callback:
await progress_callback(
- "kb_documents", total_kbs, total_kbs, "知识库文档导出完成"
+ "kb_documents", total_kbs, total_kbs, "知识库文档导出完成",
)
# 3. 导出配置文件
@@ -178,7 +179,7 @@ class AstrBotExporter:
# 5. 导出插件和其他目录
if progress_callback:
await progress_callback(
- "directories", 0, 100, "正在导出插件和数据目录..."
+ "directories", 0, 100, "正在导出插件和数据目录...",
)
dir_stats = await self._export_directories(zf)
if progress_callback:
@@ -216,7 +217,7 @@ class AstrBotExporter:
self._model_to_dict(record) for record in records
]
logger.debug(
- f"导出表 {table_name}: {len(export_data[table_name])} 条记录"
+ f"导出表 {table_name}: {len(export_data[table_name])} 条记录",
)
except Exception as e:
logger.warning(f"导出表 {table_name} 失败: {e}")
@@ -240,7 +241,7 @@ class AstrBotExporter:
self._model_to_dict(record) for record in records
]
logger.debug(
- f"导出知识库表 {table_name}: {len(export_data[table_name])} 条记录"
+ f"导出知识库表 {table_name}: {len(export_data[table_name])} 条记录",
)
except Exception as e:
logger.warning(f"导出知识库表 {table_name} 失败: {e}")
@@ -286,7 +287,7 @@ class AstrBotExporter:
logger.warning(f"导出 FAISS 索引失败: {e}")
async def _export_kb_media_files(
- self, zf: zipfile.ZipFile, kb_helper: Any, kb_id: str
+ self, zf: zipfile.ZipFile, kb_helper: Any, kb_id: str,
) -> None:
"""导出知识库的多媒体文件"""
try:
@@ -305,12 +306,13 @@ class AstrBotExporter:
logger.warning(f"导出知识库媒体文件失败: {e}")
async def _export_directories(
- self, zf: zipfile.ZipFile
+ self, zf: zipfile.ZipFile,
) -> dict[str, dict[str, int]]:
"""导出插件和其他数据目录
Returns:
dict: 每个目录的统计信息 {dir_name: {"files": count, "size": bytes}}
+
"""
stats: dict[str, dict[str, int]] = {}
backup_directories = get_backup_directories()
@@ -347,7 +349,7 @@ class AstrBotExporter:
stats[dir_name] = {"files": file_count, "size": total_size}
logger.debug(
- f"导出目录 {dir_name}: {file_count} 个文件, {total_size} 字节"
+ f"导出目录 {dir_name}: {file_count} 个文件, {total_size} 字节",
)
except Exception as e:
logger.warning(f"导出目录 {dir_path} 失败: {e}")
@@ -356,7 +358,7 @@ class AstrBotExporter:
return stats
async def _export_attachments(
- self, zf: zipfile.ZipFile, attachments: list[dict]
+ self, zf: zipfile.ZipFile, attachments: list[dict],
) -> None:
"""导出附件文件"""
for attachment in attachments:
diff --git a/astrbot/core/backup/importer.py b/astrbot/core/backup/importer.py
index b51c7d956..182c28a1f 100644
--- a/astrbot/core/backup/importer.py
+++ b/astrbot/core/backup/importer.py
@@ -46,6 +46,7 @@ def _get_major_version(version_str: str) -> str:
Returns:
主版本字符串,如 "4.9", "4.10"
+
"""
if not version_str:
return "0.0"
@@ -54,7 +55,7 @@ def _get_major_version(version_str: str) -> str:
parts = [p for p in version.split(".") if p] # 过滤空字符串
if len(parts) >= 2:
return f"{parts[0]}.{parts[1]}"
- elif len(parts) == 1 and parts[0]:
+ if len(parts) == 1 and parts[0]:
return f"{parts[0]}.0"
return "0.0"
@@ -245,6 +246,7 @@ class AstrBotImporter:
Returns:
ImportPreCheckResult: 预检查结果
+
"""
result = ImportPreCheckResult()
result.current_version = VERSION
@@ -306,6 +308,7 @@ class AstrBotImporter:
Returns:
dict: {status, can_import, message}
+
"""
if not backup_version:
return {
@@ -361,6 +364,7 @@ class AstrBotImporter:
Returns:
ImportResult: 导入结果
+
"""
result = ImportResult()
@@ -464,7 +468,7 @@ class AstrBotImporter:
await progress_callback("attachments", 0, 100, "正在导入附件...")
attachment_count = await self._import_attachments(
- zf, main_data.get("attachments", [])
+ zf, main_data.get("attachments", []),
)
result.imported_files["attachments"] = attachment_count
@@ -474,7 +478,7 @@ class AstrBotImporter:
# 6. 导入插件和其他目录
if progress_callback:
await progress_callback(
- "directories", 0, 100, "正在导入插件和数据目录..."
+ "directories", 0, 100, "正在导入插件和数据目录...",
)
dir_stats = await self._import_directories(zf, manifest, result)
@@ -515,16 +519,15 @@ class AstrBotImporter:
async def _clear_main_db(self) -> None:
"""清空主数据库所有表"""
- async with self.main_db.get_db() as session:
- async with session.begin():
- for table_name, model_class in MAIN_DB_MODELS.items():
- try:
- await session.execute(delete(model_class))
- logger.debug(f"已清空表 {table_name}")
- except Exception as e:
- raise DatabaseClearError(
- f"清空表 {table_name} 失败: {e}"
- ) from e
+ async with self.main_db.get_db() as session, session.begin():
+ for table_name, model_class in MAIN_DB_MODELS.items():
+ try:
+ await session.execute(delete(model_class))
+ logger.debug(f"已清空表 {table_name}")
+ except Exception as e:
+ raise DatabaseClearError(
+ f"清空表 {table_name} 失败: {e}",
+ ) from e
async def _clear_kb_data(self) -> None:
"""清空知识库数据"""
@@ -532,14 +535,13 @@ class AstrBotImporter:
return
# 清空知识库元数据表
- async with self.kb_manager.kb_db.get_db() as session:
- async with session.begin():
- for table_name, model_class in KB_METADATA_MODELS.items():
- try:
- await session.execute(delete(model_class))
- logger.debug(f"已清空知识库表 {table_name}")
- except Exception as e:
- logger.warning(f"清空知识库表 {table_name} 失败: {e}")
+ async with self.kb_manager.kb_db.get_db() as session, session.begin():
+ for table_name, model_class in KB_METADATA_MODELS.items():
+ try:
+ await session.execute(delete(model_class))
+ logger.debug(f"已清空知识库表 {table_name}")
+ except Exception as e:
+ logger.warning(f"清空知识库表 {table_name} 失败: {e}")
# 删除知识库文件目录
for kb_id in list(self.kb_manager.kb_insts.keys()):
@@ -554,38 +556,37 @@ class AstrBotImporter:
self.kb_manager.kb_insts.clear()
async def _import_main_database(
- self, data: dict[str, list[dict]]
+ self, data: dict[str, list[dict]],
) -> dict[str, int]:
"""导入主数据库数据"""
imported: dict[str, int] = {}
- async with self.main_db.get_db() as session:
- async with session.begin():
- for table_name, rows in data.items():
- model_class = MAIN_DB_MODELS.get(table_name)
- if not model_class:
- logger.warning(f"未知的表: {table_name}")
- continue
- normalized_rows = self._preprocess_main_table_rows(table_name, rows)
+ async with self.main_db.get_db() as session, session.begin():
+ for table_name, rows in data.items():
+ model_class = MAIN_DB_MODELS.get(table_name)
+ if not model_class:
+ logger.warning(f"未知的表: {table_name}")
+ continue
+ normalized_rows = self._preprocess_main_table_rows(table_name, rows)
- count = 0
- for row in normalized_rows:
- try:
- # 转换 datetime 字符串为 datetime 对象
- row = self._convert_datetime_fields(row, model_class)
- obj = model_class(**row)
- session.add(obj)
- count += 1
- except Exception as e:
- logger.warning(f"导入记录到 {table_name} 失败: {e}")
+ count = 0
+ for row in normalized_rows:
+ try:
+ # 转换 datetime 字符串为 datetime 对象
+ row = self._convert_datetime_fields(row, model_class)
+ obj = model_class(**row)
+ session.add(obj)
+ count += 1
+ except Exception as e:
+ logger.warning(f"导入记录到 {table_name} 失败: {e}")
- imported[table_name] = count
- logger.debug(f"导入表 {table_name}: {count} 条记录")
+ imported[table_name] = count
+ logger.debug(f"导入表 {table_name}: {count} 条记录")
return imported
def _preprocess_main_table_rows(
- self, table_name: str, rows: list[dict[str, Any]]
+ self, table_name: str, rows: list[dict[str, Any]],
) -> list[dict[str, Any]]:
if table_name == "platform_stats":
normalized_rows = self._merge_platform_stats_rows(rows)
@@ -600,7 +601,7 @@ class AstrBotImporter:
return rows
def _merge_platform_stats_rows(
- self, rows: list[dict[str, Any]]
+ self, rows: list[dict[str, Any]],
) -> list[dict[str, Any]]:
"""Merge duplicate platform_stats rows by normalized timestamp/platform key.
@@ -608,6 +609,7 @@ class AstrBotImporter:
- Invalid/empty timestamps are kept as distinct rows to avoid accidental merging.
- Non-string platform_id/platform_type are kept as distinct rows.
- Invalid count warnings are rate-limited per function invocation.
+
"""
merged: dict[tuple[str, str, str], dict[str, Any]] = {}
result: list[dict[str, Any]] = []
@@ -707,24 +709,23 @@ class AstrBotImporter:
return
# 1. 导入知识库元数据
- async with self.kb_manager.kb_db.get_db() as session:
- async with session.begin():
- for table_name, rows in kb_meta_data.items():
- model_class = KB_METADATA_MODELS.get(table_name)
- if not model_class:
- continue
+ async with self.kb_manager.kb_db.get_db() as session, session.begin():
+ for table_name, rows in kb_meta_data.items():
+ model_class = KB_METADATA_MODELS.get(table_name)
+ if not model_class:
+ continue
- count = 0
- for row in rows:
- try:
- row = self._convert_datetime_fields(row, model_class)
- obj = model_class(**row)
- session.add(obj)
- count += 1
- except Exception as e:
- logger.warning(f"导入知识库记录到 {table_name} 失败: {e}")
+ count = 0
+ for row in rows:
+ try:
+ row = self._convert_datetime_fields(row, model_class)
+ obj = model_class(**row)
+ session.add(obj)
+ count += 1
+ except Exception as e:
+ logger.warning(f"导入知识库记录到 {table_name} 失败: {e}")
- result.imported_tables[f"kb_{table_name}"] = count
+ result.imported_tables[f"kb_{table_name}"] = count
# 2. 导入每个知识库的文档和文件
for kb_data in kb_meta_data.get("knowledge_bases", []):
@@ -851,6 +852,7 @@ class AstrBotImporter:
Returns:
dict: 每个目录导入的文件数量
+
"""
dir_stats: dict[str, int] = {}
diff --git a/astrbot/core/computer/booters/base.py b/astrbot/core/computer/booters/base.py
index 4c74e5edd..2b7fb4dfc 100644
--- a/astrbot/core/computer/booters/base.py
+++ b/astrbot/core/computer/booters/base.py
@@ -38,12 +38,9 @@ class ComputerBooter:
Should return a dict with `success` (bool) and `file_path` (str) keys.
"""
- ...
async def download_file(self, remote_path: str, local_path: str) -> None:
"""Download file from the computer."""
- ...
async def available(self) -> bool:
"""Check if the computer is available."""
- ...
diff --git a/astrbot/core/computer/booters/bay_manager.py b/astrbot/core/computer/booters/bay_manager.py
index 61ccc1b3a..4fecda157 100644
--- a/astrbot/core/computer/booters/bay_manager.py
+++ b/astrbot/core/computer/booters/bay_manager.py
@@ -60,7 +60,7 @@ class BayContainerManager:
raise RuntimeError(
"Failed to connect to Docker daemon. "
"Ensure Docker is installed and running, or configure "
- "an explicit Bay endpoint instead of auto-start mode."
+ "an explicit Bay endpoint instead of auto-start mode.",
) from exc
# 1. Look for an existing managed container
@@ -72,13 +72,12 @@ class BayContainerManager:
logger.info("[BayManager] Reusing existing Bay container: %s", cid)
self._container = await self._docker.containers.get(existing["Id"])
return f"http://127.0.0.1:{self._host_port}"
- else:
- # Container exists but stopped — restart it
- logger.info("[BayManager] Restarting stopped Bay container")
- container = await self._docker.containers.get(existing["Id"])
- await container.start()
- self._container = container
- return f"http://127.0.0.1:{self._host_port}"
+ # Container exists but stopped — restart it
+ logger.info("[BayManager] Restarting stopped Bay container")
+ container = await self._docker.containers.get(existing["Id"])
+ await container.start()
+ self._container = container
+ return f"http://127.0.0.1:{self._host_port}"
# 2. Pull image if needed
await self._pull_image_if_needed()
@@ -111,7 +110,7 @@ class BayContainerManager:
},
}
self._container = await self._docker.containers.create_or_replace(
- BAY_CONTAINER_NAME, config
+ BAY_CONTAINER_NAME, config,
)
await self._container.start()
logger.info("[BayManager] Bay container started: %s", BAY_CONTAINER_NAME)
@@ -129,7 +128,7 @@ class BayContainerManager:
while loop.time() < deadline:
try:
async with session.get(
- url, timeout=aiohttp.ClientTimeout(total=3)
+ url, timeout=aiohttp.ClientTimeout(total=3),
) as resp:
if resp.status == 200:
logger.info("[BayManager] Bay is healthy")
@@ -141,7 +140,7 @@ class BayContainerManager:
await asyncio.sleep(HEALTH_POLL_INTERVAL_S)
raise TimeoutError(
- f"Bay did not become healthy within {timeout}s (last error: {last_error})"
+ f"Bay did not become healthy within {timeout}s (last error: {last_error})",
)
async def read_credentials(self) -> str:
@@ -202,7 +201,7 @@ class BayContainerManager:
return api_key
except Exception as exc:
logger.debug(
- "[BayManager] Failed to read credentials from container: %s", exc
+ "[BayManager] Failed to read credentials from container: %s", exc,
)
return ""
diff --git a/astrbot/core/computer/booters/boxlite.py b/astrbot/core/computer/booters/boxlite.py
index 70064fdd4..8eb4807cf 100644
--- a/astrbot/core/computer/booters/boxlite.py
+++ b/astrbot/core/computer/booters/boxlite.py
@@ -34,11 +34,10 @@ class MockShipyardSandboxClient:
) as response:
if response.status == 200:
return await response.json()
- else:
- error_text = await response.text()
- raise Exception(
- f"Failed to exec operation: {response.status} {error_text}"
- )
+ error_text = await response.text()
+ raise Exception(
+ f"Failed to exec operation: {response.status} {error_text}",
+ )
async def upload_file(self, path: str, remote_path: str) -> dict:
"""Upload a file to the sandbox"""
@@ -54,7 +53,7 @@ class MockShipyardSandboxClient:
data.add_field(
"file",
file_content,
- filename=remote_path.split("/")[-1],
+ filename=remote_path.rsplit("/", maxsplit=1)[-1],
content_type="application/octet-stream",
)
data.add_field("file_path", remote_path)
@@ -73,19 +72,18 @@ class MockShipyardSandboxClient:
"message": "File uploaded successfully",
"file_path": remote_path,
}
- else:
- error_text = await response.text()
- return {
- "success": False,
- "error": f"Server returned {response.status}: {error_text}",
- "message": "File upload failed",
- }
+ error_text = await response.text()
+ return {
+ "success": False,
+ "error": f"Server returned {response.status}: {error_text}",
+ "message": "File upload failed",
+ }
except aiohttp.ClientError as e:
logger.error(f"Failed to upload file: {e}")
return {
"success": False,
- "error": f"Connection error: {str(e)}",
+ "error": f"Connection error: {e!s}",
"message": "File upload failed",
}
except asyncio.TimeoutError:
@@ -105,7 +103,7 @@ class MockShipyardSandboxClient:
logger.error(f"Unexpected error uploading file: {e}")
return {
"success": False,
- "error": f"Internal error: {str(e)}",
+ "error": f"Internal error: {e!s}",
"message": "File upload failed",
}
@@ -115,7 +113,7 @@ class MockShipyardSandboxClient:
while loop > 0:
try:
logger.info(
- f"Checking health for sandbox {ship_id} on {self.sb_url}..."
+ f"Checking health for sandbox {ship_id} on {self.sb_url}...",
)
url = f"{self.sb_url}/health"
async with aiohttp.ClientSession() as session:
@@ -131,7 +129,7 @@ class MockShipyardSandboxClient:
class BoxliteBooter(ComputerBooter):
async def boot(self, session_id: str) -> None:
logger.info(
- f"Booting(Boxlite) for session: {session_id}, this may take a while..."
+ f"Booting(Boxlite) for session: {session_id}, this may take a while...",
)
random_port = random.randint(20000, 30000)
self.box = boxlite.SimpleBox(
@@ -142,13 +140,13 @@ class BoxliteBooter(ComputerBooter):
{
"host_port": random_port,
"guest_port": 8123,
- }
+ },
],
)
await self.box.start()
logger.info(f"Boxlite booter started for session: {session_id}")
self.mocked = MockShipyardSandboxClient(
- sb_url=f"http://127.0.0.1:{random_port}"
+ sb_url=f"http://127.0.0.1:{random_port}",
)
self._fs = ShipyardFileSystemComponent(
client=self.mocked, # type: ignore
diff --git a/astrbot/core/computer/booters/local.py b/astrbot/core/computer/booters/local.py
index f11bc329f..be2ba56da 100644
--- a/astrbot/core/computer/booters/local.py
+++ b/astrbot/core/computer/booters/local.py
@@ -115,7 +115,7 @@ class LocalShellComponent(ShellComponent):
# `command` is intentionally executed through the current shell so
# local computer-use behavior matches existing tool semantics.
# Safety relies on `_is_safe_command()` and the allowed-root checks.
- proc = subprocess.Popen( # noqa: S602 # nosemgrep: python.lang.security.audit.dangerous-subprocess-use-audit
+ proc = subprocess.Popen( # nosemgrep: python.lang.security.audit.dangerous-subprocess-use-audit
command,
shell=shell,
cwd=working_dir,
@@ -127,7 +127,7 @@ class LocalShellComponent(ShellComponent):
# `command` is intentionally executed through the current shell so
# local computer-use behavior matches existing tool semantics.
# Safety relies on `_is_safe_command()` and the allowed-root checks.
- result = subprocess.run( # noqa: S602 # nosemgrep: python.lang.security.audit.dangerous-subprocess-use-audit
+ result = subprocess.run( # nosemgrep: python.lang.security.audit.dangerous-subprocess-use-audit
command,
shell=shell,
cwd=working_dir,
@@ -167,14 +167,14 @@ class LocalPythonComponent(PythonComponent):
"data": {
"output": {"text": stdout, "images": []},
"error": stderr,
- }
+ },
}
except subprocess.TimeoutExpired:
return {
"data": {
"output": {"text": "", "images": []},
"error": "Execution timed out.",
- }
+ },
}
return await asyncio.to_thread(_run)
@@ -183,7 +183,7 @@ class LocalPythonComponent(PythonComponent):
@dataclass
class LocalFileSystemComponent(FileSystemComponent):
async def create_file(
- self, path: str, content: str = "", mode: int = 0o644
+ self, path: str, content: str = "", mode: int = 0o644,
) -> dict[str, Any]:
def _run() -> dict[str, Any]:
abs_path = _ensure_safe_path(path)
@@ -209,7 +209,7 @@ class LocalFileSystemComponent(FileSystemComponent):
return await asyncio.to_thread(_run)
async def write_file(
- self, path: str, content: str, mode: str = "w", encoding: str = "utf-8"
+ self, path: str, content: str, mode: str = "w", encoding: str = "utf-8",
) -> dict[str, Any]:
def _run() -> dict[str, Any]:
abs_path = _ensure_safe_path(path)
@@ -232,7 +232,7 @@ class LocalFileSystemComponent(FileSystemComponent):
return await asyncio.to_thread(_run)
async def list_dir(
- self, path: str = ".", show_hidden: bool = False
+ self, path: str = ".", show_hidden: bool = False,
) -> dict[str, Any]:
def _run() -> dict[str, Any]:
abs_path = _ensure_safe_path(path)
@@ -270,12 +270,12 @@ class LocalBooter(ComputerBooter):
async def upload_file(self, path: str, file_name: str) -> dict:
raise NotImplementedError(
- "LocalBooter does not support upload_file operation. Use shell instead."
+ "LocalBooter does not support upload_file operation. Use shell instead.",
)
async def download_file(self, remote_path: str, local_path: str) -> None:
raise NotImplementedError(
- "LocalBooter does not support download_file operation. Use shell instead."
+ "LocalBooter does not support download_file operation. Use shell instead.",
)
async def available(self) -> bool:
diff --git a/astrbot/core/computer/booters/shipyard.py b/astrbot/core/computer/booters/shipyard.py
index 6379d1e48..f403f8575 100644
--- a/astrbot/core/computer/booters/shipyard.py
+++ b/astrbot/core/computer/booters/shipyard.py
@@ -15,7 +15,7 @@ class ShipyardBooter(ComputerBooter):
session_num: int = 10,
) -> None:
self._sandbox_client = ShipyardClient(
- endpoint_url=endpoint_url, access_token=access_token
+ endpoint_url=endpoint_url, access_token=access_token,
)
self._ttl = ttl
self._session_num = session_num
diff --git a/astrbot/core/computer/booters/shipyard_neo.py b/astrbot/core/computer/booters/shipyard_neo.py
index 6304696ad..c7af0f55f 100644
--- a/astrbot/core/computer/booters/shipyard_neo.py
+++ b/astrbot/core/computer/booters/shipyard_neo.py
@@ -328,12 +328,12 @@ class ShipyardNeoBooter(ComputerBooter):
if self._bay_manager is not None:
raise ValueError(
"Bay container started but credentials could not be read. "
- "Ensure Bay generated credentials.json, or set access_token manually."
+ "Ensure Bay generated credentials.json, or set access_token manually.",
)
raise ValueError(
"Shipyard Neo sandbox configuration is incomplete. "
"Set endpoint (default http://127.0.0.1:8114) and access token, "
- "or ensure Bay's credentials.json is accessible for auto-discovery."
+ "or ensure Bay's credentials.json is accessible for auto-discovery.",
)
from shipyard_neo import BayClient
@@ -428,7 +428,7 @@ class ShipyardNeoBooter(ComputerBooter):
if self._client is not None:
sandbox_id = getattr(self._sandbox, "id", "unknown")
logger.info(
- "[Computer] Shutting down Shipyard Neo sandbox: id=%s", sandbox_id
+ "[Computer] Shutting down Shipyard Neo sandbox: id=%s", sandbox_id,
)
await self._client.__aexit__(None, None, None)
self._client = None
@@ -487,7 +487,7 @@ class ShipyardNeoBooter(ComputerBooter):
if local_dir:
os.makedirs(local_dir, exist_ok=True)
with open(local_path, "wb") as f:
- f.write(cast(bytes, content))
+ f.write(cast("bytes", content))
logger.info(
"[Computer] File downloaded from Neo sandbox: %s -> %s",
remote_path,
diff --git a/astrbot/core/computer/computer_client.py b/astrbot/core/computer/computer_client.py
index 715f93867..bd06f346e 100644
--- a/astrbot/core/computer/computer_client.py
+++ b/astrbot/core/computer/computer_client.py
@@ -41,6 +41,7 @@ def _discover_bay_credentials(endpoint: str) -> str:
Returns:
API key string, or empty string if not found.
+
"""
candidates: list[Path] = []
@@ -405,7 +406,7 @@ async def _sync_skills_to_sandbox(booter: ComputerBooter) -> None:
raise RuntimeError("Failed to upload skills bundle to sandbox.")
else:
logger.info(
- "No local skills found. Keeping sandbox built-ins and refreshing metadata."
+ "No local skills found. Keeping sandbox built-ins and refreshing metadata.",
)
await booter.shell.exec(f"rm -f {SANDBOX_SKILLS_ROOT}/skills.zip")
@@ -436,7 +437,7 @@ async def get_booter(
runtime = config.get("provider_settings", {}).get("computer_use_runtime", "local")
if runtime == "local":
return get_local_booter()
- elif runtime == "none":
+ if runtime == "none":
raise RuntimeError("Sandbox runtime is disabled by configuration.")
sandbox_cfg = config.get("provider_settings", {}).get("sandbox", {})
@@ -450,7 +451,7 @@ async def get_booter(
if session_id not in session_booter:
uuid_str = uuid.uuid5(uuid.NAMESPACE_DNS, session_id).hex
logger.info(
- f"[Computer] Initializing booter: type={booter_type}, session={session_id}"
+ f"[Computer] Initializing booter: type={booter_type}, session={session_id}",
)
if booter_type == "shipyard":
from .booters.shipyard import ShipyardBooter
@@ -461,7 +462,7 @@ async def get_booter(
max_sessions = sandbox_cfg.get("shipyard_max_sessions", 10)
client = ShipyardBooter(
- endpoint_url=ep, access_token=token, ttl=ttl, session_num=max_sessions
+ endpoint_url=ep, access_token=token, ttl=ttl, session_num=max_sessions,
)
elif booter_type == "shipyard_neo":
from .booters.shipyard_neo import ShipyardNeoBooter
@@ -476,7 +477,7 @@ async def get_booter(
token = _discover_bay_credentials(ep)
logger.info(
- f"[Computer] Shipyard Neo config: endpoint={ep}, profile={profile}, ttl={ttl}"
+ f"[Computer] Shipyard Neo config: endpoint={ep}, profile={profile}, ttl={ttl}",
)
client = ShipyardNeoBooter(
endpoint_url=ep,
@@ -494,7 +495,7 @@ async def get_booter(
try:
await client.boot(uuid_str)
logger.info(
- f"[Computer] Sandbox booted successfully: type={booter_type}, session={session_id}"
+ f"[Computer] Sandbox booted successfully: type={booter_type}, session={session_id}",
)
await _sync_skills_to_sandbox(client)
except Exception as e:
@@ -508,7 +509,7 @@ async def get_booter(
async def sync_skills_to_active_sandboxes() -> None:
"""Best-effort skills synchronization for all active sandbox sessions."""
logger.info(
- "[Computer] Syncing skills to %d active sandbox(es)", len(session_booter)
+ "[Computer] Syncing skills to %d active sandbox(es)", len(session_booter),
)
for session_id, booter in list(session_booter.items()):
try:
diff --git a/astrbot/core/computer/olayer/__init__.py b/astrbot/core/computer/olayer/__init__.py
index e2348671e..261f9de9c 100644
--- a/astrbot/core/computer/olayer/__init__.py
+++ b/astrbot/core/computer/olayer/__init__.py
@@ -4,8 +4,8 @@ from .python import PythonComponent
from .shell import ShellComponent
__all__ = [
+ "BrowserComponent",
+ "FileSystemComponent",
"PythonComponent",
"ShellComponent",
- "FileSystemComponent",
- "BrowserComponent",
]
diff --git a/astrbot/core/computer/olayer/browser.py b/astrbot/core/computer/olayer/browser.py
index aa69f4501..942bd9aa3 100644
--- a/astrbot/core/computer/olayer/browser.py
+++ b/astrbot/core/computer/olayer/browser.py
@@ -1,5 +1,4 @@
-"""
-Browser automation component
+"""Browser automation component
"""
from typing import Any, Protocol
diff --git a/astrbot/core/computer/olayer/filesystem.py b/astrbot/core/computer/olayer/filesystem.py
index 21f36d111..c50173803 100644
--- a/astrbot/core/computer/olayer/filesystem.py
+++ b/astrbot/core/computer/olayer/filesystem.py
@@ -1,5 +1,4 @@
-"""
-File system component
+"""File system component
"""
from typing import Any, Protocol
@@ -7,7 +6,7 @@ from typing import Any, Protocol
class FileSystemComponent(Protocol):
async def create_file(
- self, path: str, content: str = "", mode: int = 0o644
+ self, path: str, content: str = "", mode: int = 0o644,
) -> dict[str, Any]:
"""Create a file with the specified content"""
...
@@ -17,7 +16,7 @@ class FileSystemComponent(Protocol):
...
async def write_file(
- self, path: str, content: str, mode: str = "w", encoding: str = "utf-8"
+ self, path: str, content: str, mode: str = "w", encoding: str = "utf-8",
) -> dict[str, Any]:
"""Write content to file"""
...
@@ -27,7 +26,7 @@ class FileSystemComponent(Protocol):
...
async def list_dir(
- self, path: str = ".", show_hidden: bool = False
+ self, path: str = ".", show_hidden: bool = False,
) -> dict[str, Any]:
"""List directory contents"""
...
diff --git a/astrbot/core/computer/olayer/python.py b/astrbot/core/computer/olayer/python.py
index 625504146..fbcf397c4 100644
--- a/astrbot/core/computer/olayer/python.py
+++ b/astrbot/core/computer/olayer/python.py
@@ -1,5 +1,4 @@
-"""
-Python/IPython component
+"""Python/IPython component
"""
from typing import Any, Protocol
diff --git a/astrbot/core/computer/olayer/shell.py b/astrbot/core/computer/olayer/shell.py
index df2263b65..82eb9845c 100644
--- a/astrbot/core/computer/olayer/shell.py
+++ b/astrbot/core/computer/olayer/shell.py
@@ -1,5 +1,4 @@
-"""
-Shell component
+"""Shell component
"""
from typing import Any, Protocol
diff --git a/astrbot/core/computer/tools/__init__.py b/astrbot/core/computer/tools/__init__.py
index 598abbb6e..9563f146e 100644
--- a/astrbot/core/computer/tools/__init__.py
+++ b/astrbot/core/computer/tools/__init__.py
@@ -17,23 +17,23 @@ from .python import LocalPythonTool, PythonTool
from .shell import ExecuteShellTool
__all__ = [
- "BrowserExecTool",
- "BrowserBatchExecTool",
- "RunBrowserSkillTool",
- "GetExecutionHistoryTool",
"AnnotateExecutionTool",
- "CreateSkillPayloadTool",
- "GetSkillPayloadTool",
+ "BrowserBatchExecTool",
+ "BrowserExecTool",
"CreateSkillCandidateTool",
- "ListSkillCandidatesTool",
+ "CreateSkillPayloadTool",
"EvaluateSkillCandidateTool",
- "PromoteSkillCandidateTool",
- "ListSkillReleasesTool",
- "RollbackSkillReleaseTool",
- "SyncSkillReleaseTool",
- "FileUploadTool",
- "PythonTool",
- "LocalPythonTool",
"ExecuteShellTool",
"FileDownloadTool",
+ "FileUploadTool",
+ "GetExecutionHistoryTool",
+ "GetSkillPayloadTool",
+ "ListSkillCandidatesTool",
+ "ListSkillReleasesTool",
+ "LocalPythonTool",
+ "PromoteSkillCandidateTool",
+ "PythonTool",
+ "RollbackSkillReleaseTool",
+ "RunBrowserSkillTool",
+ "SyncSkillReleaseTool",
]
diff --git a/astrbot/core/computer/tools/browser.py b/astrbot/core/computer/tools/browser.py
index cd8484acb..cb1eb9115 100644
--- a/astrbot/core/computer/tools/browser.py
+++ b/astrbot/core/computer/tools/browser.py
@@ -24,7 +24,7 @@ async def _get_browser_component(context: ContextWrapper[AstrAgentContext]) -> A
if browser is None:
raise RuntimeError(
"Current sandbox booter does not support browser capability. "
- "Please switch to shipyard_neo."
+ "Please switch to shipyard_neo.",
)
return browser
@@ -56,7 +56,7 @@ class BrowserExecTool(FunctionTool):
},
},
"required": ["cmd"],
- }
+ },
)
async def call(
@@ -83,7 +83,7 @@ class BrowserExecTool(FunctionTool):
)
return _to_json(result)
except Exception as e:
- return f"Error executing browser command: {str(e)}"
+ return f"Error executing browser command: {e!s}"
@dataclass
@@ -118,7 +118,7 @@ class BrowserBatchExecTool(FunctionTool):
},
},
"required": ["commands"],
- }
+ },
)
async def call(
@@ -147,7 +147,7 @@ class BrowserBatchExecTool(FunctionTool):
)
return _to_json(result)
except Exception as e:
- return f"Error executing browser batch command: {str(e)}"
+ return f"Error executing browser batch command: {e!s}"
@dataclass
@@ -166,7 +166,7 @@ class RunBrowserSkillTool(FunctionTool):
"tags": {"type": "string"},
},
"required": ["skill_key"],
- }
+ },
)
async def call(
@@ -193,4 +193,4 @@ class RunBrowserSkillTool(FunctionTool):
)
return _to_json(result)
except Exception as e:
- return f"Error running browser skill: {str(e)}"
+ return f"Error running browser skill: {e!s}"
diff --git a/astrbot/core/computer/tools/fs.py b/astrbot/core/computer/tools/fs.py
index f2a698f76..50e16ff77 100644
--- a/astrbot/core/computer/tools/fs.py
+++ b/astrbot/core/computer/tools/fs.py
@@ -100,7 +100,7 @@ class FileUploadTool(FunctionTool):
# },
},
"required": ["local_path"],
- }
+ },
)
async def call(
@@ -139,7 +139,7 @@ class FileUploadTool(FunctionTool):
return f"File uploaded successfully to {file_path}"
except Exception as e:
logger.error(f"Error uploading file {local_path}: {e}")
- return f"Error uploading file: {str(e)}"
+ return f"Error uploading file: {e!s}"
@dataclass
@@ -164,7 +164,7 @@ class FileDownloadTool(FunctionTool):
},
},
"required": ["remote_path"],
- }
+ },
)
async def call(
@@ -183,7 +183,7 @@ class FileDownloadTool(FunctionTool):
name = os.path.basename(remote_path)
local_path = os.path.join(
- get_astrbot_temp_path(), f"sandbox_{uuid.uuid4().hex[:4]}_{name}"
+ get_astrbot_temp_path(), f"sandbox_{uuid.uuid4().hex[:4]}_{name}",
)
# Download file from sandbox
@@ -194,7 +194,7 @@ class FileDownloadTool(FunctionTool):
try:
name = os.path.basename(local_path)
await context.context.event.send(
- MessageChain(chain=[File(name=name, file=local_path)])
+ MessageChain(chain=[File(name=name, file=local_path)]),
)
except Exception as e:
logger.error(f"Error sending file message: {e}")
@@ -210,4 +210,4 @@ class FileDownloadTool(FunctionTool):
return f"File downloaded successfully to {local_path}"
except Exception as e:
logger.error(f"Error downloading file {remote_path}: {e}")
- return f"Error downloading file: {str(e)}"
+ return f"Error downloading file: {e!s}"
diff --git a/astrbot/core/computer/tools/neo_skills.py b/astrbot/core/computer/tools/neo_skills.py
index 327f14472..6519c285f 100644
--- a/astrbot/core/computer/tools/neo_skills.py
+++ b/astrbot/core/computer/tools/neo_skills.py
@@ -39,7 +39,7 @@ async def _get_neo_context(
if client is None or sandbox is None:
raise RuntimeError(
"Current sandbox booter does not support Neo skill lifecycle APIs. "
- "Please switch to shipyard_neo."
+ "Please switch to shipyard_neo.",
)
return client, sandbox
@@ -61,7 +61,7 @@ class NeoSkillToolBase(FunctionTool):
result = await neo_call(client, sandbox)
return _to_json_text(result)
except Exception as e:
- return f"{self.error_prefix} {error_action}: {str(e)}"
+ return f"{self.error_prefix} {error_action}: {e!s}"
@dataclass
@@ -81,7 +81,7 @@ class GetExecutionHistoryTool(NeoSkillToolBase):
"has_description": {"type": "boolean", "default": False},
},
"required": [],
- }
+ },
)
async def call(
@@ -124,7 +124,7 @@ class AnnotateExecutionTool(NeoSkillToolBase):
"notes": {"type": "string"},
},
"required": ["execution_id"],
- }
+ },
)
async def call(
@@ -175,7 +175,7 @@ class CreateSkillPayloadTool(NeoSkillToolBase):
},
},
"required": ["payload"],
- }
+ },
)
async def call(
@@ -205,7 +205,7 @@ class GetSkillPayloadTool(NeoSkillToolBase):
"payload_ref": {"type": "string"},
},
"required": ["payload_ref"],
- }
+ },
)
async def call(
@@ -250,7 +250,7 @@ class CreateSkillCandidateTool(NeoSkillToolBase):
},
},
"required": ["skill_key", "source_execution_ids"],
- }
+ },
)
async def call(
@@ -287,7 +287,7 @@ class ListSkillCandidatesTool(NeoSkillToolBase):
"offset": {"type": "integer", "default": 0},
},
"required": [],
- }
+ },
)
async def call(
@@ -325,7 +325,7 @@ class EvaluateSkillCandidateTool(NeoSkillToolBase):
"report": {"type": "string"},
},
"required": ["candidate_id", "passed"],
- }
+ },
)
async def call(
@@ -377,7 +377,7 @@ class PromoteSkillCandidateTool(NeoSkillToolBase):
},
},
"required": ["candidate_id"],
- }
+ },
)
async def call(
@@ -414,10 +414,10 @@ class PromoteSkillCandidateTool(NeoSkillToolBase):
"release": result.get("release"),
"sync": result.get("sync"),
"rollback": result.get("rollback"),
- }
+ },
)
except Exception as e:
- return f"Error promoting skill candidate: {str(e)}"
+ return f"Error promoting skill candidate: {e!s}"
@dataclass
@@ -435,7 +435,7 @@ class ListSkillReleasesTool(NeoSkillToolBase):
"offset": {"type": "integer", "default": 0},
},
"required": [],
- }
+ },
)
async def call(
@@ -471,7 +471,7 @@ class RollbackSkillReleaseTool(NeoSkillToolBase):
"release_id": {"type": "string"},
},
"required": ["release_id"],
- }
+ },
)
async def call(
@@ -501,7 +501,7 @@ class SyncSkillReleaseTool(NeoSkillToolBase):
"require_stable": {"type": "boolean", "default": True},
},
"required": [],
- }
+ },
)
async def call(
diff --git a/astrbot/core/computer/tools/permissions.py b/astrbot/core/computer/tools/permissions.py
index 489f485f9..e8287e423 100644
--- a/astrbot/core/computer/tools/permissions.py
+++ b/astrbot/core/computer/tools/permissions.py
@@ -3,10 +3,10 @@ from astrbot.core.astr_agent_context import AstrAgentContext
def check_admin_permission(
- context: ContextWrapper[AstrAgentContext], operation_name: str
+ context: ContextWrapper[AstrAgentContext], operation_name: str,
) -> str | None:
cfg = context.context.context.get_config(
- umo=context.context.event.unified_msg_origin
+ umo=context.context.event.unified_msg_origin,
)
provider_settings = cfg.get("provider_settings", {})
require_admin = provider_settings.get("computer_use_require_admin", True)
diff --git a/astrbot/core/computer/tools/python.py b/astrbot/core/computer/tools/python.py
index bf9aaa14e..2d92034d0 100644
--- a/astrbot/core/computer/tools/python.py
+++ b/astrbot/core/computer/tools/python.py
@@ -46,8 +46,8 @@ async def handle_result(result: dict, event: AstrMessageEvent) -> ToolExecResult
for img in images:
resp.content.append(
mcp.types.ImageContent(
- type="image", data=img["image/png"], mimeType="image/png"
- )
+ type="image", data=img["image/png"], mimeType="image/png",
+ ),
)
if event.get_platform_name() == "webchat":
@@ -68,7 +68,7 @@ class PythonTool(FunctionTool):
parameters: dict = field(default_factory=lambda: param_schema)
async def call(
- self, context: ContextWrapper[AstrAgentContext], code: str, silent: bool = False
+ self, context: ContextWrapper[AstrAgentContext], code: str, silent: bool = False,
) -> ToolExecResult:
if permission_error := check_admin_permission(context, "Python execution"):
return permission_error
@@ -80,7 +80,7 @@ class PythonTool(FunctionTool):
result = await sb.python.exec(code, silent=silent)
return await handle_result(result, context.context.event)
except Exception as e:
- return f"Error executing code: {str(e)}"
+ return f"Error executing code: {e!s}"
@dataclass
@@ -94,7 +94,7 @@ class LocalPythonTool(FunctionTool):
parameters: dict = field(default_factory=lambda: param_schema)
async def call(
- self, context: ContextWrapper[AstrAgentContext], code: str, silent: bool = False
+ self, context: ContextWrapper[AstrAgentContext], code: str, silent: bool = False,
) -> ToolExecResult:
if permission_error := check_admin_permission(context, "Python execution"):
return permission_error
@@ -103,4 +103,4 @@ class LocalPythonTool(FunctionTool):
result = await sb.python.exec(code, silent=silent)
return await handle_result(result, context.context.event)
except Exception as e:
- return f"Error executing code: {str(e)}"
+ return f"Error executing code: {e!s}"
diff --git a/astrbot/core/computer/tools/shell.py b/astrbot/core/computer/tools/shell.py
index b5009d30f..657110902 100644
--- a/astrbot/core/computer/tools/shell.py
+++ b/astrbot/core/computer/tools/shell.py
@@ -35,7 +35,7 @@ class ExecuteShellTool(FunctionTool):
},
},
"required": ["command"],
- }
+ },
)
is_local: bool = False
@@ -61,4 +61,4 @@ class ExecuteShellTool(FunctionTool):
result = await sb.shell.exec(command, background=background, env=env)
return json.dumps(result)
except Exception as e:
- return f"Error executing command: {str(e)}"
+ return f"Error executing command: {e!s}"
diff --git a/astrbot/core/config/astrbot_config.py b/astrbot/core/config/astrbot_config.py
index 77c298cac..42e16c9c6 100644
--- a/astrbot/core/config/astrbot_config.py
+++ b/astrbot/core/config/astrbot_config.py
@@ -53,8 +53,7 @@ class AstrBotConfig(dict):
with open(config_path, encoding="utf-8-sig") as f:
conf_str = f.read()
# Handle UTF-8 BOM if present
- if conf_str.startswith("\ufeff"):
- conf_str = conf_str[1:]
+ conf_str = conf_str.removeprefix("\ufeff")
conf = json.loads(conf_str)
# 检查配置完整性,并插入
diff --git a/astrbot/core/config/i18n_utils.py b/astrbot/core/config/i18n_utils.py
index cb6b6429b..429a1fdf9 100644
--- a/astrbot/core/config/i18n_utils.py
+++ b/astrbot/core/config/i18n_utils.py
@@ -1,5 +1,4 @@
-"""
-配置元数据国际化工具
+"""配置元数据国际化工具
提供配置元数据的国际化键转换功能
"""
@@ -12,8 +11,7 @@ class ConfigMetadataI18n:
@staticmethod
def _get_i18n_key(group: str, section: str, field: str, attr: str) -> str:
- """
- 生成国际化键
+ """生成国际化键
Args:
group: 配置组,如 'ai_group', 'platform_group'
@@ -23,27 +21,27 @@ class ConfigMetadataI18n:
Returns:
国际化键,格式如: 'ai_group.agent_runner.enable.description'
+
"""
if field:
return f"{group}.{section}.{field}.{attr}"
- else:
- return f"{group}.{section}.{attr}"
+ return f"{group}.{section}.{attr}"
@staticmethod
def convert_to_i18n_keys(metadata: dict[str, Any]) -> dict[str, Any]:
- """
- 将配置元数据转换为使用国际化键
+ """将配置元数据转换为使用国际化键
Args:
metadata: 原始配置元数据字典
Returns:
使用国际化键的配置元数据字典
+
"""
result = {}
def convert_items(
- group: str, section: str, items: dict[str, Any], prefix: str = ""
+ group: str, section: str, items: dict[str, Any], prefix: str = "",
) -> dict[str, Any]:
items_result: dict[str, Any] = {}
@@ -74,11 +72,11 @@ class ConfigMetadataI18n:
if "items" in field_data and isinstance(field_data["items"], dict):
field_result["items"] = convert_items(
- group, section, field_data["items"], field_path
+ group, section, field_data["items"], field_path,
)
if "template_schema" in field_data and isinstance(
- field_data["template_schema"], dict
+ field_data["template_schema"], dict,
):
field_result["template_schema"] = convert_items(
group,
@@ -110,7 +108,7 @@ class ConfigMetadataI18n:
if "items" in section_data and isinstance(section_data["items"], dict):
section_result["items"] = convert_items(
- group_key, section_key, section_data["items"]
+ group_key, section_key, section_data["items"],
)
group_result["metadata"][section_key] = section_result
diff --git a/astrbot/core/conversation_mgr.py b/astrbot/core/conversation_mgr.py
index 2c282867f..25c7af57e 100644
--- a/astrbot/core/conversation_mgr.py
+++ b/astrbot/core/conversation_mgr.py
@@ -110,7 +110,7 @@ class ConversationManager:
return conv.conversation_id
async def switch_conversation(
- self, unified_msg_origin: str, conversation_id: str
+ self, unified_msg_origin: str, conversation_id: str,
) -> None:
"""切换会话的对话
@@ -344,6 +344,7 @@ class ConversationManager:
Raises:
Exception: If the conversation with the given ID is not found
+
"""
conv = await self.db.get_conversation_by_id(cid=cid)
if not conv:
diff --git a/astrbot/core/core_lifecycle.py b/astrbot/core/core_lifecycle.py
index fe6b1c351..1c1369b9a 100644
--- a/astrbot/core/core_lifecycle.py
+++ b/astrbot/core/core_lifecycle.py
@@ -106,7 +106,7 @@ class AstrBotCoreLifecycle:
logger.info("AstrBot v" + VERSION)
if os.environ.get("TESTING", ""):
LogManager.configure_logger(
- logger, self.astrbot_config, override_level="DEBUG"
+ logger, self.astrbot_config, override_level="DEBUG",
)
LogManager.configure_trace_logger(self.astrbot_config)
else:
@@ -258,7 +258,7 @@ class AstrBotCoreLifecycle:
for task in self.star_context._register_tasks:
extra_tasks.append(asyncio.create_task(task, name=task.__name__)) # type: ignore
- tasks_ = [event_bus_task, *(extra_tasks if extra_tasks else [])]
+ tasks_ = [event_bus_task, *(extra_tasks or [])]
if cron_task:
tasks_.append(cron_task)
if temp_dir_cleaner_task:
diff --git a/astrbot/core/cron/manager.py b/astrbot/core/cron/manager.py
index c86fc160f..477a63ff1 100644
--- a/astrbot/core/cron/manager.py
+++ b/astrbot/core/cron/manager.py
@@ -178,8 +178,8 @@ class CronJobManager:
)
asyncio.create_task(
self.db.update_cron_job(
- job.job_id, next_run_time=self._get_next_run_time(job.job_id)
- )
+ job.job_id, next_run_time=self._get_next_run_time(job.job_id),
+ ),
)
except Exception as e:
logger.error(f"Failed to schedule cron job {job.job_id}: {e!s}")
@@ -194,7 +194,7 @@ class CronJobManager:
return
start_time = datetime.now(timezone.utc)
await self.db.update_cron_job(
- job_id, status="running", last_run_at=start_time, last_error=None
+ job_id, status="running", last_run_at=start_time, last_error=None,
)
status = "completed"
last_error = None
@@ -308,7 +308,7 @@ class CronJobManager:
cron_event.role = "admin"
tool_call_timeout = cfg.get("provider_settings", {}).get(
- "tool_call_timeout", 120
+ "tool_call_timeout", 120,
)
config = MainAgentBuildConfig(
tool_call_timeout=tool_call_timeout,
@@ -332,7 +332,7 @@ class CronJobManager:
)
cron_job_str = json.dumps(extras.get("cron_job", {}), ensure_ascii=False)
req.system_prompt += PROACTIVE_AGENT_CRON_WOKE_SYSTEM_PROMPT.format(
- cron_job=cron_job_str
+ cron_job=cron_job_str,
)
req.prompt = (
"You are now responding to a scheduled task. "
@@ -343,11 +343,11 @@ class CronJobManager:
if not req.func_tool:
req.func_tool = ToolSet()
req.func_tool.add_tool(
- self.ctx.get_llm_tool_manager().get_builtin_tool(SendMessageToUserTool)
+ self.ctx.get_llm_tool_manager().get_builtin_tool(SendMessageToUserTool),
)
result = await build_main_agent(
- event=cron_event, plugin_context=self.ctx, config=config, req=req
+ event=cron_event, plugin_context=self.ctx, config=config, req=req,
)
if not result:
logger.error("Failed to build main agent for cron job.")
diff --git a/astrbot/core/db/__init__.py b/astrbot/core/db/__init__.py
index 087aa625b..6eae9462d 100644
--- a/astrbot/core/db/__init__.py
+++ b/astrbot/core/db/__init__.py
@@ -345,6 +345,7 @@ class BaseDatabase(abc.ABC):
custom_error_message: Optional persona-level fallback error message
folder_id: Optional folder ID to place the persona in (None means root)
sort_order: Sort order within the folder (default 0)
+
"""
...
@@ -398,7 +399,7 @@ class BaseDatabase(abc.ABC):
@abc.abstractmethod
async def get_persona_folders(
- self, parent_id: str | None = None
+ self, parent_id: str | None = None,
) -> list[PersonaFolder]:
"""Get all persona folders, optionally filtered by parent_id."""
...
@@ -427,14 +428,14 @@ class BaseDatabase(abc.ABC):
@abc.abstractmethod
async def move_persona_to_folder(
- self, persona_id: str, folder_id: str | None
+ self, persona_id: str, folder_id: str | None,
) -> Persona | None:
"""Move a persona to a folder (or root if folder_id is None)."""
...
@abc.abstractmethod
async def get_personas_by_folder(
- self, folder_id: str | None = None
+ self, folder_id: str | None = None,
) -> list[Persona]:
"""Get all personas in a specific folder."""
...
@@ -451,6 +452,7 @@ class BaseDatabase(abc.ABC):
- id: The persona_id or folder_id
- type: Either "persona" or "folder"
- sort_order: The new sort_order value
+
"""
...
@@ -666,14 +668,14 @@ class BaseDatabase(abc.ABC):
@abc.abstractmethod
async def get_platform_session_by_id(
- self, session_id: str
+ self, session_id: str,
) -> PlatformSession | None:
"""Get a Platform session by its ID."""
...
@abc.abstractmethod
async def get_platform_sessions_by_ids(
- self, session_ids: list[str]
+ self, session_ids: list[str],
) -> list[PlatformSession]:
"""Get platform sessions by IDs."""
...
@@ -705,6 +707,7 @@ class BaseDatabase(abc.ABC):
Returns:
tuple[list[dict], int]: (sessions_with_project_info, total_count)
+
"""
...
@@ -794,7 +797,7 @@ class BaseDatabase(abc.ABC):
@abc.abstractmethod
async def get_project_by_session(
- self, session_id: str, creator: str
+ self, session_id: str, creator: str,
) -> ChatUIProject | None:
"""Get the project that a session belongs to."""
...
diff --git a/astrbot/core/db/migration/migra_token_usage.py b/astrbot/core/db/migration/migra_token_usage.py
index 76bf8ce01..920171ff9 100644
--- a/astrbot/core/db/migration/migra_token_usage.py
+++ b/astrbot/core/db/migration/migra_token_usage.py
@@ -19,7 +19,7 @@ async def migrate_token_usage(db_helper: BaseDatabase) -> None:
"""
# 检查是否已经完成迁移
migration_done = await db_helper.get_preference(
- "global", "global", "migration_done_token_usage_1"
+ "global", "global", "migration_done_token_usage_1",
)
if migration_done:
return
@@ -38,15 +38,15 @@ async def migrate_token_usage(db_helper: BaseDatabase) -> None:
if "token_usage" in column_names:
logger.info("token_usage 列已存在,跳过迁移")
await sp.put_async(
- "global", "global", "migration_done_token_usage_1", True
+ "global", "global", "migration_done_token_usage_1", True,
)
return
# 添加 token_usage 列
await session.execute(
text(
- "ALTER TABLE conversations ADD COLUMN token_usage INTEGER NOT NULL DEFAULT 0"
- )
+ "ALTER TABLE conversations ADD COLUMN token_usage INTEGER NOT NULL DEFAULT 0",
+ ),
)
await session.commit()
diff --git a/astrbot/core/db/migration/migra_webchat_session.py b/astrbot/core/db/migration/migra_webchat_session.py
index 46025fc64..ddfd20e5d 100644
--- a/astrbot/core/db/migration/migra_webchat_session.py
+++ b/astrbot/core/db/migration/migra_webchat_session.py
@@ -25,7 +25,7 @@ async def migrate_webchat_session(db_helper: BaseDatabase) -> None:
"""
# 检查是否已经完成迁移
migration_done = await db_helper.get_preference(
- "global", "global", "migration_done_webchat_session_1"
+ "global", "global", "migration_done_webchat_session_1",
)
if migration_done:
return
@@ -53,7 +53,7 @@ async def migrate_webchat_session(db_helper: BaseDatabase) -> None:
if not webchat_users:
logger.info("没有找到需要迁移的 WebChat 数据")
await sp.put_async(
- "global", "global", "migration_done_webchat_session_1", True
+ "global", "global", "migration_done_webchat_session_1", True,
)
return
@@ -71,7 +71,7 @@ async def migrate_webchat_session(db_helper: BaseDatabase) -> None:
for user_id, _, _, _ in webchat_users
]
conv_query = select(
- col(ConversationV2.user_id), col(ConversationV2.title)
+ col(ConversationV2.user_id), col(ConversationV2.title),
).where(col(ConversationV2.user_id).in_(user_ids_to_query))
conv_result = await session.execute(conv_query)
# 创建 user_id -> title 的映射字典
@@ -89,7 +89,7 @@ async def migrate_webchat_session(db_helper: BaseDatabase) -> None:
session_id = user_id
# sender_name 通常是 username,但可能为 None
- creator = sender_name if sender_name else "guest"
+ creator = sender_name or "guest"
# 检查是否已经存在该会话
if session_id in existing_session_ids:
diff --git a/astrbot/core/db/migration/sqlite_v3.py b/astrbot/core/db/migration/sqlite_v3.py
index b326ebb44..cd48795d3 100644
--- a/astrbot/core/db/migration/sqlite_v3.py
+++ b/astrbot/core/db/migration/sqlite_v3.py
@@ -227,7 +227,7 @@ class SQLiteDatabase:
return Stats(platform)
def get_conversation_by_user_id(
- self, user_id: str, cid: str
+ self, user_id: str, cid: str,
) -> Conversation | None:
try:
c = self.conn.cursor()
@@ -306,7 +306,7 @@ class SQLiteDatabase:
)
def update_conversation_persona_id(
- self, user_id: str, cid: str, persona_id: str
+ self, user_id: str, cid: str, persona_id: str,
) -> None:
self._exec_sql(
"""
diff --git a/astrbot/core/db/po.py b/astrbot/core/db/po.py
index cabc3432c..126f3ddb5 100644
--- a/astrbot/core/db/po.py
+++ b/astrbot/core/db/po.py
@@ -438,7 +438,7 @@ class CommandConflict(TimestampMixin, SQLModel, table=True):
__tablename__ = "command_conflicts" # type: ignore
id: int | None = Field(
- default=None, primary_key=True, sa_column_kwargs={"autoincrement": True}
+ default=None, primary_key=True, sa_column_kwargs={"autoincrement": True},
)
conflict_key: str = Field(nullable=False, max_length=255)
handler_full_name: str = Field(nullable=False, max_length=512)
diff --git a/astrbot/core/db/sqlite.py b/astrbot/core/db/sqlite.py
index fd6668c0c..b14a4c5be 100644
--- a/astrbot/core/db/sqlite.py
+++ b/astrbot/core/db/sqlite.py
@@ -74,12 +74,12 @@ class SQLiteDatabase(BaseDatabase):
if "folder_id" not in columns:
await conn.execute(
text(
- "ALTER TABLE personas ADD COLUMN folder_id VARCHAR(36) DEFAULT NULL"
- )
+ "ALTER TABLE personas ADD COLUMN folder_id VARCHAR(36) DEFAULT NULL",
+ ),
)
if "sort_order" not in columns:
await conn.execute(
- text("ALTER TABLE personas ADD COLUMN sort_order INTEGER DEFAULT 0")
+ text("ALTER TABLE personas ADD COLUMN sort_order INTEGER DEFAULT 0"),
)
async def _ensure_persona_skills_column(self, conn) -> None:
@@ -101,7 +101,7 @@ class SQLiteDatabase(BaseDatabase):
if "custom_error_message" not in columns:
await conn.execute(
- text("ALTER TABLE personas ADD COLUMN custom_error_message TEXT")
+ text("ALTER TABLE personas ADD COLUMN custom_error_message TEXT"),
)
# ====
@@ -340,7 +340,7 @@ class SQLiteDatabase(BaseDatabase):
return new_conversation
async def update_conversation(
- self, cid, title=None, persona_id=None, content=None, token_usage=None
+ self, cid, title=None, persona_id=None, content=None, token_usage=None,
):
async with self.get_db() as session:
session: AsyncSession
@@ -379,7 +379,7 @@ class SQLiteDatabase(BaseDatabase):
async with session.begin():
await session.execute(
delete(ConversationV2).where(
- col(ConversationV2.user_id) == user_id
+ col(ConversationV2.user_id) == user_id,
),
)
@@ -557,13 +557,13 @@ class SQLiteDatabase(BaseDatabase):
return result.scalars().all()
async def get_platform_message_history_by_id(
- self, message_id: int
+ self, message_id: int,
) -> PlatformMessageHistory | None:
"""Get a platform message history record by its ID."""
async with self.get_db() as session:
session: AsyncSession
query = select(PlatformMessageHistory).where(
- PlatformMessageHistory.id == message_id
+ PlatformMessageHistory.id == message_id,
)
result = await session.execute(query)
return result.scalar_one_or_none()
@@ -596,7 +596,7 @@ class SQLiteDatabase(BaseDatabase):
async with self.get_db() as session:
session: AsyncSession
query = select(Attachment).where(
- col(Attachment.attachment_id).in_(attachment_ids)
+ col(Attachment.attachment_id).in_(attachment_ids),
)
result = await session.execute(query)
return list(result.scalars().all())
@@ -610,9 +610,9 @@ class SQLiteDatabase(BaseDatabase):
session: AsyncSession
async with session.begin():
query = delete(Attachment).where(
- col(Attachment.attachment_id) == attachment_id
+ col(Attachment.attachment_id) == attachment_id,
)
- result = T.cast(CursorResult, await session.execute(query))
+ result = T.cast("CursorResult", await session.execute(query))
return result.rowcount > 0
async def delete_attachments(self, attachment_ids: list[str]) -> int:
@@ -626,9 +626,9 @@ class SQLiteDatabase(BaseDatabase):
session: AsyncSession
async with session.begin():
query = delete(Attachment).where(
- col(Attachment.attachment_id).in_(attachment_ids)
+ col(Attachment.attachment_id).in_(attachment_ids),
)
- result = T.cast(CursorResult, await session.execute(query))
+ result = T.cast("CursorResult", await session.execute(query))
return result.rowcount
async def create_api_key(
@@ -662,7 +662,7 @@ class SQLiteDatabase(BaseDatabase):
async with self.get_db() as session:
session: AsyncSession
result = await session.execute(
- select(ApiKey).order_by(desc(ApiKey.created_at))
+ select(ApiKey).order_by(desc(ApiKey.created_at)),
)
return list(result.scalars().all())
@@ -671,7 +671,7 @@ class SQLiteDatabase(BaseDatabase):
async with self.get_db() as session:
session: AsyncSession
result = await session.execute(
- select(ApiKey).where(ApiKey.key_id == key_id)
+ select(ApiKey).where(ApiKey.key_id == key_id),
)
return result.scalar_one_or_none()
@@ -709,7 +709,7 @@ class SQLiteDatabase(BaseDatabase):
.where(col(ApiKey.key_id) == key_id)
.values(revoked_at=datetime.now(timezone.utc))
)
- result = T.cast(CursorResult, await session.execute(query))
+ result = T.cast("CursorResult", await session.execute(query))
return result.rowcount > 0
async def delete_api_key(self, key_id: str) -> bool:
@@ -718,9 +718,9 @@ class SQLiteDatabase(BaseDatabase):
session: AsyncSession
async with session.begin():
result = T.cast(
- CursorResult,
+ "CursorResult",
await session.execute(
- delete(ApiKey).where(col(ApiKey.key_id) == key_id)
+ delete(ApiKey).where(col(ApiKey.key_id) == key_id),
),
)
return result.rowcount > 0
@@ -846,13 +846,14 @@ class SQLiteDatabase(BaseDatabase):
return result.scalar_one_or_none()
async def get_persona_folders(
- self, parent_id: str | None = None
+ self, parent_id: str | None = None,
) -> list[PersonaFolder]:
"""Get all persona folders, optionally filtered by parent_id.
Args:
parent_id: If None, returns root folders only. If specified, returns
children of that folder.
+
"""
async with self.get_db() as session:
session: AsyncSession
@@ -877,7 +878,7 @@ class SQLiteDatabase(BaseDatabase):
async with self.get_db() as session:
session: AsyncSession
query = select(PersonaFolder).order_by(
- col(PersonaFolder.sort_order), col(PersonaFolder.name)
+ col(PersonaFolder.sort_order), col(PersonaFolder.name),
)
result = await session.execute(query)
return list(result.scalars().all())
@@ -895,7 +896,7 @@ class SQLiteDatabase(BaseDatabase):
session: AsyncSession
async with session.begin():
query = update(PersonaFolder).where(
- col(PersonaFolder.folder_id) == folder_id
+ col(PersonaFolder.folder_id) == folder_id,
)
values: dict[str, T.Any] = {}
if name is not None:
@@ -925,17 +926,17 @@ class SQLiteDatabase(BaseDatabase):
await session.execute(
update(Persona)
.where(col(Persona.folder_id) == folder_id)
- .values(folder_id=None)
+ .values(folder_id=None),
)
# Delete the folder
await session.execute(
delete(PersonaFolder).where(
- col(PersonaFolder.folder_id) == folder_id
+ col(PersonaFolder.folder_id) == folder_id,
),
)
async def move_persona_to_folder(
- self, persona_id: str, folder_id: str | None
+ self, persona_id: str, folder_id: str | None,
) -> Persona | None:
"""Move a persona to a folder (or root if folder_id is None)."""
async with self.get_db() as session:
@@ -944,17 +945,18 @@ class SQLiteDatabase(BaseDatabase):
await session.execute(
update(Persona)
.where(col(Persona.persona_id) == persona_id)
- .values(folder_id=folder_id)
+ .values(folder_id=folder_id),
)
return await self.get_persona_by_id(persona_id)
async def get_personas_by_folder(
- self, folder_id: str | None = None
+ self, folder_id: str | None = None,
) -> list[Persona]:
"""Get all personas in a specific folder.
Args:
folder_id: If None, returns personas in root directory.
+
"""
async with self.get_db() as session:
session: AsyncSession
@@ -984,6 +986,7 @@ class SQLiteDatabase(BaseDatabase):
- id: The persona_id or folder_id
- type: Either "persona" or "folder"
- sort_order: The new sort_order value
+
"""
if not items:
return
@@ -1003,13 +1006,13 @@ class SQLiteDatabase(BaseDatabase):
await session.execute(
update(Persona)
.where(col(Persona.persona_id) == item_id)
- .values(sort_order=sort_order)
+ .values(sort_order=sort_order),
)
elif item_type == "folder":
await session.execute(
update(PersonaFolder)
.where(col(PersonaFolder.folder_id) == item_id)
- .values(sort_order=sort_order)
+ .values(sort_order=sort_order),
)
async def insert_preference_or_update(self, scope, scope_id, key, value):
@@ -1452,7 +1455,7 @@ class SQLiteDatabase(BaseDatabase):
return new_session
async def get_platform_session_by_id(
- self, session_id: str
+ self, session_id: str,
) -> PlatformSession | None:
"""Get a Platform session by its ID."""
async with self.get_db() as session:
@@ -1464,7 +1467,7 @@ class SQLiteDatabase(BaseDatabase):
return result.scalar_one_or_none()
async def get_platform_sessions_by_ids(
- self, session_ids: list[str]
+ self, session_ids: list[str],
) -> list[PlatformSession]:
"""Get platform sessions by IDs."""
if not session_ids:
@@ -1473,7 +1476,7 @@ class SQLiteDatabase(BaseDatabase):
async with self.get_db() as session:
session: AsyncSession
query = select(PlatformSession).where(
- col(PlatformSession.session_id).in_(session_ids)
+ col(PlatformSession.session_id).in_(session_ids),
)
result = await session.execute(query)
return list(result.scalars().all())
@@ -1572,7 +1575,7 @@ class SQLiteDatabase(BaseDatabase):
)
total_result = await session.execute(
- select(func.count()).select_from(base_query.subquery())
+ select(func.count()).select_from(base_query.subquery()),
)
total = int(total_result.scalar_one() or 0)
@@ -1776,7 +1779,7 @@ class SQLiteDatabase(BaseDatabase):
return list(result.scalars().all())
async def get_project_by_session(
- self, session_id: str, creator: str
+ self, session_id: str, creator: str,
) -> ChatUIProject | None:
"""Get the project that a session belongs to."""
async with self.get_db() as session:
@@ -1883,7 +1886,7 @@ class SQLiteDatabase(BaseDatabase):
)
await session.execute(stmt)
result = await session.execute(
- select(CronJob).where(col(CronJob.job_id) == job_id)
+ select(CronJob).where(col(CronJob.job_id) == job_id),
)
return result.scalar_one_or_none()
@@ -1892,14 +1895,14 @@ class SQLiteDatabase(BaseDatabase):
session: AsyncSession
async with session.begin():
await session.execute(
- delete(CronJob).where(col(CronJob.job_id) == job_id)
+ delete(CronJob).where(col(CronJob.job_id) == job_id),
)
async def get_cron_job(self, job_id: str) -> CronJob | None:
async with self.get_db() as session:
session: AsyncSession
result = await session.execute(
- select(CronJob).where(col(CronJob.job_id) == job_id)
+ select(CronJob).where(col(CronJob.job_id) == job_id),
)
return result.scalar_one_or_none()
diff --git a/astrbot/core/db/vec_db/faiss_impl/vec_db.py b/astrbot/core/db/vec_db/faiss_impl/vec_db.py
index bc729aac8..44fe671a5 100644
--- a/astrbot/core/db/vec_db/faiss_impl/vec_db.py
+++ b/astrbot/core/db/vec_db/faiss_impl/vec_db.py
@@ -76,7 +76,7 @@ class FaissVecDB(BaseVecDB):
if not contents:
logger.debug(
- "No contents provided for batch insert; skipping embedding generation."
+ "No contents provided for batch insert; skipping embedding generation.",
)
return []
diff --git a/astrbot/core/event_bus.py b/astrbot/core/event_bus.py
index 70b5f054e..76e4fc262 100644
--- a/astrbot/core/event_bus.py
+++ b/astrbot/core/event_bus.py
@@ -44,7 +44,7 @@ class EventBus:
scheduler = self.pipeline_scheduler_mapping.get(conf_id)
if not scheduler:
logger.error(
- f"PipelineScheduler not found for id: {conf_id}, event ignored."
+ f"PipelineScheduler not found for id: {conf_id}, event ignored.",
)
continue
asyncio.create_task(scheduler.execute(event))
diff --git a/astrbot/core/knowledge_base/kb_db_sqlite.py b/astrbot/core/knowledge_base/kb_db_sqlite.py
index 6a2cb5e0a..efb594d90 100644
--- a/astrbot/core/knowledge_base/kb_db_sqlite.py
+++ b/astrbot/core/knowledge_base/kb_db_sqlite.py
@@ -260,7 +260,7 @@ class KBSQLiteDatabase:
}
async def get_documents_with_metadata_batch(
- self, doc_ids: set[str]
+ self, doc_ids: set[str],
) -> dict[str, dict]:
"""批量获取文档及其所属知识库元数据
diff --git a/astrbot/core/knowledge_base/kb_helper.py b/astrbot/core/knowledge_base/kb_helper.py
index 0863f7e6d..9b75132cd 100644
--- a/astrbot/core/knowledge_base/kb_helper.py
+++ b/astrbot/core/knowledge_base/kb_helper.py
@@ -61,8 +61,7 @@ async def _repair_and_translate_chunk_with_retry(
rate_limiter: RateLimiter,
max_retries: int = 2,
) -> list[str]:
- """
- Repairs, translates, and optionally re-chunks a single text chunk using the small LLM, with rate limiting.
+ """Repairs, translates, and optionally re-chunks a single text chunk using the small LLM, with rate limiting.
"""
# 为了防止 LLM 上下文污染,在 user_prompt 中也加入明确的指令
user_prompt = f"""IGNORE ALL PREVIOUS INSTRUCTIONS. Your ONLY task is to process the following text chunk according to the system prompt provided.
@@ -76,7 +75,7 @@ Text chunk to process:
try:
async with rate_limiter:
response = await repair_llm_service.text_chat(
- prompt=user_prompt, system_prompt=TEXT_REPAIR_SYSTEM_PROMPT
+ prompt=user_prompt, system_prompt=TEXT_REPAIR_SYSTEM_PROMPT,
)
llm_output = response.completion_text
@@ -94,16 +93,15 @@ Text chunk to process:
if matches:
# Further cleaning to ensure no empty strings are returned
return [m.strip() for m in matches if m.strip()]
- else:
- # If no valid tags and not explicitly discarded, discard it to be safe.
- return []
+ # If no valid tags and not explicitly discarded, discard it to be safe.
+ return []
except Exception as e:
logger.warning(
- f" - LLM call failed on attempt {attempt + 1}/{max_retries + 1}. Error: {str(e)}"
+ f" - LLM call failed on attempt {attempt + 1}/{max_retries + 1}. Error: {e!s}",
)
logger.error(
- f" - Failed to process chunk after {max_retries + 1} attempts. Using original text."
+ f" - Failed to process chunk after {max_retries + 1} attempts. Using original text.",
)
return [chunk]
@@ -255,7 +253,7 @@ class KBHelper:
# 否则,执行标准的文件解析和分块流程
if file_content is None:
raise ValueError(
- "当未提供 pre_chunked_text 时,file_content 不能为空。"
+ "当未提供 pre_chunked_text 时,file_content 不能为空。",
)
file_size = len(file_content)
@@ -516,11 +514,11 @@ class KBHelper:
# 获取 Tavily API 密钥
config = self.prov_mgr.acm.default_conf
tavily_keys = config.get("provider_settings", {}).get(
- "websearch_tavily_key", []
+ "websearch_tavily_key", [],
)
if not tavily_keys:
raise ValueError(
- "Error: Tavily API key is not configured in provider_settings."
+ "Error: Tavily API key is not configured in provider_settings.",
)
# 阶段1: 从 URL 提取内容
@@ -552,11 +550,11 @@ class KBHelper:
if enable_cleaning and not final_chunks:
raise ValueError(
- "内容清洗后未提取到有效文本。请尝试关闭内容清洗功能,或更换更高性能的LLM模型后重试。"
+ "内容清洗后未提取到有效文本。请尝试关闭内容清洗功能,或更换更高性能的LLM模型后重试。",
)
# 创建一个虚拟文件名
- file_name = url.split("/")[-1] or f"document_from_{url}"
+ file_name = url.rsplit("/", maxsplit=1)[-1] or f"document_from_{url}"
if not Path(file_name).suffix:
file_name += ".url"
@@ -585,21 +583,20 @@ class KBHelper:
chunk_size: int = 512,
chunk_overlap: int = 50,
) -> list[str]:
- """
- 对从 URL 获取的内容进行清洗、修复、翻译和重新分块。
+ """对从 URL 获取的内容进行清洗、修复、翻译和重新分块。
"""
if not enable_cleaning:
# 如果不启用清洗,则使用从前端传递的参数进行分块
logger.info(
- f"内容清洗未启用,使用指定参数进行分块: chunk_size={chunk_size}, chunk_overlap={chunk_overlap}"
+ f"内容清洗未启用,使用指定参数进行分块: chunk_size={chunk_size}, chunk_overlap={chunk_overlap}",
)
return await self.chunker.chunk(
- content, chunk_size=chunk_size, chunk_overlap=chunk_overlap
+ content, chunk_size=chunk_size, chunk_overlap=chunk_overlap,
)
if not cleaning_provider_id:
logger.warning(
- "启用了内容清洗,但未提供 cleaning_provider_id,跳过清洗并使用默认分块。"
+ "启用了内容清洗,但未提供 cleaning_provider_id,跳过清洗并使用默认分块。",
)
return await self.chunker.chunk(content)
@@ -611,7 +608,7 @@ class KBHelper:
llm_provider = await self.prov_mgr.get_provider_by_id(cleaning_provider_id)
if not llm_provider or not isinstance(llm_provider, LLMProvider):
raise ValueError(
- f"无法找到 ID 为 {cleaning_provider_id} 的 LLM Provider 或类型不正确"
+ f"无法找到 ID 为 {cleaning_provider_id} 的 LLM Provider 或类型不正确",
)
# 初步分块
@@ -628,7 +625,7 @@ class KBHelper:
rate_limiter = RateLimiter(repair_max_rpm)
tasks = [
_repair_and_translate_chunk_with_retry(
- chunk, llm_provider, rate_limiter
+ chunk, llm_provider, rate_limiter,
)
for chunk in initial_chunks
]
@@ -638,13 +635,13 @@ class KBHelper:
final_chunks = []
for i, result in enumerate(repaired_results):
if isinstance(result, Exception):
- logger.warning(f"块 {i} 处理异常: {str(result)}. 回退到原始块。")
+ logger.warning(f"块 {i} 处理异常: {result!s}. 回退到原始块。")
final_chunks.append(initial_chunks[i])
elif isinstance(result, list):
final_chunks.extend(result)
logger.info(
- f"文本修复完成: {len(initial_chunks)} 个原始块 -> {len(final_chunks)} 个最终块。"
+ f"文本修复完成: {len(initial_chunks)} 个原始块 -> {len(final_chunks)} 个最终块。",
)
if progress_callback:
diff --git a/astrbot/core/knowledge_base/kb_mgr.py b/astrbot/core/knowledge_base/kb_mgr.py
index 8dea163cb..848999593 100644
--- a/astrbot/core/knowledge_base/kb_mgr.py
+++ b/astrbot/core/knowledge_base/kb_mgr.py
@@ -386,6 +386,7 @@ class KnowledgeBaseManager:
Raises:
ValueError: 如果知识库不存在或 URL 为空
IOError: 如果网络请求失败
+
"""
kb_helper = await self.get_kb(kb_id)
if not kb_helper:
diff --git a/astrbot/core/knowledge_base/parsers/url_parser.py b/astrbot/core/knowledge_base/parsers/url_parser.py
index 2867164a9..c87b5c55e 100644
--- a/astrbot/core/knowledge_base/parsers/url_parser.py
+++ b/astrbot/core/knowledge_base/parsers/url_parser.py
@@ -7,11 +7,11 @@ class URLExtractor:
"""URL 内容提取器,封装了 Tavily API 调用和密钥管理"""
def __init__(self, tavily_keys: list[str]) -> None:
- """
- 初始化 URL 提取器
+ """初始化 URL 提取器
Args:
tavily_keys: Tavily API 密钥列表
+
"""
if not tavily_keys:
raise ValueError("Error: Tavily API keys are not configured.")
@@ -28,8 +28,7 @@ class URLExtractor:
return key
async def extract_text_from_url(self, url: str) -> str:
- """
- 使用 Tavily API 从 URL 提取主要文本内容。
+ """使用 Tavily API 从 URL 提取主要文本内容。
这是 web_searcher 插件中 tavily_extract_web_page 方法的简化版本,
专门为知识库模块设计,不依赖 AstrMessageEvent。
@@ -42,6 +41,7 @@ class URLExtractor:
Raises:
ValueError: 如果 URL 为空或 API 密钥未配置
IOError: 如果请求失败或返回错误
+
"""
if not url:
raise ValueError("Error: url must be a non-empty string.")
@@ -69,7 +69,7 @@ class URLExtractor:
if response.status != 200:
reason = await response.text()
raise OSError(
- f"Tavily web extraction failed: {reason}, status: {response.status}"
+ f"Tavily web extraction failed: {reason}, status: {response.status}",
)
data = await response.json()
@@ -89,8 +89,7 @@ class URLExtractor:
# 为了向后兼容,提供一个简单的函数接口
async def extract_text_from_url(url: str, tavily_keys: list[str]) -> str:
- """
- 简单的函数接口,用于从 URL 提取文本内容
+ """简单的函数接口,用于从 URL 提取文本内容
Args:
url: 要提取内容的网页 URL
@@ -98,6 +97,7 @@ async def extract_text_from_url(url: str, tavily_keys: list[str]) -> str:
Returns:
提取的文本内容
+
"""
extractor = URLExtractor(tavily_keys)
return await extractor.extract_text_from_url(url)
diff --git a/astrbot/core/log.py b/astrbot/core/log.py
index 3dd0719b1..bbac91f86 100644
--- a/astrbot/core/log.py
+++ b/astrbot/core/log.py
@@ -111,7 +111,7 @@ class _LoguruInterceptHandler(logging.Handler):
),
"astrbot_version_tag": getattr(record, "astrbot_version_tag", ""),
"source_file": getattr(
- record, "source_file", _build_source_file(record.pathname)
+ record, "source_file", _build_source_file(record.pathname),
),
"source_line": getattr(record, "source_line", record.lineno),
"is_trace": getattr(record, "is_trace", record.name == "astrbot.trace"),
@@ -387,7 +387,7 @@ class LogManager:
enable = bool(
config.get("trace_log_enable")
- or (config.get("log_file", {}) or {}).get("trace_enable", False)
+ or (config.get("log_file", {}) or {}).get("trace_enable", False),
)
path = config.get("trace_log_path")
max_mb = config.get("trace_log_max_mb")
diff --git a/astrbot/core/message/components.py b/astrbot/core/message/components.py
index 2f19434c9..9dcd65a63 100644
--- a/astrbot/core/message/components.py
+++ b/astrbot/core/message/components.py
@@ -157,7 +157,7 @@ class Record(BaseMessageComponent):
bs64_data = self.file.removeprefix("base64://")
image_bytes = base64.b64decode(bs64_data)
file_path = os.path.join(
- get_astrbot_temp_path(), f"recordseg_{uuid.uuid4()}.jpg"
+ get_astrbot_temp_path(), f"recordseg_{uuid.uuid4()}.jpg",
)
with open(file_path, "wb") as f:
f.write(image_bytes)
@@ -246,7 +246,7 @@ class Video(BaseMessageComponent):
return url[8:]
if url and url.startswith("http"):
video_file_path = os.path.join(
- get_astrbot_temp_path(), f"videoseg_{uuid.uuid4().hex}"
+ get_astrbot_temp_path(), f"videoseg_{uuid.uuid4().hex}",
)
await download_file(url, video_file_path)
if os.path.exists(video_file_path):
@@ -442,7 +442,7 @@ class Image(BaseMessageComponent):
bs64_data = url.removeprefix("base64://")
image_bytes = base64.b64decode(bs64_data)
image_file_path = os.path.join(
- get_astrbot_temp_path(), f"imgseg_{uuid.uuid4()}.jpg"
+ get_astrbot_temp_path(), f"imgseg_{uuid.uuid4()}.jpg",
)
with open(image_file_path, "wb") as f:
f.write(image_bytes)
diff --git a/astrbot/core/message/message_event_result.py b/astrbot/core/message/message_event_result.py
index 0965fe7f7..65ec0deff 100644
--- a/astrbot/core/message/message_event_result.py
+++ b/astrbot/core/message/message_event_result.py
@@ -123,21 +123,21 @@ class MessageChain:
Args:
with_other_comps_mark (bool): 是否在纯文本中标记其他组件的位置
+
"""
if not with_other_comps_mark:
return " ".join(
- [comp.text for comp in self.chain if isinstance(comp, Plain)]
+ [comp.text for comp in self.chain if isinstance(comp, Plain)],
)
- else:
- texts = []
- for comp in self.chain:
- if isinstance(comp, Plain):
- texts.append(comp.text)
- elif isinstance(comp, Json):
- texts.append(f"{comp.data}")
- else:
- texts.append(f"[{comp.__class__.__name__}]")
- return " ".join(texts)
+ texts = []
+ for comp in self.chain:
+ if isinstance(comp, Plain):
+ texts.append(comp.text)
+ elif isinstance(comp, Json):
+ texts.append(f"{comp.data}")
+ else:
+ texts.append(f"[{comp.__class__.__name__}]")
+ return " ".join(texts)
def squash_plain(self):
"""将消息链中的所有 Plain 消息段聚合到第一个 Plain 消息段中。"""
diff --git a/astrbot/core/persona_error_reply.py b/astrbot/core/persona_error_reply.py
index 5a99e0918..e6ea1bb10 100644
--- a/astrbot/core/persona_error_reply.py
+++ b/astrbot/core/persona_error_reply.py
@@ -35,7 +35,7 @@ def extract_persona_custom_error_message_from_event(event: Any) -> str | None:
def set_persona_custom_error_message_on_event(
- event: Any, message: object
+ event: Any, message: object,
) -> str | None:
"""Normalize and store persona custom error reply text into event extras."""
normalized = normalize_persona_custom_error_message(message)
@@ -70,16 +70,16 @@ async def resolve_persona_custom_error_message(
async def resolve_event_conversation_persona_id(
- event: Any, conversation_manager: Any
+ event: Any, conversation_manager: Any,
) -> str | None:
"""Resolve current conversation persona_id from event and conversation manager."""
curr_cid = await conversation_manager.get_curr_conversation_id(
- event.unified_msg_origin
+ event.unified_msg_origin,
)
if not curr_cid:
return None
conversation = await conversation_manager.get_conversation(
- event.unified_msg_origin, curr_cid
+ event.unified_msg_origin, curr_cid,
)
if not conversation:
return None
diff --git a/astrbot/core/persona_mgr.py b/astrbot/core/persona_mgr.py
index 6320ac3bb..eb57183de 100644
--- a/astrbot/core/persona_mgr.py
+++ b/astrbot/core/persona_mgr.py
@@ -88,6 +88,7 @@ class PersonaManager:
- selected persona object
- force applied persona_id from session rule
- whether use webchat special default persona
+
"""
session_service_config = (
await sp.get_async(
@@ -174,23 +175,25 @@ class PersonaManager:
return await self.db.get_personas()
async def get_personas_by_folder(
- self, folder_id: str | None = None
+ self, folder_id: str | None = None,
) -> list[Persona]:
"""获取指定文件夹中的 personas
Args:
folder_id: 文件夹 ID,None 表示根目录
+
"""
return await self.db.get_personas_by_folder(folder_id)
async def move_persona_to_folder(
- self, persona_id: str, folder_id: str | None
+ self, persona_id: str, folder_id: str | None,
) -> Persona | None:
"""移动 persona 到指定文件夹
Args:
persona_id: Persona ID
folder_id: 目标文件夹 ID,None 表示移动到根目录
+
"""
persona = await self.db.move_persona_to_folder(persona_id, folder_id)
if persona:
@@ -228,6 +231,7 @@ class PersonaManager:
Args:
parent_id: 父文件夹 ID,None 表示获取根目录下的文件夹
+
"""
return await self.db.get_persona_folders(parent_id)
@@ -267,6 +271,7 @@ class PersonaManager:
- id: persona_id 或 folder_id
- type: "persona" 或 "folder"
- sort_order: 新的排序顺序值
+
"""
await self.db.batch_update_sort_order(items)
# 刷新缓存
@@ -278,6 +283,7 @@ class PersonaManager:
Returns:
树形结构的文件夹列表,每个文件夹包含 children 子列表
+
"""
all_folders = await self.get_all_folders()
folder_map: dict[str, dict] = {}
@@ -333,6 +339,7 @@ class PersonaManager:
skills: Skills 列表,None 表示使用所有 Skills,空列表表示不使用任何 Skills
folder_id: 所属文件夹 ID,None 表示根目录
sort_order: 排序顺序
+
"""
if await self.db.get_persona_by_id(persona_id):
raise ValueError(f"Persona with ID {persona_id} already exists.")
diff --git a/astrbot/core/pipeline/__init__.py b/astrbot/core/pipeline/__init__.py
index 6a6069ff7..4d851c2f7 100644
--- a/astrbot/core/pipeline/__init__.py
+++ b/astrbot/core/pipeline/__init__.py
@@ -80,6 +80,7 @@ if TYPE_CHECKING:
from .whitelist_check.stage import WhitelistCheckStage
__all__ = [
+ "STAGES_ORDER",
"ContentSafetyCheckStage",
"EventResultType",
"MessageEventResult",
@@ -89,7 +90,6 @@ __all__ = [
"RespondStage",
"ResultDecorateStage",
"SessionStatusCheckStage",
- "STAGES_ORDER",
"WakingCheckStage",
"WhitelistCheckStage",
]
diff --git a/astrbot/core/pipeline/content_safety_check/stage.py b/astrbot/core/pipeline/content_safety_check/stage.py
index 19037eb08..4e8d5d8ab 100644
--- a/astrbot/core/pipeline/content_safety_check/stage.py
+++ b/astrbot/core/pipeline/content_safety_check/stage.py
@@ -26,7 +26,7 @@ class ContentSafetyCheckStage(Stage):
check_text: str | None = None,
) -> AsyncGenerator[None, None]:
"""检查内容安全"""
- text = check_text if check_text else event.get_message_str()
+ text = check_text or event.get_message_str()
ok, info = self.strategy_selector.check(text)
if not ok:
if event.is_at_or_wake_command:
diff --git a/astrbot/core/pipeline/content_safety_check/strategies/baidu_aip.py b/astrbot/core/pipeline/content_safety_check/strategies/baidu_aip.py
index dd8ca629e..eb8d6f8f0 100644
--- a/astrbot/core/pipeline/content_safety_check/strategies/baidu_aip.py
+++ b/astrbot/core/pipeline/content_safety_check/strategies/baidu_aip.py
@@ -26,7 +26,7 @@ class BaiduAipStrategy(ContentSafetyStrategy):
parts = [f"百度审核服务发现 {count} 处违规:\n"]
for i in res["data"]:
# 百度 AIP 返回结构是动态 dict;类型检查时 i 可能被推断为序列,转成 dict 后用 get 取字段
- parts.append(f"{cast(dict[str, Any], i).get('msg', '')};\n")
+ parts.append(f"{cast('dict[str, Any]', i).get('msg', '')};\n")
parts.append("\n判断结果:" + res["conclusion"])
info = "".join(parts)
return False, info
diff --git a/astrbot/core/pipeline/process_stage/follow_up.py b/astrbot/core/pipeline/process_stage/follow_up.py
index 79ec16a85..da650b827 100644
--- a/astrbot/core/pipeline/process_stage/follow_up.py
+++ b/astrbot/core/pipeline/process_stage/follow_up.py
@@ -185,7 +185,7 @@ def try_capture_follow_up(event: AstrMessageEvent) -> FollowUpCapture | None:
event.unified_msg_origin,
ticket,
order_seq,
- )
+ ),
)
logger.info(
"Captured follow-up message for active agent run, umo=%s, order_seq=%s",
diff --git a/astrbot/core/pipeline/process_stage/method/agent_request.py b/astrbot/core/pipeline/process_stage/method/agent_request.py
index 9efe53814..be8c970fd 100644
--- a/astrbot/core/pipeline/process_stage/method/agent_request.py
+++ b/astrbot/core/pipeline/process_stage/method/agent_request.py
@@ -34,13 +34,13 @@ class AgentRequestSubStage(Stage):
async def process(self, event: AstrMessageEvent) -> AsyncGenerator[None, None]:
if not self.ctx.astrbot_config["provider_settings"]["enable"]:
logger.debug(
- "This pipeline does not enable AI capability, skip processing."
+ "This pipeline does not enable AI capability, skip processing.",
)
return
if not await SessionServiceManager.should_process_llm_request(event):
logger.debug(
- f"The session {event.unified_msg_origin} has disabled AI capability, skipping processing."
+ f"The session {event.unified_msg_origin} has disabled AI capability, skipping processing.",
)
return
diff --git a/astrbot/core/pipeline/process_stage/method/agent_sub_stages/internal.py b/astrbot/core/pipeline/process_stage/method/agent_sub_stages/internal.py
index e0ba2463c..08167b3b8 100644
--- a/astrbot/core/pipeline/process_stage/method/agent_sub_stages/internal.py
+++ b/astrbot/core/pipeline/process_stage/method/agent_sub_stages/internal.py
@@ -77,19 +77,19 @@ class InternalAgentSubStage(Stage):
self.file_extract_enabled: bool = file_extract_conf.get("enable", False)
self.file_extract_prov: str = file_extract_conf.get("provider", "moonshotai")
self.file_extract_msh_api_key: str = file_extract_conf.get(
- "moonshotai_api_key", ""
+ "moonshotai_api_key", "",
)
# 上下文管理相关
self.context_limit_reached_strategy: str = settings.get(
- "context_limit_reached_strategy", "truncate_by_turns"
+ "context_limit_reached_strategy", "truncate_by_turns",
)
self.llm_compress_instruction: str = settings.get(
- "llm_compress_instruction", ""
+ "llm_compress_instruction", "",
)
self.llm_compress_keep_recent: int = settings.get("llm_compress_keep_recent", 4)
self.llm_compress_provider_id: str = settings.get(
- "llm_compress_provider_id", ""
+ "llm_compress_provider_id", "",
)
self.max_context_length = settings["max_context_length"] # int
self.dequeue_context_length: int = min(
@@ -101,7 +101,7 @@ class InternalAgentSubStage(Stage):
self.llm_safety_mode = settings.get("llm_safety_mode", True)
self.safety_mode_strategy = settings.get(
- "safety_mode_strategy", "system_prompt"
+ "safety_mode_strategy", "system_prompt",
)
self.computer_use_runtime = settings.get("computer_use_runtime")
@@ -139,7 +139,7 @@ class InternalAgentSubStage(Stage):
)
async def process(
- self, event: AstrMessageEvent, provider_wake_prefix: str
+ self, event: AstrMessageEvent, provider_wake_prefix: str,
) -> AsyncGenerator[None, None]:
follow_up_capture: FollowUpCapture | None = None
follow_up_consumed_marked = False
@@ -259,13 +259,13 @@ class InternalAgentSubStage(Stage):
# 获取 TTS Provider
tts_provider = (
self.ctx.plugin_manager.context.get_using_tts_provider(
- event.unified_msg_origin
+ event.unified_msg_origin,
)
)
if not tts_provider:
logger.warning(
- "[Live Mode] TTS Provider 未配置,将使用普通流式模式"
+ "[Live Mode] TTS Provider 未配置,将使用普通流式模式",
)
# 使用 run_live_agent,总是使用流式响应
@@ -357,7 +357,7 @@ class InternalAgentSubStage(Stage):
req,
agent_runner,
final_resp,
- )
+ ),
)
# 检查事件是否被停止,如果被停止则不保存历史记录
@@ -385,7 +385,7 @@ class InternalAgentSubStage(Stage):
except Exception as e:
logger.error(f"Error occurred while processing agent: {e}")
custom_error_message = extract_persona_custom_error_message_from_event(
- event
+ event,
)
error_text = custom_error_message or (
f"Error occurred while processing agent request: {e}"
diff --git a/astrbot/core/pipeline/process_stage/method/agent_sub_stages/third_party.py b/astrbot/core/pipeline/process_stage/method/agent_sub_stages/third_party.py
index 9ab315779..91321acb4 100644
--- a/astrbot/core/pipeline/process_stage/method/agent_sub_stages/third_party.py
+++ b/astrbot/core/pipeline/process_stage/method/agent_sub_stages/third_party.py
@@ -64,8 +64,7 @@ async def run_third_party_agent(
stream_to_general: bool = False,
custom_error_message: str | None = None,
) -> AsyncGenerator[tuple[MessageChain, bool], None]:
- """
- 运行第三方 agent runner 并转换响应格式
+ """运行第三方 agent runner 并转换响应格式
类似于 run_agent 函数,但专门处理第三方 agent runner
"""
try:
@@ -86,7 +85,7 @@ async def run_third_party_agent(
err_msg = (
f"Error occurred during AI execution.\n"
f"Error Type: {type(e).__name__} (3rd party)\n"
- f"Error Message: {str(e)}"
+ f"Error Message: {e!s}"
)
yield MessageChain().message(err_msg), True
@@ -187,7 +186,7 @@ class ThirdPartyAgentSubStage(Stage):
)
async def _resolve_persona_custom_error_message(
- self, event: AstrMessageEvent
+ self, event: AstrMessageEvent,
) -> str | None:
try:
conversation_persona_id = await resolve_event_conversation_persona_id(
@@ -241,7 +240,7 @@ class ThirdPartyAgentSubStage(Stage):
if runner.done():
final_chain, is_runner_error = aggregator.finalize(
- runner.get_final_llm_resp()
+ runner.get_final_llm_resp(),
)
event.set_extra(THIRD_PARTY_RUNNER_ERROR_EXTRA_KEY, is_runner_error)
event.set_result(
@@ -287,12 +286,12 @@ class ThirdPartyAgentSubStage(Stage):
yield
async def process(
- self, event: AstrMessageEvent, provider_wake_prefix: str
+ self, event: AstrMessageEvent, provider_wake_prefix: str,
) -> AsyncGenerator[None, None]:
req: ProviderRequest | None = None
if provider_wake_prefix and not event.message_str.startswith(
- provider_wake_prefix
+ provider_wake_prefix,
):
return
@@ -305,7 +304,7 @@ class ThirdPartyAgentSubStage(Stage):
return
if not self.prov_cfg:
logger.error(
- f"Agent Runner 提供商 {self.prov_id} 配置不存在,请前往配置页面修改配置。"
+ f"Agent Runner 提供商 {self.prov_id} 配置不存在,请前往配置页面修改配置。",
)
return
diff --git a/astrbot/core/pipeline/respond/stage.py b/astrbot/core/pipeline/respond/stage.py
index aea6a74b3..715e01430 100644
--- a/astrbot/core/pipeline/respond/stage.py
+++ b/astrbot/core/pipeline/respond/stage.py
@@ -40,7 +40,7 @@ class RespondStage(Stage):
), # 音乐分享
Comp.Forward: lambda comp: bool(comp.id), # 合并转发
Comp.Location: lambda comp: bool(
- comp.lat is not None and comp.lon is not None
+ comp.lat is not None and comp.lon is not None,
), # 位置
Comp.Contact: lambda comp: bool(comp._type and comp.id), # 推荐好友 or 群
Comp.Shake: lambda _: True, # 窗口抖动(戳一戳)
diff --git a/astrbot/core/pipeline/result_decorate/stage.py b/astrbot/core/pipeline/result_decorate/stage.py
index 4ee746130..3e0a4d3c8 100644
--- a/astrbot/core/pipeline/result_decorate/stage.py
+++ b/astrbot/core/pipeline/result_decorate/stage.py
@@ -76,10 +76,10 @@ class ResultDecorateStage(Stage):
].get("split_words", ["。", "?", "!", "~", "…"])
if self.split_words:
escaped_words = sorted(
- [re.escape(word) for word in self.split_words], key=len, reverse=True
+ [re.escape(word) for word in self.split_words], key=len, reverse=True,
)
self.split_words_pattern = re.compile(
- f"(.*?({'|'.join(escaped_words)})|.+$)", re.DOTALL
+ f"(.*?({'|'.join(escaped_words)})|.+$)", re.DOTALL,
)
else:
self.split_words_pattern = None
@@ -121,7 +121,7 @@ class ResultDecorateStage(Stage):
result.append(content)
elif seg and seg.strip():
result.append(seg)
- return result if result else [text]
+ return result or [text]
async def process(
self,
diff --git a/astrbot/core/pipeline/waking_check/stage.py b/astrbot/core/pipeline/waking_check/stage.py
index ddc2a6cb8..26134bdb2 100644
--- a/astrbot/core/pipeline/waking_check/stage.py
+++ b/astrbot/core/pipeline/waking_check/stage.py
@@ -69,7 +69,7 @@ class WakingCheckStage(Stage):
False,
)
self.disable_builtin_commands = self.ctx.astrbot_config.get(
- "disable_builtin_commands", False
+ "disable_builtin_commands", False,
)
platform_settings = self.ctx.astrbot_config.get("platform_settings", {})
self.unique_session = platform_settings.get("unique_session", False)
diff --git a/astrbot/core/platform/astr_message_event.py b/astrbot/core/platform/astr_message_event.py
index 645436702..5b6882e71 100644
--- a/astrbot/core/platform/astr_message_event.py
+++ b/astrbot/core/platform/astr_message_event.py
@@ -60,7 +60,7 @@ class AstrMessageEvent(abc.ABC):
except (ValueError, TypeError, AttributeError):
logger.warning(
f"Failed to convert message type {message_obj.type!r} to MessageType. "
- f"Falling back to FRIEND_MESSAGE."
+ f"Falling back to FRIEND_MESSAGE.",
)
message_type = MessageType.FRIEND_MESSAGE
self.session = MessageSession(
diff --git a/astrbot/core/platform/astrbot_message.py b/astrbot/core/platform/astrbot_message.py
index 3db53fd48..05da0b8c5 100644
--- a/astrbot/core/platform/astrbot_message.py
+++ b/astrbot/core/platform/astrbot_message.py
@@ -15,7 +15,7 @@ class MessageMember:
# 使用 f-string 来构建返回的字符串表示形式
return (
f"User ID: {self.user_id},"
- f"Nickname: {self.nickname if self.nickname else 'N/A'}"
+ f"Nickname: {self.nickname or 'N/A'}"
)
@@ -38,10 +38,10 @@ class Group:
# 使用 f-string 来构建返回的字符串表示形式
return (
f"Group ID: {self.group_id}\n"
- f"Name: {self.group_name if self.group_name else 'N/A'}\n"
- f"Avatar: {self.group_avatar if self.group_avatar else 'N/A'}\n"
- f"Owner ID: {self.group_owner if self.group_owner else 'N/A'}\n"
- f"Admin IDs: {self.group_admins if self.group_admins else 'N/A'}\n"
+ f"Name: {self.group_name or 'N/A'}\n"
+ f"Avatar: {self.group_avatar or 'N/A'}\n"
+ f"Owner ID: {self.group_owner or 'N/A'}\n"
+ f"Admin IDs: {self.group_admins or 'N/A'}\n"
f"Members Len: {len(self.members) if self.members else 0}\n"
f"First Member: {self.members[0] if self.members else 'N/A'}\n"
)
diff --git a/astrbot/core/platform/manager.py b/astrbot/core/platform/manager.py
index d592eb2fb..cff2bfe95 100644
--- a/astrbot/core/platform/manager.py
+++ b/astrbot/core/platform/manager.py
@@ -228,7 +228,7 @@ class PlatformManager:
logger.error(traceback.format_exc())
async def _task_wrapper(
- self, task: asyncio.Task, platform: Platform | None = None
+ self, task: asyncio.Task, platform: Platform | None = None,
) -> None:
# 设置平台状态为运行中
if platform:
@@ -309,6 +309,7 @@ class PlatformManager:
Returns:
包含所有平台统计信息的字典
+
"""
stats_list = []
total_errors = 0
@@ -334,7 +335,7 @@ class PlatformManager:
"status": "unknown",
"error_count": 0,
"last_error": None,
- }
+ },
)
return {
diff --git a/astrbot/core/platform/platform.py b/astrbot/core/platform/platform.py
index a7c181217..dea6b73c0 100644
--- a/astrbot/core/platform/platform.py
+++ b/astrbot/core/platform/platform.py
@@ -84,7 +84,7 @@ class Platform(abc.ABC):
"""是否正在使用统一 Webhook 模式"""
return bool(
self.config.get("unified_webhook_mode", False)
- and self.config.get("webhook_uuid")
+ and self.config.get("webhook_uuid"),
)
def get_stats(self) -> dict:
@@ -161,5 +161,6 @@ class Platform(abc.ABC):
Raises:
NotImplementedError: 平台未实现统一 Webhook 模式
+
"""
raise NotImplementedError(f"平台 {self.meta().name} 未实现统一 Webhook 模式")
diff --git a/astrbot/core/platform/register.py b/astrbot/core/platform/register.py
index 62ec5070a..32d6b6b3e 100644
--- a/astrbot/core/platform/register.py
+++ b/astrbot/core/platform/register.py
@@ -73,6 +73,7 @@ def unregister_platform_adapters_by_module(module_path_prefix: str) -> list[str]
Returns:
被注销的平台适配器名称列表
+
"""
unregistered = []
to_remove = []
diff --git a/astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py b/astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py
index 7110199af..d023d7421 100644
--- a/astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py
+++ b/astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py
@@ -48,7 +48,7 @@ class AiocqhttpAdapter(Platform):
self.metadata = PlatformMetadata(
name="aiocqhttp",
description="适用于 OneBot 标准的消息平台适配器,支持反向 WebSockets。",
- id=cast(str, self.config.get("id")),
+ id=cast("str", self.config.get("id")),
support_streaming_message=False,
)
@@ -145,7 +145,7 @@ class AiocqhttpAdapter(Platform):
abm = AstrBotMessage()
abm.self_id = str(event.self_id)
abm.sender = MessageMember(
- user_id=str(event.user_id), nickname=str(event.user_id)
+ user_id=str(event.user_id), nickname=str(event.user_id),
)
abm.type = MessageType.OTHER_MESSAGE
if event.get("group_id"):
@@ -170,7 +170,7 @@ class AiocqhttpAdapter(Platform):
abm = AstrBotMessage()
abm.self_id = str(event.self_id)
abm.sender = MessageMember(
- user_id=str(event.user_id), nickname=str(event.user_id)
+ user_id=str(event.user_id), nickname=str(event.user_id),
)
abm.type = MessageType.OTHER_MESSAGE
if event.get("group_id"):
@@ -399,14 +399,14 @@ class AiocqhttpAdapter(Platform):
try:
if t not in ComponentTypes:
logger.warning(
- f"不支持的消息段类型,已忽略: {t}, data={m['data']}"
+ f"不支持的消息段类型,已忽略: {t}, data={m['data']}",
)
continue
a = ComponentTypes[t](**m["data"])
abm.message.append(a)
except Exception as e:
logger.exception(
- f"消息段解析失败: type={t}, data={m['data']}. {e}"
+ f"消息段解析失败: type={t}, data={m['data']}. {e}",
)
continue
diff --git a/astrbot/core/platform/sources/dingtalk/dingtalk_adapter.py b/astrbot/core/platform/sources/dingtalk/dingtalk_adapter.py
index 37c3b09ab..d35dc6489 100644
--- a/astrbot/core/platform/sources/dingtalk/dingtalk_adapter.py
+++ b/astrbot/core/platform/sources/dingtalk/dingtalk_adapter.py
@@ -47,7 +47,7 @@ class MyEventHandler(dingtalk_stream.EventHandler):
@register_platform_adapter(
- "dingtalk", "钉钉机器人官方 API 适配器", support_streaming_message=True
+ "dingtalk", "钉钉机器人官方 API 适配器", support_streaming_message=True,
)
class DingtalkPlatformAdapter(Platform):
def __init__(
@@ -140,7 +140,7 @@ class DingtalkPlatformAdapter(Platform):
return PlatformMetadata(
name="dingtalk",
description="钉钉机器人官方 API 适配器",
- id=cast(str, self.config.get("id")),
+ id=cast("str", self.config.get("id")),
support_streaming_message=True,
support_proactive_message=True,
)
@@ -152,7 +152,7 @@ class DingtalkPlatformAdapter(Platform):
abm = AstrBotMessage()
abm.message = []
abm.message_str = ""
- abm.timestamp = int(cast(int, message.create_at) / 1000)
+ abm.timestamp = int(cast("int", message.create_at) / 1000)
abm.type = (
MessageType.GROUP_MESSAGE
if message.conversation_type == "2"
@@ -163,7 +163,7 @@ class DingtalkPlatformAdapter(Platform):
nickname=message.sender_nick,
)
abm.self_id = self._id_to_sid(message.chatbot_user_id)
- abm.message_id = cast(str, message.message_id)
+ abm.message_id = cast("str", message.message_id)
abm.raw_message = message
if abm.type == MessageType.GROUP_MESSAGE:
@@ -177,9 +177,9 @@ class DingtalkPlatformAdapter(Platform):
else:
abm.session_id = abm.sender.user_id
- message_type: str = cast(str, message.message_type)
- robot_code = cast(str, message.robot_code or "")
- raw_content = cast(dict, message.extensions.get("content") or {})
+ message_type: str = cast("str", message.message_type)
+ robot_code = cast("str", message.robot_code or "")
+ raw_content = cast("dict", message.extensions.get("content") or {})
if not isinstance(raw_content, dict):
raw_content = {}
match message_type:
@@ -192,11 +192,11 @@ class DingtalkPlatformAdapter(Platform):
await self._remember_sender_binding(message, abm)
return abm
image_content = cast(
- dingtalk_stream.ImageContent | None,
+ "dingtalk_stream.ImageContent | None",
message.image_content,
)
download_code = cast(
- str, (image_content.download_code if image_content else "") or ""
+ "str", (image_content.download_code if image_content else "") or "",
)
if not download_code:
logger.warning("钉钉图片消息缺少 downloadCode,已跳过")
@@ -212,26 +212,26 @@ class DingtalkPlatformAdapter(Platform):
logger.warning("钉钉图片消息下载失败,无法解析为图片")
case "richText":
rtc: dingtalk_stream.RichTextContent = cast(
- dingtalk_stream.RichTextContent, message.rich_text_content
+ "dingtalk_stream.RichTextContent", message.rich_text_content,
)
- contents: list[dict] = cast(list[dict], rtc.rich_text_list)
+ contents: list[dict] = cast("list[dict]", rtc.rich_text_list)
plain_parts: list[str] = []
for content in contents:
if "text" in content:
- plain_text = cast(str, content.get("text") or "")
+ plain_text = cast("str", content.get("text") or "")
if plain_text:
plain_parts.append(plain_text)
abm.message.append(Plain(plain_text))
elif "type" in content and content["type"] == "picture":
- download_code = cast(str, content.get("downloadCode") or "")
+ download_code = cast("str", content.get("downloadCode") or "")
if not download_code:
logger.warning(
- "钉钉富文本图片消息缺少 downloadCode,已跳过"
+ "钉钉富文本图片消息缺少 downloadCode,已跳过",
)
continue
if not robot_code:
logger.error(
- "钉钉富文本图片消息解析失败: 回调中缺少 robotCode"
+ "钉钉富文本图片消息解析失败: 回调中缺少 robotCode",
)
continue
f_path = await self.download_ding_file(
@@ -243,13 +243,13 @@ class DingtalkPlatformAdapter(Platform):
abm.message.append(Image.fromFileSystem(f_path))
abm.message_str = "".join(plain_parts).strip()
case "audio" | "voice":
- download_code = cast(str, raw_content.get("downloadCode") or "")
+ download_code = cast("str", raw_content.get("downloadCode") or "")
if not download_code:
logger.warning("钉钉语音消息缺少 downloadCode,已跳过")
elif not robot_code:
logger.error("钉钉语音消息解析失败: 回调中缺少 robotCode")
else:
- voice_ext = cast(str, raw_content.get("fileExtension") or "")
+ voice_ext = cast("str", raw_content.get("fileExtension") or "")
if not voice_ext:
voice_ext = "amr"
voice_ext = voice_ext.lstrip(".")
@@ -261,16 +261,16 @@ class DingtalkPlatformAdapter(Platform):
if f_path:
abm.message.append(Record.fromFileSystem(f_path))
case "file":
- download_code = cast(str, raw_content.get("downloadCode") or "")
+ download_code = cast("str", raw_content.get("downloadCode") or "")
if not download_code:
logger.warning("钉钉文件消息缺少 downloadCode,已跳过")
elif not robot_code:
logger.error("钉钉文件消息解析失败: 回调中缺少 robotCode")
else:
- file_name = cast(str, raw_content.get("fileName") or "")
+ file_name = cast("str", raw_content.get("fileName") or "")
file_ext = Path(file_name).suffix.lstrip(".") if file_name else ""
if not file_ext:
- file_ext = cast(str, raw_content.get("fileExtension") or "")
+ file_ext = cast("str", raw_content.get("fileExtension") or "")
if not file_ext:
file_ext = "file"
f_path = await self.download_ding_file(
@@ -294,14 +294,14 @@ class DingtalkPlatformAdapter(Platform):
try:
if abm.type == MessageType.FRIEND_MESSAGE:
sender_id = abm.sender.user_id
- sender_staff_id = cast(str, message.sender_staff_id or "")
+ sender_staff_id = cast("str", message.sender_staff_id or "")
if sender_staff_id:
umo = str(
MessageSesion(
platform_name=self.meta().id,
message_type=abm.type,
session_id=sender_id,
- )
+ ),
)
await sp.put_async(
"global",
@@ -352,7 +352,7 @@ class DingtalkPlatformAdapter(Platform):
return ""
resp_data = await resp.json()
download_url = cast(
- str,
+ "str",
(
resp_data.get("downloadUrl")
or resp_data.get("data", {}).get("downloadUrl")
@@ -388,7 +388,7 @@ class DingtalkPlatformAdapter(Platform):
)
return ""
data = await resp.json()
- return cast(str, data.get("data", {}).get("accessToken", ""))
+ return cast("str", data.get("data", {}).get("accessToken", ""))
async def _get_sender_staff_id(self, session: MessageSesion) -> str:
try:
@@ -398,7 +398,7 @@ class DingtalkPlatformAdapter(Platform):
"dingtalk_staffid",
"",
)
- return cast(str, staff_id or "")
+ return cast("str", staff_id or "")
except Exception as e:
logger.warning(f"读取钉钉 staff_id 映射失败: {e}")
return ""
@@ -425,16 +425,15 @@ class DingtalkPlatformAdapter(Platform):
"Content-Type": "application/json",
"x-acs-dingtalk-access-token": access_token,
}
- async with aiohttp.ClientSession() as session:
- async with session.post(
- "https://api.dingtalk.com/v1.0/robot/groupMessages/send",
- headers=headers,
- json=payload,
- ) as resp:
- if resp.status != 200:
- logger.error(
- f"钉钉群消息发送失败: {resp.status}, {await resp.text()}",
- )
+ async with aiohttp.ClientSession() as session, session.post(
+ "https://api.dingtalk.com/v1.0/robot/groupMessages/send",
+ headers=headers,
+ json=payload,
+ ) as resp:
+ if resp.status != 200:
+ logger.error(
+ f"钉钉群消息发送失败: {resp.status}, {await resp.text()}",
+ )
async def _send_private_message(
self,
@@ -458,16 +457,15 @@ class DingtalkPlatformAdapter(Platform):
"Content-Type": "application/json",
"x-acs-dingtalk-access-token": access_token,
}
- async with aiohttp.ClientSession() as session:
- async with session.post(
- "https://api.dingtalk.com/v1.0/robot/oToMessages/batchSend",
- headers=headers,
- json=payload,
- ) as resp:
- if resp.status != 200:
- logger.error(
- f"钉钉私聊消息发送失败: {resp.status}, {await resp.text()}",
- )
+ async with aiohttp.ClientSession() as session, session.post(
+ "https://api.dingtalk.com/v1.0/robot/oToMessages/batchSend",
+ headers=headers,
+ json=payload,
+ ) as resp:
+ if resp.status != 200:
+ logger.error(
+ f"钉钉私聊消息发送失败: {resp.status}, {await resp.text()}",
+ )
def _safe_remove_file(self, file_path: str | None) -> None:
if not file_path:
@@ -514,14 +512,14 @@ class DingtalkPlatformAdapter(Platform):
) as resp:
if resp.status != 200:
logger.error(
- f"钉钉媒体上传失败: {resp.status}, {await resp.text()}"
+ f"钉钉媒体上传失败: {resp.status}, {await resp.text()}",
)
return ""
data = await resp.json()
if data.get("errcode") != 0:
logger.error(f"钉钉媒体上传失败: {data}")
return ""
- return cast(str, data.get("media_id", ""))
+ return cast("str", data.get("media_id", ""))
async def upload_image(self, image: Image) -> str:
image_file_path = await image.convert_to_file_path()
@@ -693,8 +691,8 @@ class DingtalkPlatformAdapter(Platform):
robot_code = self.client_id
# at_list: list[str] = []
- sender_id = cast(str, incoming_message.sender_id or "")
- sender_staff_id = cast(str, incoming_message.sender_staff_id or "")
+ sender_id = cast("str", incoming_message.sender_id or "")
+ sender_staff_id = cast("str", incoming_message.sender_staff_id or "")
normalized_sender_id = self._id_to_sid(sender_id)
# 现在用的发消息接口不支持 at
# for segment in message_chain.chain:
@@ -710,7 +708,7 @@ class DingtalkPlatformAdapter(Platform):
if incoming_message.conversation_type == "2":
await self.send_message_chain_to_group(
- open_conversation_id=cast(str, incoming_message.conversation_id),
+ open_conversation_id=cast("str", incoming_message.conversation_id),
robot_code=robot_code,
message_chain=message_chain,
# at_str=at_str,
diff --git a/astrbot/core/platform/sources/discord/discord_platform_adapter.py b/astrbot/core/platform/sources/discord/discord_platform_adapter.py
index 19b9c81b4..e1268420e 100644
--- a/astrbot/core/platform/sources/discord/discord_platform_adapter.py
+++ b/astrbot/core/platform/sources/discord/discord_platform_adapter.py
@@ -35,7 +35,7 @@ else:
# 注册平台适配器
@register_platform_adapter(
- "discord", "Discord 适配器 (基于 Pycord)", support_streaming_message=False
+ "discord", "Discord 适配器 (基于 Pycord)", support_streaming_message=False,
)
class DiscordPlatformAdapter(Platform):
def __init__(
@@ -64,7 +64,7 @@ class DiscordPlatformAdapter(Platform):
"""通过会话发送消息"""
if self.client.user is None:
logger.error(
- "[Discord] Client is not ready (self.client.user is None); message send skipped"
+ "[Discord] Client is not ready (self.client.user is None); message send skipped",
)
return
@@ -95,7 +95,7 @@ class DiscordPlatformAdapter(Platform):
user_id=str(self.bot_self_id),
nickname=self.client.user.display_name,
)
- message_obj.self_id = cast(str, self.bot_self_id)
+ message_obj.self_id = cast("str", self.bot_self_id)
message_obj.session_id = session.session_id
message_obj.message = message_chain.chain
@@ -116,7 +116,7 @@ class DiscordPlatformAdapter(Platform):
return PlatformMetadata(
"discord",
"Discord Adapter",
- id=cast(str, self.config.get("id")),
+ id=cast("str", self.config.get("id")),
default_config_tmpl=self.config,
support_streaming_message=False,
)
@@ -137,7 +137,7 @@ class DiscordPlatformAdapter(Platform):
token = str(self.config.get("discord_token"))
if not token:
logger.error(
- "[Discord] Bot token is not configured. Please set a valid token in the config file."
+ "[Discord] Bot token is not configured. Please set a valid token in the config file.",
)
return
@@ -156,7 +156,7 @@ class DiscordPlatformAdapter(Platform):
)
except Exception as e:
logger.error(
- f"[Discord] on_ready_once_callback err: {e}", exc_info=True
+ f"[Discord] on_ready_once_callback err: {e}", exc_info=True,
)
self.client.on_ready_once_callback = callback
@@ -166,7 +166,7 @@ class DiscordPlatformAdapter(Platform):
await self.shutdown_event.wait()
except discord.errors.LoginFailure:
logger.error(
- "[Discord] Login failed. Please check whether the bot token is correct."
+ "[Discord] Login failed. Please check whether the bot token is correct.",
)
except discord.errors.ConnectionClosed:
logger.warning("[Discord] Connection with Discord has been closed.")
@@ -189,7 +189,7 @@ class DiscordPlatformAdapter(Platform):
return MessageType.GROUP_MESSAGE
def _get_channel_id(
- self, channel: Messageable | GuildChannel | PrivateChannel
+ self, channel: Messageable | GuildChannel | PrivateChannel,
) -> str:
"""根据 channel 对象获取ID"""
return str(getattr(channel, "id", None))
@@ -253,7 +253,7 @@ class DiscordPlatformAdapter(Platform):
)
abm.message = message_chain
abm.raw_message = message
- abm.self_id = cast(str, self.bot_self_id)
+ abm.self_id = cast("str", self.bot_self_id)
abm.session_id = str(message.channel.id)
abm.message_id = str(message.id)
return abm
@@ -276,7 +276,7 @@ class DiscordPlatformAdapter(Platform):
if self.client.user is None:
logger.error(
- "[Discord] Client is not ready (self.client.user is None); message handling skipped"
+ "[Discord] Client is not ready (self.client.user is None); message handling skipped",
)
return
@@ -295,7 +295,7 @@ class DiscordPlatformAdapter(Platform):
raw_message = message.raw_message
if not isinstance(raw_message, discord.Message):
logger.warning(
- f"[Discord] Non-Message type received and ignored: {type(raw_message)}"
+ f"[Discord] Non-Message type received and ignored: {type(raw_message)}",
)
return
@@ -351,7 +351,7 @@ class DiscordPlatformAdapter(Platform):
logger.info("[Discord] Commands cleaned up successfully.")
except Exception as e:
logger.warning(
- f"[Discord] Error occurred while cleaning up commands: {e}"
+ f"[Discord] Error occurred while cleaning up commands: {e}",
)
if self._polling_task:
@@ -362,7 +362,7 @@ class DiscordPlatformAdapter(Platform):
logger.info("[Discord] Polling task cancelled successfully.")
except Exception as e:
logger.warning(
- f"[Discord] Error occurred while cancelling polling task: {e}"
+ f"[Discord] Error occurred while cancelling polling task: {e}",
)
logger.info("[Discord] Closing client connection...")
if self.client and hasattr(self.client, "close"):
@@ -433,7 +433,7 @@ class DiscordPlatformAdapter(Platform):
"""为每个指令动态创建一个异步回调函数"""
async def dynamic_callback(
- ctx: discord.ApplicationContext, params: str | None = None
+ ctx: discord.ApplicationContext, params: str | None = None,
) -> None:
# 将平台特定的前缀'/'剥离,以适配通用的CommandFilter
logger.debug(f"[Discord] Callback triggered: {cmd_name}")
@@ -479,7 +479,7 @@ class DiscordPlatformAdapter(Platform):
)
abm.message = [Plain(text=message_str_for_filter)]
abm.raw_message = ctx.interaction
- abm.self_id = cast(str, self.bot_self_id)
+ abm.self_id = cast("str", self.bot_self_id)
abm.session_id = str(ctx.channel_id)
abm.message_id = str(ctx.interaction.id)
diff --git a/astrbot/core/platform/sources/discord/discord_platform_event.py b/astrbot/core/platform/sources/discord/discord_platform_event.py
index 02d4dae86..4b8ea6f9f 100644
--- a/astrbot/core/platform/sources/discord/discord_platform_event.py
+++ b/astrbot/core/platform/sources/discord/discord_platform_event.py
@@ -98,7 +98,7 @@ class DiscordPlatformEvent(AstrMessageEvent):
await super().send(message)
async def send_streaming(
- self, generator: AsyncGenerator[MessageChain, None], use_fallback: bool = False
+ self, generator: AsyncGenerator[MessageChain, None], use_fallback: bool = False,
):
buffer = None
async for chain in generator:
@@ -274,8 +274,8 @@ class DiscordPlatformEvent(AstrMessageEvent):
self.message_obj.raw_message,
"add_reaction",
):
- await cast(discord.Message, self.message_obj.raw_message).add_reaction(
- emoji
+ await cast("discord.Message", self.message_obj.raw_message).add_reaction(
+ emoji,
)
except Exception as e:
logger.error(f"[Discord] 添加反应失败: {e}")
@@ -285,7 +285,7 @@ class DiscordPlatformEvent(AstrMessageEvent):
return (
hasattr(self.message_obj, "raw_message")
and hasattr(self.message_obj.raw_message, "type")
- and cast(discord.Interaction, self.message_obj.raw_message).type
+ and cast("discord.Interaction", self.message_obj.raw_message).type
== discord.InteractionType.application_command
)
@@ -294,7 +294,7 @@ class DiscordPlatformEvent(AstrMessageEvent):
return (
hasattr(self.message_obj, "raw_message")
and hasattr(self.message_obj.raw_message, "type")
- and cast(discord.Interaction, self.message_obj.raw_message).type
+ and cast("discord.Interaction", self.message_obj.raw_message).type
== discord.InteractionType.component
)
@@ -303,8 +303,8 @@ class DiscordPlatformEvent(AstrMessageEvent):
if self.is_button_interaction():
try:
return cast(
- ComponentInteractionData,
- cast(discord.Interaction, self.message_obj.raw_message).data,
+ "ComponentInteractionData",
+ cast("discord.Interaction", self.message_obj.raw_message).data,
).get("custom_id", "")
except Exception:
pass
@@ -319,7 +319,7 @@ class DiscordPlatformEvent(AstrMessageEvent):
return any(
mention.id == int(self.message_obj.self_id)
for mention in cast(
- discord.Message, self.message_obj.raw_message
+ "discord.Message", self.message_obj.raw_message,
).mentions
)
return False
@@ -330,5 +330,5 @@ class DiscordPlatformEvent(AstrMessageEvent):
self.message_obj.raw_message,
"clean_content",
):
- return cast(discord.Message, self.message_obj.raw_message).clean_content
+ return cast("discord.Message", self.message_obj.raw_message).clean_content
return self.message_str
diff --git a/astrbot/core/platform/sources/kook/kook_adapter.py b/astrbot/core/platform/sources/kook/kook_adapter.py
index 7095d7447..010d0d97e 100644
--- a/astrbot/core/platform/sources/kook/kook_adapter.py
+++ b/astrbot/core/platform/sources/kook/kook_adapter.py
@@ -43,7 +43,7 @@ KOOK_AT_SELECTOR_REGEX = re.compile(r"\(met\)([^()]+)\(met\)")
)
class KookPlatformAdapter(Platform):
def __init__(
- self, platform_config: dict, platform_settings: dict, event_queue: asyncio.Queue
+ self, platform_config: dict, platform_settings: dict, event_queue: asyncio.Queue,
) -> None:
super().__init__(platform_config, event_queue)
self.kook_config = KookConfig.from_dict(platform_config)
@@ -55,7 +55,7 @@ class KookPlatformAdapter(Platform):
self._main_task = None
async def send_by_session(
- self, session: MessageSesion, message_chain: MessageChain
+ self, session: MessageSesion, message_chain: MessageChain,
):
inner_message = AstrBotMessage()
inner_message.session_id = session.session_id
@@ -71,7 +71,7 @@ class KookPlatformAdapter(Platform):
def meta(self) -> PlatformMetadata:
return PlatformMetadata(
- name="kook", description="KOOK 适配器", id=self.kook_config.id
+ name="kook", description="KOOK 适配器", id=self.kook_config.id,
)
def _should_ignore_event_by_bot_nickname(self, author_id: str) -> bool:
@@ -79,7 +79,7 @@ class KookPlatformAdapter(Platform):
async def _on_received(self, event: KookMessageEventData):
logger.debug(
- f'[KOOK] 收到来自"{event.channel_type.name}"渠道的消息, 消息类型为: {event.type.name}({event.type.value})'
+ f'[KOOK] 收到来自"{event.channel_type.name}"渠道的消息, 消息类型为: {event.type.name}({event.type.value})',
)
event_type = event.type
if event_type in (KookMessageType.KMARKDOWN, KookMessageType.CARD):
@@ -136,7 +136,7 @@ class KookPlatformAdapter(Platform):
# 等待 client 内部触发 _stop_event,或者超时 1 秒后重试
# 使用 wait_for 配合 timeout 是为了防止极端情况下 self.running 变化没被察觉
await asyncio.wait_for(
- self.client.wait_until_closed(), timeout=1.0
+ self.client.wait_until_closed(), timeout=1.0,
)
except asyncio.TimeoutError:
# 正常超时,继续下一轮 while 检查
@@ -148,7 +148,7 @@ class KookPlatformAdapter(Platform):
else:
consecutive_failures += 1
logger.error(
- f"[KOOK] 连接失败,连续失败次数: {consecutive_failures}"
+ f"[KOOK] 连接失败,连续失败次数: {consecutive_failures}",
)
if consecutive_failures >= max_consecutive_failures:
@@ -157,7 +157,7 @@ class KookPlatformAdapter(Platform):
# 等待一段时间后重试
wait_time = min(
- 2**consecutive_failures, max_retry_delay
+ 2**consecutive_failures, max_retry_delay,
) # 指数退避
logger.info(f"[KOOK] 等待 {wait_time} 秒后重试...")
await asyncio.sleep(wait_time)
@@ -192,13 +192,13 @@ class KookPlatformAdapter(Platform):
logger.info("[KOOK] 资源清理完成")
def _parse_kmarkdown_text_message(
- self, data: KookMessageEventData, self_id: str
+ self, data: KookMessageEventData, self_id: str,
) -> tuple[list, str]:
kmarkdown = data.extra.kmarkdown
content = data.content or ""
if kmarkdown is None:
logger.error(
- f'[KOOK] 无法转换"{KookMessageType.KMARKDOWN.name}"消息, 消息中找不到kmarkdown字段'
+ f'[KOOK] 无法转换"{KookMessageType.KMARKDOWN.name}"消息, 消息中找不到kmarkdown字段',
)
logger.error(f"[KOOK] 原始消息内容: {data.to_json()}")
return [], ""
@@ -237,7 +237,7 @@ class KookPlatformAdapter(Platform):
At(
qq=mention_target,
name=mention_name_map.get(mention_target, ""),
- )
+ ),
)
cursor = match.end()
@@ -341,7 +341,7 @@ class KookPlatformAdapter(Platform):
return ""
def _handle_image_group(
- self, module: ContainerModule | ImageGroupModule
+ self, module: ContainerModule | ImageGroupModule,
) -> list[str]:
"""专门处理图片组/容器里的合法 URL 提取"""
valid_urls = []
diff --git a/astrbot/core/platform/sources/kook/kook_client.py b/astrbot/core/platform/sources/kook/kook_client.py
index 32874f78a..b9213b80b 100644
--- a/astrbot/core/platform/sources/kook/kook_client.py
+++ b/astrbot/core/platform/sources/kook/kook_client.py
@@ -39,7 +39,7 @@ class KookClient:
self._http_client = aiohttp.ClientSession(
headers={
"Authorization": f"Bot {self.config.token}",
- }
+ },
)
self.event_callback = event_callback # 回调函数,用于处理接收到的事件
self.ws = None
@@ -73,21 +73,21 @@ class KookClient:
async with self._http_client.get(url) as resp:
if resp.status != 200:
logger.error(
- f"[KOOK] 获取机器人账号信息失败,状态码: {resp.status} , {await resp.text()}"
+ f"[KOOK] 获取机器人账号信息失败,状态码: {resp.status} , {await resp.text()}",
)
return
try:
resp_content = KookUserMeResponse.from_dict(await resp.json())
except pydantic.ValidationError as e:
logger.error(
- f"[KOOK] 获取机器人账号信息失败, 响应数据格式错误: \n{e}"
+ f"[KOOK] 获取机器人账号信息失败, 响应数据格式错误: \n{e}",
)
logger.error(f"[KOOK] 响应内容: {await resp.text()}")
return
if not resp_content.success():
logger.error(
- f"[KOOK] 获取机器人账号信息失败: {resp_content.model_dump_json()}"
+ f"[KOOK] 获取机器人账号信息失败: {resp_content.model_dump_json()}",
)
return
@@ -125,7 +125,7 @@ class KookClient:
return None
gateway_url: str = resp_content.data.url
- logger.info(f"[KOOK] 获取gateway成功: {gateway_url.split('?')[0]}")
+ logger.info(f"[KOOK] 获取gateway成功: {gateway_url.split('?', maxsplit=1)[0]}")
return gateway_url
except pydantic.ValidationError as e:
@@ -149,7 +149,7 @@ class KookClient:
try:
# 获取gateway地址
gateway_url = await self.get_gateway_url(
- resume=resume, sn=self.last_sn, session_id=self.session_id
+ resume=resume, sn=self.last_sn, session_id=self.session_id,
)
await self.get_bot_info()
@@ -251,7 +251,7 @@ class KookClient:
case _:
logger.debug(
- f"[KOOK] 未处理的信令类型: {event.signal.name}({event.signal.value})"
+ f"[KOOK] 未处理的信令类型: {event.signal.name}({event.signal.value})",
)
async def _handle_hello(self, data: KookHelloEventData):
@@ -293,7 +293,7 @@ class KookClient:
try:
# 随机化心跳间隔 (±5秒)
interval = max(
- 1, self.config.heartbeat_interval + random.randint(-5, 5)
+ 1, self.config.heartbeat_interval + random.randint(-5, 5),
)
await asyncio.sleep(interval)
@@ -313,7 +313,7 @@ class KookClient:
):
self.heartbeat_failed_count += 1
logger.warning(
- f"[KOOK] 心跳超时,失败次数: {self.heartbeat_failed_count}"
+ f"[KOOK] 心跳超时,失败次数: {self.heartbeat_failed_count}",
)
if (
@@ -376,19 +376,19 @@ class KookClient:
result = await resp.json()
if result.get("code") != 0:
raise RuntimeError(
- f'发送kook消息类型 "{kook_message_type.name}" 失败: {result}'
+ f'发送kook消息类型 "{kook_message_type.name}" 失败: {result}',
)
# else:
# logger.info("[KOOK] 发送消息成功")
else:
raise RuntimeError(
- f'发送kook消息类型 "{kook_message_type.name}" HTTP错误: {resp.status} , 响应内容 : {await resp.text()}'
+ f'发送kook消息类型 "{kook_message_type.name}" HTTP错误: {resp.status} , 响应内容 : {await resp.text()}',
)
except RuntimeError:
raise
except Exception as e:
logger.error(
- f'[KOOK] 发送kook消息类型 "{kook_message_type.name}" 异常: {e}'
+ f'[KOOK] 发送kook消息类型 "{kook_message_type.name}" 异常: {e}',
)
async def upload_asset(self, file_url: str | None) -> str:
@@ -418,7 +418,7 @@ class KookClient:
except Exception as exp:
logger.error(f'[KOOK] 获取文件 "{file_url}" 绝对路径失败: "{exp}"')
raise FileNotFoundError(
- f'获取文件 "{file_url}" 绝对路径失败: "{exp}"'
+ f'获取文件 "{file_url}" 绝对路径失败: "{exp}"',
) from exp
if not target_path.is_file():
@@ -445,12 +445,10 @@ class KookClient:
remote_url = result["data"]["url"]
logger.debug(f"[KOOK] 文件远端URL: {remote_url}")
return remote_url
- else:
- raise RuntimeError(f"上传文件到kook服务器失败: {result}")
- else:
- raise RuntimeError(
- f"上传文件到kook服务器 HTTP错误: {resp.status} , {await resp.text()}"
- )
+ raise RuntimeError(f"上传文件到kook服务器失败: {result}")
+ raise RuntimeError(
+ f"上传文件到kook服务器 HTTP错误: {resp.status} , {await resp.text()}",
+ )
except RuntimeError:
raise
except Exception as e:
diff --git a/astrbot/core/platform/sources/kook/kook_event.py b/astrbot/core/platform/sources/kook/kook_event.py
index 884d066d8..bbe757eb5 100644
--- a/astrbot/core/platform/sources/kook/kook_event.py
+++ b/astrbot/core/platform/sources/kook/kook_event.py
@@ -48,10 +48,10 @@ class KookEvent(AstrMessageEvent):
self._file_message_counter = 0
def _wrap_message(
- self, index: int, message_component: BaseMessageComponent
+ self, index: int, message_component: BaseMessageComponent,
) -> Coroutine[Any, Any, OrderMessage]:
async def wrap_upload(
- index: int, message_type: KookMessageType, upload_coro
+ index: int, message_type: KookMessageType, upload_coro,
) -> OrderMessage:
url = await upload_coro
return OrderMessage(index=index, text=url, type=message_type)
@@ -93,7 +93,7 @@ class KookEvent(AstrMessageEvent):
f_data = await f_item.get_file()
url = await self.client.upload_asset(f_data)
return OrderMessage(
- index=index, text=url, type=KookMessageType.FILE
+ index=index, text=url, type=KookMessageType.FILE,
)
self._file_message_counter += 1
@@ -115,10 +115,10 @@ class KookEvent(AstrMessageEvent):
type=KookModuleType.AUDIO,
title=title,
src=url,
- )
- ]
- )
- ]
+ ),
+ ],
+ ),
+ ],
).to_json(),
type=KookMessageType.CARD,
)
@@ -147,7 +147,7 @@ class KookEvent(AstrMessageEvent):
)
case _:
raise NotImplementedError(
- f'kook适配器尚未实现对 "{message_component.type}" 消息类型的支持'
+ f'kook适配器尚未实现对 "{message_component.type}" 消息类型的支持',
)
async def send(self, message: MessageChain):
diff --git a/astrbot/core/platform/sources/kook/kook_types.py b/astrbot/core/platform/sources/kook/kook_types.py
index 5efaf2a14..d7a7c9fc9 100644
--- a/astrbot/core/platform/sources/kook/kook_types.py
+++ b/astrbot/core/platform/sources/kook/kook_types.py
@@ -57,7 +57,7 @@ class KookModuleType(str, Enum):
ThemeType = Literal[
- "primary", "success", "danger", "warning", "info", "secondary", "none", "invisible"
+ "primary", "success", "danger", "warning", "info", "secondary", "none", "invisible",
]
"""主题,可选的值为:primary, success, danger, warning, info, secondary, none.默认为 primary,为 none 时不显示侧边框。"""
SizeType = Literal["xs", "sm", "md", "lg"]
@@ -276,7 +276,7 @@ class KookCardMessageContainer(list[KookCardMessage]):
def to_json(self, indent: int | None = None, ensure_ascii: bool = True) -> str:
return json.dumps(
- [i.to_dict() for i in self], indent=indent, ensure_ascii=ensure_ascii
+ [i.to_dict() for i in self], indent=indent, ensure_ascii=ensure_ascii,
)
@classmethod
@@ -293,7 +293,8 @@ class OrderMessage(BaseModel):
class KookMessageSignal(IntEnum):
"""KOOK WebSocket 信令类型
- ws文档: https://developer.kookapp.cn/doc/websocket""" # noqa: W291
+ ws文档: https://developer.kookapp.cn/doc/websocket
+ """
MESSAGE = 0
"""server->client 消息(s包含聊天和通知消息)"""
@@ -350,7 +351,7 @@ class KookExtra(KookBaseDataClass):
class KookMessageEventData(KookBaseDataClass):
signal: Literal[KookMessageSignal.MESSAGE] = Field(
- KookMessageSignal.MESSAGE, exclude=True
+ KookMessageSignal.MESSAGE, exclude=True,
)
"""only for type hint"""
@@ -368,7 +369,7 @@ class KookMessageEventData(KookBaseDataClass):
class KookHelloEventData(KookBaseDataClass):
signal: Literal[KookMessageSignal.HELLO] = Field(
- KookMessageSignal.HELLO, exclude=True
+ KookMessageSignal.HELLO, exclude=True,
)
"""only for type hint"""
@@ -378,28 +379,28 @@ class KookHelloEventData(KookBaseDataClass):
class KookPingEventData(KookBaseDataClass):
signal: Literal[KookMessageSignal.PING] = Field(
- KookMessageSignal.PING, exclude=True
+ KookMessageSignal.PING, exclude=True,
)
"""only for type hint"""
class KookPongEventData(KookBaseDataClass):
signal: Literal[KookMessageSignal.PONG] = Field(
- KookMessageSignal.PONG, exclude=True
+ KookMessageSignal.PONG, exclude=True,
)
"""only for type hint"""
class KookResumeEventData(KookBaseDataClass):
signal: Literal[KookMessageSignal.RESUME] = Field(
- KookMessageSignal.RESUME, exclude=True
+ KookMessageSignal.RESUME, exclude=True,
)
"""only for type hint"""
class KookReconnectEventData(KookBaseDataClass):
signal: Literal[KookMessageSignal.RECONNECT] = Field(
- KookMessageSignal.RECONNECT, exclude=True
+ KookMessageSignal.RECONNECT, exclude=True,
)
"""only for type hint"""
@@ -409,7 +410,7 @@ class KookReconnectEventData(KookBaseDataClass):
class KookResumeAckEventData(KookBaseDataClass):
signal: Literal[KookMessageSignal.RESUME_ACK] = Field(
- KookMessageSignal.RESUME_ACK, exclude=True
+ KookMessageSignal.RESUME_ACK, exclude=True,
)
"""only for type hint"""
@@ -420,7 +421,7 @@ class KookWebsocketEvent(KookBaseDataClass):
"""KOOK WebSocket 原始推送结构"""
signal: KookMessageSignal = Field(
- ..., validation_alias="s", serialization_alias="s"
+ ..., validation_alias="s", serialization_alias="s",
)
"""信令类型"""
data: Annotated[
diff --git a/astrbot/core/platform/sources/lark/lark_adapter.py b/astrbot/core/platform/sources/lark/lark_adapter.py
index 60e8e0d93..15bb93a2f 100644
--- a/astrbot/core/platform/sources/lark/lark_adapter.py
+++ b/astrbot/core/platform/sources/lark/lark_adapter.py
@@ -34,7 +34,7 @@ from .server import LarkWebhookServer
@register_platform_adapter(
- "lark", "飞书机器人官方 API 适配器", support_streaming_message=True
+ "lark", "飞书机器人官方 API 适配器", support_streaming_message=True,
)
class LarkPlatformAdapter(Platform):
def __init__(
@@ -455,6 +455,7 @@ class LarkPlatformAdapter(Platform):
Returns:
True 表示重复事件,False 表示新事件
+
"""
self._clean_expired_events()
if event_id in self.event_id_timestamps:
@@ -490,7 +491,7 @@ class LarkPlatformAdapter(Platform):
return PlatformMetadata(
name="lark",
description="飞书机器人官方 API 适配器",
- id=cast(str, self.config.get("id")),
+ id=cast("str", self.config.get("id")),
support_streaming_message=True,
)
@@ -531,7 +532,7 @@ class LarkPlatformAdapter(Platform):
if m.id is None:
continue
# 飞书 open_id 可能是 None,这里做个防护
- open_id = m.id.open_id if m.id.open_id else ""
+ open_id = m.id.open_id or ""
at_list[m.key] = Comp.At(qq=open_id, name=m.name)
if m.name == self.bot_name:
@@ -603,6 +604,7 @@ class LarkPlatformAdapter(Platform):
Args:
event_data: Webhook 事件数据
+
"""
try:
header = event_data.get("header", {})
@@ -654,5 +656,5 @@ class LarkPlatformAdapter(Platform):
def unified_webhook(self) -> bool:
return bool(
self.config.get("lark_connection_mode", "") == "webhook"
- and self.config.get("webhook_uuid")
+ and self.config.get("webhook_uuid"),
)
diff --git a/astrbot/core/platform/sources/lark/lark_event.py b/astrbot/core/platform/sources/lark/lark_event.py
index 1c7dd0b43..f10ca7cc6 100644
--- a/astrbot/core/platform/sources/lark/lark_event.py
+++ b/astrbot/core/platform/sources/lark/lark_event.py
@@ -74,6 +74,7 @@ class LarkMessageEvent(AstrMessageEvent):
Returns:
是否发送成功
+
"""
if lark_client.im is None:
logger.error("[Lark] API Client im 模块未初始化")
@@ -89,7 +90,7 @@ class LarkMessageEvent(AstrMessageEvent):
.msg_type(msg_type)
.uuid(str(uuid.uuid4()))
.reply_in_thread(False)
- .build()
+ .build(),
)
.build()
)
@@ -115,7 +116,7 @@ class LarkMessageEvent(AstrMessageEvent):
.content(content)
.msg_type(msg_type)
.uuid(str(uuid.uuid4()))
- .build()
+ .build(),
)
.build()
)
@@ -145,6 +146,7 @@ class LarkMessageEvent(AstrMessageEvent):
Returns:
成功返回file_key,失败返回None
+
"""
if not path or not os.path.exists(path):
logger.error(f"[Lark] 文件不存在: {path}")
@@ -174,7 +176,7 @@ class LarkMessageEvent(AstrMessageEvent):
if not response.success():
logger.error(
- f"[Lark] 无法上传文件({response.code}): {response.msg}"
+ f"[Lark] 无法上传文件({response.code}): {response.msg}",
)
return None
@@ -207,7 +209,7 @@ class LarkMessageEvent(AstrMessageEvent):
file_path = comp.file.replace("file:///", "")
elif comp.file and comp.file.startswith("http"):
image_file_path = await download_image_by_url(comp.file)
- file_path = image_file_path if image_file_path else ""
+ file_path = image_file_path or ""
elif comp.file and comp.file.startswith("base64://"):
base64_str = comp.file.removeprefix("base64://")
image_data = base64.b64decode(base64_str)
@@ -220,7 +222,7 @@ class LarkMessageEvent(AstrMessageEvent):
with open(file_path, "wb") as f:
f.write(BytesIO(image_data).getvalue())
else:
- file_path = comp.file if comp.file else ""
+ file_path = comp.file or ""
if image_file is None:
if not file_path:
@@ -307,7 +309,7 @@ class LarkMessageEvent(AstrMessageEvent):
{
"tag": "markdown",
"content": reasoning_content,
- }
+ },
],
}
@@ -321,8 +323,8 @@ class LarkMessageEvent(AstrMessageEvent):
reasoning_content=reasoning_content,
title=title,
expanded=False,
- )
- ]
+ ),
+ ],
},
}
@@ -341,7 +343,7 @@ class LarkMessageEvent(AstrMessageEvent):
reasoning_content=reasoning_content,
title=str(comp.data.get("title", "💭 Thinking")),
expanded=bool(comp.data.get("expanded", False)),
- )
+ ),
)
elif isinstance(comp, Plain):
if comp.text:
@@ -379,9 +381,9 @@ class LarkMessageEvent(AstrMessageEvent):
CreateCardRequestBody.builder()
.type("card_json")
.data(json.dumps(card_json, ensure_ascii=False))
- .build()
+ .build(),
)
- .build()
+ .build(),
)
except Exception as e:
logger.error(f"[Lark] 创建卡片失败: {e}")
@@ -446,6 +448,7 @@ class LarkMessageEvent(AstrMessageEvent):
reply_message_id: 回复的消息ID(用于回复消息)
receive_id: 接收者ID(用于主动发送)
receive_id_type: 接收者ID类型,如 'open_id', 'chat_id'(用于主动发送)
+
"""
if lark_client.im is None:
logger.error("[Lark] API Client im 模块未初始化")
@@ -554,17 +557,17 @@ class LarkMessageEvent(AstrMessageEvent):
# 发送附件
for file_comp in file_components:
await LarkMessageEvent._send_file_message(
- file_comp, lark_client, reply_message_id, receive_id, receive_id_type
+ file_comp, lark_client, reply_message_id, receive_id, receive_id_type,
)
for audio_comp in audio_components:
await LarkMessageEvent._send_audio_message(
- audio_comp, lark_client, reply_message_id, receive_id, receive_id_type
+ audio_comp, lark_client, reply_message_id, receive_id, receive_id_type,
)
for media_comp in media_components:
await LarkMessageEvent._send_media_message(
- media_comp, lark_client, reply_message_id, receive_id, receive_id_type
+ media_comp, lark_client, reply_message_id, receive_id, receive_id_type,
)
async def send(self, message: MessageChain) -> None:
@@ -592,10 +595,11 @@ class LarkMessageEvent(AstrMessageEvent):
reply_message_id: 回复的消息ID(用于回复消息)
receive_id: 接收者ID(用于主动发送)
receive_id_type: 接收者ID类型(用于主动发送)
+
"""
file_path = file_comp.file or ""
file_key = await LarkMessageEvent._upload_lark_file(
- lark_client, path=file_path, file_type="stream"
+ lark_client, path=file_path, file_type="stream",
)
if not file_key:
return
@@ -626,6 +630,7 @@ class LarkMessageEvent(AstrMessageEvent):
reply_message_id: 回复的消息ID(用于回复消息)
receive_id: 接收者ID(用于主动发送)
receive_id_type: 接收者ID类型(用于主动发送)
+
"""
# 获取音频文件路径
try:
@@ -699,6 +704,7 @@ class LarkMessageEvent(AstrMessageEvent):
reply_message_id: 回复的消息ID(用于回复消息)
receive_id: 接收者ID(用于主动发送)
receive_id_type: 接收者ID类型(用于主动发送)
+
"""
# 获取视频文件路径
try:
@@ -803,8 +809,8 @@ class LarkMessageEvent(AstrMessageEvent):
"tag": "markdown",
"content": "",
"element_id": "markdown_1",
- }
- ]
+ },
+ ],
},
}
@@ -814,7 +820,7 @@ class LarkMessageEvent(AstrMessageEvent):
CreateCardRequestBody.builder()
.type("card_json")
.data(json.dumps(card_json, ensure_ascii=False))
- .build()
+ .build(),
)
.build()
)
@@ -827,7 +833,7 @@ class LarkMessageEvent(AstrMessageEvent):
if not response.success():
logger.error(
- f"[Lark] 创建流式卡片实体失败({response.code}): {response.msg}"
+ f"[Lark] 创建流式卡片实体失败({response.code}): {response.msg}",
)
return None
@@ -880,7 +886,7 @@ class LarkMessageEvent(AstrMessageEvent):
.content(content)
.sequence(sequence)
.uuid(str(uuid.uuid4()))
- .build()
+ .build(),
)
.build()
)
@@ -920,7 +926,7 @@ class LarkMessageEvent(AstrMessageEvent):
.settings(settings_json)
.sequence(sequence)
.uuid(str(uuid.uuid4()))
- .build()
+ .build(),
)
.build()
)
@@ -1046,7 +1052,7 @@ class LarkMessageEvent(AstrMessageEvent):
card_id = await self._create_streaming_card()
if not card_id:
logger.warning(
- "[Lark] 无法创建流式卡片,回退到非流式发送"
+ "[Lark] 无法创建流式卡片,回退到非流式发送",
)
await _consume_rest_and_fallback(generator, delta)
return
@@ -1057,7 +1063,7 @@ class LarkMessageEvent(AstrMessageEvent):
)
if not sent:
logger.error(
- "[Lark] 发送流式卡片消息失败,回退到非流式发送"
+ "[Lark] 发送流式卡片消息失败,回退到非流式发送",
)
await _consume_rest_and_fallback(generator, delta)
return
@@ -1076,7 +1082,7 @@ class LarkMessageEvent(AstrMessageEvent):
if card_id is None:
if not fallback_used:
await Metric.upload(
- msg_event_tick=1, adapter_name=self.platform_meta.name
+ msg_event_tick=1, adapter_name=self.platform_meta.name,
)
self._has_send_oper = True
return
diff --git a/astrbot/core/platform/sources/lark/server.py b/astrbot/core/platform/sources/lark/server.py
index 52177ebb0..a81a92893 100644
--- a/astrbot/core/platform/sources/lark/server.py
+++ b/astrbot/core/platform/sources/lark/server.py
@@ -58,6 +58,7 @@ class LarkWebhookServer:
Args:
config: 飞书配置
event_queue: 事件队列
+
"""
self.app_id = config["app_id"]
self.app_secret = config["app_secret"]
@@ -91,6 +92,7 @@ class LarkWebhookServer:
Returns:
签名是否有效
+
"""
# 拼接字符串: timestamp + nonce + encrypt_key + body
bytes_b1 = (timestamp + nonce + encrypt_key).encode("utf-8")
@@ -107,6 +109,7 @@ class LarkWebhookServer:
Returns:
解密后的事件字典
+
"""
if not self.cipher:
raise ValueError("未配置 encrypt_key,无法解密事件")
@@ -122,6 +125,7 @@ class LarkWebhookServer:
Returns:
包含 challenge 的响应
+
"""
challenge = event_data.get("challenge", "")
logger.info(f"[Lark Webhook] 收到 challenge 验证请求: {challenge}")
@@ -136,6 +140,7 @@ class LarkWebhookServer:
Returns:
响应数据
+
"""
# 获取原始请求体
body = await request.get_data()
@@ -158,7 +163,7 @@ class LarkWebhookServer:
if timestamp and nonce and signature:
if not self.verify_signature(
- timestamp, nonce, self.encrypt_key, body, signature
+ timestamp, nonce, self.encrypt_key, body, signature,
):
logger.error("[Lark Webhook] 签名验证失败")
return {"error": "Invalid signature"}, 401
@@ -202,5 +207,6 @@ class LarkWebhookServer:
Args:
callback: 处理事件的异步函数
+
"""
self.callback = callback
diff --git a/astrbot/core/platform/sources/line/line_adapter.py b/astrbot/core/platform/sources/line/line_adapter.py
index c13677b13..bf9d47da1 100644
--- a/astrbot/core/platform/sources/line/line_adapter.py
+++ b/astrbot/core/platform/sources/line/line_adapter.py
@@ -108,7 +108,7 @@ class LinePlatformAdapter(Platform):
return PlatformMetadata(
name="line",
description="LINE Messaging API 适配器",
- id=cast(str, self.config.get("id", "line")),
+ id=cast("str", self.config.get("id", "line")),
support_streaming_message=False,
)
@@ -193,7 +193,7 @@ class LinePlatformAdapter(Platform):
message.get("id")
or event.get("webhookEventId")
or event.get("deliveryContext", {}).get("deliveryId", "")
- or uuid.uuid4().hex
+ or uuid.uuid4().hex,
)
event_timestamp = event.get("timestamp")
diff --git a/astrbot/core/platform/sources/line/line_event.py b/astrbot/core/platform/sources/line/line_event.py
index 8b82ad182..1b5766d99 100644
--- a/astrbot/core/platform/sources/line/line_event.py
+++ b/astrbot/core/platform/sources/line/line_event.py
@@ -220,7 +220,7 @@ class LineMessageEvent(AstrMessageEvent):
if len(messages) > 5:
logger.warning(
- "[LINE] message count exceeds 5, extra segments will be dropped."
+ "[LINE] message count exceeds 5, extra segments will be dropped.",
)
messages = messages[:5]
return messages
diff --git a/astrbot/core/platform/sources/mattermost/client.py b/astrbot/core/platform/sources/mattermost/client.py
index 88619e215..1e3a1744d 100644
--- a/astrbot/core/platform/sources/mattermost/client.py
+++ b/astrbot/core/platform/sources/mattermost/client.py
@@ -45,7 +45,7 @@ class MattermostClient:
if resp.status >= 400:
body = await resp.text()
raise RuntimeError(
- f"Mattermost GET {path} failed: {resp.status} {body}"
+ f"Mattermost GET {path} failed: {resp.status} {body}",
)
data = await resp.json()
if not isinstance(data, dict):
@@ -59,7 +59,7 @@ class MattermostClient:
if resp.status >= 400:
body = await resp.text()
raise RuntimeError(
- f"Mattermost POST {path} failed: {resp.status} {body}"
+ f"Mattermost POST {path} failed: {resp.status} {body}",
)
data = await resp.json()
if not isinstance(data, dict):
@@ -82,7 +82,7 @@ class MattermostClient:
if resp.status >= 400:
body = await resp.text()
raise RuntimeError(
- f"Mattermost download file {file_id} failed: {resp.status} {body}"
+ f"Mattermost download file {file_id} failed: {resp.status} {body}",
)
return await resp.read()
@@ -107,7 +107,7 @@ class MattermostClient:
if resp.status >= 400:
body = await resp.text()
raise RuntimeError(
- f"Mattermost upload file failed: {resp.status} {body}"
+ f"Mattermost upload file failed: {resp.status} {body}",
)
data = await resp.json()
file_infos = data.get("file_infos", [])
@@ -139,7 +139,7 @@ class MattermostClient:
async def ws_connect(self) -> aiohttp.ClientWebSocketResponse:
session = await self.ensure_session()
ws_url = self.base_url.replace("https://", "wss://", 1).replace(
- "http://", "ws://", 1
+ "http://", "ws://", 1,
)
ws_url = f"{ws_url}/api/v4/websocket"
return await session.ws_connect(ws_url, heartbeat=30.0)
@@ -173,7 +173,7 @@ class MattermostClient:
file_bytes,
file_path.name,
mimetypes.guess_type(file_path.name)[0] or "image/jpeg",
- )
+ ),
)
elif isinstance(segment, (File, Record, Video)):
if isinstance(segment, File):
@@ -190,7 +190,7 @@ class MattermostClient:
file_bytes,
filename,
mimetypes.guess_type(filename)[0] or "application/octet-stream",
- )
+ ),
)
else:
logger.debug(
@@ -218,7 +218,7 @@ class MattermostClient:
file_bytes = await self.download_file(file_id)
except Exception as exc:
logger.warning(
- "Mattermost fetch attachment failed %s: %s", file_id, exc
+ "Mattermost fetch attachment failed %s: %s", file_id, exc,
)
continue
diff --git a/astrbot/core/platform/sources/mattermost/mattermost_adapter.py b/astrbot/core/platform/sources/mattermost/mattermost_adapter.py
index 583e0d0af..85ba2df91 100644
--- a/astrbot/core/platform/sources/mattermost/mattermost_adapter.py
+++ b/astrbot/core/platform/sources/mattermost/mattermost_adapter.py
@@ -41,7 +41,7 @@ class MattermostPlatformAdapter(Platform):
self.base_url = str(platform_config.get("mattermost_url", "")).rstrip("/")
self.bot_token = str(platform_config.get("mattermost_bot_token", "")).strip()
self.reconnect_delay = float(
- platform_config.get("mattermost_reconnect_delay", 5.0)
+ platform_config.get("mattermost_reconnect_delay", 5.0),
)
if not self.base_url:
@@ -53,7 +53,7 @@ class MattermostPlatformAdapter(Platform):
self.metadata = PlatformMetadata(
name="mattermost",
description="Mattermost 平台适配器",
- id=cast(str, self.config.get("id", "mattermost")),
+ id=cast("str", self.config.get("id", "mattermost")),
support_streaming_message=False,
)
self.bot_self_id = ""
@@ -112,7 +112,7 @@ class MattermostPlatformAdapter(Platform):
"seq": 1,
"action": "authentication_challenge",
"data": {"token": self.bot_token},
- }
+ },
)
async for message in ws:
@@ -229,7 +229,7 @@ class MattermostPlatformAdapter(Platform):
temp_paths,
) = await self.client.parse_post_attachments(file_ids)
abm.message.extend(attachment_components)
- setattr(abm, "temporary_file_paths", temp_paths)
+ abm.temporary_file_paths = temp_paths
abm.message_str = self._build_message_str(
abm.message,
diff --git a/astrbot/core/platform/sources/mattermost/mattermost_event.py b/astrbot/core/platform/sources/mattermost/mattermost_event.py
index 5faaf7134..1d67c3d92 100644
--- a/astrbot/core/platform/sources/mattermost/mattermost_event.py
+++ b/astrbot/core/platform/sources/mattermost/mattermost_event.py
@@ -44,10 +44,10 @@ class MattermostMessageEvent(AstrMessageEvent):
else:
message_buffer.chain.extend(chain.chain)
if not message_buffer:
- return None
+ return
message_buffer.squash_plain()
await self.send(message_buffer)
- return None
+ return
text_buffer = ""
@@ -67,7 +67,7 @@ class MattermostMessageEvent(AstrMessageEvent):
if text_buffer.strip():
await self.send(MessageChain([Plain(text_buffer)]))
- return None
+ return
async def get_group(self, group_id=None, **kwargs):
channel_id = group_id or self.get_group_id()
@@ -83,6 +83,6 @@ class MattermostMessageEvent(AstrMessageEvent):
MessageMember(
user_id=self.get_sender_id(),
nickname=self.get_sender_name(),
- )
+ ),
],
)
diff --git a/astrbot/core/platform/sources/misskey/misskey_adapter.py b/astrbot/core/platform/sources/misskey/misskey_adapter.py
index 1692c251c..c6ee19fb7 100644
--- a/astrbot/core/platform/sources/misskey/misskey_adapter.py
+++ b/astrbot/core/platform/sources/misskey/misskey_adapter.py
@@ -45,7 +45,7 @@ DEFAULT_UPLOAD_CONCURRENCY = 3
@register_platform_adapter(
- "misskey", "Misskey 平台适配器", support_streaming_message=False
+ "misskey", "Misskey 平台适配器", support_streaming_message=False,
)
class MisskeyPlatformAdapter(Platform):
def __init__(
@@ -703,7 +703,7 @@ class MisskeyPlatformAdapter(Platform):
files = raw_data.get("files", [])
process_files(message, files, include_text_parts=False)
- message.message_str = raw_text if raw_text else ""
+ message.message_str = raw_text or ""
return message
async def convert_room_message(self, raw_data: dict[str, Any]) -> AstrBotMessage:
diff --git a/astrbot/core/platform/sources/misskey/misskey_api.py b/astrbot/core/platform/sources/misskey/misskey_api.py
index 3e5eb9a90..6b6cb3df2 100644
--- a/astrbot/core/platform/sources/misskey/misskey_api.py
+++ b/astrbot/core/platform/sources/misskey/misskey_api.py
@@ -550,7 +550,7 @@ class MisskeyAPI:
form.add_field("i", self.access_token)
try:
- filename = name or file_path.split("/")[-1]
+ filename = name or file_path.rsplit("/", maxsplit=1)[-1]
if folder_id:
form.add_field("folderId", str(folder_id))
diff --git a/astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py b/astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py
index 97b2b2fb4..9ed31888a 100644
--- a/astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py
+++ b/astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py
@@ -32,12 +32,11 @@ def _patch_qq_botpy_formdata() -> None:
aiohttp.FormData to have a private flag named _is_processed, which is no
longer present in newer aiohttp versions.
"""
-
try:
from botpy.http import _FormData # type: ignore
if not hasattr(_FormData, "_is_processed"):
- setattr(_FormData, "_is_processed", False)
+ _FormData._is_processed = False
except Exception:
logger.debug("[QQOfficial] Skip botpy FormData patch.")
@@ -123,7 +122,7 @@ class QQOfficialMessageEvent(AstrMessageEvent):
current_time = asyncio.get_running_loop().time()
if current_time - last_edit_time >= throttle_interval:
ret = cast(
- message.Message,
+ "message.Message",
await self._post_send(stream=stream_payload),
)
stream_payload["index"] += 1
@@ -146,7 +145,6 @@ class QQOfficialMessageEvent(AstrMessageEvent):
# 如需兜底,应该只发送未发送 delta(后续可继续优化)
self.send_buffer = None
- return None
@staticmethod
def _extract_response_message_id(ret) -> str | None:
@@ -407,16 +405,16 @@ class QQOfficialMessageEvent(AstrMessageEvent):
markdown_payload = retry_payload.get("markdown")
if isinstance(markdown_payload, dict):
- md_content = cast(str, markdown_payload.get("content", "") or "")
+ md_content = cast("str", markdown_payload.get("content", "") or "")
if md_content and not md_content.endswith("\n"):
retry_payload["markdown"] = {"content": md_content + "\n"}
- content = cast(str | None, retry_payload.get("content"))
+ content = cast("str | None", retry_payload.get("content"))
if content and not content.endswith("\n"):
retry_payload["content"] = content + "\n"
logger.warning(
- "[QQOfficial] 流式 markdown 分片换行校验失败,已修正后重试一次。"
+ "[QQOfficial] 流式 markdown 分片换行校验失败,已修正后重试一次。",
)
return await send_func(retry_payload)
@@ -428,7 +426,7 @@ class QQOfficialMessageEvent(AstrMessageEvent):
raise
logger.warning(
- "[QQOfficial] markdown 发送被拒绝,回退到 content 模式重试。"
+ "[QQOfficial] markdown 发送被拒绝,回退到 content 模式重试。",
)
fallback_payload = payload.copy()
fallback_payload.pop("markdown", None)
@@ -436,7 +434,7 @@ class QQOfficialMessageEvent(AstrMessageEvent):
if fallback_payload.get("msg_type") == 2:
fallback_payload["msg_type"] = 0
if stream:
- fallback_content = cast(str, fallback_payload.get("content") or "")
+ fallback_content = cast("str", fallback_payload.get("content") or "")
if fallback_content and not fallback_content.endswith("\n"):
fallback_payload["content"] = fallback_content + "\n"
return await send_func(fallback_payload)
@@ -471,7 +469,7 @@ class QQOfficialMessageEvent(AstrMessageEvent):
if not isinstance(result, dict):
raise RuntimeError(
- f"Failed to upload image, response is not dict: {result}"
+ f"Failed to upload image, response is not dict: {result}",
)
return Media(
diff --git a/astrbot/core/platform/sources/qqofficial/qqofficial_platform_adapter.py b/astrbot/core/platform/sources/qqofficial/qqofficial_platform_adapter.py
index 3037ab2d8..6821d7382 100644
--- a/astrbot/core/platform/sources/qqofficial/qqofficial_platform_adapter.py
+++ b/astrbot/core/platform/sources/qqofficial/qqofficial_platform_adapter.py
@@ -71,13 +71,13 @@ class botClient(Client):
# 收到群消息
async def on_group_at_message_create(
- self, message: botpy.message.GroupMessage
+ self, message: botpy.message.GroupMessage,
) -> None:
abm = await QQOfficialPlatformAdapter._parse_from_qqofficial(
message,
MessageType.GROUP_MESSAGE,
)
- abm.group_id = cast(str, message.group_openid)
+ abm.group_id = cast("str", message.group_openid)
abm.session_id = abm.group_id
self.platform.remember_session_scene(abm.session_id, "group")
self._commit(abm)
@@ -95,7 +95,7 @@ class botClient(Client):
# 收到私聊消息
async def on_direct_message_create(
- self, message: botpy.message.DirectMessage
+ self, message: botpy.message.DirectMessage,
) -> None:
abm = await QQOfficialPlatformAdapter._parse_from_qqofficial(
message,
@@ -378,7 +378,7 @@ class QQOfficialPlatformAdapter(Platform):
return PlatformMetadata(
name="qq_official",
description="QQ 机器人官方 API 适配器",
- id=cast(str, self.config.get("id")),
+ id=cast("str", self.config.get("id")),
support_proactive_message=True,
)
@@ -415,11 +415,11 @@ class QQOfficialPlatformAdapter(Platform):
for attachment in attachments:
content_type = cast(
- str,
+ "str",
getattr(attachment, "content_type", "") or "",
).lower()
url = QQOfficialPlatformAdapter._normalize_attachment_url(
- cast(str | None, getattr(attachment, "url", None))
+ cast("str | None", getattr(attachment, "url", None)),
)
if not url:
continue
@@ -428,7 +428,7 @@ class QQOfficialPlatformAdapter(Platform):
msg.append(Image.fromURL(url))
else:
filename = cast(
- str,
+ "str",
getattr(attachment, "filename", None)
or getattr(attachment, "name", None)
or "attachment",
@@ -457,7 +457,7 @@ class QQOfficialPlatformAdapter(Platform):
await QQOfficialPlatformAdapter._prepare_audio_attachment(
url,
filename,
- )
+ ),
)
except Exception as e:
logger.warning(
@@ -488,6 +488,7 @@ class QQOfficialPlatformAdapter(Platform):
Returns:
Content with face tags replaced by readable emoji descriptions.
+
"""
import base64
import json
@@ -541,13 +542,13 @@ class QQOfficialPlatformAdapter(Platform):
abm.sender = MessageMember(message.author.user_openid, "")
# Parse face messages to readable text
abm.message_str = QQOfficialPlatformAdapter._parse_face_message(
- message.content.strip()
+ message.content.strip(),
)
abm.self_id = "unknown_selfid"
msg.append(At(qq="qq_official"))
msg.append(Plain(abm.message_str))
await QQOfficialPlatformAdapter._append_attachments(
- msg, message.attachments
+ msg, message.attachments,
)
abm.message = msg
@@ -564,11 +565,11 @@ class QQOfficialPlatformAdapter(Platform):
message.content.replace(
"<@!" + str(abm.self_id) + ">",
"",
- ).strip()
+ ).strip(),
)
await QQOfficialPlatformAdapter._append_attachments(
- msg, message.attachments
+ msg, message.attachments,
)
abm.message = msg
abm.message_str = plain_content
diff --git a/astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_adapter.py b/astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_adapter.py
index d2e14826a..b117751a5 100644
--- a/astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_adapter.py
+++ b/astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_adapter.py
@@ -29,13 +29,13 @@ class botClient(Client):
# 收到群消息
async def on_group_at_message_create(
- self, message: botpy.message.GroupMessage
+ self, message: botpy.message.GroupMessage,
) -> None:
abm = await QQOfficialPlatformAdapter._parse_from_qqofficial(
message,
MessageType.GROUP_MESSAGE,
)
- abm.group_id = cast(str, message.group_openid)
+ abm.group_id = cast("str", message.group_openid)
abm.session_id = abm.group_id
self.platform.remember_session_scene(abm.session_id, "group")
self._commit(abm)
@@ -53,7 +53,7 @@ class botClient(Client):
# 收到私聊消息
async def on_direct_message_create(
- self, message: botpy.message.DirectMessage
+ self, message: botpy.message.DirectMessage,
) -> None:
abm = await QQOfficialPlatformAdapter._parse_from_qqofficial(
message,
@@ -121,7 +121,7 @@ class QQOfficialWebhookPlatformAdapter(Platform):
message_chain: MessageChain,
) -> None:
await QQOfficialPlatformAdapter._send_by_session_common(
- cast(Any, self),
+ cast("Any", self),
session,
message_chain,
)
@@ -149,7 +149,7 @@ class QQOfficialWebhookPlatformAdapter(Platform):
return PlatformMetadata(
name="qq_official_webhook",
description="QQ 机器人官方 API 适配器",
- id=cast(str, self.config.get("id")),
+ id=cast("str", self.config.get("id")),
support_proactive_message=True,
)
diff --git a/astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_server.py b/astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_server.py
index 7af066020..7671c9af6 100644
--- a/astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_server.py
+++ b/astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_server.py
@@ -16,7 +16,7 @@ for handler in logging.root.handlers[:]:
class QQOfficialWebhook:
def __init__(
- self, config: dict, event_queue: asyncio.Queue, botpy_client: Client
+ self, config: dict, event_queue: asyncio.Queue, botpy_client: Client,
) -> None:
self.appid = config["appid"]
self.secret = config["secret"]
@@ -96,6 +96,7 @@ class QQOfficialWebhook:
Returns:
响应数据
+
"""
msg: dict = await request.json
logger.debug(f"收到 qq_official_webhook 回调: {msg}")
@@ -106,7 +107,7 @@ class QQOfficialWebhook:
if opcode == 13:
# validation
- signed = await self.webhook_validation(cast(dict, data))
+ signed = await self.webhook_validation(cast("dict", data))
print(signed)
return signed
diff --git a/astrbot/core/platform/sources/satori/satori_adapter.py b/astrbot/core/platform/sources/satori/satori_adapter.py
index 5c2f7a37f..c1bf6b150 100644
--- a/astrbot/core/platform/sources/satori/satori_adapter.py
+++ b/astrbot/core/platform/sources/satori/satori_adapter.py
@@ -29,7 +29,7 @@ from astrbot.core.platform.astr_message_event import MessageSession
@register_platform_adapter(
- "satori", "Satori 协议适配器", support_streaming_message=False
+ "satori", "Satori 协议适配器", support_streaming_message=False,
)
class SatoriPlatformAdapter(Platform):
def __init__(
diff --git a/astrbot/core/platform/sources/slack/client.py b/astrbot/core/platform/sources/slack/client.py
index efd7a6f3d..5f0b7af22 100644
--- a/astrbot/core/platform/sources/slack/client.py
+++ b/astrbot/core/platform/sources/slack/client.py
@@ -65,10 +65,11 @@ class SlackWebhookClient:
Returns:
Response 对象或字典
+
"""
try:
# 获取请求体和头部
- body = cast(bytes, await req.get_data())
+ body = cast("bytes", await req.get_data())
event_data = json.loads(body.decode("utf-8"))
# Verify Slack request signature
@@ -142,7 +143,7 @@ class SlackSocketClient:
self.socket_client = None
async def _handle_events(
- self, _: AsyncBaseSocketModeClient, req: SocketModeRequest
+ self, _: AsyncBaseSocketModeClient, req: SocketModeRequest,
) -> None:
"""处理 Socket Mode 事件"""
try:
diff --git a/astrbot/core/platform/sources/slack/slack_adapter.py b/astrbot/core/platform/sources/slack/slack_adapter.py
index 13e317e49..ddf5b5eb6 100644
--- a/astrbot/core/platform/sources/slack/slack_adapter.py
+++ b/astrbot/core/platform/sources/slack/slack_adapter.py
@@ -66,7 +66,7 @@ class SlackAdapter(Platform):
self.metadata = PlatformMetadata(
name="slack",
description="适用于 Slack 的消息平台适配器,支持 Socket Mode 和 Webhook Mode。",
- id=cast(str, self.config.get("id")),
+ id=cast("str", self.config.get("id")),
support_streaming_message=False,
)
@@ -98,14 +98,14 @@ class SlackAdapter(Platform):
await self.web_client.chat_postMessage(
channel=channel_id,
text=text,
- blocks=blocks if blocks else None,
+ blocks=blocks or None,
)
else:
# 发送私信
await self.web_client.chat_postMessage(
channel=session.session_id,
text=text,
- blocks=blocks if blocks else None,
+ blocks=blocks or None,
)
except Exception as e:
logger.error(f"Slack 发送消息失败: {e}")
@@ -116,13 +116,13 @@ class SlackAdapter(Platform):
logger.debug(f"[slack] RawMessage {event}")
abm = AstrBotMessage()
- abm.self_id = cast(str, self.bot_self_id)
+ abm.self_id = cast("str", self.bot_self_id)
# 获取用户信息
user_id = event.get("user", "")
try:
user_info = await self.web_client.users_info(user=user_id)
- user_data = cast(dict, user_info["user"])
+ user_data = cast("dict", user_info["user"])
user_name = user_data.get("real_name") or user_data.get("name", user_id)
except Exception:
user_name = user_id
@@ -133,7 +133,7 @@ class SlackAdapter(Platform):
channel_id = event.get("channel", "")
try:
channel_info = await self.web_client.conversations_info(channel=channel_id)
- is_im = cast(dict, channel_info["channel"])["is_im"]
+ is_im = cast("dict", channel_info["channel"])["is_im"]
if is_im:
abm.type = MessageType.FRIEND_MESSAGE
@@ -174,7 +174,7 @@ class SlackAdapter(Platform):
for mention in mentions:
try:
mentioned_user = await self.web_client.users_info(user=mention)
- user_data = cast(dict, mentioned_user["user"])
+ user_data = cast("dict", mentioned_user["user"])
user_name = user_data.get("real_name") or user_data.get(
"name",
mention,
@@ -429,5 +429,5 @@ class SlackAdapter(Platform):
return bool(
self.config.get("unified_webhook_mode", False)
and self.config.get("slack_connection_mode", "") == "webhook"
- and self.config.get("webhook_uuid")
+ and self.config.get("webhook_uuid"),
)
diff --git a/astrbot/core/platform/sources/slack/slack_event.py b/astrbot/core/platform/sources/slack/slack_event.py
index 3f62690b5..5be6a5ea4 100644
--- a/astrbot/core/platform/sources/slack/slack_event.py
+++ b/astrbot/core/platform/sources/slack/slack_event.py
@@ -56,7 +56,7 @@ class SlackMessageEvent(AstrMessageEvent):
"type": "section",
"text": {"type": "mrkdwn", "text": "图片上传失败"},
}
- image_url = cast(list, response["files"])[0]["url_private"]
+ image_url = cast("list", response["files"])[0]["url_private"]
logger.debug(f"Slack file upload response: {response}")
return {
"type": "image",
@@ -78,7 +78,7 @@ class SlackMessageEvent(AstrMessageEvent):
"type": "section",
"text": {"type": "mrkdwn", "text": "文件上传失败"},
}
- file_url = cast(list, response["files"])[0]["permalink"]
+ file_url = cast("list", response["files"])[0]["permalink"]
return {
"type": "section",
"text": {
@@ -226,10 +226,10 @@ class SlackMessageEvent(AstrMessageEvent):
)
members = []
- for member_id in cast(Iterable, members_response["members"]):
+ for member_id in cast("Iterable", members_response["members"]):
try:
user_info = await self.web_client.users_info(user=member_id)
- user_data = cast(dict, user_info["user"])
+ user_data = cast("dict", user_info["user"])
members.append(
MessageMember(
user_id=member_id,
@@ -241,7 +241,7 @@ class SlackMessageEvent(AstrMessageEvent):
# 如果获取用户信息失败,使用默认信息
members.append(MessageMember(user_id=member_id, nickname=member_id))
- channel_data = cast(dict, channel_info["channel"])
+ channel_data = cast("dict", channel_info["channel"])
return Group(
group_id=channel_id,
group_name=channel_data.get("name", ""),
diff --git a/astrbot/core/platform/sources/telegram/tg_adapter.py b/astrbot/core/platform/sources/telegram/tg_adapter.py
index 46887344d..9a2cedca7 100644
--- a/astrbot/core/platform/sources/telegram/tg_adapter.py
+++ b/astrbot/core/platform/sources/telegram/tg_adapter.py
@@ -118,10 +118,10 @@ class TelegramPlatformAdapter(Platform):
# Cache structure: {media_group_id: {"created_at": datetime, "items": [(update, context), ...]}}
self.media_group_cache: dict[str, dict] = {}
self.media_group_timeout = self.config.get(
- "telegram_media_group_timeout", 2.5
+ "telegram_media_group_timeout", 2.5,
) # seconds - debounce delay between messages
self.media_group_max_wait = self.config.get(
- "telegram_media_group_max_wait", 10.0
+ "telegram_media_group_max_wait", 10.0,
) # max seconds - hard cap to prevent indefinite delay
@override
@@ -169,7 +169,7 @@ class TelegramPlatformAdapter(Platform):
try:
logger.info("Starting Telegram polling...")
await self.application.updater.start_polling(
- error_callback=self._on_polling_error
+ error_callback=self._on_polling_error,
)
logger.info("Telegram Platform Adapter is running.")
while self.application.updater.running and not self._terminating: # noqa: ASYNC110
@@ -178,13 +178,13 @@ class TelegramPlatformAdapter(Platform):
if not self._terminating:
logger.warning(
"Telegram polling loop exited unexpectedly, "
- f"retrying in {self._polling_restart_delay}s."
+ f"retrying in {self._polling_restart_delay}s.",
)
except asyncio.CancelledError:
raise
except (Forbidden, InvalidToken) as e:
logger.error(
- f"Telegram token is invalid or unauthorized: {e}. Polling stopped."
+ f"Telegram token is invalid or unauthorized: {e}. Polling stopped.",
)
break
except Exception as e:
@@ -246,7 +246,7 @@ class TelegramPlatformAdapter(Platform):
if cmd_name in command_dict:
logger.warning(
f"命令名 '{cmd_name}' 重复注册,将使用首次注册的定义: "
- f"'{command_dict[cmd_name]}'"
+ f"'{command_dict[cmd_name]}'",
)
command_dict.setdefault(cmd_name, description)
@@ -293,7 +293,7 @@ class TelegramPlatformAdapter(Platform):
description = description[:30] + "..."
result.append((cmd_name, description))
- return result if result else None
+ return result or None
async def start(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
if not update.effective_chat:
@@ -307,7 +307,7 @@ class TelegramPlatformAdapter(Platform):
)
async def message_handler(
- self, update: Update, context: ContextTypes.DEFAULT_TYPE
+ self, update: Update, context: ContextTypes.DEFAULT_TYPE,
) -> None:
logger.debug(f"Telegram message: {update.message}")
@@ -452,10 +452,10 @@ class TelegramPlatformAdapter(Platform):
elif update.message.voice:
file = await update.message.voice.get_file()
- file_basename = os.path.basename(cast(str, file.file_path))
+ file_basename = os.path.basename(cast("str", file.file_path))
temp_dir = get_astrbot_temp_path()
temp_path = os.path.join(temp_dir, file_basename)
- await download_file(cast(str, file.file_path), path=temp_path)
+ await download_file(cast("str", file.file_path), path=temp_path)
path_wav = os.path.join(
temp_dir,
f"{file_basename}.wav",
@@ -491,7 +491,7 @@ class TelegramPlatformAdapter(Platform):
)
else:
message.message.append(
- Comp.File(file=file_path, name=file_name, url=file_path)
+ Comp.File(file=file_path, name=file_name, url=file_path),
)
_apply_caption()
@@ -510,7 +510,7 @@ class TelegramPlatformAdapter(Platform):
return message
async def handle_media_group_message(
- self, update: Update, context: ContextTypes.DEFAULT_TYPE
+ self, update: Update, context: ContextTypes.DEFAULT_TYPE,
):
"""Handle messages that are part of a media group (album).
@@ -556,7 +556,7 @@ class TelegramPlatformAdapter(Platform):
delay = self.media_group_timeout
logger.debug(
f"Scheduled media group {media_group_id} to be processed in {delay} seconds "
- f"(already waited {elapsed:.1f}s)"
+ f"(already waited {elapsed:.1f}s)",
)
# Schedule/reschedule processing (replace_existing=True handles debounce)
@@ -575,6 +575,7 @@ class TelegramPlatformAdapter(Platform):
Args:
media_group_id: The unique identifier for this media group
+
"""
if media_group_id not in self.media_group_cache:
logger.warning(f"Media group {media_group_id} not found in cache")
@@ -587,7 +588,7 @@ class TelegramPlatformAdapter(Platform):
return
logger.info(
- f"Processing media group {media_group_id}, total {len(updates_and_contexts)} items"
+ f"Processing media group {media_group_id}, total {len(updates_and_contexts)} items",
)
# Use the first update to create the base message (with reply, caption, etc.)
@@ -596,7 +597,7 @@ class TelegramPlatformAdapter(Platform):
if not abm:
logger.warning(
- f"Failed to convert the first message of media group {media_group_id}"
+ f"Failed to convert the first message of media group {media_group_id}",
)
return
@@ -610,7 +611,7 @@ class TelegramPlatformAdapter(Platform):
# Merge only the message components (keep base session/meta from first)
abm.message.extend(extra.message)
logger.debug(
- f"Added {len(extra.message)} components to media group {media_group_id}"
+ f"Added {len(extra.message)} components to media group {media_group_id}",
)
# Process the merged message
diff --git a/astrbot/core/platform/sources/telegram/tg_event.py b/astrbot/core/platform/sources/telegram/tg_event.py
index 8445a8ea1..db4440a86 100644
--- a/astrbot/core/platform/sources/telegram/tg_event.py
+++ b/astrbot/core/platform/sources/telegram/tg_event.py
@@ -121,13 +121,13 @@ class TelegramPlatformEvent(AstrMessageEvent):
await client.send_message(
text=markdown_text,
parse_mode="MarkdownV2",
- **cast(Any, payload),
+ **cast("Any", payload),
)
except (ValueError, BadRequest) as e:
logger.warning(
- f"Failed to convert message to Markdown,using normal text: {e!s}"
+ f"Failed to convert message to Markdown,using normal text: {e!s}",
)
- await client.send_message(text=chunk, **cast(Any, payload))
+ await client.send_message(text=chunk, **cast("Any", payload))
@classmethod
async def _send_chat_action(
@@ -167,17 +167,17 @@ class TelegramPlatformEvent(AstrMessageEvent):
) -> None:
"""发送媒体时显示 upload action,发送完成后恢复 typing"""
effective_thread_id = message_thread_id or cast(
- str | None, payload.get("message_thread_id")
+ "str | None", payload.get("message_thread_id"),
)
await cls._send_chat_action(
- client, user_name, upload_action, effective_thread_id
+ client, user_name, upload_action, effective_thread_id,
)
send_payload = dict(payload)
if effective_thread_id and "message_thread_id" not in send_payload:
send_payload["message_thread_id"] = effective_thread_id
await send_coro(**send_payload)
await cls._send_chat_action(
- client, user_name, ChatAction.TYPING, effective_thread_id
+ client, user_name, ChatAction.TYPING, effective_thread_id,
)
@classmethod
@@ -210,10 +210,10 @@ class TelegramPlatformEvent(AstrMessageEvent):
client.send_voice,
user_name=user_name,
voice=path,
- **cast(Any, media_payload),
+ **cast("Any", media_payload),
)
else:
- await client.send_voice(voice=path, **cast(Any, payload))
+ await client.send_voice(voice=path, **cast("Any", payload))
except BadRequest as e:
# python-telegram-bot raises BadRequest for Voice_messages_forbidden;
# distinguish the voice-privacy case via the API error message.
@@ -221,7 +221,7 @@ class TelegramPlatformEvent(AstrMessageEvent):
raise
logger.warning(
"User privacy settings prevent receiving voice messages, falling back to sending an audio file. "
- "To enable voice messages, go to Telegram Settings → Privacy and Security → Voice Messages → set to 'Everyone'."
+ "To enable voice messages, go to Telegram Settings → Privacy and Security → Voice Messages → set to 'Everyone'.",
)
if use_media_action:
media_payload = dict(payload)
@@ -234,13 +234,13 @@ class TelegramPlatformEvent(AstrMessageEvent):
user_name=user_name,
document=path,
caption=caption,
- **cast(Any, media_payload),
+ **cast("Any", media_payload),
)
else:
await client.send_document(
document=path,
caption=caption,
- **cast(Any, payload),
+ **cast("Any", payload),
)
async def _ensure_typing(
@@ -250,7 +250,7 @@ class TelegramPlatformEvent(AstrMessageEvent):
) -> None:
"""确保显示 typing 状态"""
await self._send_chat_action(
- self.client, user_name, ChatAction.TYPING, message_thread_id
+ self.client, user_name, ChatAction.TYPING, message_thread_id,
)
async def send_typing(self) -> None:
@@ -316,12 +316,12 @@ class TelegramPlatformEvent(AstrMessageEvent):
else:
send_coro = client.send_photo
media_kwarg = {"photo": image_path}
- await send_coro(**media_kwarg, **cast(Any, payload))
+ await send_coro(**media_kwarg, **cast("Any", payload))
elif isinstance(i, File):
path = await i.get_file()
name = i.name or os.path.basename(path)
await client.send_document(
- document=path, filename=name, **cast(Any, payload)
+ document=path, filename=name, **cast("Any", payload),
)
elif isinstance(i, Record):
path = await i.convert_to_file_path()
@@ -337,7 +337,7 @@ class TelegramPlatformEvent(AstrMessageEvent):
await client.send_video(
video=path,
caption=getattr(i, "text", None) or None,
- **cast(Any, payload),
+ **cast("Any", payload),
)
async def send(self, message: MessageChain) -> None:
@@ -397,6 +397,7 @@ class TelegramPlatformEvent(AstrMessageEvent):
text: 消息文本,1-4096 字符
message_thread_id: 可选,目标消息线程 ID
parse_mode: 可选,消息文本的解析模式
+
"""
if not text or not text.strip():
return
@@ -409,7 +410,7 @@ class TelegramPlatformEvent(AstrMessageEvent):
try:
logger.debug(
- f"[Telegram] sendMessageDraft: chat_id={chat_id}, draft_id={draft_id}, text_len={len(text)}"
+ f"[Telegram] sendMessageDraft: chat_id={chat_id}, draft_id={draft_id}, text_len={len(text)}",
)
await self.client.send_message_draft(
chat_id=int(chat_id),
@@ -448,7 +449,7 @@ class TelegramPlatformEvent(AstrMessageEvent):
send_coro,
user_name=user_name,
**media_kwarg,
- **cast(Any, payload),
+ **cast("Any", payload),
)
elif isinstance(i, File):
path = await i.get_file()
@@ -460,7 +461,7 @@ class TelegramPlatformEvent(AstrMessageEvent):
user_name=user_name,
document=path,
filename=name,
- **cast(Any, payload),
+ **cast("Any", payload),
)
elif isinstance(i, Record):
path = await i.convert_to_file_path()
@@ -481,7 +482,7 @@ class TelegramPlatformEvent(AstrMessageEvent):
self.client.send_video,
user_name=user_name,
video=path,
- **cast(Any, payload),
+ **cast("Any", payload),
)
else:
logger.warning(f"不支持的消息类型: {type(i)}")
@@ -513,12 +514,12 @@ class TelegramPlatformEvent(AstrMessageEvent):
if is_private:
logger.info("[Telegram] 流式输出: 使用 sendMessageDraft (私聊)")
await self._send_streaming_draft(
- user_name, message_thread_id, payload, generator
+ user_name, message_thread_id, payload, generator,
)
else:
logger.info("[Telegram] 流式输出: 使用 edit_message_text fallback (群聊)")
await self._send_streaming_edit(
- user_name, message_thread_id, payload, generator
+ user_name, message_thread_id, payload, generator,
)
# 内联父类 send_streaming 的副作用(避免传入已消费的 generator)
@@ -581,7 +582,7 @@ class TelegramPlatformEvent(AstrMessageEvent):
last_sent_text = draft_text
except Exception as e2:
logger.debug(
- f"[Telegram] sendMessageDraft failed (ignored): {e2!s}"
+ f"[Telegram] sendMessageDraft failed (ignored): {e2!s}",
)
sender_task = asyncio.create_task(_draft_sender_loop())
@@ -613,7 +614,7 @@ class TelegramPlatformEvent(AstrMessageEvent):
continue
await self._process_chain_items(
- chain, payload, user_name, message_thread_id, _append_text
+ chain, payload, user_name, message_thread_id, _append_text,
)
finally:
done = True
@@ -674,7 +675,7 @@ class TelegramPlatformEvent(AstrMessageEvent):
continue
await self._process_chain_items(
- chain, payload, user_name, message_thread_id, _append_text
+ chain, payload, user_name, message_thread_id, _append_text,
)
# 编辑或发送消息
@@ -704,7 +705,7 @@ class TelegramPlatformEvent(AstrMessageEvent):
last_chat_action_time = current_time
try:
msg = await self.client.send_message(
- text=delta, **cast(Any, payload)
+ text=delta, **cast("Any", payload),
)
current_content = delta
except Exception as e:
diff --git a/astrbot/core/platform/sources/webchat/message_parts_helper.py b/astrbot/core/platform/sources/webchat/message_parts_helper.py
index 43072ec1c..5a24a704f 100644
--- a/astrbot/core/platform/sources/webchat/message_parts_helper.py
+++ b/astrbot/core/platform/sources/webchat/message_parts_helper.py
@@ -56,6 +56,7 @@ async def parse_webchat_message_parts(
Returns:
tuple[list, list[str], bool]:
(components, plain_text_parts, has_non_reply_content)
+
"""
components = []
text_parts: list[str] = []
@@ -123,7 +124,7 @@ async def parse_webchat_message_parts(
chain=reply_chain,
sender_id=sender_id,
sender_nickname=sender_name,
- )
+ ),
)
continue
@@ -201,7 +202,7 @@ async def build_webchat_message_parts(
"type": "reply",
"message_id": message_id,
"selected_text": str(part.get("selected_text", "")),
- }
+ },
)
continue
@@ -229,7 +230,7 @@ async def build_webchat_message_parts(
"attachment_id": attachment.attachment_id,
"filename": attachment_path.name,
"path": str(attachment_path),
- }
+ },
)
return message_parts
@@ -268,7 +269,7 @@ def webchat_message_parts_to_message_chain(
id=str(message_id),
message_str=str(part.get("selected_text", "")),
chain=[],
- )
+ ),
)
continue
@@ -377,7 +378,7 @@ async def message_chain_to_storage_message_parts(
if isinstance(comp, Json):
parts.append(
- {"type": "plain", "text": json.dumps(comp.data, ensure_ascii=False)}
+ {"type": "plain", "text": json.dumps(comp.data, ensure_ascii=False)},
)
continue
diff --git a/astrbot/core/platform/sources/webchat/webchat_adapter.py b/astrbot/core/platform/sources/webchat/webchat_adapter.py
index 26b434573..e0ca40a0a 100644
--- a/astrbot/core/platform/sources/webchat/webchat_adapter.py
+++ b/astrbot/core/platform/sources/webchat/webchat_adapter.py
@@ -89,7 +89,7 @@ class WebChatAdapter(Platform):
) -> None:
conversation_id = _extract_conversation_id(session.session_id)
active_request_ids = self._webchat_queue_mgr.list_back_request_ids(
- conversation_id
+ conversation_id,
)
stream_request_ids = [
req_id for req_id in active_request_ids if not req_id.startswith("ws_sub_")
@@ -154,7 +154,7 @@ class WebChatAdapter(Platform):
)
async def _get_message_history(
- self, message_id: int
+ self, message_id: int,
) -> PlatformMessageHistory | None:
return await db_helper.get_platform_message_history_by_id(message_id)
@@ -173,6 +173,7 @@ class WebChatAdapter(Platform):
Returns:
tuple[list, list[str]]: (消息组件列表, 纯文本列表)
+
"""
async def get_reply_parts(
@@ -247,7 +248,7 @@ class WebChatAdapter(Platform):
message_event.set_extra("selected_provider", payload.get("selected_provider"))
message_event.set_extra("selected_model", payload.get("selected_model"))
message_event.set_extra(
- "enable_streaming", payload.get("enable_streaming", True)
+ "enable_streaming", payload.get("enable_streaming", True),
)
message_event.set_extra("action_type", payload.get("action_type"))
diff --git a/astrbot/core/platform/sources/webchat/webchat_event.py b/astrbot/core/platform/sources/webchat/webchat_event.py
index bc1e1a6bc..8d1db88e1 100644
--- a/astrbot/core/platform/sources/webchat/webchat_event.py
+++ b/astrbot/core/platform/sources/webchat/webchat_event.py
@@ -51,7 +51,7 @@ class WebChatMessageEvent(AstrMessageEvent):
"message_id": message_id,
}, # end means this request is finished
)
- return
+ return None
data = ""
for comp in message.chain:
@@ -78,7 +78,7 @@ class WebChatMessageEvent(AstrMessageEvent):
)
elif isinstance(comp, Image):
# save image to local
- filename = f"{str(uuid.uuid4())}.jpg"
+ filename = f"{uuid.uuid4()!s}.jpg"
path = os.path.join(attachments_dir, filename)
image_base64 = await comp.convert_to_base64()
with open(path, "wb") as f:
@@ -94,7 +94,7 @@ class WebChatMessageEvent(AstrMessageEvent):
)
elif isinstance(comp, Record):
# save record to local
- filename = f"{str(uuid.uuid4())}.wav"
+ filename = f"{uuid.uuid4()!s}.wav"
path = os.path.join(attachments_dir, filename)
record_base64 = await comp.convert_to_base64()
with open(path, "wb") as f:
diff --git a/astrbot/core/platform/sources/webchat/webchat_queue_mgr.py b/astrbot/core/platform/sources/webchat/webchat_queue_mgr.py
index f3ade1589..1f7a178fc 100644
--- a/astrbot/core/platform/sources/webchat/webchat_queue_mgr.py
+++ b/astrbot/core/platform/sources/webchat/webchat_queue_mgr.py
@@ -34,7 +34,7 @@ class WebChatQueueMgr:
"""Get or create a back queue for the given request ID"""
if request_id not in self.back_queues:
self.back_queues[request_id] = asyncio.Queue(
- maxsize=self.back_queue_maxsize
+ maxsize=self.back_queue_maxsize,
)
if conversation_id:
self._request_conversation[request_id] = conversation_id
@@ -57,7 +57,7 @@ class WebChatQueueMgr:
def remove_queues(self, conversation_id: str) -> None:
"""Remove queues for the given conversation ID"""
for request_id in list(
- self._conversation_back_requests.get(conversation_id, set())
+ self._conversation_back_requests.get(conversation_id, set()),
):
self.remove_back_queue(request_id)
self._conversation_back_requests.pop(conversation_id, None)
@@ -121,7 +121,7 @@ class WebChatQueueMgr:
)
self._listener_tasks[conversation_id] = task
task.add_done_callback(
- lambda _: self._listener_tasks.pop(conversation_id, None)
+ lambda _: self._listener_tasks.pop(conversation_id, None),
)
logger.debug(f"Started listener for conversation: {conversation_id}")
@@ -150,7 +150,7 @@ class WebChatQueueMgr:
await self._listener_callback(data)
except Exception as e:
logger.error(
- f"Error processing message from conversation {conversation_id}: {e}"
+ f"Error processing message from conversation {conversation_id}: {e}",
)
except asyncio.CancelledError:
break
diff --git a/astrbot/core/platform/sources/wecom/wecom_adapter.py b/astrbot/core/platform/sources/wecom/wecom_adapter.py
index a30afcd8f..4f09ba273 100644
--- a/astrbot/core/platform/sources/wecom/wecom_adapter.py
+++ b/astrbot/core/platform/sources/wecom/wecom_adapter.py
@@ -42,7 +42,7 @@ else:
class WecomServer:
def __init__(self, event_queue: asyncio.Queue, config: dict) -> None:
self.server = quart.Quart(__name__)
- self.port = int(cast(str, config.get("port")))
+ self.port = int(cast("str", config.get("port")))
self.callback_server_host = config.get("callback_server_host", "0.0.0.0")
self.server.add_url_rule(
"/callback/command",
@@ -77,6 +77,7 @@ class WecomServer:
Returns:
验证响应
+
"""
logger.info(f"验证请求有效性: {request.args}")
args = request.args
@@ -105,6 +106,7 @@ class WecomServer:
Returns:
响应内容
+
"""
data = await request.get_data()
msg_signature = request.args.get("msg_signature")
@@ -116,7 +118,7 @@ class WecomServer:
logger.error("解密失败,签名异常,请检查配置。")
raise
else:
- msg = cast(BaseMessage, parse_message(xml))
+ msg = cast("BaseMessage", parse_message(xml))
logger.info(f"解析成功: {msg}")
if self.callback:
@@ -306,8 +308,7 @@ class WecomPlatformAdapter(Platform):
# 根据请求方法分发到不同的处理函数
if request.method == "GET":
return await self.server.handle_verify(request)
- else:
- return await self.server.handle_callback(request)
+ return await self.server.handle_callback(request)
async def convert_message(self, msg: BaseMessage) -> AstrBotMessage | None:
abm = AstrBotMessage()
@@ -317,11 +318,11 @@ class WecomPlatformAdapter(Platform):
abm.message = [Plain(msg.content)]
abm.type = MessageType.FRIEND_MESSAGE
abm.sender = MessageMember(
- cast(str, msg.source),
- cast(str, msg.source),
+ cast("str", msg.source),
+ cast("str", msg.source),
)
abm.message_id = str(msg.id)
- abm.timestamp = int(cast(int | str, msg.time))
+ abm.timestamp = int(cast("int | str", msg.time))
abm.session_id = abm.sender.user_id
abm.raw_message = msg
elif isinstance(msg, ImageMessage):
@@ -330,11 +331,11 @@ class WecomPlatformAdapter(Platform):
abm.message = [Image(file=msg.image, url=msg.image)]
abm.type = MessageType.FRIEND_MESSAGE
abm.sender = MessageMember(
- cast(str, msg.source),
- cast(str, msg.source),
+ cast("str", msg.source),
+ cast("str", msg.source),
)
abm.message_id = str(msg.id)
- abm.timestamp = int(cast(int | str, msg.time))
+ abm.timestamp = int(cast("int | str", msg.time))
abm.session_id = abm.sender.user_id
abm.raw_message = msg
elif isinstance(msg, VoiceMessage):
@@ -361,11 +362,11 @@ class WecomPlatformAdapter(Platform):
abm.message = [Record(file=path_wav, url=path_wav)]
abm.type = MessageType.FRIEND_MESSAGE
abm.sender = MessageMember(
- cast(str, msg.source),
- cast(str, msg.source),
+ cast("str", msg.source),
+ cast("str", msg.source),
)
abm.message_id = str(msg.id)
- abm.timestamp = int(cast(int | str, msg.time))
+ abm.timestamp = int(cast("int | str", msg.time))
abm.session_id = abm.sender.user_id
abm.raw_message = msg
else:
@@ -378,7 +379,7 @@ class WecomPlatformAdapter(Platform):
async def convert_wechat_kf_message(self, msg: dict) -> AstrBotMessage | None:
msgtype = msg.get("msgtype")
- external_userid = cast(str, msg.get("external_userid"))
+ external_userid = cast("str", msg.get("external_userid"))
abm = AstrBotMessage()
abm.raw_message = msg
abm.raw_message["_wechat_kf_flag"] = None # 方便处理
diff --git a/astrbot/core/platform/sources/wecom/wecom_event.py b/astrbot/core/platform/sources/wecom/wecom_event.py
index 265b41014..1589775ae 100644
--- a/astrbot/core/platform/sources/wecom/wecom_event.py
+++ b/astrbot/core/platform/sources/wecom/wecom_event.py
@@ -102,10 +102,10 @@ class WecomPlatformEvent(AstrMessageEvent):
if getattr(e, "errcode", None) == 40096:
# 40096: invalid external userid, fallback to regular message API
logger.warning(
- f"kf API error 40096 for user {user_id}, falling back to regular message API"
+ f"kf API error 40096 for user {user_id}, falling back to regular message API",
)
self.client.message.send_text(
- self.get_self_id(), user_id, chunk
+ self.get_self_id(), user_id, chunk,
)
else:
raise
@@ -140,7 +140,7 @@ class WecomPlatformEvent(AstrMessageEvent):
logger.error(f"微信客服上传语音失败: {e}")
await self.send(
MessageChain().message(
- f"微信客服上传语音失败: {e}"
+ f"微信客服上传语音失败: {e}",
),
)
return
@@ -239,7 +239,7 @@ class WecomPlatformEvent(AstrMessageEvent):
logger.error(f"企业微信上传语音失败: {e}")
await self.send(
MessageChain().message(
- f"企业微信上传语音失败: {e}"
+ f"企业微信上传语音失败: {e}",
),
)
return
diff --git a/astrbot/core/platform/sources/wecom_ai_bot/WXBizJsonMsgCrypt.py b/astrbot/core/platform/sources/wecom_ai_bot/WXBizJsonMsgCrypt.py
index 260b950d1..520ea80fc 100644
--- a/astrbot/core/platform/sources/wecom_ai_bot/WXBizJsonMsgCrypt.py
+++ b/astrbot/core/platform/sources/wecom_ai_bot/WXBizJsonMsgCrypt.py
@@ -215,7 +215,7 @@ class Prpcrypt:
@return: 16位字符串
"""
return str(
- secrets.randbelow(self.RANDOM_RANGE) + self.MIN_RANDOM_VALUE
+ secrets.randbelow(self.RANDOM_RANGE) + self.MIN_RANDOM_VALUE,
).encode()
diff --git a/astrbot/core/platform/sources/wecom_ai_bot/wecomai_adapter.py b/astrbot/core/platform/sources/wecom_ai_bot/wecomai_adapter.py
index 79fe6f8ed..952d07d2e 100644
--- a/astrbot/core/platform/sources/wecom_ai_bot/wecomai_adapter.py
+++ b/astrbot/core/platform/sources/wecom_ai_bot/wecomai_adapter.py
@@ -80,11 +80,11 @@ class WecomAIBotAdapter(Platform):
# 初始化配置参数
self.connection_mode = self.config.get(
- "wecom_ai_bot_connection_mode", "webhook"
+ "wecom_ai_bot_connection_mode", "webhook",
)
self.token = self.config.get("token", self.config.get("wecomaibot_token", ""))
self.encoding_aes_key = self.config.get(
- "encoding_aes_key", self.config.get("wecomaibot_encoding_aes_key", "")
+ "encoding_aes_key", self.config.get("wecomaibot_encoding_aes_key", ""),
)
self.port = int(self.config["port"])
self.host = self.config.get("callback_server_host", "0.0.0.0")
@@ -103,10 +103,10 @@ class WecomAIBotAdapter(Platform):
self.config.get("only_use_webhook_url_to_send", False),
)
self.long_connection_bot_id = self.config.get(
- "wecomaibot_ws_bot_id", self.config.get("long_connection_bot_id", "")
+ "wecomaibot_ws_bot_id", self.config.get("long_connection_bot_id", ""),
)
self.long_connection_secret = self.config.get(
- "wecomaibot_ws_secret", self.config.get("long_connection_secret", "")
+ "wecomaibot_ws_secret", self.config.get("long_connection_secret", ""),
)
self.long_connection_ws_url = self.config.get(
"wecomaibot_ws_url",
@@ -131,7 +131,7 @@ class WecomAIBotAdapter(Platform):
if self.connection_mode == "long_connection":
if not self.long_connection_bot_id or not self.long_connection_secret:
logger.warning(
- "企业微信智能机器人长连接模式缺少 BotID 或 Secret,连接可能失败"
+ "企业微信智能机器人长连接模式缺少 BotID 或 Secret,连接可能失败",
)
self.long_connection_client = WecomAIBotLongConnectionClient(
bot_id=self.long_connection_bot_id,
@@ -238,7 +238,7 @@ class WecomAIBotAdapter(Platform):
self._stream_plain_cache.pop(stream_id, None)
if self.queue_mgr.is_stream_finished(stream_id):
logger.debug(
- f"Stream already finished, returning end message: {stream_id}"
+ f"Stream already finished, returning end message: {stream_id}",
)
else:
logger.warning(f"Cannot find back queue for stream_id: {stream_id}")
@@ -372,7 +372,7 @@ class WecomAIBotAdapter(Platform):
session_id = self._extract_session_id(body)
stream_id = f"{session_id}_{generate_random_string(10)}"
await self._enqueue_message(
- body, {"req_id": req_id or ""}, stream_id, session_id
+ body, {"req_id": req_id or ""}, stream_id, session_id,
)
self.queue_mgr.set_pending_response(
stream_id,
@@ -407,7 +407,7 @@ class WecomAIBotAdapter(Platform):
await self._send_long_connection_respond_welcome(req_id)
elif event_type == "disconnected_event":
logger.warning(
- "[WecomAI][LongConn] 收到 disconnected_event,旧连接将被关闭"
+ "[WecomAI][LongConn] 收到 disconnected_event,旧连接将被关闭",
)
async def _send_long_connection_respond_welcome(self, req_id: str) -> bool:
@@ -447,9 +447,8 @@ class WecomAIBotAdapter(Platform):
if chattype == "group":
chat_id = message_data.get("chatid", "default_group")
return format_session_id("wecomai", chat_id)
- else:
- user_id = message_data.get("from", {}).get("userid", "default_user")
- return format_session_id("wecomai", user_id)
+ user_id = message_data.get("from", {}).get("userid", "default_user")
+ return format_session_id("wecomai", user_id)
async def _enqueue_message(
self,
@@ -505,7 +504,7 @@ class WecomAIBotAdapter(Platform):
image_url = image_payload.get("url", "")
if image_url:
_img_url_to_process.append(
- (image_url, image_payload.get("aeskey"))
+ (image_url, image_payload.get("aeskey")),
)
content = " ".join(text_parts) if text_parts else ""
else:
@@ -593,7 +592,7 @@ class WecomAIBotAdapter(Platform):
if not self.long_connection_client:
raise RuntimeError("长连接客户端未初始化")
logger.info(
- "启动企业微信智能机器人长连接模式: %s", self.long_connection_ws_url
+ "启动企业微信智能机器人长连接模式: %s", self.long_connection_ws_url,
)
await asyncio.gather(
self.long_connection_client.start(),
@@ -604,7 +603,7 @@ class WecomAIBotAdapter(Platform):
webhook_uuid = self.config.get("webhook_uuid")
if self.unified_webhook_mode and webhook_uuid:
log_webhook_info(
- f"{self.meta().id}(企业微信智能机器人)", webhook_uuid
+ f"{self.meta().id}(企业微信智能机器人)", webhook_uuid,
)
# 只运行队列监听器
await self.queue_listener.run()
@@ -612,7 +611,7 @@ class WecomAIBotAdapter(Platform):
if not self.server:
raise RuntimeError("Webhook 服务器未初始化")
logger.info(
- "启动企业微信智能机器人适配器,监听 %s:%d", self.host, self.port
+ "启动企业微信智能机器人适配器,监听 %s:%d", self.host, self.port,
)
# 同时运行HTTP服务器和队列监听器
await asyncio.gather(
@@ -629,8 +628,7 @@ class WecomAIBotAdapter(Platform):
# 根据请求方法分发到不同的处理函数
if request.method == "GET":
return await self.server.handle_verify(request)
- else:
- return await self.server.handle_callback(request)
+ return await self.server.handle_callback(request)
async def terminate(self) -> None:
"""终止适配器"""
diff --git a/astrbot/core/platform/sources/wecom_ai_bot/wecomai_api.py b/astrbot/core/platform/sources/wecom_ai_bot/wecomai_api.py
index 97831fbb2..929c06cae 100644
--- a/astrbot/core/platform/sources/wecom_ai_bot/wecomai_api.py
+++ b/astrbot/core/platform/sources/wecom_ai_bot/wecomai_api.py
@@ -142,7 +142,7 @@ class WecomAIBotAPIClient:
return "verify fail"
logger.info("URL 验证成功")
- return echo_result if echo_result else "verify fail"
+ return echo_result or "verify fail"
except Exception as e:
logger.error(f"URL 验证发生异常: {e}")
diff --git a/astrbot/core/platform/sources/wecom_ai_bot/wecomai_event.py b/astrbot/core/platform/sources/wecom_ai_bot/wecomai_event.py
index f27d4671e..f1b700a48 100644
--- a/astrbot/core/platform/sources/wecom_ai_bot/wecomai_event.py
+++ b/astrbot/core/platform/sources/wecom_ai_bot/wecomai_event.py
@@ -116,11 +116,10 @@ class WecomAIBotMessageEvent(AstrMessageEvent):
logger.warning("图片数据为空,跳过")
except Exception as e:
logger.error("处理图片消息失败: %s", e)
- else:
- if not suppress_unsupported_log:
- logger.warning(
- f"[WecomAI] 不支持的消息组件类型: {type(comp)}, 跳过"
- )
+ elif not suppress_unsupported_log:
+ logger.warning(
+ f"[WecomAI] 不支持的消息组件类型: {type(comp)}, 跳过",
+ )
return data
@@ -147,7 +146,7 @@ class WecomAIBotMessageEvent(AstrMessageEvent):
stream_id = raw.get("stream_id", self.session_id)
pending_response = self.queue_mgr.get_pending_response(stream_id) or {}
connection_mode = pending_response.get("callback_params", {}).get(
- "connection_mode"
+ "connection_mode",
)
req_id = pending_response.get("callback_params", {}).get("req_id")
@@ -213,7 +212,7 @@ class WecomAIBotMessageEvent(AstrMessageEvent):
stream_id = raw.get("stream_id", self.session_id)
pending_response = self.queue_mgr.get_pending_response(stream_id) or {}
connection_mode = pending_response.get("callback_params", {}).get(
- "connection_mode"
+ "connection_mode",
)
req_id = pending_response.get("callback_params", {}).get("req_id")
back_queue = self.queue_mgr.get_or_create_back_queue(stream_id)
@@ -315,7 +314,7 @@ class WecomAIBotMessageEvent(AstrMessageEvent):
async for chain in generator:
if self.webhook_client:
await self.webhook_client.send_message_chain(
- chain, unsupported_only=True
+ chain, unsupported_only=True,
)
if chain.type == "break" and final_data:
diff --git a/astrbot/core/platform/sources/wecom_ai_bot/wecomai_long_connection.py b/astrbot/core/platform/sources/wecom_ai_bot/wecomai_long_connection.py
index 1017dd230..82663aca0 100644
--- a/astrbot/core/platform/sources/wecom_ai_bot/wecomai_long_connection.py
+++ b/astrbot/core/platform/sources/wecom_ai_bot/wecomai_long_connection.py
@@ -61,7 +61,7 @@ class WecomAIBotLongConnectionClient:
self._session = session
logger.info("[WecomAI][LongConn] 正在连接: %s", self.ws_url)
async with session.ws_connect(
- self.ws_url, heartbeat=None, autoping=True
+ self.ws_url, heartbeat=None, autoping=True,
) as ws:
self._ws = ws
await self._subscribe()
@@ -107,7 +107,7 @@ class WecomAIBotLongConnectionClient:
data = json.loads(reply.data)
if data.get("errcode") != 0:
raise RuntimeError(
- f"订阅失败 errcode={data.get('errcode')} errmsg={data.get('errmsg')}"
+ f"订阅失败 errcode={data.get('errcode')} errmsg={data.get('errmsg')}",
)
async def _heartbeat_loop(self) -> None:
diff --git a/astrbot/core/platform/sources/wecom_ai_bot/wecomai_queue_mgr.py b/astrbot/core/platform/sources/wecom_ai_bot/wecomai_queue_mgr.py
index efa94b58e..aa701609d 100644
--- a/astrbot/core/platform/sources/wecom_ai_bot/wecomai_queue_mgr.py
+++ b/astrbot/core/platform/sources/wecom_ai_bot/wecomai_queue_mgr.py
@@ -60,7 +60,7 @@ class WecomAIQueueMgr:
"""
if session_id not in self.back_queues:
self.back_queues[session_id] = asyncio.Queue(
- maxsize=self.back_queue_maxsize
+ maxsize=self.back_queue_maxsize,
)
logger.debug(f"[WecomAI] 创建输出队列: {session_id}")
return self.back_queues[session_id]
@@ -125,7 +125,7 @@ class WecomAIQueueMgr:
return session_id in self.back_queues
def set_pending_response(
- self, session_id: str, callback_params: dict[str, str]
+ self, session_id: str, callback_params: dict[str, str],
) -> None:
"""设置待处理的响应参数
diff --git a/astrbot/core/platform/sources/wecom_ai_bot/wecomai_server.py b/astrbot/core/platform/sources/wecom_ai_bot/wecomai_server.py
index 80ec5179e..ce4770996 100644
--- a/astrbot/core/platform/sources/wecom_ai_bot/wecomai_server.py
+++ b/astrbot/core/platform/sources/wecom_ai_bot/wecomai_server.py
@@ -70,6 +70,7 @@ class WecomAIBotServer:
Returns:
验证响应元组 (content, status_code, headers)
+
"""
args = request.args
msg_signature = args.get("msg_signature")
@@ -103,6 +104,7 @@ class WecomAIBotServer:
Returns:
响应元组 (content, status_code, headers)
+
"""
args = request.args
msg_signature = args.get("msg_signature")
diff --git a/astrbot/core/platform/sources/wecom_ai_bot/wecomai_webhook.py b/astrbot/core/platform/sources/wecom_ai_bot/wecomai_webhook.py
index 6f42f264b..797b22a43 100644
--- a/astrbot/core/platform/sources/wecom_ai_bot/wecomai_webhook.py
+++ b/astrbot/core/platform/sources/wecom_ai_bot/wecomai_webhook.py
@@ -69,12 +69,12 @@ class WecomAIBotWebhookClient:
text = await response.text()
if response.status != 200:
raise WecomAIBotWebhookError(
- f"Webhook 请求失败: HTTP {response.status}, {text}"
+ f"Webhook 请求失败: HTTP {response.status}, {text}",
)
result = await response.json(content_type=None)
if result.get("errcode") != 0:
raise WecomAIBotWebhookError(
- f"Webhook 返回错误: {result.get('errcode')} {result.get('errmsg')}"
+ f"Webhook 返回错误: {result.get('errcode')} {result.get('errmsg')}",
)
logger.debug("企业微信消息推送成功: %s", payload.get("msgtype", "unknown"))
@@ -84,7 +84,7 @@ class WecomAIBotWebhookClient:
{
"msgtype": "markdown_v2",
"markdown_v2": {"content": chunk},
- }
+ },
)
async def send_image_base64(self, image_base64: str) -> None:
@@ -97,11 +97,11 @@ class WecomAIBotWebhookClient:
"base64": image_base64,
"md5": md5,
},
- }
+ },
)
async def upload_media(
- self, file_path: Path, media_type: Literal["file", "voice"]
+ self, file_path: Path, media_type: Literal["file", "voice"],
) -> str:
if not file_path.exists() or not file_path.is_file():
raise WecomAIBotWebhookError(f"文件不存在: {file_path}")
@@ -126,12 +126,12 @@ class WecomAIBotWebhookClient:
text = await response.text()
if response.status != 200:
raise WecomAIBotWebhookError(
- f"上传媒体失败: HTTP {response.status}, {text}"
+ f"上传媒体失败: HTTP {response.status}, {text}",
)
result = await response.json(content_type=None)
if result.get("errcode") != 0:
raise WecomAIBotWebhookError(
- f"上传媒体失败: {result.get('errcode')} {result.get('errmsg')}"
+ f"上传媒体失败: {result.get('errcode')} {result.get('errmsg')}",
)
media_id = result.get("media_id", "")
if not media_id:
@@ -144,7 +144,7 @@ class WecomAIBotWebhookClient:
{
"msgtype": "file",
"file": {"media_id": media_id},
- }
+ },
)
async def send_voice(self, file_path: Path) -> None:
@@ -153,7 +153,7 @@ class WecomAIBotWebhookClient:
{
"msgtype": "voice",
"voice": {"media_id": media_id},
- }
+ },
)
@staticmethod
@@ -214,7 +214,7 @@ class WecomAIBotWebhookClient:
target_voice_path.unlink()
except Exception as e:
logger.warning(
- "清理临时语音文件失败 %s: %s", target_voice_path, e
+ "清理临时语音文件失败 %s: %s", target_voice_path, e,
)
else:
logger.warning(
diff --git a/astrbot/core/platform/sources/weixin_oc/weixin_oc_adapter.py b/astrbot/core/platform/sources/weixin_oc/weixin_oc_adapter.py
index b317cb5b8..9ad469d88 100644
--- a/astrbot/core/platform/sources/weixin_oc/weixin_oc_adapter.py
+++ b/astrbot/core/platform/sources/weixin_oc/weixin_oc_adapter.py
@@ -8,7 +8,7 @@ import time
import uuid
from dataclasses import dataclass, field
from pathlib import Path
-from typing import TYPE_CHECKING, Any, cast
+from typing import Any, cast
from urllib.parse import quote
import qrcode as qrcode_lib
@@ -31,9 +31,6 @@ from astrbot.core.utils.astrbot_path import get_astrbot_temp_path
from .weixin_oc_client import WeixinOCClient
from .weixin_oc_event import WeixinOCMessageEvent
-if TYPE_CHECKING: # pragma: no cover - typing-only helper
- pass
-
@dataclass
class OpenClawLoginSession:
@@ -84,7 +81,7 @@ class WeixinOCAdapter(Platform):
self.settings = platform_settings
self.base_url = str(
- platform_config.get("weixin_oc_base_url", "https://ilinkai.weixin.qq.com")
+ platform_config.get("weixin_oc_base_url", "https://ilinkai.weixin.qq.com"),
).rstrip("/")
self.bot_type = str(platform_config.get("weixin_oc_bot_type", "3"))
self.qr_poll_interval = max(
@@ -101,13 +98,13 @@ class WeixinOCAdapter(Platform):
platform_config.get(
"weixin_oc_cdn_base_url",
"https://novac2c.cdn.weixin.qq.com/c2c",
- )
+ ),
).rstrip("/")
self.metadata = PlatformMetadata(
name="weixin_oc",
description="个人微信",
- id=cast(str, self.config.get("id", "weixin_oc")),
+ id=cast("str", self.config.get("id", "weixin_oc")),
support_streaming_message=False,
)
@@ -229,7 +226,7 @@ class WeixinOCAdapter(Platform):
payload = await self.client.send_typing_state(user_id, ticket, cancel=cancel)
if int(payload.get("ret") or 0) != 0:
raise RuntimeError(
- f"sendtyping failed for {user_id}: {payload.get('errmsg', '')}"
+ f"sendtyping failed for {user_id}: {payload.get('errmsg', '')}",
)
async def _run_typing_keepalive(self, user_id: str) -> None:
@@ -270,7 +267,7 @@ class WeixinOCAdapter(Platform):
if not state.owners or state.keepalive_task is not None:
return
state.keepalive_task = asyncio.create_task(
- self._run_typing_keepalive(user_id)
+ self._run_typing_keepalive(user_id),
)
async def _typing_keepalive_loop(self, user_id: str) -> None:
@@ -421,7 +418,7 @@ class WeixinOCAdapter(Platform):
if ticket:
if state.cancel_task is None or state.cancel_task.done():
state.cancel_task = asyncio.create_task(
- self._delayed_cancel_typing(user_id, ticket)
+ self._delayed_cancel_typing(user_id, ticket),
)
async def _cleanup_typing_tasks(self) -> None:
@@ -501,7 +498,7 @@ class WeixinOCAdapter(Platform):
astrbot_config.save_config()
def _is_login_session_valid(
- self, login_session: OpenClawLoginSession | None
+ self, login_session: OpenClawLoginSession | None,
) -> bool:
if not login_session:
return False
@@ -654,15 +651,15 @@ class WeixinOCAdapter(Platform):
item_type = int(item.get("type") or 0)
if item_type == self.IMAGE_ITEM_TYPE:
- image_item = cast(dict[str, Any], item.get("image_item", {}) or {})
- media = cast(dict[str, Any], image_item.get("media", {}) or {})
+ image_item = cast("dict[str, Any]", item.get("image_item", {}) or {})
+ media = cast("dict[str, Any]", image_item.get("media", {}) or {})
encrypted_query_param = str(media.get("encrypt_query_param", "")).strip()
if not encrypted_query_param:
return None
image_aes_key = str(image_item.get("aeskey", "")).strip()
if image_aes_key:
aes_key_value = base64.b64encode(bytes.fromhex(image_aes_key)).decode(
- "utf-8"
+ "utf-8",
)
else:
aes_key_value = str(media.get("aes_key", "")).strip()
@@ -682,8 +679,8 @@ class WeixinOCAdapter(Platform):
return Image.fromFileSystem(str(image_path))
if item_type == self.VIDEO_ITEM_TYPE:
- video_item = cast(dict[str, Any], item.get("video_item", {}) or {})
- media = cast(dict[str, Any], video_item.get("media", {}) or {})
+ video_item = cast("dict[str, Any]", item.get("video_item", {}) or {})
+ media = cast("dict[str, Any]", video_item.get("media", {}) or {})
encrypted_query_param = str(media.get("encrypt_query_param", "")).strip()
aes_key_value = str(media.get("aes_key", "")).strip()
if not encrypted_query_param or not aes_key_value:
@@ -701,8 +698,8 @@ class WeixinOCAdapter(Platform):
return Video.fromFileSystem(str(video_path))
if item_type == self.FILE_ITEM_TYPE:
- file_item = cast(dict[str, Any], item.get("file_item", {}) or {})
- media = cast(dict[str, Any], file_item.get("media", {}) or {})
+ file_item = cast("dict[str, Any]", item.get("file_item", {}) or {})
+ media = cast("dict[str, Any]", file_item.get("media", {}) or {})
encrypted_query_param = str(media.get("encrypt_query_param", "")).strip()
aes_key_value = str(media.get("aes_key", "")).strip()
if not encrypted_query_param or not aes_key_value:
@@ -724,8 +721,8 @@ class WeixinOCAdapter(Platform):
return File(name=file_name, file=str(file_path))
if item_type == self.VOICE_ITEM_TYPE:
- voice_item = cast(dict[str, Any], item.get("voice_item", {}) or {})
- media = cast(dict[str, Any], voice_item.get("media", {}) or {})
+ voice_item = cast("dict[str, Any]", item.get("voice_item", {}) or {})
+ media = cast("dict[str, Any]", voice_item.get("media", {}) or {})
encrypted_query_param = str(media.get("encrypt_query_param", "")).strip()
aes_key_value = str(media.get("aes_key", "")).strip()
if not encrypted_query_param or not aes_key_value:
@@ -745,7 +742,7 @@ class WeixinOCAdapter(Platform):
return None
async def _resolve_media_file_path(
- self, segment: Image | Video | File
+ self, segment: Image | Video | File,
) -> Path | None:
try:
if isinstance(segment, File):
@@ -817,7 +814,7 @@ class WeixinOCAdapter(Platform):
) -> bool:
if not self.token:
logger.warning(
- "weixin_oc(%s): missing token, skip media send", self.meta().id
+ "weixin_oc(%s): missing token, skip media send", self.meta().id,
)
return False
media_path = await self._resolve_media_file_path(segment)
@@ -961,7 +958,7 @@ class WeixinOCAdapter(Platform):
await self._save_account_state()
def _message_text_from_item_list(
- self, item_list: list[dict[str, Any]] | None
+ self, item_list: list[dict[str, Any]] | None,
) -> str:
if not item_list:
return ""
@@ -995,7 +992,7 @@ class WeixinOCAdapter(Platform):
return "\n".join(text_parts).strip()
async def _item_list_to_components(
- self, item_list: list[dict[str, Any]] | None
+ self, item_list: list[dict[str, Any]] | None,
) -> list[Any]:
if not item_list:
return []
@@ -1030,7 +1027,7 @@ class WeixinOCAdapter(Platform):
if context_token:
self._context_tokens[from_user_id] = context_token
- item_list = cast(list[dict[str, Any]], msg.get("item_list", []))
+ item_list = cast("list[dict[str, Any]]", msg.get("item_list", []))
components = await self._item_list_to_components(item_list)
text = self._message_text_from_item_list(item_list)
message_id = str(msg.get("message_id") or msg.get("msg_id") or uuid.uuid4().hex)
@@ -1060,7 +1057,7 @@ class WeixinOCAdapter(Platform):
platform_meta=self.meta(),
session_id=abm.session_id,
platform=self,
- )
+ ),
)
async def _poll_inbound_updates(self) -> None:
@@ -1116,7 +1113,7 @@ class WeixinOCAdapter(Platform):
return text.strip()
async def _send_to_session(
- self, user_id: str, text: str, _components: list[Any] | None = None
+ self, user_id: str, text: str, _components: list[Any] | None = None,
) -> bool:
if not text:
text = self._message_chain_to_text(MessageChain(_components or []))
diff --git a/astrbot/core/platform/sources/weixin_oc/weixin_oc_client.py b/astrbot/core/platform/sources/weixin_oc/weixin_oc_client.py
index 1ea44df52..cdfb40bb3 100644
--- a/astrbot/core/platform/sources/weixin_oc/weixin_oc_client.py
+++ b/astrbot/core/platform/sources/weixin_oc/weixin_oc_client.py
@@ -46,7 +46,7 @@ class WeixinOCClient:
"Content-Type": "application/json",
"AuthorizationType": "ilink_bot_token",
"X-WECHAT-UIN": base64.b64encode(
- str(random.getrandbits(32)).encode("utf-8")
+ str(random.getrandbits(32)).encode("utf-8"),
).decode("utf-8"),
}
if token_required and self.token:
@@ -120,7 +120,7 @@ class WeixinOCClient:
cdn_url = self._build_cdn_upload_url(upload_param, file_key)
else:
raise ValueError(
- "CDN upload URL missing (need upload_full_url or upload_param)"
+ "CDN upload URL missing (need upload_full_url or upload_param)",
)
raw_data = media_path.read_bytes()
@@ -164,16 +164,16 @@ class WeixinOCClient:
)
if resp.status >= 400 and resp.status < 500:
raise RuntimeError(
- f"upload media to cdn failed: {resp.status} {detail}"
+ f"upload media to cdn failed: {resp.status} {detail}",
)
if resp.status != 200:
raise RuntimeError(
- f"upload media to cdn failed: {resp.status} {detail}"
+ f"upload media to cdn failed: {resp.status} {detail}",
)
download_param = resp.headers.get("x-encrypted-param")
if not download_param:
raise RuntimeError(
- "upload media to cdn failed: missing x-encrypted-param"
+ "upload media to cdn failed: missing x-encrypted-param",
)
return download_param
@@ -188,7 +188,7 @@ class WeixinOCClient:
if resp.status >= 400:
detail = await resp.text()
raise RuntimeError(
- f"download media from cdn failed: {resp.status} {detail}"
+ f"download media from cdn failed: {resp.status} {detail}",
)
return await resp.read()
@@ -234,7 +234,7 @@ class WeixinOCClient:
raise RuntimeError(f"{method} {endpoint} failed: {resp.status} {text}")
if not text:
return {}
- return cast(dict[str, Any], json.loads(text))
+ return cast("dict[str, Any]", json.loads(text))
async def get_typing_config(
self,
diff --git a/astrbot/core/platform/sources/weixin_official_account/weixin_offacc_adapter.py b/astrbot/core/platform/sources/weixin_official_account/weixin_offacc_adapter.py
index 8b646e43f..6be122e6c 100644
--- a/astrbot/core/platform/sources/weixin_official_account/weixin_offacc_adapter.py
+++ b/astrbot/core/platform/sources/weixin_official_account/weixin_offacc_adapter.py
@@ -45,7 +45,7 @@ class WeixinOfficialAccountServer:
user_buffer: dict[Any, dict[str, Any]],
) -> None:
self.server = quart.Quart(__name__)
- self.port = int(cast(int | str, config.get("port")))
+ self.port = int(cast("int | str", config.get("port")))
self.callback_server_host = config.get("callback_server_host", "0.0.0.0")
self.token = config.get("token")
self.encoding_aes_key = config.get("encoding_aes_key")
@@ -85,6 +85,7 @@ class WeixinOfficialAccountServer:
Returns:
验证响应
+
"""
logger.info(f"验证请求有效性: {request.args}")
@@ -117,7 +118,7 @@ class WeixinOfficialAccountServer:
def _preview(self, msg: BaseMessage, limit: int = 24) -> str:
"""生成消息预览文本,供占位符使用"""
if isinstance(msg, TextMessage):
- t = cast(str, msg.content).strip()
+ t = cast("str", msg.content).strip()
return (t[:limit] + "...") if len(t) > limit else (t or "空消息")
if isinstance(msg, ImageMessage):
return "图片"
@@ -133,6 +134,7 @@ class WeixinOfficialAccountServer:
Returns:
响应内容
+
"""
data = await request.get_data()
msg_signature = request.args.get("msg_signature")
@@ -163,7 +165,7 @@ class WeixinOfficialAccountServer:
# passive reply
from_user = str(getattr(msg, "source", ""))
- msg_id = str(cast(str | int, getattr(msg, "id", "")))
+ msg_id = str(cast("str | int", getattr(msg, "id", "")))
state = self.user_buffer.get(from_user)
def _reply_text(text: str) -> str:
@@ -182,13 +184,12 @@ class WeixinOfficialAccountServer:
if len(cached) == 0:
self.user_buffer.pop(from_user, None)
return _reply_text(cached_xml)
- else:
- return _reply_text(
- cached_xml
- + "\n【后续消息还在缓冲中,回复任意文字继续获取】"
- )
+ return _reply_text(
+ cached_xml
+ + "\n【后续消息还在缓冲中,回复任意文字继续获取】",
+ )
- task: asyncio.Task | None = cast(asyncio.Task | None, state.get("task"))
+ task: asyncio.Task | None = cast("asyncio.Task | None", state.get("task"))
placeholder = (
f"【正在思考'{state.get('preview', '...')}'中,已思考"
f"{int(time.monotonic() - state.get('started_at', time.monotonic()))}s,回复任意文字尝试获取回复】"
@@ -207,36 +208,35 @@ class WeixinOfficialAccountServer:
# send one cached each time, if cached is empty after pop, remove the buffer
if cached and len(cached) > 0:
logger.info(
- f"wx buffer hit on retry window: user={from_user}"
+ f"wx buffer hit on retry window: user={from_user}",
)
cached_xml = cached.pop(0)
if len(cached) == 0:
self.user_buffer.pop(from_user, None)
logger.debug(
- f"wx finished message sending in passive window: user={from_user} msg_id={msg_id} "
+ f"wx finished message sending in passive window: user={from_user} msg_id={msg_id} ",
)
return _reply_text(cached_xml)
- else:
- logger.debug(
- f"wx finished message sending in passive window but not final: user={from_user} msg_id={msg_id} "
- )
- return _reply_text(
- cached_xml
- + "\n【后续消息还在缓冲中,回复任意文字继续获取】"
- )
+ logger.debug(
+ f"wx finished message sending in passive window but not final: user={from_user} msg_id={msg_id} ",
+ )
+ return _reply_text(
+ cached_xml
+ + "\n【后续消息还在缓冲中,回复任意文字继续获取】",
+ )
logger.info(
- f"wx finished in window but not final; return placeholder: user={from_user} msg_id={msg_id} "
+ f"wx finished in window but not final; return placeholder: user={from_user} msg_id={msg_id} ",
)
return _reply_text(placeholder)
except Exception:
logger.critical(
- "wx task failed in passive window", exc_info=True
+ "wx task failed in passive window", exc_info=True,
)
self.user_buffer.pop(from_user, None)
return _reply_text("处理消息失败,请稍后再试。")
logger.info(
- f"wx passive window timeout: user={from_user} msg_id={msg_id}"
+ f"wx passive window timeout: user={from_user} msg_id={msg_id}",
)
return _reply_text(placeholder)
@@ -250,7 +250,7 @@ class WeixinOfficialAccountServer:
f"【正在思考'{preview}'中,已思考0s,回复任意文字尝试获取回复】"
)
logger.info(
- f"wx start task: user={from_user} msg_id={msg_id} preview={preview}"
+ f"wx start task: user={from_user} msg_id={msg_id} preview={preview}",
)
self.user_buffer[from_user] = state = {
@@ -261,7 +261,7 @@ class WeixinOfficialAccountServer:
"started_at": time.monotonic(),
}
self.user_buffer[from_user]["task"] = task = asyncio.create_task(
- self.callback(msg)
+ self.callback(msg),
)
# immediate return if done
@@ -272,7 +272,7 @@ class WeixinOfficialAccountServer:
)
if done:
try:
- cached = state.get("cached_xml", None)
+ cached = state.get("cached_xml")
# send one cached each time, if cached is empty after pop, remove the buffer
if cached and len(cached) > 0:
logger.info(f"wx buffer hit immediately: user={from_user}")
@@ -280,13 +280,12 @@ class WeixinOfficialAccountServer:
if len(cached) == 0:
self.user_buffer.pop(from_user, None)
return _reply_text(cached_xml)
- else:
- return _reply_text(
- cached_xml
- + "\n【后续消息还在缓冲中,回复任意文字继续获取】"
- )
+ return _reply_text(
+ cached_xml
+ + "\n【后续消息还在缓冲中,回复任意文字继续获取】",
+ )
logger.info(
- f"wx not finished in first window; return placeholder: user={from_user} msg_id={msg_id} "
+ f"wx not finished in first window; return placeholder: user={from_user} msg_id={msg_id} ",
)
return _reply_text(placeholder)
except Exception:
@@ -312,7 +311,7 @@ class WeixinOfficialAccountServer:
@register_platform_adapter(
- "weixin_official_account", "微信公众平台 适配器", support_streaming_message=False
+ "weixin_official_account", "微信公众平台 适配器", support_streaming_message=False,
)
class WeixinOfficialAccountPlatformAdapter(Platform):
def __init__(
@@ -342,7 +341,7 @@ class WeixinOfficialAccountPlatformAdapter(Platform):
self.user_buffer: dict[str, dict[str, Any]] = {} # from_user -> state
self.server = WeixinOfficialAccountServer(
- self._event_queue, self.config, self.user_buffer
+ self._event_queue, self.config, self.user_buffer,
)
self.client = WeChatClient(
@@ -362,7 +361,7 @@ class WeixinOfficialAccountPlatformAdapter(Platform):
await self.convert_message(msg, None)
return None
- msg_id = str(cast(str | int, msg.id))
+ msg_id = str(cast("str | int", msg.id))
future = self.wexin_event_workers.get(msg_id)
if future:
logger.debug(f"duplicate message id checked: {msg.id}")
@@ -383,7 +382,7 @@ class WeixinOfficialAccountPlatformAdapter(Platform):
except Exception as e:
logger.error(f"转换消息时出现异常: {e}")
finally:
- self.wexin_event_workers.pop(str(cast(str | int, msg.id)), None)
+ self.wexin_event_workers.pop(str(cast("str | int", msg.id)), None)
self.server.callback = callback
self.server.active_send_mode = self.active_send_mode
@@ -422,8 +421,7 @@ class WeixinOfficialAccountPlatformAdapter(Platform):
# 根据请求方法分发到不同的处理函数
if request.method == "GET":
return await self.server.handle_verify(request)
- else:
- return await self.server.handle_callback(request)
+ return await self.server.handle_callback(request)
async def convert_message(
self,
@@ -432,29 +430,29 @@ class WeixinOfficialAccountPlatformAdapter(Platform):
) -> AstrBotMessage | None:
abm = AstrBotMessage()
if isinstance(msg, TextMessage):
- abm.message_str = cast(str, msg.content)
+ abm.message_str = cast("str", msg.content)
abm.self_id = str(msg.target)
- abm.message = [Plain(cast(str, msg.content))]
+ abm.message = [Plain(cast("str", msg.content))]
abm.type = MessageType.FRIEND_MESSAGE
abm.sender = MessageMember(
- cast(str, msg.source),
- cast(str, msg.source),
+ cast("str", msg.source),
+ cast("str", msg.source),
)
- abm.message_id = str(cast(str | int, msg.id))
- abm.timestamp = cast(int, msg.time)
+ abm.message_id = str(cast("str | int", msg.id))
+ abm.timestamp = cast("int", msg.time)
abm.session_id = abm.sender.user_id
elif msg.type == "image":
assert isinstance(msg, ImageMessage)
abm.message_str = "[图片]"
abm.self_id = str(msg.target)
- abm.message = [Image(file=cast(str, msg.image), url=cast(str, msg.image))]
+ abm.message = [Image(file=cast("str", msg.image), url=cast("str", msg.image))]
abm.type = MessageType.FRIEND_MESSAGE
abm.sender = MessageMember(
- cast(str, msg.source),
- cast(str, msg.source),
+ cast("str", msg.source),
+ cast("str", msg.source),
)
- abm.message_id = str(cast(str | int, msg.id))
- abm.timestamp = cast(int, msg.time)
+ abm.message_id = str(cast("str | int", msg.id))
+ abm.timestamp = cast("int", msg.time)
abm.session_id = abm.sender.user_id
elif msg.type == "voice":
assert isinstance(msg, VoiceMessage)
@@ -487,11 +485,11 @@ class WeixinOfficialAccountPlatformAdapter(Platform):
abm.message = [Record(file=path_wav, url=path_wav)]
abm.type = MessageType.FRIEND_MESSAGE
abm.sender = MessageMember(
- cast(str, msg.source),
- cast(str, msg.source),
+ cast("str", msg.source),
+ cast("str", msg.source),
)
- abm.message_id = str(cast(str | int, msg.id))
- abm.timestamp = cast(int, msg.time)
+ abm.message_id = str(cast("str | int", msg.id))
+ abm.timestamp = cast("int", msg.time)
abm.session_id = abm.sender.user_id
else:
logger.warning(f"暂未实现的事件: {msg.type}")
@@ -511,7 +509,7 @@ class WeixinOfficialAccountPlatformAdapter(Platform):
buffer = self.user_buffer.get(message.sender.user_id, None)
if buffer is None:
logger.critical(
- f"用户消息未找到缓冲状态,无法处理消息: user={message.sender.user_id} message_id={message.message_id}"
+ f"用户消息未找到缓冲状态,无法处理消息: user={message.sender.user_id} message_id={message.message_id}",
)
return
message_event = WeixinOfficialAccountPlatformEvent(
diff --git a/astrbot/core/platform/sources/weixin_official_account/weixin_offacc_event.py b/astrbot/core/platform/sources/weixin_official_account/weixin_offacc_event.py
index ae536593c..f0053933e 100644
--- a/astrbot/core/platform/sources/weixin_official_account/weixin_offacc_event.py
+++ b/astrbot/core/platform/sources/weixin_official_account/weixin_offacc_event.py
@@ -82,8 +82,8 @@ class WeixinOfficialAccountPlatformEvent(AstrMessageEvent):
async def send(self, message: MessageChain) -> None:
message_obj = self.message_obj
- active_send_mode = cast(dict, message_obj.raw_message).get(
- "active_send_mode", False
+ active_send_mode = cast("dict", message_obj.raw_message).get(
+ "active_send_mode", False,
)
for comp in message.chain:
if isinstance(comp, Plain):
@@ -95,7 +95,7 @@ class WeixinOfficialAccountPlatformEvent(AstrMessageEvent):
else:
# disable passive sending, just store the chunks in
logger.debug(
- f"split plain into {len(plain_chunks)} chunks for passive reply. Message not sent."
+ f"split plain into {len(plain_chunks)} chunks for passive reply. Message not sent.",
)
self.message_out["cached_xml"] = plain_chunks
elif isinstance(comp, Image):
@@ -120,10 +120,10 @@ class WeixinOfficialAccountPlatformEvent(AstrMessageEvent):
else:
reply = ImageReply(
media_id=response["media_id"],
- message=cast(dict, self.message_obj.raw_message)["message"],
+ message=cast("dict", self.message_obj.raw_message)["message"],
)
xml = reply.render()
- future = cast(dict, self.message_obj.raw_message)["future"]
+ future = cast("dict", self.message_obj.raw_message)["future"]
assert isinstance(future, asyncio.Future)
future.set_result(xml)
@@ -139,7 +139,7 @@ class WeixinOfficialAccountPlatformEvent(AstrMessageEvent):
logger.error(f"微信公众平台上传语音失败: {e}")
await self.send(
MessageChain().message(
- f"微信公众平台上传语音失败: {e}"
+ f"微信公众平台上传语音失败: {e}",
),
)
return
@@ -153,17 +153,17 @@ class WeixinOfficialAccountPlatformEvent(AstrMessageEvent):
else:
reply = VoiceReply(
media_id=response["media_id"],
- message=cast(dict, self.message_obj.raw_message)[
+ message=cast("dict", self.message_obj.raw_message)[
"message"
],
)
xml = reply.render()
- future = cast(dict, self.message_obj.raw_message)["future"]
+ future = cast("dict", self.message_obj.raw_message)["future"]
assert isinstance(future, asyncio.Future)
future.set_result(xml)
finally:
if record_path_amr != record_path and os.path.exists(
- record_path_amr
+ record_path_amr,
):
try:
os.remove(record_path_amr)
diff --git a/astrbot/core/platform_message_history_mgr.py b/astrbot/core/platform_message_history_mgr.py
index ad8bb44f6..37a6feb3d 100644
--- a/astrbot/core/platform_message_history_mgr.py
+++ b/astrbot/core/platform_message_history_mgr.py
@@ -41,7 +41,7 @@ class PlatformMessageHistoryManager:
return history
async def delete(
- self, platform_id: str, user_id: str, offset_sec: int = 86400
+ self, platform_id: str, user_id: str, offset_sec: int = 86400,
) -> None:
"""Delete platform message history records older than the specified offset."""
await self.db.delete_platform_message_offset(
diff --git a/astrbot/core/provider/entities.py b/astrbot/core/provider/entities.py
index b27775cdd..42b8743c2 100644
--- a/astrbot/core/provider/entities.py
+++ b/astrbot/core/provider/entities.py
@@ -479,7 +479,7 @@ class LLMResponse:
),
# the extra_content will not serialize if it's None when calling ToolCall.model_dump()
extra_content=self.tools_call_extra_content.get(
- self.tools_call_ids[idx]
+ self.tools_call_ids[idx],
),
),
)
diff --git a/astrbot/core/provider/func_tool_manager.py b/astrbot/core/provider/func_tool_manager.py
index bf16a3ec9..67f4c6987 100644
--- a/astrbot/core/provider/func_tool_manager.py
+++ b/astrbot/core/provider/func_tool_manager.py
@@ -88,7 +88,7 @@ class _MCPClientDictView(Mapping[str, MCPClient]):
def _resolve_timeout(
- timeout: float | int | str | None = None,
+ timeout: float | str | None = None,
*,
env_name: str = MCP_INIT_TIMEOUT_ENV,
default: float = DEFAULT_MCP_INIT_TIMEOUT_SECONDS,
@@ -104,20 +104,20 @@ def _resolve_timeout(
timeout_value = float(timeout)
except (TypeError, ValueError):
logger.warning(
- f"超时配置({source})={timeout!r} 无效,使用默认值 {default:g} 秒。"
+ f"超时配置({source})={timeout!r} 无效,使用默认值 {default:g} 秒。",
)
return default
if timeout_value <= 0:
logger.warning(
- f"超时配置({source})={timeout_value:g} 必须大于 0,使用默认值 {default:g} 秒。"
+ f"超时配置({source})={timeout_value:g} 必须大于 0,使用默认值 {default:g} 秒。",
)
return default
if timeout_value > MAX_MCP_TIMEOUT_SECONDS:
logger.warning(
f"超时配置({source})={timeout_value:g} 过大,已限制为最大值 "
- f"{MAX_MCP_TIMEOUT_SECONDS:g} 秒,以避免长时间等待。"
+ f"{MAX_MCP_TIMEOUT_SECONDS:g} 秒,以避免长时间等待。",
)
return MAX_MCP_TIMEOUT_SECONDS
@@ -415,7 +415,7 @@ class FunctionToolManager:
logger.debug(f" 主机: {scheme}://{host}{port}")
async def init_mcp_clients(
- self, raise_on_all_failed: bool = False
+ self, raise_on_all_failed: bool = False,
) -> MCPInitSummary:
"""从项目根目录读取 mcp_server.json 文件,初始化 MCP 服务列表。文件格式如下:
```
@@ -487,7 +487,7 @@ class FunctionToolManager:
if isinstance(result, Exception):
if isinstance(result, MCPInitTimeoutError):
logger.error(
- f"Connected to MCP server {name} timeout ({timeout_display} seconds)"
+ f"Connected to MCP server {name} timeout ({timeout_display} seconds)",
)
else:
logger.error(f"Failed to initialize MCP server {name}: {result}")
@@ -502,14 +502,14 @@ class FunctionToolManager:
if failed_services:
logger.warning(
f"The following MCP services failed to initialize: {', '.join(failed_services)}. "
- f"Please check the mcp_server.json file and server availability."
+ f"Please check the mcp_server.json file and server availability.",
)
summary = MCPInitSummary(
- total=len(active_configs), success=success_count, failed=failed_services
+ total=len(active_configs), success=success_count, failed=failed_services,
)
logger.info(
- f"MCP services initialization completed: {summary.success}/{summary.total} successful, {len(summary.failed)} failed."
+ f"MCP services initialization completed: {summary.success}/{summary.total} successful, {len(summary.failed)} failed.",
)
if summary.total > 0 and summary.success == 0:
msg = "All MCP services failed to initialize, please check the mcp_server.json and server availability."
@@ -534,7 +534,7 @@ class FunctionToolManager:
async with self._runtime_lock:
if name in self._mcp_server_runtime or name in self._mcp_starting:
logger.warning(
- f"Connected to MCP server {name}, ignoring this startup request (timeout={timeout:g})."
+ f"Connected to MCP server {name}, ignoring this startup request (timeout={timeout:g}).",
)
self._log_safe_mcp_debug_config(cfg)
return
@@ -551,7 +551,7 @@ class FunctionToolManager:
)
except asyncio.TimeoutError as exc:
raise MCPInitTimeoutError(
- f"Connected to MCP server {name} timeout ({timeout:g} seconds)"
+ f"Connected to MCP server {name} timeout ({timeout:g} seconds)",
) from exc
except Exception:
logger.error(f"Failed to initialize MCP client {name}", exc_info=True)
@@ -635,14 +635,14 @@ class FunctionToolManager:
return []
async def _cleanup_mcp_client_safely(
- self, mcp_client: MCPClient, name: str
+ self, mcp_client: MCPClient, name: str,
) -> None:
"""安全清理单个 MCP 客户端,避免清理异常中断主流程。"""
try:
await mcp_client.cleanup()
except Exception as cleanup_exc: # noqa: BLE001 - only log here
logger.error(
- f"Failed to cleanup MCP client resources {name}: {cleanup_exc}"
+ f"Failed to cleanup MCP client resources {name}: {cleanup_exc}",
)
async def _init_mcp_client(self, name: str, config: dict) -> MCPClient:
@@ -732,7 +732,7 @@ class FunctionToolManager:
name: str,
config: dict,
shutdown_event: asyncio.Event | None = None,
- timeout: float | int | str | None = None,
+ timeout: float | str | None = None,
) -> None:
"""Enable a new MCP server and initialize it.
@@ -746,6 +746,7 @@ class FunctionToolManager:
Raises:
MCPInitTimeoutError: If initialization does not complete within timeout.
Exception: If there is an error during initialization.
+
"""
if timeout is None:
timeout_value = self._enable_timeout_default
diff --git a/astrbot/core/provider/manager.py b/astrbot/core/provider/manager.py
index c40d851d7..3e5bf8385 100644
--- a/astrbot/core/provider/manager.py
+++ b/astrbot/core/provider/manager.py
@@ -211,7 +211,7 @@ class ProviderManager:
return self.inst_map.get(provider_id)
def get_using_provider(
- self, provider_type: ProviderType, umo=None
+ self, provider_type: ProviderType, umo=None,
) -> Providers | None:
"""获取正在使用的提供商实例。
@@ -269,7 +269,7 @@ class ProviderManager:
if not provider and provider_id:
logger.warning(
- f"没有找到 ID 为 {provider_id} 的提供商,这可能是由于您修改了提供商(模型)ID 导致的。"
+ f"没有找到 ID 为 {provider_id} 的提供商,这可能是由于您修改了提供商(模型)ID 导致的。",
)
return provider
@@ -355,6 +355,7 @@ class ProviderManager:
Raises:
ImportError: 如果提供商类型未知或无法导入对应模块,则抛出异常。
+
"""
match type:
case "openai_chat_completion":
@@ -487,6 +488,7 @@ class ProviderManager:
Returns:
dict: 合并后的 provider 配置,key 为 provider id,value 为合并后的配置字典
+
"""
pc = copy.deepcopy(provider_config)
provider_source_id = pc.get("provider_source_id", "")
@@ -587,7 +589,7 @@ class ProviderManager:
# STT 任务
if not issubclass(cls_type, STTProvider):
raise TypeError(
- f"Provider class {cls_type} is not a subclass of STTProvider"
+ f"Provider class {cls_type} is not a subclass of STTProvider",
)
inst = cls_type(provider_config, self.provider_settings)
@@ -610,7 +612,7 @@ class ProviderManager:
# TTS 任务
if not issubclass(cls_type, TTSProvider):
raise TypeError(
- f"Provider class {cls_type} is not a subclass of TTSProvider"
+ f"Provider class {cls_type} is not a subclass of TTSProvider",
)
inst = cls_type(provider_config, self.provider_settings)
@@ -633,7 +635,7 @@ class ProviderManager:
# 文本生成任务
if not issubclass(cls_type, Provider):
raise TypeError(
- f"Provider class {cls_type} is not a subclass of Provider"
+ f"Provider class {cls_type} is not a subclass of Provider",
)
inst = cls_type(
provider_config,
@@ -658,7 +660,7 @@ class ProviderManager:
case ProviderType.EMBEDDING:
if not issubclass(cls_type, EmbeddingProvider):
raise TypeError(
- f"Provider class {cls_type} is not a subclass of EmbeddingProvider"
+ f"Provider class {cls_type} is not a subclass of EmbeddingProvider",
)
inst = cls_type(provider_config, self.provider_settings)
if isinstance(inst, HasInitialize):
@@ -667,7 +669,7 @@ class ProviderManager:
case ProviderType.RERANK:
if not issubclass(cls_type, RerankProvider):
raise TypeError(
- f"Provider class {cls_type} is not a subclass of RerankProvider"
+ f"Provider class {cls_type} is not a subclass of RerankProvider",
)
inst = cls_type(provider_config, self.provider_settings)
if isinstance(inst, HasInitialize):
@@ -677,7 +679,7 @@ class ProviderManager:
# 未知供应商抛出异常,确保inst初始化
# Should be unreachable
raise Exception(
- f"未知的提供商类型:{provider_metadata.provider_type}"
+ f"未知的提供商类型:{provider_metadata.provider_type}",
)
self.inst_map[provider_config["id"]] = inst
@@ -770,7 +772,7 @@ class ProviderManager:
del self.inst_map[provider_id]
async def delete_provider(
- self, provider_id: str | None = None, provider_source_id: str | None = None
+ self, provider_id: str | None = None, provider_source_id: str | None = None,
) -> None:
"""Delete provider and/or provider source from config and terminate the instances. Config will be saved after deletion."""
async with self.resource_lock:
@@ -794,7 +796,7 @@ class ProviderManager:
async def update_provider(self, origin_provider_id: str, new_config: dict) -> None:
"""Update provider config and reload the instance. Config will be saved after update."""
async with self.resource_lock:
- npid = new_config.get("id", None)
+ npid = new_config.get("id")
if not npid:
raise ValueError("New provider config must have an 'id' field")
config = self.acm.default_conf
@@ -818,7 +820,7 @@ class ProviderManager:
async def create_provider(self, new_config: dict) -> None:
"""Add new provider config and load the instance. Config will be saved after addition."""
async with self.resource_lock:
- npid = new_config.get("id", None)
+ npid = new_config.get("id")
if not npid:
raise ValueError("New provider config must have an 'id' field")
config = self.acm.default_conf
diff --git a/astrbot/core/provider/provider.py b/astrbot/core/provider/provider.py
index f2571b506..9aaa1ae5a 100644
--- a/astrbot/core/provider/provider.py
+++ b/astrbot/core/provider/provider.py
@@ -55,12 +55,12 @@ class AbstractProvider(abc.ABC):
return meta
async def test(self) -> None:
- """test the provider is a
+ """Test the provider is a
- raises:
+ Raises:
Exception: if the provider is not available
+
"""
- ...
class Provider(AbstractProvider):
@@ -165,7 +165,7 @@ class Provider(AbstractProvider):
"""
if False: # pragma: no cover - make this an async generator for typing
yield None # type: ignore
- raise NotImplementedError()
+ raise NotImplementedError
async def pop_record(self, context: list) -> None:
"""弹出 context 第一条非系统提示词对话记录"""
@@ -239,6 +239,7 @@ class TTSProvider(AbstractProvider):
Notes:
子类可以重写此方法返回 True 来启用流式 TTS 支持
+
"""
return False
@@ -265,6 +266,7 @@ class TTSProvider(AbstractProvider):
- 默认实现会将文本累积后一次性调用 get_audio 生成完整音频
- 子类可以重写此方法实现真正的流式 TTS
- 音频数据应该是 WAV 格式的 bytes
+
"""
accumulated_text = ""
@@ -301,7 +303,7 @@ class TTSProvider(AbstractProvider):
if file_size == 0:
raise Exception(
"TTS test failed: generated audio file is empty (0 bytes). "
- "Please check your TTS provider configuration, especially required parameters like group_id for MiniMax."
+ "Please check your TTS provider configuration, especially required parameters like group_id for MiniMax.",
)
# 清理测试文件
diff --git a/astrbot/core/provider/sources/anthropic_source.py b/astrbot/core/provider/sources/anthropic_source.py
index 83f2e16db..ffdfd40e1 100644
--- a/astrbot/core/provider/sources/anthropic_source.py
+++ b/astrbot/core/provider/sources/anthropic_source.py
@@ -44,7 +44,7 @@ class ProviderAnthropic(Provider):
return
raise EmptyModelOutputError(
"Anthropic completion has no usable output. "
- f"completion_id={completion_id}, stop_reason={stop_reason}"
+ f"completion_id={completion_id}, stop_reason={stop_reason}",
)
@staticmethod
@@ -222,7 +222,7 @@ class ProviderAnthropic(Provider):
# Detect actual image format from binary data
image_bytes = base64.b64decode(base64_data)
media_type = self._detect_image_mime_type(
- image_bytes
+ image_bytes,
)
converted_content.append(
{
@@ -232,22 +232,22 @@ class ProviderAnthropic(Provider):
"media_type": media_type,
"data": base64_data,
},
- }
+ },
)
except ValueError:
logger.warning(
- f"Failed to parse image data URI: {url[:50]}..."
+ f"Failed to parse image data URI: {url[:50]}...",
)
else:
logger.warning(
- f"Unsupported image URL format for Anthropic: {url[:50]}..."
+ f"Unsupported image URL format for Anthropic: {url[:50]}...",
)
elif part.get("type") == "audio_url":
converted_content.append(
{
"type": "text",
"text": "[Audio Attachment]",
- }
+ },
)
else:
converted_content.append(part)
@@ -255,7 +255,7 @@ class ProviderAnthropic(Provider):
{
"role": "user",
"content": converted_content,
- }
+ },
)
else:
new_messages.append(message)
@@ -287,7 +287,7 @@ class ProviderAnthropic(Provider):
payloads["tool_choice"] = {
"type": "any"
if payloads.get("tool_choice") == "required"
- else "auto"
+ else "auto",
}
extra_body = self.provider_config.get("custom_extra_body", {})
@@ -298,7 +298,7 @@ class ProviderAnthropic(Provider):
try:
completion = await self.client.messages.create(
- **payloads, stream=False, extra_body=extra_body
+ **payloads, stream=False, extra_body=extra_body,
)
except httpx.RequestError as e:
proxy = self.provider_config.get("proxy", "")
@@ -315,7 +315,7 @@ class ProviderAnthropic(Provider):
if len(completion.content) == 0:
raise EmptyModelOutputError(
- f"Anthropic completion is empty. completion_id={completion.id}"
+ f"Anthropic completion is empty. completion_id={completion.id}",
)
llm_response = LLMResponse(role="assistant")
@@ -346,13 +346,13 @@ class ProviderAnthropic(Provider):
if not llm_response.reasoning_content:
raise EmptyModelOutputError(
"Anthropic completion has no usable output. "
- f"completion_id={completion.id}, stop_reason={completion.stop_reason}"
+ f"completion_id={completion.id}, stop_reason={completion.stop_reason}",
)
# We have reasoning content (ThinkingBlock) - this is valid
stop_reason = getattr(completion, "stop_reason", "unknown")
logger.debug(
- f"Completion contains only ThinkingBlock (stop_reason={stop_reason})"
+ f"Completion contains only ThinkingBlock (stop_reason={stop_reason})",
)
llm_response.completion_text = "" # Ensure empty string, not None
@@ -374,7 +374,7 @@ class ProviderAnthropic(Provider):
payloads["tool_choice"] = {
"type": "any"
if payloads.get("tool_choice") == "required"
- else "auto"
+ else "auto",
}
# 用于累积工具调用信息
@@ -393,7 +393,7 @@ class ProviderAnthropic(Provider):
self._apply_thinking_config(payloads)
async with self.client.messages.stream(
- **payloads, extra_body=extra_body
+ **payloads, extra_body=extra_body,
) as stream:
assert isinstance(stream, anthropic.AsyncMessageStream)
async for event in stream:
diff --git a/astrbot/core/provider/sources/azure_tts_source.py b/astrbot/core/provider/sources/azure_tts_source.py
index fc2bb6c09..57ea9fbb4 100644
--- a/astrbot/core/provider/sources/azure_tts_source.py
+++ b/astrbot/core/provider/sources/azure_tts_source.py
@@ -41,13 +41,13 @@ class OTTSProvider:
def client(self) -> AsyncClient:
if self._client is None:
raise RuntimeError(
- "Client not initialized. Please use 'async with' context."
+ "Client not initialized. Please use 'async with' context.",
)
return self._client
async def __aenter__(self):
self._client = AsyncClient(
- timeout=self.timeout, proxy=self.proxy if self.proxy else None
+ timeout=self.timeout, proxy=self.proxy or None,
)
return self
@@ -141,7 +141,7 @@ class AzureNativeProvider(TTSProvider):
def client(self) -> AsyncClient:
if self._client is None:
raise RuntimeError(
- "Client not initialized. Please use 'async with' context."
+ "Client not initialized. Please use 'async with' context.",
)
return self._client
@@ -152,7 +152,7 @@ class AzureNativeProvider(TTSProvider):
"Content-Type": "application/ssml+xml",
"X-Microsoft-OutputFormat": "riff-48khz-16bit-mono-pcm",
},
- proxy=self.proxy if self.proxy else None,
+ proxy=self.proxy or None,
)
return self
@@ -213,7 +213,7 @@ class AzureTTSProvider(TTSProvider):
self.provider = self._parse_provider(key_value, provider_config)
def _parse_provider(
- self, key_value: str, config: dict
+ self, key_value: str, config: dict,
) -> OTTSProvider | AzureNativeProvider:
if key_value.lower().startswith("other["):
json_str = ""
diff --git a/astrbot/core/provider/sources/bailian_rerank_source.py b/astrbot/core/provider/sources/bailian_rerank_source.py
index 4513c2c85..17ec5bbdf 100644
--- a/astrbot/core/provider/sources/bailian_rerank_source.py
+++ b/astrbot/core/provider/sources/bailian_rerank_source.py
@@ -12,23 +12,20 @@ from ..register import register_provider_adapter
class BailianRerankError(Exception):
"""百炼重排序服务异常基类"""
- pass
class BailianAPIError(BailianRerankError):
"""百炼API返回错误"""
- pass
class BailianNetworkError(BailianRerankError):
"""百炼网络请求错误"""
- pass
@register_provider_adapter(
- "bailian_rerank", "阿里云百炼文本排序适配器", provider_type=ProviderType.RERANK
+ "bailian_rerank", "阿里云百炼文本排序适配器", provider_type=ProviderType.RERANK,
)
class BailianRerankProvider(RerankProvider):
"""阿里云百炼文本重排序适配器."""
@@ -42,7 +39,7 @@ class BailianRerankProvider(RerankProvider):
# API配置
self.api_key = provider_config.get("rerank_api_key") or os.getenv(
- "DASHSCOPE_API_KEY", ""
+ "DASHSCOPE_API_KEY", "",
)
if not self.api_key:
raise ValueError("阿里云百炼 API Key 不能为空。")
@@ -64,7 +61,7 @@ class BailianRerankProvider(RerankProvider):
}
self.client = aiohttp.ClientSession(
- headers=headers, timeout=aiohttp.ClientTimeout(total=self.timeout)
+ headers=headers, timeout=aiohttp.ClientTimeout(total=self.timeout),
)
# 设置模型名称
@@ -73,7 +70,7 @@ class BailianRerankProvider(RerankProvider):
logger.info(f"AstrBot 百炼 Rerank 初始化完成。模型: {self.model}")
def _build_payload(
- self, query: str, documents: list[str], top_n: int | None
+ self, query: str, documents: list[str], top_n: int | None,
) -> dict:
"""构建请求载荷
@@ -84,6 +81,7 @@ class BailianRerankProvider(RerankProvider):
Returns:
请求载荷字典
+
"""
normalized_model = self.model.strip().lower()
normalized_top_n = top_n if top_n is not None and top_n > 0 else None
@@ -103,7 +101,7 @@ class BailianRerankProvider(RerankProvider):
if self.return_documents:
logger.warning(
"qwen3-rerank does not support return_documents; "
- "this option will be ignored."
+ "this option will be ignored.",
)
return payload
@@ -135,11 +133,12 @@ class BailianRerankProvider(RerankProvider):
Raises:
BailianAPIError: API返回错误
KeyError: 结果缺少必要字段
+
"""
# 检查响应状态
if data.get("code", "200") != "200":
raise BailianAPIError(
- f"百炼 API 错误: {data.get('code')} – {data.get('message', '')}"
+ f"百炼 API 错误: {data.get('code')} – {data.get('message', '')}",
)
# 兼容旧版 API (output.results) 和新版 compatible API (results)
@@ -160,7 +159,7 @@ class BailianRerankProvider(RerankProvider):
relevance_score = 0.0
rerank_result = RerankResult(
- index=index, relevance_score=relevance_score
+ index=index, relevance_score=relevance_score,
)
rerank_results.append(rerank_result)
except Exception as e:
@@ -174,6 +173,7 @@ class BailianRerankProvider(RerankProvider):
Args:
data: API响应数据
+
"""
tokens = data.get("usage", {}).get("total_tokens", 0)
if tokens > 0:
@@ -185,8 +185,7 @@ class BailianRerankProvider(RerankProvider):
documents: list[str],
top_n: int | None = None,
) -> list[RerankResult]:
- """
- 对文档进行重排序
+ """对文档进行重排序
Args:
query: 查询文本
@@ -195,6 +194,7 @@ class BailianRerankProvider(RerankProvider):
Returns:
重排序结果列表
+
"""
if not self.client:
logger.error("百炼 Rerank 客户端会话已关闭,返回空结果")
@@ -211,7 +211,7 @@ class BailianRerankProvider(RerankProvider):
# 检查限制
if len(documents) > 500:
logger.warning(
- f"文档数量({len(documents)})超过限制(500),将截断前500个文档"
+ f"文档数量({len(documents)})超过限制(500),将截断前500个文档",
)
documents = documents[:500]
@@ -220,7 +220,7 @@ class BailianRerankProvider(RerankProvider):
payload = self._build_payload(query, documents, top_n)
logger.debug(
- f"百炼 Rerank 请求: query='{query[:50]}...', 文档数量={len(documents)}"
+ f"百炼 Rerank 请求: query='{query[:50]}...', 文档数量={len(documents)}",
)
# 发送请求
diff --git a/astrbot/core/provider/sources/fishaudio_tts_api_source.py b/astrbot/core/provider/sources/fishaudio_tts_api_source.py
index 35945b7b6..645cd050b 100644
--- a/astrbot/core/provider/sources/fishaudio_tts_api_source.py
+++ b/astrbot/core/provider/sources/fishaudio_tts_api_source.py
@@ -85,7 +85,7 @@ class ProviderFishAudioTTSAPI(TTSProvider):
sort_options = ["score", "task_count", "created_at"]
async with AsyncClient(
base_url=self.api_base.replace("/v1", ""),
- proxy=self.proxy if self.proxy else None,
+ proxy=self.proxy or None,
) as client:
for sort_by in sort_options:
params = {"title": character, "sort_by": sort_by}
@@ -149,7 +149,7 @@ class ProviderFishAudioTTSAPI(TTSProvider):
async with AsyncClient(
base_url=self.api_base,
timeout=self.timeout,
- proxy=self.proxy if self.proxy else None,
+ proxy=self.proxy or None,
).stream(
"POST",
"/tts",
@@ -157,7 +157,7 @@ class ProviderFishAudioTTSAPI(TTSProvider):
content=ormsgpack.packb(request, option=ormsgpack.OPT_SERIALIZE_PYDANTIC),
) as response:
if response.status_code == 200 and response.headers.get(
- "content-type", ""
+ "content-type", "",
).startswith("audio/"):
with open(path, "wb") as f:
async for chunk in response.aiter_bytes():
@@ -166,5 +166,5 @@ class ProviderFishAudioTTSAPI(TTSProvider):
error_bytes = await response.aread()
error_text = error_bytes.decode("utf-8", errors="replace")[:1024]
raise Exception(
- f"Fish Audio API请求失败: 状态码 {response.status_code}, 响应内容: {error_text}"
+ f"Fish Audio API请求失败: 状态码 {response.status_code}, 响应内容: {error_text}",
)
diff --git a/astrbot/core/provider/sources/gemini_embedding_source.py b/astrbot/core/provider/sources/gemini_embedding_source.py
index 61ba9cadb..d8f67eb4e 100644
--- a/astrbot/core/provider/sources/gemini_embedding_source.py
+++ b/astrbot/core/provider/sources/gemini_embedding_source.py
@@ -63,7 +63,7 @@ class GeminiEmbeddingProvider(EmbeddingProvider):
try:
result = await self.client.models.embed_content(
model=self.model,
- contents=cast(types.ContentListUnion, text),
+ contents=cast("types.ContentListUnion", text),
config=types.EmbedContentConfig(
output_dimensionality=self.get_dim(),
),
diff --git a/astrbot/core/provider/sources/gemini_source.py b/astrbot/core/provider/sources/gemini_source.py
index 158170d51..da8c047f5 100644
--- a/astrbot/core/provider/sources/gemini_source.py
+++ b/astrbot/core/provider/sources/gemini_source.py
@@ -155,7 +155,7 @@ class ProviderGoogleGenAI(Provider):
modalities = ["TEXT"]
tool_list: list[types.Tool] | None = []
- model_name = cast(str, payloads.get("model", self.get_model()))
+ model_name = cast("str", payloads.get("model", self.get_model()))
native_coderunner = self.provider_config.get("gm_native_coderunner", False)
native_search = self.provider_config.get("gm_native_search", False)
url_context = self.provider_config.get("gm_url_context", False)
@@ -223,8 +223,8 @@ class ProviderGoogleGenAI(Provider):
types.FunctionCallingConfigMode.ANY
if tool_choice == "required"
else types.FunctionCallingConfigMode.AUTO
- )
- )
+ ),
+ ),
)
# oper thinking config
@@ -241,7 +241,7 @@ class ProviderGoogleGenAI(Provider):
]:
# The thinkingBudget parameter, introduced with the Gemini 2.5 series
thinking_budget = self.provider_config.get("gm_thinking_config", {}).get(
- "budget", 0
+ "budget", 0,
)
if thinking_budget is not None:
thinking_config = types.ThinkingConfig(
@@ -253,19 +253,19 @@ class ProviderGoogleGenAI(Provider):
# covered without needing to keep an exhaustive list up to date.
# Gemini 2.5 series models don't support thinkingLevel; use thinkingBudget instead.
thinking_level = self.provider_config.get("gm_thinking_config", {}).get(
- "level", "HIGH"
+ "level", "HIGH",
)
if thinking_level and isinstance(thinking_level, str):
thinking_level = thinking_level.upper()
if thinking_level not in ["MINIMAL", "LOW", "MEDIUM", "HIGH"]:
logger.warning(
- f"Invalid thinking level: {thinking_level}, using HIGH"
+ f"Invalid thinking level: {thinking_level}, using HIGH",
)
thinking_level = "HIGH"
level = types.ThinkingLevel(thinking_level)
thinking_config = types.ThinkingConfig()
if not hasattr(types.ThinkingConfig, "thinking_level"):
- setattr(types.ThinkingConfig, "thinking_level", level)
+ types.ThinkingConfig.thinking_level = level
else:
thinking_config.thinking_level = level
@@ -286,9 +286,9 @@ class ProviderGoogleGenAI(Provider):
logprobs=payloads.get("logprobs"),
seed=payloads.get("seed"),
response_modalities=modalities,
- tools=cast(types.ToolListUnion | None, tool_list),
+ tools=cast("types.ToolListUnion | None", tool_list),
tool_config=tool_config,
- safety_settings=self.safety_settings if self.safety_settings else None,
+ safety_settings=self.safety_settings or None,
thinking_config=thinking_config,
automatic_function_calling=types.AutomaticFunctionCallingConfig(
disable=True,
@@ -299,7 +299,7 @@ class ProviderGoogleGenAI(Provider):
"""准备 Gemini SDK 的 Content 列表"""
def create_text_part(text: str) -> types.Part:
- content_a = text if text else " "
+ content_a = text or " "
if not text:
logger.warning("文本内容为空,已添加空格占位")
return types.Part.from_text(text=content_a)
@@ -383,7 +383,7 @@ class ProviderGoogleGenAI(Provider):
types.Part(
text=text,
thought_signature=thinking_signature,
- )
+ ),
)
append_or_extend(gemini_contents, parts, types.ModelContent)
@@ -397,7 +397,7 @@ class ProviderGoogleGenAI(Provider):
# we should set thought_signature back to part if exists
# for more info about thought_signature, see:
# https://ai.google.dev/gemini-api/docs/thought-signatures
- if "extra_content" in tool and tool["extra_content"]:
+ if tool.get("extra_content"):
ts_bs64 = (
tool["extra_content"]
.get("google", {})
@@ -445,7 +445,7 @@ class ProviderGoogleGenAI(Provider):
return "".join(thought_buf).strip()
def _extract_usage(
- self, usage_metadata: types.GenerateContentResponseUsageMetadata
+ self, usage_metadata: types.GenerateContentResponseUsageMetadata,
) -> TokenUsage:
"""Extract usage from candidate"""
return TokenUsage(
@@ -468,7 +468,7 @@ class ProviderGoogleGenAI(Provider):
return
raise EmptyModelOutputError(
"Gemini completion has no usable output. "
- f"response_id={response_id}, finish_reason={finish_reason}"
+ f"response_id={response_id}, finish_reason={finish_reason}",
)
def _process_content_parts(
@@ -484,7 +484,7 @@ class ProviderGoogleGenAI(Provider):
if validate_output:
raise EmptyModelOutputError(
"Gemini candidate content is empty. "
- f"finish_reason={candidate.finish_reason}"
+ f"finish_reason={candidate.finish_reason}",
)
llm_response.result_chain = MessageChain(chain=[])
return llm_response.result_chain
@@ -512,7 +512,7 @@ class ProviderGoogleGenAI(Provider):
if validate_output:
raise EmptyModelOutputError(
"Gemini candidate content parts are empty. "
- f"finish_reason={candidate.finish_reason}"
+ f"finish_reason={candidate.finish_reason}",
)
llm_response.result_chain = MessageChain(chain=[])
return llm_response.result_chain
@@ -556,7 +556,7 @@ class ProviderGoogleGenAI(Provider):
if part.thought_signature:
ts_bs64 = base64.b64encode(part.thought_signature).decode("utf-8")
llm_response.tools_call_extra_content[tool_call_id] = {
- "google": {"thought_signature": ts_bs64}
+ "google": {"thought_signature": ts_bs64},
}
if (
@@ -609,7 +609,7 @@ class ProviderGoogleGenAI(Provider):
)
result = await self.client.models.generate_content(
model=model,
- contents=cast(types.ContentListUnion, conversation),
+ contents=cast("types.ContentListUnion", conversation),
config=config,
)
logger.debug(f"genai result: {result}")
@@ -689,7 +689,7 @@ class ProviderGoogleGenAI(Provider):
)
result = await self.client.models.generate_content_stream(
model=model,
- contents=cast(types.ContentListUnion, conversation),
+ contents=cast("types.ContentListUnion", conversation),
config=config,
)
break
@@ -970,7 +970,7 @@ class ProviderGoogleGenAI(Provider):
temp_dir = Path(get_astrbot_temp_path())
temp_dir.mkdir(parents=True, exist_ok=True)
resolved_path = str(
- temp_dir / f"provider_audio_{uuid.uuid4().hex}{suffix}"
+ temp_dir / f"provider_audio_{uuid.uuid4().hex}{suffix}",
)
await download_file(audio_path, resolved_path)
elif audio_path.startswith("file:///"):
@@ -987,7 +987,7 @@ class ProviderGoogleGenAI(Provider):
audio_bytes = Path(resolved_path).read_bytes()
except OSError as exc:
logger.warning(
- f"Failed to read audio file {resolved_path}, skipping. Error: {exc}"
+ f"Failed to read audio file {resolved_path}, skipping. Error: {exc}",
)
return None
diff --git a/astrbot/core/provider/sources/groq_source.py b/astrbot/core/provider/sources/groq_source.py
index af4029f67..fb8b77c03 100644
--- a/astrbot/core/provider/sources/groq_source.py
+++ b/astrbot/core/provider/sources/groq_source.py
@@ -3,7 +3,7 @@ from .openai_source import ProviderOpenAIOfficial
@register_provider_adapter(
- "groq_chat_completion", "Groq Chat Completion Provider Adapter"
+ "groq_chat_completion", "Groq Chat Completion Provider Adapter",
)
class ProviderGroq(ProviderOpenAIOfficial):
def __init__(
diff --git a/astrbot/core/provider/sources/longcat_source.py b/astrbot/core/provider/sources/longcat_source.py
index e251fb310..21d8043b9 100644
--- a/astrbot/core/provider/sources/longcat_source.py
+++ b/astrbot/core/provider/sources/longcat_source.py
@@ -3,7 +3,7 @@ from .openai_source import ProviderOpenAIOfficial
@register_provider_adapter(
- "longcat_chat_completion", "LongCat Chat Completion Provider Adapter"
+ "longcat_chat_completion", "LongCat Chat Completion Provider Adapter",
)
class ProviderLongCat(ProviderOpenAIOfficial):
def __init__(
diff --git a/astrbot/core/provider/sources/mimo_stt_api_source.py b/astrbot/core/provider/sources/mimo_stt_api_source.py
index 9b03e2efc..725802400 100644
--- a/astrbot/core/provider/sources/mimo_stt_api_source.py
+++ b/astrbot/core/provider/sources/mimo_stt_api_source.py
@@ -82,7 +82,7 @@ class ProviderMiMoSTTAPI(STTProvider):
except Exception as exc:
error_text = response.text[:1024]
raise MiMoAPIError(
- f"MiMo STT API request failed: HTTP {response.status_code}, response: {error_text}"
+ f"MiMo STT API request failed: HTTP {response.status_code}, response: {error_text}",
) from exc
data = response.json()
diff --git a/astrbot/core/provider/sources/mimo_tts_api_source.py b/astrbot/core/provider/sources/mimo_tts_api_source.py
index 2966bfb7d..88c1ee742 100644
--- a/astrbot/core/provider/sources/mimo_tts_api_source.py
+++ b/astrbot/core/provider/sources/mimo_tts_api_source.py
@@ -39,7 +39,7 @@ class ProviderMiMoTTSAPI(TTSProvider):
self.style_prompt = provider_config.get("mimo-tts-style-prompt", "")
self.dialect = provider_config.get("mimo-tts-dialect", "")
self.seed_text = provider_config.get(
- "mimo-tts-seed-text", DEFAULT_MIMO_TTS_SEED_TEXT
+ "mimo-tts-seed-text", DEFAULT_MIMO_TTS_SEED_TEXT,
)
self.set_model(provider_config.get("model", DEFAULT_MIMO_TTS_MODEL))
self.client = create_http_client(self.timeout, self.proxy)
@@ -78,14 +78,14 @@ class ProviderMiMoTTSAPI(TTSProvider):
{
"role": "user",
"content": user_prompt,
- }
+ },
)
messages.append(
{
"role": "assistant",
"content": self._build_assistant_content(text),
- }
+ },
)
return {
@@ -109,7 +109,7 @@ class ProviderMiMoTTSAPI(TTSProvider):
except Exception as exc:
error_text = response.text[:1024]
raise MiMoAPIError(
- f"MiMo TTS API request failed: HTTP {response.status_code}, response: {error_text}"
+ f"MiMo TTS API request failed: HTTP {response.status_code}, response: {error_text}",
) from exc
data = response.json()
diff --git a/astrbot/core/provider/sources/minimax_tts_api_source.py b/astrbot/core/provider/sources/minimax_tts_api_source.py
index f40cb968a..4f5927bf3 100644
--- a/astrbot/core/provider/sources/minimax_tts_api_source.py
+++ b/astrbot/core/provider/sources/minimax_tts_api_source.py
@@ -121,7 +121,7 @@ class ProviderMiniMaxTTSAPI(TTSProvider):
if "extra_info" in data:
continue
audio: str | None = data.get("data", {}).get(
- "audio"
+ "audio",
)
if audio is not None:
yield audio
@@ -159,7 +159,7 @@ class ProviderMiniMaxTTSAPI(TTSProvider):
raise Exception(
"MiniMax TTS API returned empty audio data. "
"Please verify your configuration, especially the 'group_id' parameter. "
- "You can find your group_id in Account Management -> Basic Information on the MiniMax platform."
+ "You can find your group_id in Account Management -> Basic Information on the MiniMax platform.",
)
# 结果保存至文件
diff --git a/astrbot/core/provider/sources/nvidia_rerank_source.py b/astrbot/core/provider/sources/nvidia_rerank_source.py
index c168da4a6..0c76e73b9 100644
--- a/astrbot/core/provider/sources/nvidia_rerank_source.py
+++ b/astrbot/core/provider/sources/nvidia_rerank_source.py
@@ -8,21 +8,21 @@ from ..register import register_provider_adapter
@register_provider_adapter(
- "nvidia_rerank", "NVIDIA Rerank 适配器", provider_type=ProviderType.RERANK
+ "nvidia_rerank", "NVIDIA Rerank 适配器", provider_type=ProviderType.RERANK,
)
class NvidiaRerankProvider(RerankProvider):
def __init__(self, provider_config: dict, provider_settings: dict) -> None:
super().__init__(provider_config, provider_settings)
self.api_key = provider_config.get("nvidia_rerank_api_key", "")
self.base_url = provider_config.get(
- "nvidia_rerank_api_base", "https://ai.api.nvidia.com/v1/retrieval"
+ "nvidia_rerank_api_base", "https://ai.api.nvidia.com/v1/retrieval",
).rstrip("/")
self.timeout = provider_config.get("timeout", 20)
self.model = provider_config.get(
- "nvidia_rerank_model", "nv-rerank-qa-mistral-4b:1"
+ "nvidia_rerank_model", "nv-rerank-qa-mistral-4b:1",
)
self.model_endpoint = provider_config.get(
- "nvidia_rerank_model_endpoint", "/reranking"
+ "nvidia_rerank_model_endpoint", "/reranking",
)
self.truncate = provider_config.get("nvidia_rerank_truncate", "")
@@ -37,13 +37,12 @@ class NvidiaRerankProvider(RerankProvider):
"Accept": "application/json",
}
self.client = aiohttp.ClientSession(
- headers=headers, timeout=aiohttp.ClientTimeout(total=self.timeout)
+ headers=headers, timeout=aiohttp.ClientTimeout(total=self.timeout),
)
return self.client
def _get_endpoint(self) -> str:
- """
- 构建完整API URL。
+ """构建完整API URL。
根据 Nvidia Rerank API 文档来看,当前URL存在不同模型格式不一致的问题。
这里针对模型名做一个基础判断用以适配,后续要等Nvidia统一API格式后再做调整。
@@ -55,7 +54,6 @@ class NvidiaRerankProvider(RerankProvider):
模型: nvidia/llama-nemotron-rerank-1b-v2
URL: .../v1/retrieval/nvidia/llama-nemotron-rerank-1b-v2/reranking
"""
-
model_path = "nvidia"
logger.debug(f"[NVIDIA Rerank] Building endpoint for model: {self.model}")
if "/" in self.model:
@@ -76,7 +74,7 @@ class NvidiaRerankProvider(RerankProvider):
return payload
def _parse_results(
- self, response_data: dict, top_n: int | None
+ self, response_data: dict, top_n: int | None,
) -> list[RerankResult]:
"""解析响应数据"""
results = response_data.get("rankings", [])
@@ -90,11 +88,11 @@ class NvidiaRerankProvider(RerankProvider):
index = item.get("index", idx)
score = item.get("relevance_score", item.get("logit", 0.0))
rerank_results.append(
- RerankResult(index=index, relevance_score=float(score))
+ RerankResult(index=index, relevance_score=float(score)),
)
except Exception as e:
logger.warning(
- f"[NVIDIA Rerank] Result parsing error: {e}, Data={item}"
+ f"[NVIDIA Rerank] Result parsing error: {e}, Data={item}",
)
rerank_results.sort(key=lambda x: x.relevance_score, reverse=True)
@@ -122,7 +120,7 @@ class NvidiaRerankProvider(RerankProvider):
if not documents or not query.strip():
logger.warning(
- "[NVIDIA Rerank] Input data is invalid, query or documents are empty"
+ "[NVIDIA Rerank] Input data is invalid, query or documents are empty",
)
return []
@@ -135,7 +133,7 @@ class NvidiaRerankProvider(RerankProvider):
try:
response_data = await response.json()
error_detail = response_data.get(
- "detail", response_data.get("message", "Unknown Error")
+ "detail", response_data.get("message", "Unknown Error"),
)
except Exception:
diff --git a/astrbot/core/provider/sources/oai_aihubmix_source.py b/astrbot/core/provider/sources/oai_aihubmix_source.py
index ca8ad5959..60da20c74 100644
--- a/astrbot/core/provider/sources/oai_aihubmix_source.py
+++ b/astrbot/core/provider/sources/oai_aihubmix_source.py
@@ -3,7 +3,7 @@ from .openai_source import ProviderOpenAIOfficial
@register_provider_adapter(
- "aihubmix_chat_completion", "AIHubMix Chat Completion Provider Adapter"
+ "aihubmix_chat_completion", "AIHubMix Chat Completion Provider Adapter",
)
class ProviderAIHubMix(ProviderOpenAIOfficial):
def __init__(
diff --git a/astrbot/core/provider/sources/openai_embedding_source.py b/astrbot/core/provider/sources/openai_embedding_source.py
index ae531996a..cd5abb393 100644
--- a/astrbot/core/provider/sources/openai_embedding_source.py
+++ b/astrbot/core/provider/sources/openai_embedding_source.py
@@ -70,7 +70,7 @@ class OpenAIEmbeddingProvider(EmbeddingProvider):
kwargs["dimensions"] = int(self.provider_config["embedding_dimensions"])
except (ValueError, TypeError):
logger.warning(
- f"embedding_dimensions in embedding configs is not a valid integer: '{self.provider_config['embedding_dimensions']}', ignored."
+ f"embedding_dimensions in embedding configs is not a valid integer: '{self.provider_config['embedding_dimensions']}', ignored.",
)
return kwargs
@@ -81,7 +81,7 @@ class OpenAIEmbeddingProvider(EmbeddingProvider):
return int(self.provider_config["embedding_dimensions"])
except (ValueError, TypeError):
logger.warning(
- f"embedding_dimensions in embedding configs is not a valid integer: '{self.provider_config['embedding_dimensions']}', ignored."
+ f"embedding_dimensions in embedding configs is not a valid integer: '{self.provider_config['embedding_dimensions']}', ignored.",
)
return 0
diff --git a/astrbot/core/provider/sources/openai_source.py b/astrbot/core/provider/sources/openai_source.py
index b19f3460d..f1ebd9411 100644
--- a/astrbot/core/provider/sources/openai_source.py
+++ b/astrbot/core/provider/sources/openai_source.py
@@ -95,7 +95,7 @@ class ProviderOpenAIOfficial(Provider):
if not text:
return
candidates.append(
- ProviderOpenAIOfficial._truncate_error_text_candidate(text)
+ ProviderOpenAIOfficial._truncate_error_text_candidate(text),
)
_append_candidate(str(error))
@@ -104,7 +104,7 @@ class ProviderOpenAIOfficial(Provider):
if isinstance(body, dict):
err_obj = body.get("error")
body_text = ProviderOpenAIOfficial._safe_json_dump(
- {"error": err_obj} if isinstance(err_obj, dict) else body
+ {"error": err_obj} if isinstance(err_obj, dict) else body,
)
_append_candidate(body_text)
if isinstance(err_obj, dict):
@@ -374,7 +374,7 @@ class ProviderOpenAIOfficial(Provider):
try:
resolved_part = await self._resolve_image_part(
- url, image_detail=image_detail
+ url, image_detail=image_detail,
)
except Exception as exc:
logger.warning(
@@ -404,7 +404,7 @@ class ProviderOpenAIOfficial(Provider):
return {**message, "content": new_content}
async def _materialize_context_image_parts(
- self, context_query: list[dict]
+ self, context_query: list[dict],
) -> list[dict]:
return [
await self._materialize_message_image_parts(message)
@@ -495,7 +495,7 @@ class ProviderOpenAIOfficial(Provider):
return bool(value)
def _apply_provider_specific_extra_body_overrides(
- self, extra_body: dict[str, Any]
+ self, extra_body: dict[str, Any],
) -> None:
if self.provider_config.get("provider") != "ollama":
return
@@ -729,6 +729,7 @@ class ProviderOpenAIOfficial(Provider):
Returns:
Normalized plain text string.
+
"""
# Handle dict format (e.g., {"type": "text", "text": "..."})
if isinstance(raw_content, dict):
@@ -795,7 +796,7 @@ class ProviderOpenAIOfficial(Provider):
text_val = part.get("text", "")
# Coerce to str in case text is null or non-string
text_parts.append(
- str(text_val) if text_val is not None else ""
+ str(text_val) if text_val is not None else "",
)
if text_parts:
return "".join(text_parts)
@@ -805,14 +806,14 @@ class ProviderOpenAIOfficial(Provider):
return str(raw_content) if raw_content is not None else ""
async def _parse_openai_completion(
- self, completion: ChatCompletion, tools: ToolSet | None
+ self, completion: ChatCompletion, tools: ToolSet | None,
) -> LLMResponse:
"""Parse OpenAI ChatCompletion into LLMResponse"""
llm_response = LLMResponse("assistant")
if not completion.choices:
raise EmptyModelOutputError(
- f"OpenAI completion has no choices. response_id={completion.id}"
+ f"OpenAI completion has no choices. response_id={completion.id}",
)
choice = completion.choices[0]
@@ -892,7 +893,7 @@ class ProviderOpenAIOfficial(Provider):
logger.error(f"OpenAI completion has no usable output: {completion}.")
raise EmptyModelOutputError(
"OpenAI completion has no usable output. "
- f"response_id={completion.id}, finish_reason={choice.finish_reason}"
+ f"response_id={completion.id}, finish_reason={choice.finish_reason}",
)
llm_response.raw_completion = completion
@@ -962,7 +963,7 @@ class ProviderOpenAIOfficial(Provider):
for message in payloads.get("messages", []):
if message.get("role") == "assistant" and isinstance(
- message.get("content"), list
+ message.get("content"), list,
):
reasoning_content = ""
new_content = [] # not including think part
@@ -986,7 +987,7 @@ class ProviderOpenAIOfficial(Provider):
json.loads(content)
except (json.JSONDecodeError, ValueError):
message["content"] = json.dumps(
- {"result": content}, ensure_ascii=False
+ {"result": content}, ensure_ascii=False,
)
async def _handle_api_error(
@@ -1286,7 +1287,6 @@ class ProviderOpenAIOfficial(Provider):
extra_user_content_parts: list[ContentPart] | None = None,
) -> dict:
"""组装成符合 OpenAI 格式的 role 为 user 的消息段"""
-
# 构建内容块列表
content_blocks = []
diff --git a/astrbot/core/provider/sources/openrouter_source.py b/astrbot/core/provider/sources/openrouter_source.py
index e49d0c929..3cbdd3eee 100644
--- a/astrbot/core/provider/sources/openrouter_source.py
+++ b/astrbot/core/provider/sources/openrouter_source.py
@@ -3,7 +3,7 @@ from .openai_source import ProviderOpenAIOfficial
@register_provider_adapter(
- "openrouter_chat_completion", "OpenRouter Chat Completion Provider Adapter"
+ "openrouter_chat_completion", "OpenRouter Chat Completion Provider Adapter",
)
class ProviderOpenRouter(ProviderOpenAIOfficial):
def __init__(
diff --git a/astrbot/core/provider/sources/sensevoice_selfhosted_source.py b/astrbot/core/provider/sources/sensevoice_selfhosted_source.py
index d41ebaf62..d3018df83 100644
--- a/astrbot/core/provider/sources/sensevoice_selfhosted_source.py
+++ b/astrbot/core/provider/sources/sensevoice_selfhosted_source.py
@@ -91,8 +91,8 @@ class ProviderSenseVoiceSTTSelfHost(STTProvider):
loop = asyncio.get_running_loop()
res = await loop.run_in_executor(
None, # 使用默认的线程池
- lambda: cast(SenseVoiceSmall, self.model)(
- audio_url, language="auto", use_itn=True
+ lambda: cast("SenseVoiceSmall", self.model)(
+ audio_url, language="auto", use_itn=True,
),
)
diff --git a/astrbot/core/provider/sources/whisper_api_source.py b/astrbot/core/provider/sources/whisper_api_source.py
index df5e8fc6b..88bfb2265 100644
--- a/astrbot/core/provider/sources/whisper_api_source.py
+++ b/astrbot/core/provider/sources/whisper_api_source.py
@@ -105,12 +105,12 @@ class ProviderOpenAIWhisperAPI(STTProvider):
if file_format == "silk":
logger.info(
- "Converting silk file to wav using tencent_silk_to_wav..."
+ "Converting silk file to wav using tencent_silk_to_wav...",
)
await tencent_silk_to_wav(audio_url, output_path)
elif file_format == "amr":
logger.info(
- "Converting amr file to wav using convert_to_pcm_wav..."
+ "Converting amr file to wav using convert_to_pcm_wav...",
)
await convert_to_pcm_wav(audio_url, output_path)
diff --git a/astrbot/core/provider/sources/whisper_selfhosted_source.py b/astrbot/core/provider/sources/whisper_selfhosted_source.py
index 0622df6fc..c633c156d 100644
--- a/astrbot/core/provider/sources/whisper_selfhosted_source.py
+++ b/astrbot/core/provider/sources/whisper_selfhosted_source.py
@@ -103,4 +103,4 @@ class ProviderOpenAIWhisperSelfHost(STTProvider):
raise RuntimeError("Whisper 模型未初始化")
result = await loop.run_in_executor(None, self.model.transcribe, audio_url)
- return cast(str, result["text"])
+ return cast("str", result["text"])
diff --git a/astrbot/core/provider/sources/xai_source.py b/astrbot/core/provider/sources/xai_source.py
index b7b432b49..9dea66f20 100644
--- a/astrbot/core/provider/sources/xai_source.py
+++ b/astrbot/core/provider/sources/xai_source.py
@@ -3,7 +3,7 @@ from .openai_source import ProviderOpenAIOfficial
@register_provider_adapter(
- "xai_chat_completion", "xAI Chat Completion Provider Adapter"
+ "xai_chat_completion", "xAI Chat Completion Provider Adapter",
)
class ProviderXAI(ProviderOpenAIOfficial):
def __init__(
diff --git a/astrbot/core/provider/sources/xinference_rerank_source.py b/astrbot/core/provider/sources/xinference_rerank_source.py
index 9c3a77c15..22230cb05 100644
--- a/astrbot/core/provider/sources/xinference_rerank_source.py
+++ b/astrbot/core/provider/sources/xinference_rerank_source.py
@@ -71,7 +71,7 @@ class XinferenceRerankProvider(RerankProvider):
if self.model_uid:
self.model = cast(
- AsyncRESTfulRerankModelHandle,
+ "AsyncRESTfulRerankModelHandle",
await self.client.get_model(self.model_uid),
)
diff --git a/astrbot/core/provider/sources/xinference_stt_provider.py b/astrbot/core/provider/sources/xinference_stt_provider.py
index 0a22e456e..07965c2b4 100644
--- a/astrbot/core/provider/sources/xinference_stt_provider.py
+++ b/astrbot/core/provider/sources/xinference_stt_provider.py
@@ -128,7 +128,7 @@ class ProviderXinferenceSTT(STTProvider):
# 3. Perform conversion if needed
if conversion_type:
logger.info(
- f"Audio requires conversion ({conversion_type}), using temporary files..."
+ f"Audio requires conversion ({conversion_type}), using temporary files...",
)
temp_dir = get_astrbot_temp_path()
os.makedirs(temp_dir, exist_ok=True)
diff --git a/astrbot/core/skills/neo_skill_sync.py b/astrbot/core/skills/neo_skill_sync.py
index 5fe2b7832..fa7098c44 100644
--- a/astrbot/core/skills/neo_skill_sync.py
+++ b/astrbot/core/skills/neo_skill_sync.py
@@ -224,7 +224,7 @@ class NeoSkillSyncManager:
items = page_json.get("items", [])
if not isinstance(items, list) or not items:
raise ValueError(
- f"No active stable release found for skill_key: {skill_key}"
+ f"No active stable release found for skill_key: {skill_key}",
)
if not isinstance(items[0], dict):
raise ValueError("Unexpected release payload format.")
@@ -242,7 +242,7 @@ class NeoSkillSyncManager:
release = await self._find_release(client, release_id=release_id)
elif skill_key:
release = await self._find_active_stable_release(
- client, skill_key=skill_key
+ client, skill_key=skill_key,
)
else:
raise ValueError("release_id or skill_key is required for sync.")
@@ -259,7 +259,7 @@ class NeoSkillSyncManager:
if require_stable and release_stage != "stable":
raise ValueError(
"Only stable releases can be synced to local SKILL.md "
- f"(got: {release_stage_raw})."
+ f"(got: {release_stage_raw}).",
)
candidate = await client.skills.get_candidate(candidate_id)
@@ -277,7 +277,7 @@ class NeoSkillSyncManager:
skill_markdown = payload.get("skill_markdown")
if not isinstance(skill_markdown, str) or not skill_markdown.strip():
raise ValueError(
- "payload.skill_markdown is required for stable sync to local skill."
+ "payload.skill_markdown is required for stable sync to local skill.",
)
mapping = self._load_map()
@@ -348,7 +348,7 @@ class NeoSkillSyncManager:
sync_error = str(err)
try:
rollback = await client.skills.rollback_release(
- str(release_json.get("id", ""))
+ str(release_json.get("id", "")),
)
rollback_json = _to_jsonable(rollback)
except Exception as rollback_err:
@@ -361,7 +361,7 @@ class NeoSkillSyncManager:
else:
raise RuntimeError(
"stable release synced failed and auto rollback also failed; "
- f"sync_error={sync_error}; rollback_error={rollback_err}"
+ f"sync_error={sync_error}; rollback_error={rollback_err}",
) from rollback_err
return {
diff --git a/astrbot/core/skills/skill_manager.py b/astrbot/core/skills/skill_manager.py
index a8121c42a..4a9e858f9 100644
--- a/astrbot/core/skills/skill_manager.py
+++ b/astrbot/core/skills/skill_manager.py
@@ -223,7 +223,7 @@ def build_skills_prompt(skills: list[SkillInfo]) -> str:
rendered_path = "//SKILL.md"
skills_lines.append(
- f"- **{display_name}**: {description}\n File: `{rendered_path}`"
+ f"- **{display_name}**: {description}\n File: `{rendered_path}`",
)
if not example_path:
example_path = rendered_path
@@ -331,7 +331,7 @@ class SkillManager:
continue
description = str(item.get("description", "") or "")
path = _normalize_cached_sandbox_skill_path(
- name, str(item.get("path", "") or "")
+ name, str(item.get("path", "") or ""),
)
deduped[name] = {
"name": name,
@@ -381,7 +381,7 @@ class SkillManager:
continue
name = str(item.get("name", "") or "").strip()
path = _normalize_cached_sandbox_skill_path(
- name, str(item.get("path", "") or "")
+ name, str(item.get("path", "") or ""),
)
if not name or not _SKILL_NAME_RE.match(name):
continue
@@ -414,7 +414,7 @@ class SkillManager:
source_label = "synced" if sandbox_exists else "local"
if runtime == "sandbox" and show_sandbox_path:
path_str = sandbox_cached_paths.get(
- skill_name
+ skill_name,
) or _default_sandbox_skill_path(skill_name)
else:
path_str = str(skill_md)
@@ -453,7 +453,7 @@ class SkillManager:
# since there is no local path to show. Always prefer the
# actual path from sandbox cache.
path_str = sandbox_cached_paths.get(
- skill_name
+ skill_name,
) or _default_sandbox_skill_path(skill_name)
skills_by_name[skill_name] = SkillInfo(
name=skill_name,
@@ -491,7 +491,7 @@ class SkillManager:
def set_skill_active(self, name: str, active: bool) -> None:
if self.is_sandbox_only_skill(name):
raise PermissionError(
- "Sandbox preset skill cannot be enabled/disabled from local skill management."
+ "Sandbox preset skill cannot be enabled/disabled from local skill management.",
)
config = self._load_config()
config.setdefault("skills", {})
@@ -519,7 +519,7 @@ class SkillManager:
def delete_skill(self, name: str) -> None:
if self.is_sandbox_only_skill(name):
raise PermissionError(
- "Sandbox preset skill cannot be deleted from local skill management."
+ "Sandbox preset skill cannot be deleted from local skill management.",
)
skill_dir = Path(self.skills_root) / name
@@ -571,7 +571,7 @@ class SkillManager:
if skill_name_hint is not None:
archive_skill_name = _normalize_skill_name(skill_name_hint)
if archive_skill_name and not _SKILL_NAME_RE.fullmatch(
- archive_skill_name
+ archive_skill_name,
):
raise ValueError("Invalid skill name.")
@@ -596,7 +596,7 @@ class SkillManager:
candidate_name = _normalize_skill_name(src_dir_name)
if not candidate_name or not _SKILL_NAME_RE.fullmatch(
- candidate_name
+ candidate_name,
):
continue
@@ -613,7 +613,7 @@ class SkillManager:
raise FileExistsError(
"One or more skills from the archive already exist and "
"overwrite=False. No skills were installed. Conflicting "
- f"paths: {', '.join(conflict_dirs)}"
+ f"paths: {', '.join(conflict_dirs)}",
)
with tempfile.TemporaryDirectory(dir=get_astrbot_temp_path()) as tmp_dir:
@@ -625,7 +625,7 @@ class SkillManager:
if root_mode:
archive_hint = _normalize_skill_name(
- archive_skill_name or zip_path_obj.stem
+ archive_skill_name or zip_path_obj.stem,
)
if not archive_hint or not _SKILL_NAME_RE.fullmatch(archive_hint):
raise ValueError("Invalid skill name.")
@@ -635,7 +635,7 @@ class SkillManager:
normalized_path = _normalize_skill_markdown_path(src_dir)
if normalized_path is None:
raise ValueError(
- "SKILL.md not found in the root of the zip archive."
+ "SKILL.md not found in the root of the zip archive.",
)
dest_dir = Path(self.skills_root) / skill_name
@@ -655,7 +655,7 @@ class SkillManager:
for archive_root_name in top_dirs:
archive_root_name_normalized = _normalize_skill_name(
- archive_root_name
+ archive_root_name,
)
if (
@@ -683,7 +683,7 @@ class SkillManager:
if dest_dir.exists():
if not overwrite:
raise FileExistsError(
- f"Skill {skill_name} already exists."
+ f"Skill {skill_name} already exists.",
)
shutil.rmtree(dest_dir)
@@ -693,7 +693,7 @@ class SkillManager:
if not installed_skills:
raise ValueError(
- "No valid SKILL.md found in any folder of the zip archive."
+ "No valid SKILL.md found in any folder of the zip archive.",
)
return ", ".join(installed_skills)
diff --git a/astrbot/core/star/command_management.py b/astrbot/core/star/command_management.py
index c60af9ea2..3f2b31a1f 100644
--- a/astrbot/core/star/command_management.py
+++ b/astrbot/core/star/command_management.py
@@ -263,7 +263,7 @@ def _collect_descriptors(include_sub_commands: bool) -> list[CommandDescriptor]:
descriptors.append(desc)
except Exception as e:
logger.warning(
- f"解析指令处理函数 {handler.handler_full_name} 失败,跳过该指令。原因: {e!s}"
+ f"解析指令处理函数 {handler.handler_full_name} 失败,跳过该指令。原因: {e!s}",
)
continue
return descriptors
@@ -285,18 +285,18 @@ def _build_descriptor(handler: StarHandlerMetadata) -> CommandDescriptor | None:
if isinstance(filter_ref, CommandFilter):
raw_fragment = getattr(
- filter_ref, "_original_command_name", filter_ref.command_name
+ filter_ref, "_original_command_name", filter_ref.command_name,
)
current_fragment = filter_ref.command_name
parent_signature = (filter_ref.parent_command_names or [""])[0].strip()
# 如果是子指令,尝试找到父指令组的 handler_full_name
if is_sub_command and parent_signature:
parent_group_handler = _find_parent_group_handler(
- handler.handler_module_path, parent_signature
+ handler.handler_module_path, parent_signature,
)
else:
raw_fragment = getattr(
- filter_ref, "_original_group_name", filter_ref.group_name
+ filter_ref, "_original_group_name", filter_ref.group_name,
)
current_fragment = filter_ref.group_name
parent_signature = _resolve_group_parent_signature(filter_ref)
@@ -491,7 +491,7 @@ def _set_filter_aliases(
current_aliases = getattr(filter_ref, "alias", set())
if set(aliases) == current_aliases:
return
- setattr(filter_ref, "alias", set(aliases))
+ filter_ref.alias = set(aliases)
if hasattr(filter_ref, "_cmpl_cmd_names"):
filter_ref._cmpl_cmd_names = None
diff --git a/astrbot/core/star/context.py b/astrbot/core/star/context.py
index 058cf61e5..988eeabc5 100644
--- a/astrbot/core/star/context.py
+++ b/astrbot/core/star/context.py
@@ -130,6 +130,7 @@ class Context:
Raises:
ChatProviderNotFoundError: If the specified chat provider ID is not found
Exception: For other errors during LLM generation
+
"""
prov = await self.provider_manager.get_provider_by_id(chat_provider_id)
if not prov or not isinstance(prov, Provider):
@@ -187,6 +188,7 @@ class Context:
Raises:
ChatProviderNotFoundError: If the specified chat provider ID is not found
Exception: For other errors during LLM generation
+
"""
# Import here to avoid circular imports
from astrbot.core.astr_agent_context import (
@@ -263,6 +265,7 @@ class Context:
Raises:
ProviderNotFoundError: 未找到。
+
"""
prov = self.get_using_provider(umo)
if not prov:
@@ -294,6 +297,7 @@ class Context:
Note:
注册的工具默认是激活状态。
+
"""
return self.provider_manager.llm_tools.activate_llm_tool(name, star_map)
@@ -305,6 +309,7 @@ class Context:
Returns:
如果成功停用返回 True,如果没找到工具返回 False。
+
"""
return self.provider_manager.llm_tools.deactivate_llm_tool(name)
@@ -324,11 +329,12 @@ class Context:
Note:
如果提供者 ID 存在但未找到提供者,会记录警告日志。
+
"""
prov = self.provider_manager.inst_map.get(provider_id)
if provider_id and not prov:
logger.warning(
- f"没有找到 ID 为 {provider_id} 的提供商,这可能是由于您修改了提供商(模型)ID 导致的。"
+ f"没有找到 ID 为 {provider_id} 的提供商,这可能是由于您修改了提供商(模型)ID 导致的。",
)
return prov
@@ -360,6 +366,7 @@ class Context:
Raises:
ValueError: 该会话来源配置的的对话模型(提供商)的类型不正确。
+
"""
prov = self.provider_manager.get_using_provider(
provider_type=ProviderType.CHAT_COMPLETION,
@@ -369,7 +376,7 @@ class Context:
return None
if not isinstance(prov, Provider):
raise ValueError(
- f"该会话来源的对话模型(提供商)的类型不正确: {type(prov)}"
+ f"该会话来源的对话模型(提供商)的类型不正确: {type(prov)}",
)
return prov
@@ -384,6 +391,7 @@ class Context:
Raises:
ValueError: 返回的提供者不是 TTSProvider 类型。
+
"""
prov = self.provider_manager.get_using_provider(
provider_type=ProviderType.TEXT_TO_SPEECH,
@@ -404,6 +412,7 @@ class Context:
Raises:
ValueError: 返回的提供者不是 STTProvider 类型。
+
"""
prov = self.provider_manager.get_using_provider(
provider_type=ProviderType.SPEECH_TO_TEXT,
@@ -424,6 +433,7 @@ class Context:
Note:
如果不提供 umo 参数,将返回默认配置。
+
"""
if not umo:
# 使用默认配置
@@ -450,6 +460,7 @@ class Context:
Note:
当 session 为字符串时,会尝试解析为 MessageSession 对象。(类名为MessageSesion是因为历史遗留拼写错误)
qq_official(QQ 官方 API 平台) 不支持此方法。
+
"""
if isinstance(session, str):
try:
@@ -462,7 +473,7 @@ class Context:
await platform.send_by_session(session, message_chain)
return True
logger.warning(
- f"cannot find platform for session {str(session)}, message not sent"
+ f"cannot find platform for session {session!s}, message not sent",
)
return False
@@ -474,6 +485,7 @@ class Context:
Note:
如果工具已存在,会替换已存在的工具。
+
"""
tool_name = {tool.name for tool in self.provider_manager.llm_tools.func_list}
module_path = ""
@@ -493,7 +505,7 @@ class Context:
else:
tool.handler_module_path = module_path
logger.info(
- f"plugin(module_path {module_path}) added LLM tool: {tool.name}"
+ f"plugin(module_path {module_path}) added LLM tool: {tool.name}",
)
if tool.name in tool_name:
@@ -518,6 +530,7 @@ class Context:
Note:
如果相同路由和方法已注册,会替换现有的 API。
+
"""
for idx, api in enumerate(self.registered_web_apis):
if api[0] == route and methods == api[2]:
@@ -545,6 +558,7 @@ class Context:
Note:
该方法已经过时,请使用 get_platform_inst 方法。(>= AstrBot v4.0.0)
+
"""
for platform in self.platform_manager.platform_insts:
name = platform.meta().name
@@ -568,6 +582,7 @@ class Context:
Note:
可以通过 event.get_platform_id() 获取平台 ID。
+
"""
for platform in self.platform_manager.platform_insts:
if platform.meta().id == platform_id:
@@ -578,6 +593,7 @@ class Context:
Returns:
数据库实例。
+
"""
return self._db
@@ -586,6 +602,7 @@ class Context:
Args:
provider: 提供者实例。
+
"""
self.provider_manager.provider_insts.append(provider)
@@ -608,6 +625,7 @@ class Context:
Note:
异步处理函数会接收到额外的关键词参数:event: AstrMessageEvent, context: Context。
该方法已弃用,请使用新的注册方式。
+
"""
md = StarHandlerMetadata(
event_type=EventType.OnLLMRequestEvent,
@@ -630,6 +648,7 @@ class Context:
Note:
如果再要启用,需要重新注册。
该方法已弃用。
+
"""
self.provider_manager.llm_tools.remove_func(name)
@@ -656,6 +675,7 @@ class Context:
Note:
推荐使用装饰器注册指令。该方法将在未来的版本中被移除。
+
"""
md = StarHandlerMetadata(
event_type=EventType.AdapterMessageEvent,
@@ -683,5 +703,6 @@ class Context:
Note:
该方法已弃用。
+
"""
self._register_tasks.append(task)
diff --git a/astrbot/core/star/filter/command.py b/astrbot/core/star/filter/command.py
index 31949b674..a26cd4c84 100755
--- a/astrbot/core/star/filter/command.py
+++ b/astrbot/core/star/filter/command.py
@@ -39,7 +39,7 @@ class CommandFilter(HandlerFilter):
parent_command_names: list[str] | None = None,
) -> None:
self.command_name = command_name
- self.alias = alias if alias else set()
+ self.alias = alias or set()
self._original_command_name = command_name
self.parent_command_names = (
parent_command_names if parent_command_names is not None else [""]
diff --git a/astrbot/core/star/filter/command_group.py b/astrbot/core/star/filter/command_group.py
index 52fb6a452..52f58924a 100755
--- a/astrbot/core/star/filter/command_group.py
+++ b/astrbot/core/star/filter/command_group.py
@@ -17,7 +17,7 @@ class CommandGroupFilter(HandlerFilter):
parent_group: CommandGroupFilter | None = None,
) -> None:
self.group_name = group_name
- self.alias = alias if alias else set()
+ self.alias = alias or set()
self._original_group_name = group_name
self.sub_command_filters: list[CommandFilter | CommandGroupFilter] = []
self.custom_filter_list: list[CustomFilter] = []
@@ -97,7 +97,7 @@ class CommandGroupFilter(HandlerFilter):
prefix + "│ ",
event=event,
cfg=cfg,
- )
+ ),
)
return "".join(parts)
diff --git a/astrbot/core/star/filter/permission.py b/astrbot/core/star/filter/permission.py
index a70299fa9..4870025d6 100644
--- a/astrbot/core/star/filter/permission.py
+++ b/astrbot/core/star/filter/permission.py
@@ -15,7 +15,7 @@ class PermissionType(enum.Flag):
class PermissionTypeFilter(HandlerFilter):
def __init__(
- self, permission_type: PermissionType, raise_error: bool = True
+ self, permission_type: PermissionType, raise_error: bool = True,
) -> None:
self.permission_type = permission_type
self.raise_error = raise_error
diff --git a/astrbot/core/star/register/__init__.py b/astrbot/core/star/register/__init__.py
index 5e99948cd..88b576de9 100644
--- a/astrbot/core/star/register/__init__.py
+++ b/astrbot/core/star/register/__init__.py
@@ -35,15 +35,15 @@ __all__ = [
"register_on_decorating_result",
"register_on_llm_request",
"register_on_llm_response",
+ "register_on_llm_tool_respond",
+ "register_on_platform_loaded",
"register_on_plugin_error",
"register_on_plugin_loaded",
"register_on_plugin_unloaded",
- "register_on_platform_loaded",
+ "register_on_using_llm_tool",
"register_on_waiting_llm_request",
"register_permission_type",
"register_platform_adapter_type",
"register_regex",
"register_star",
- "register_on_using_llm_tool",
- "register_on_llm_tool_respond",
]
diff --git a/astrbot/core/star/register/star_handler.py b/astrbot/core/star/register/star_handler.py
index 1385b5056..808cb0999 100644
--- a/astrbot/core/star/register/star_handler.py
+++ b/astrbot/core/star/register/star_handler.py
@@ -407,7 +407,7 @@ def register_on_waiting_llm_request(**kwargs):
def decorator(awaitable):
_ = get_handler_or_create(
- awaitable, EventType.OnWaitingLLMRequestEvent, **kwargs
+ awaitable, EventType.OnWaitingLLMRequestEvent, **kwargs,
)
return awaitable
@@ -554,7 +554,7 @@ def register_llm_tool(name: str | None = None, **kwargs):
| Awaitable[MessageEventResult | str | None],
],
):
- llm_tool_name = name_ if name_ else awaitable.__name__
+ llm_tool_name = name_ or awaitable.__name__
func_doc = awaitable.__doc__ or ""
docstring = docstring_parser.parse(func_doc)
args = []
diff --git a/astrbot/core/star/star_manager.py b/astrbot/core/star/star_manager.py
index 1ec0b8366..ea8dfc640 100644
--- a/astrbot/core/star/star_manager.py
+++ b/astrbot/core/star/star_manager.py
@@ -136,7 +136,7 @@ async def _install_requirements_with_precheck(
if install_plan is None:
logger.info(
f"正在安装插件 {plugin_label} 的依赖库(缺失依赖预检查不可裁剪,回退到完整安装): "
- f"{requirements_path}"
+ f"{requirements_path}",
)
await pip_installer.install(requirements_path=requirements_path)
return
@@ -161,7 +161,7 @@ async def _install_requirements_with_precheck(
logger.info(
f"检测到插件 {plugin_label} 缺失依赖,正在按 requirements.txt 安装: "
- f"{requirements_path} -> {sorted(install_plan.missing_names)}"
+ f"{requirements_path} -> {sorted(install_plan.missing_names)}",
)
with _temporary_filtered_requirements_file(
@@ -189,7 +189,7 @@ class PluginManager:
self.plugin_config_path = get_astrbot_config_path()
"""存储插件配置的路径。data/config"""
self.reserved_plugin_path = os.path.join(
- get_astrbot_path(), "astrbot", "builtin_stars"
+ get_astrbot_path(), "astrbot", "builtin_stars",
)
"""保留插件的路径。在 astrbot/builtin_stars 目录下"""
self.conf_schema_fname = "_conf_schema.json"
@@ -307,7 +307,7 @@ class PluginManager:
return plugins
async def _check_plugin_dept_update(
- self, target_plugin: str | None = None
+ self, target_plugin: str | None = None,
) -> bool | None:
"""检查插件的依赖
如果 target_plugin 为 None,则检查所有插件的依赖
@@ -366,7 +366,7 @@ class PluginManager:
install_plan = plan_missing_requirements_install(requirements_path)
if install_plan is None:
return ImportDependencyRecoveryState(
- ImportDependencyRecoveryMode.RECOVER_ON_FAILURE
+ ImportDependencyRecoveryMode.RECOVER_ON_FAILURE,
)
if install_plan.version_mismatch_names:
return ImportDependencyRecoveryState(
@@ -389,19 +389,19 @@ class PluginManager:
) -> ModuleType | None:
try:
logger.info(
- f"插件 {root_dir_name} 导入失败,尝试从已安装依赖恢复: {import_exc!s}"
+ f"插件 {root_dir_name} 导入失败,尝试从已安装依赖恢复: {import_exc!s}",
)
pip_installer.prefer_installed_dependencies(
- requirements_path=requirements_path
+ requirements_path=requirements_path,
)
module = __import__(path, fromlist=[module_str])
logger.info(
- f"插件 {root_dir_name} 已从 site-packages 恢复依赖,跳过重新安装。"
+ f"插件 {root_dir_name} 已从 site-packages 恢复依赖,跳过重新安装。",
)
return module
except (ImportError, ModuleNotFoundError) as recover_exc:
logger.info(
- f"插件 {root_dir_name} 已安装依赖恢复失败,将重新安装依赖: {recover_exc!s}"
+ f"插件 {root_dir_name} 已安装依赖恢复失败,将重新安装依赖: {recover_exc!s}",
)
return None
@@ -422,11 +422,11 @@ class PluginManager:
if recovery_state.mode is ImportDependencyRecoveryMode.PRELOAD_AND_RECOVER:
try:
pip_installer.prefer_installed_dependencies(
- requirements_path=requirements_path
+ requirements_path=requirements_path,
)
except Exception as preload_exc:
logger.info(
- f"插件 {root_dir_name} 预加载已安装依赖失败,将继续常规导入: {preload_exc!s}"
+ f"插件 {root_dir_name} 预加载已安装依赖失败,将继续常规导入: {preload_exc!s}",
)
try:
@@ -525,11 +525,11 @@ class PluginManager:
def _validate_importable_name(plugin_name: str) -> None:
if "/" in plugin_name or "\\" in plugin_name:
raise ValueError(
- "metadata.yaml 中 name 含有路径分隔符,不可用于 importlib 加载。"
+ "metadata.yaml 中 name 含有路径分隔符,不可用于 importlib 加载。",
)
if not plugin_name.isidentifier() or keyword.iskeyword(plugin_name):
raise Exception(
- "metadata.yaml 中 name 不是合法的模块名称(应为合法 Python 标识符且非关键字)。"
+ "metadata.yaml 中 name 不是合法的模块名称(应为合法 Python 标识符且非关键字)。",
)
@staticmethod
@@ -701,7 +701,7 @@ class PluginManager:
"display_name": metadata.display_name,
"support_platforms": metadata.support_platforms,
"astrbot_version": metadata.astrbot_version,
- }
+ },
)
except Exception as metadata_error:
logger.debug(
@@ -736,8 +736,7 @@ class PluginManager:
self.failed_plugin_info = "\n".join(lines) + "\n"
async def reload_failed_plugin(self, dir_name):
- """
- 重新加载未注册(加载失败)的插件
+ """重新加载未注册(加载失败)的插件
Args:
dir_name (str): 要重载的特定插件名称。
Returns:
@@ -745,7 +744,6 @@ class PluginManager:
- success (bool): 重载是否成功
- error_message (str|None): 错误信息,成功时为 None
"""
-
async with self._pm_lock:
if dir_name not in self.failed_plugin_dict:
return False, "插件不存在于失败列表中"
@@ -760,8 +758,7 @@ class PluginManager:
self.failed_plugin_dict.pop(dir_name, None)
self._rebuild_failed_plugin_info()
return success, None
- else:
- return False, error
+ return False, error
async def reload(self, specified_plugin_name=None):
"""重新加载插件
@@ -956,7 +953,7 @@ class PluginManager:
if not is_valid:
raise PluginVersionIncompatibleError(
error_message
- or "The plugin is not compatible with the current AstrBot version."
+ or "The plugin is not compatible with the current AstrBot version.",
)
logger.info(metadata)
@@ -967,9 +964,9 @@ class PluginManager:
# 在实例化前注入类属性,保证插件 __init__ 可读取这些值
if metadata.star_cls_type:
- setattr(metadata.star_cls_type, "name", p_name)
- setattr(metadata.star_cls_type, "author", p_author)
- setattr(metadata.star_cls_type, "plugin_id", plugin_id)
+ metadata.star_cls_type.name = p_name
+ metadata.star_cls_type.author = p_author
+ metadata.star_cls_type.plugin_id = plugin_id
if path not in inactivated_plugins:
# 只有没有禁用插件时才实例化插件类
@@ -989,9 +986,9 @@ class PluginManager:
)
if metadata.star_cls:
- setattr(metadata.star_cls, "name", p_name)
- setattr(metadata.star_cls, "author", p_author)
- setattr(metadata.star_cls, "plugin_id", plugin_id)
+ metadata.star_cls.name = p_name
+ metadata.star_cls.author = p_author
+ metadata.star_cls.plugin_id = plugin_id
else:
logger.info(f"插件 {metadata.name} 已被禁用。")
@@ -1079,7 +1076,7 @@ class PluginManager:
if not is_valid:
raise PluginVersionIncompatibleError(
error_message
- or "The plugin is not compatible with the current AstrBot version."
+ or "The plugin is not compatible with the current AstrBot version.",
)
metadata.star_cls = obj
@@ -1306,7 +1303,7 @@ class PluginManager:
self._rebuild_failed_plugin_info()
async def install_plugin(
- self, repo_url: str, proxy: str = "", ignore_version_check: bool = False
+ self, repo_url: str, proxy: str = "", ignore_version_check: bool = False,
):
"""从仓库 URL 安装插件
@@ -1340,7 +1337,7 @@ class PluginManager:
plugin_path = os.path.join(self.plugin_store_path, repo_name)
if os.path.exists(plugin_path):
raise Exception(
- f"安装失败:目录 {os.path.basename(plugin_path)} 已存在。"
+ f"安装失败:目录 {os.path.basename(plugin_path)} 已存在。",
)
plugin_path = await self.updator.install(repo_url, proxy)
@@ -1352,7 +1349,7 @@ class PluginManager:
metadata_dir_name,
)
if target_plugin_path != plugin_path and os.path.exists(
- target_plugin_path
+ target_plugin_path,
):
raise Exception(f"安装失败:目录 {metadata_dir_name} 已存在。")
if target_plugin_path != plugin_path:
@@ -1370,7 +1367,7 @@ class PluginManager:
if not success:
raise Exception(
error_message
- or f"安装插件 {dir_name} 失败,请检查插件依赖或兼容性。"
+ or f"安装插件 {dir_name} 失败,请检查插件依赖或兼容性。",
)
# Get the plugin metadata to return repo info
@@ -1578,7 +1575,7 @@ class PluginManager:
module_prefix = ".".join(plugin_module_path.split(".")[:-1])
if module_prefix:
unregistered_adapters = unregister_platform_adapters_by_module(
- module_prefix
+ module_prefix,
)
for adapter_name in unregistered_adapters:
logger.info(
@@ -1725,11 +1722,11 @@ class PluginManager:
await self.reload(plugin_name)
async def install_plugin_from_file(
- self, zip_file_path: str, ignore_version_check: bool = False
+ self, zip_file_path: str, ignore_version_check: bool = False,
):
dir_name = os.path.splitext(os.path.basename(zip_file_path))[0]
desti_dir = tempfile.mkdtemp(
- dir=self.plugin_store_path, prefix="plugin_upload_"
+ dir=self.plugin_store_path, prefix="plugin_upload_",
)
temp_desti_dir = desti_dir
@@ -1761,7 +1758,7 @@ class PluginManager:
if not success:
raise Exception(
error_message
- or f"安装插件 {dir_name} 失败,请检查插件依赖或兼容性。"
+ or f"安装插件 {dir_name} 失败,请检查插件依赖或兼容性。",
)
# Get the plugin metadata to return repo info
diff --git a/astrbot/core/subagent_orchestrator.py b/astrbot/core/subagent_orchestrator.py
index c6c595dfc..b54cb393d 100644
--- a/astrbot/core/subagent_orchestrator.py
+++ b/astrbot/core/subagent_orchestrator.py
@@ -20,7 +20,7 @@ class SubAgentOrchestrator:
"""
def __init__(
- self, tool_mgr: FunctionToolManager, persona_mgr: PersonaManager
+ self, tool_mgr: FunctionToolManager, persona_mgr: PersonaManager,
) -> None:
self._tool_mgr = tool_mgr
self._persona_mgr = persona_mgr
@@ -68,7 +68,7 @@ class SubAgentOrchestrator:
if prompt:
instructions = prompt
begin_dialogs = copy.deepcopy(
- persona_data.get("_begin_dialogs_processed")
+ persona_data.get("_begin_dialogs_processed"),
)
tools = persona_data.get("tools")
if public_description == "" and prompt:
diff --git a/astrbot/core/tools/cron_tools.py b/astrbot/core/tools/cron_tools.py
index ccc93172d..eea6b0c4d 100644
--- a/astrbot/core/tools/cron_tools.py
+++ b/astrbot/core/tools/cron_tools.py
@@ -70,11 +70,11 @@ class FutureTaskTool(FunctionTool[AstrAgentContext]):
},
},
"required": ["action"],
- }
+ },
)
async def call(
- self, context: ContextWrapper[AstrAgentContext], **kwargs
+ self, context: ContextWrapper[AstrAgentContext], **kwargs,
) -> ToolExecResult:
cron_mgr = context.context.context.cron_manager
if cron_mgr is None:
@@ -219,7 +219,7 @@ class FutureTaskTool(FunctionTool[AstrAgentContext]):
lines = []
for j in jobs:
lines.append(
- f"{j.job_id} | {j.name} | {j.job_type} | run_once={getattr(j, 'run_once', False)} | enabled={j.enabled} | next={j.next_run_time}"
+ f"{j.job_id} | {j.name} | {j.job_type} | run_once={getattr(j, 'run_once', False)} | enabled={j.enabled} | next={j.next_run_time}",
)
return "\n".join(lines)
diff --git a/astrbot/core/tools/knowledge_base_tools.py b/astrbot/core/tools/knowledge_base_tools.py
index e27a883d4..a3b7fb837 100644
--- a/astrbot/core/tools/knowledge_base_tools.py
+++ b/astrbot/core/tools/knowledge_base_tools.py
@@ -103,11 +103,11 @@ class KnowledgeBaseQueryTool(FunctionTool[AstrAgentContext]):
},
},
"required": ["query"],
- }
+ },
)
async def call(
- self, context: ContextWrapper[AstrAgentContext], **kwargs
+ self, context: ContextWrapper[AstrAgentContext], **kwargs,
) -> ToolExecResult:
query = kwargs.get("query", "")
if not query:
diff --git a/astrbot/core/tools/message_tools.py b/astrbot/core/tools/message_tools.py
index 020c1ad5a..9c4e6ca71 100644
--- a/astrbot/core/tools/message_tools.py
+++ b/astrbot/core/tools/message_tools.py
@@ -71,11 +71,11 @@ class SendMessageToUserTool(FunctionTool[AstrAgentContext]):
},
},
"required": ["messages"],
- }
+ },
)
async def _resolve_path_from_sandbox(
- self, context: ContextWrapper[AstrAgentContext], path: str
+ self, context: ContextWrapper[AstrAgentContext], path: str,
) -> tuple[str, bool]:
if os.path.exists(path):
return path, False
@@ -90,7 +90,7 @@ class SendMessageToUserTool(FunctionTool[AstrAgentContext]):
if "_&exists_" in json.dumps(result):
name = os.path.basename(path)
local_path = os.path.join(
- get_astrbot_temp_path(), f"sandbox_{uuid.uuid4().hex[:4]}_{name}"
+ get_astrbot_temp_path(), f"sandbox_{uuid.uuid4().hex[:4]}_{name}",
)
await sb.download_file(path, local_path)
logger.info(f"Downloaded file from sandbox: {path} -> {local_path}")
@@ -101,7 +101,7 @@ class SendMessageToUserTool(FunctionTool[AstrAgentContext]):
return path, False
async def call(
- self, context: ContextWrapper[AstrAgentContext], **kwargs
+ self, context: ContextWrapper[AstrAgentContext], **kwargs,
) -> ToolExecResult:
session = kwargs.get("session") or context.context.event.unified_msg_origin
messages = kwargs.get("messages")
@@ -128,7 +128,7 @@ class SendMessageToUserTool(FunctionTool[AstrAgentContext]):
url = msg.get("url")
if path:
local_path, _ = await self._resolve_path_from_sandbox(
- context, path
+ context, path,
)
components.append(Comp.Image.fromFileSystem(path=local_path))
elif url:
@@ -140,7 +140,7 @@ class SendMessageToUserTool(FunctionTool[AstrAgentContext]):
url = msg.get("url")
if path:
local_path, _ = await self._resolve_path_from_sandbox(
- context, path
+ context, path,
)
components.append(Comp.Record.fromFileSystem(path=local_path))
elif url:
@@ -152,7 +152,7 @@ class SendMessageToUserTool(FunctionTool[AstrAgentContext]):
url = msg.get("url")
if path:
local_path, _ = await self._resolve_path_from_sandbox(
- context, path
+ context, path,
)
components.append(Comp.Video.fromFileSystem(path=local_path))
elif url:
@@ -170,7 +170,7 @@ class SendMessageToUserTool(FunctionTool[AstrAgentContext]):
)
if path:
local_path, _ = await self._resolve_path_from_sandbox(
- context, path
+ context, path,
)
components.append(Comp.File(name=name, file=local_path))
elif url:
diff --git a/astrbot/core/tools/web_search_tools.py b/astrbot/core/tools/web_search_tools.py
index 5ca8c3e08..ee94f4b63 100644
--- a/astrbot/core/tools/web_search_tools.py
+++ b/astrbot/core/tools/web_search_tools.py
@@ -41,7 +41,7 @@ class _KeyRotator:
keys = provider_settings.get(self.setting_name, [])
if not keys:
raise ValueError(
- f"Error: {self.provider_name} API key is not configured in AstrBot."
+ f"Error: {self.provider_name} API key is not configured in AstrBot.",
)
async with self.lock:
@@ -62,7 +62,7 @@ def normalize_legacy_web_search_config(cfg) -> None:
changed = False
if provider_settings.get(
- "websearch_provider"
+ "websearch_provider",
) == "default" and provider_settings.get("web_search", False):
provider_settings["web_search"] = False
changed = True
@@ -109,7 +109,7 @@ def _search_result_payload(results: list[SearchResult]) -> str:
"url": f"{result.url}",
"snippet": f"{result.snippet}",
"index": index,
- }
+ },
)
_cache_favicon(result.url, result.favicon)
return json.dumps({"results": ret_ls}, ensure_ascii=False)
@@ -124,27 +124,26 @@ async def _tavily_search(
"Authorization": f"Bearer {tavily_key}",
"Content-Type": "application/json",
}
- async with aiohttp.ClientSession(trust_env=True) as session:
- async with session.post(
- "https://api.tavily.com/search",
- json=payload,
- headers=header,
- ) as response:
- if response.status != 200:
- reason = await response.text()
- raise Exception(
- f"Tavily web search failed: {reason}, status: {response.status}",
- )
- data = await response.json()
- return [
- SearchResult(
- title=item.get("title"),
- url=item.get("url"),
- snippet=item.get("content"),
- favicon=item.get("favicon"),
- )
- for item in data.get("results", [])
- ]
+ async with aiohttp.ClientSession(trust_env=True) as session, session.post(
+ "https://api.tavily.com/search",
+ json=payload,
+ headers=header,
+ ) as response:
+ if response.status != 200:
+ reason = await response.text()
+ raise Exception(
+ f"Tavily web search failed: {reason}, status: {response.status}",
+ )
+ data = await response.json()
+ return [
+ SearchResult(
+ title=item.get("title"),
+ url=item.get("url"),
+ snippet=item.get("content"),
+ favicon=item.get("favicon"),
+ )
+ for item in data.get("results", [])
+ ]
async def _tavily_extract(provider_settings: dict, payload: dict) -> list[dict]:
@@ -153,24 +152,23 @@ async def _tavily_extract(provider_settings: dict, payload: dict) -> list[dict]:
"Authorization": f"Bearer {tavily_key}",
"Content-Type": "application/json",
}
- async with aiohttp.ClientSession(trust_env=True) as session:
- async with session.post(
- "https://api.tavily.com/extract",
- json=payload,
- headers=header,
- ) as response:
- if response.status != 200:
- reason = await response.text()
- raise Exception(
- f"Tavily web search failed: {reason}, status: {response.status}",
- )
- data = await response.json()
- results: list[dict] = data.get("results", [])
- if not results:
- raise ValueError(
- "Error: Tavily web searcher does not return any results."
- )
- return results
+ async with aiohttp.ClientSession(trust_env=True) as session, session.post(
+ "https://api.tavily.com/extract",
+ json=payload,
+ headers=header,
+ ) as response:
+ if response.status != 200:
+ reason = await response.text()
+ raise Exception(
+ f"Tavily web search failed: {reason}, status: {response.status}",
+ )
+ data = await response.json()
+ results: list[dict] = data.get("results", [])
+ if not results:
+ raise ValueError(
+ "Error: Tavily web searcher does not return any results.",
+ )
+ return results
async def _bocha_search(
@@ -182,28 +180,27 @@ async def _bocha_search(
"Authorization": f"Bearer {bocha_key}",
"Content-Type": "application/json",
}
- async with aiohttp.ClientSession(trust_env=True) as session:
- async with session.post(
- "https://api.bochaai.com/v1/web-search",
- json=payload,
- headers=header,
- ) as response:
- if response.status != 200:
- reason = await response.text()
- raise Exception(
- f"BoCha web search failed: {reason}, status: {response.status}",
- )
- data = await response.json()
- rows = data["data"]["webPages"]["value"]
- return [
- SearchResult(
- title=item.get("name"),
- url=item.get("url"),
- snippet=item.get("snippet"),
- favicon=item.get("siteIcon"),
- )
- for item in rows
- ]
+ async with aiohttp.ClientSession(trust_env=True) as session, session.post(
+ "https://api.bochaai.com/v1/web-search",
+ json=payload,
+ headers=header,
+ ) as response:
+ if response.status != 200:
+ reason = await response.text()
+ raise Exception(
+ f"BoCha web search failed: {reason}, status: {response.status}",
+ )
+ data = await response.json()
+ rows = data["data"]["webPages"]["value"]
+ return [
+ SearchResult(
+ title=item.get("name"),
+ url=item.get("url"),
+ snippet=item.get("snippet"),
+ favicon=item.get("siteIcon"),
+ )
+ for item in rows
+ ]
async def _brave_search(
@@ -215,27 +212,26 @@ async def _brave_search(
"Accept": "application/json",
"X-Subscription-Token": brave_key,
}
- async with aiohttp.ClientSession(trust_env=True) as session:
- async with session.get(
- "https://api.search.brave.com/res/v1/web/search",
- params=payload,
- headers=header,
- ) as response:
- if response.status != 200:
- reason = await response.text()
- raise Exception(
- f"Brave web search failed: {reason}, status: {response.status}",
- )
- data = await response.json()
- rows = data.get("web", {}).get("results", [])
- return [
- SearchResult(
- title=item.get("title", ""),
- url=item.get("url", ""),
- snippet=item.get("description", ""),
- )
- for item in rows
- ]
+ async with aiohttp.ClientSession(trust_env=True) as session, session.get(
+ "https://api.search.brave.com/res/v1/web/search",
+ params=payload,
+ headers=header,
+ ) as response:
+ if response.status != 200:
+ reason = await response.text()
+ raise Exception(
+ f"Brave web search failed: {reason}, status: {response.status}",
+ )
+ data = await response.json()
+ rows = data.get("web", {}).get("results", [])
+ return [
+ SearchResult(
+ title=item.get("title", ""),
+ url=item.get("url", ""),
+ snippet=item.get("description", ""),
+ )
+ for item in rows
+ ]
async def _baidu_search(
@@ -251,29 +247,28 @@ async def _baidu_search(
"X-Appbuilder-Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
}
- async with aiohttp.ClientSession(trust_env=True) as session:
- async with session.post(
- "https://qianfan.baidubce.com/v2/ai_search/web_search",
- json=payload,
- headers=headers,
- ) as response:
- if response.status != 200:
- reason = await response.text()
- raise Exception(
- f"Baidu AI Search failed: {reason}, status: {response.status}",
- )
- data = await response.json()
- references = data.get("references", [])
- return [
- SearchResult(
- title=item.get("title", ""),
- url=item.get("url", ""),
- snippet=item.get("content", ""),
- favicon=item.get("icon"),
- )
- for item in references
- if item.get("url")
- ]
+ async with aiohttp.ClientSession(trust_env=True) as session, session.post(
+ "https://qianfan.baidubce.com/v2/ai_search/web_search",
+ json=payload,
+ headers=headers,
+ ) as response:
+ if response.status != 200:
+ reason = await response.text()
+ raise Exception(
+ f"Baidu AI Search failed: {reason}, status: {response.status}",
+ )
+ data = await response.json()
+ references = data.get("references", [])
+ return [
+ SearchResult(
+ title=item.get("title", ""),
+ url=item.get("url", ""),
+ snippet=item.get("content", ""),
+ favicon=item.get("icon"),
+ )
+ for item in references
+ if item.get("url")
+ ]
@builtin_tool
@@ -319,7 +314,7 @@ class TavilyWebSearchTool(FunctionTool[AstrAgentContext]):
},
},
"required": ["query"],
- }
+ },
)
async def call(self, context, **kwargs) -> ToolExecResult:
@@ -378,7 +373,7 @@ class TavilyExtractWebPageTool(FunctionTool[AstrAgentContext]):
},
},
"required": ["url"],
- }
+ },
)
async def call(self, context, **kwargs) -> ToolExecResult:
@@ -444,7 +439,7 @@ class BochaWebSearchTool(FunctionTool[AstrAgentContext]):
},
},
"required": ["query"],
- }
+ },
)
async def call(self, context, **kwargs) -> ToolExecResult:
@@ -498,7 +493,7 @@ class BraveWebSearchTool(FunctionTool[AstrAgentContext]):
},
},
"required": ["query"],
- }
+ },
)
async def call(self, context, **kwargs) -> ToolExecResult:
@@ -507,10 +502,8 @@ class BraveWebSearchTool(FunctionTool[AstrAgentContext]):
return "Error: Brave API key is not configured in AstrBot."
count = int(kwargs.get("count", 10))
- if count < 1:
- count = 1
- if count > 20:
- count = 20
+ count = max(count, 1)
+ count = min(count, 20)
payload = {
"q": kwargs["query"],
@@ -555,7 +548,7 @@ class BaiduWebSearchTool(FunctionTool[AstrAgentContext]):
},
},
"required": ["query"],
- }
+ },
)
async def call(self, context, **kwargs) -> ToolExecResult:
@@ -564,10 +557,8 @@ class BaiduWebSearchTool(FunctionTool[AstrAgentContext]):
return "Error: Baidu AI Search API key is not configured in AstrBot."
top_k = int(kwargs.get("top_k", 10))
- if top_k < 1:
- top_k = 1
- if top_k > 50:
- top_k = 50
+ top_k = max(top_k, 1)
+ top_k = min(top_k, 50)
payload = {
"messages": [{"role": "user", "content": str(kwargs["query"])[:72]}],
@@ -592,11 +583,11 @@ class BaiduWebSearchTool(FunctionTool[AstrAgentContext]):
__all__ = [
+ "WEB_SEARCH_TOOL_NAMES",
"BaiduWebSearchTool",
"BochaWebSearchTool",
"BraveWebSearchTool",
"TavilyExtractWebPageTool",
"TavilyWebSearchTool",
- "WEB_SEARCH_TOOL_NAMES",
"normalize_legacy_web_search_config",
]
diff --git a/astrbot/core/umop_config_router.py b/astrbot/core/umop_config_router.py
index c2588e6c2..f734966be 100644
--- a/astrbot/core/umop_config_router.py
+++ b/astrbot/core/umop_config_router.py
@@ -107,8 +107,8 @@ class UmopConfigRouter:
Raises:
ValueError: 当 umo 格式不正确时抛出
- """
+ """
if self._split_umo(umo) is None:
raise ValueError(
"umop must be a string in the format [platform_id]:[message_type]:[session_id], with optional wildcards * or empty for all",
diff --git a/astrbot/core/updator.py b/astrbot/core/updator.py
index df2cfb82c..e67bf891f 100644
--- a/astrbot/core/updator.py
+++ b/astrbot/core/updator.py
@@ -150,7 +150,7 @@ class AstrBotUpdator(RepoZipUpdator):
if os.environ.get("ASTRBOT_CLI") or os.environ.get("ASTRBOT_LAUNCHER"):
raise Exception(
- "Error: You are running AstrBot via CLI, please use `pip` or `uv tool upgrade` to update AstrBot."
+ "Error: You are running AstrBot via CLI, please use `pip` or `uv tool upgrade` to update AstrBot.",
) # 避免版本管理混乱
if latest:
diff --git a/astrbot/core/utils/active_event_registry.py b/astrbot/core/utils/active_event_registry.py
index d98cdee37..e6933d04a 100644
--- a/astrbot/core/utils/active_event_registry.py
+++ b/astrbot/core/utils/active_event_registry.py
@@ -38,6 +38,7 @@ class ActiveEventRegistry:
Returns:
被终止的事件数量。
+
"""
count = 0
for event in list(self._events.get(umo, [])):
diff --git a/astrbot/core/utils/core_constraints.py b/astrbot/core/utils/core_constraints.py
index b43f00122..4b0c36daf 100644
--- a/astrbot/core/utils/core_constraints.py
+++ b/astrbot/core/utils/core_constraints.py
@@ -103,7 +103,7 @@ class CoreConstraintsProvider:
import tempfile
with tempfile.NamedTemporaryFile(
- mode="w", suffix="_constraints.txt", delete=False, encoding="utf-8"
+ mode="w", suffix="_constraints.txt", delete=False, encoding="utf-8",
) as f:
f.write("\n".join(constraints))
path = f.name
diff --git a/astrbot/core/utils/error_redaction.py b/astrbot/core/utils/error_redaction.py
index dcab07ac5..46b977787 100644
--- a/astrbot/core/utils/error_redaction.py
+++ b/astrbot/core/utils/error_redaction.py
@@ -5,19 +5,19 @@ _SECRET_KEYS = (
)
_JSON_FIELD_PATTERN = re.compile(
- rf"(?i)(?P(?P['\"]){_SECRET_KEYS}(?P=kq)\s*:\s*)(?P['\"])(?P[^'\"]+)(?P=vq)"
+ rf"(?i)(?P(?P['\"]){_SECRET_KEYS}(?P=kq)\s*:\s*)(?P['\"])(?P[^'\"]+)(?P=vq)",
)
_AUTH_JSON_FIELD_PATTERN = re.compile(
- r"(?i)(?P(?P['\"])authorization(?P=kq)\s*:\s*)(?P['\"])bearer\s+[^'\"]+(?P=vq)"
+ r"(?i)(?P(?P['\"])authorization(?P=kq)\s*:\s*)(?P['\"])bearer\s+[^'\"]+(?P=vq)",
)
_QUERY_FIELD_PATTERN = re.compile(
- rf"(?i)(?P{_SECRET_KEYS}\s*=\s*)(?P[^&'\" ]+)"
+ rf"(?i)(?P{_SECRET_KEYS}\s*=\s*)(?P[^&'\" ]+)",
)
_QUERY_PARAM_PATTERN = re.compile(
- r"(?i)(?P[?&](?:api_?key|key|access_?token|auth_?token)=)(?P[^&'\" ]+)"
+ r"(?i)(?P[?&](?:api_?key|key|access_?token|auth_?token)=)(?P[^&'\" ]+)",
)
_AUTH_HEADER_PATTERN = re.compile(
- r"(?i)(?P\bauthorization\s*:\s*bearer\s+)(?P[A-Za-z0-9._\-]+)"
+ r"(?i)(?P\bauthorization\s*:\s*bearer\s+)(?P[A-Za-z0-9._\-]+)",
)
_BEARER_PATTERN = re.compile(r"(?i)(?P\bbearer\s+)(?P[A-Za-z0-9._\-]+)")
_SK_PATTERN = re.compile(r"\bsk-[A-Za-z0-9]{16,}\b")
diff --git a/astrbot/core/utils/io.py b/astrbot/core/utils/io.py
index b56592674..c422d12d7 100644
--- a/astrbot/core/utils/io.py
+++ b/astrbot/core/utils/io.py
@@ -99,7 +99,7 @@ async def download_image_by_url(
f"SSL certificate verification failed for {url}. "
"Disabling SSL verification (CERT_NONE) as a fallback. "
"This is insecure and exposes the application to man-in-the-middle attacks. "
- "Please investigate and resolve certificate issues."
+ "Please investigate and resolve certificate issues.",
)
ssl_context = ssl.create_default_context()
ssl_context.check_hostname = False
@@ -163,13 +163,13 @@ async def download_file(url: str, path: str, show_progress: bool = False) -> Non
except (aiohttp.ClientConnectorSSLError, aiohttp.ClientConnectorCertificateError):
# 关闭SSL验证(仅在证书验证失败时作为fallback)
logger.warning(
- "SSL 证书验证失败,已关闭 SSL 验证(不安全,仅用于临时下载)。请检查目标服务器的证书配置。"
+ "SSL 证书验证失败,已关闭 SSL 验证(不安全,仅用于临时下载)。请检查目标服务器的证书配置。",
)
logger.warning(
f"SSL certificate verification failed for {url}. "
"Falling back to unverified connection (CERT_NONE). "
"This is insecure and exposes the application to man-in-the-middle attacks. "
- "Please investigate certificate issues with the remote server."
+ "Please investigate certificate issues with the remote server.",
)
ssl_context = ssl.create_default_context()
ssl_context.check_hostname = False
diff --git a/astrbot/core/utils/llm_metadata.py b/astrbot/core/utils/llm_metadata.py
index ef88e9490..254446bc9 100644
--- a/astrbot/core/utils/llm_metadata.py
+++ b/astrbot/core/utils/llm_metadata.py
@@ -34,33 +34,32 @@ async def update_llm_metadata() -> None:
url = "https://models.dev/api.json"
try:
async with aiohttp.ClientSession(
- trust_env=True, connector=build_tls_connector()
- ) as session:
- async with session.get(url) as response:
- data = await response.json()
- global LLM_METADATAS
- models = {}
- for info in data.values():
- for model in info.get("models", {}).values():
- model_id = model.get("id")
- if not model_id:
- continue
- models[model_id] = LLMMetadata(
- id=model_id,
- reasoning=model.get("reasoning", False),
- tool_call=model.get("tool_call", False),
- knowledge=model.get("knowledge", "none"),
- release_date=model.get("release_date", ""),
- modalities=model.get(
- "modalities", {"input": [], "output": []}
- ),
- open_weights=model.get("open_weights", False),
- limit=model.get("limit", {"context": 0, "output": 0}),
- )
- # Replace the global cache in-place so references remain valid
- LLM_METADATAS.clear()
- LLM_METADATAS.update(models)
- logger.info(f"Successfully fetched metadata for {len(models)} LLMs.")
+ trust_env=True, connector=build_tls_connector(),
+ ) as session, session.get(url) as response:
+ data = await response.json()
+ global LLM_METADATAS
+ models = {}
+ for info in data.values():
+ for model in info.get("models", {}).values():
+ model_id = model.get("id")
+ if not model_id:
+ continue
+ models[model_id] = LLMMetadata(
+ id=model_id,
+ reasoning=model.get("reasoning", False),
+ tool_call=model.get("tool_call", False),
+ knowledge=model.get("knowledge", "none"),
+ release_date=model.get("release_date", ""),
+ modalities=model.get(
+ "modalities", {"input": [], "output": []},
+ ),
+ open_weights=model.get("open_weights", False),
+ limit=model.get("limit", {"context": 0, "output": 0}),
+ )
+ # Replace the global cache in-place so references remain valid
+ LLM_METADATAS.clear()
+ LLM_METADATAS.update(models)
+ logger.info(f"Successfully fetched metadata for {len(models)} LLMs.")
except Exception as e:
logger.error(f"Failed to fetch LLM metadata: {e}")
return
diff --git a/astrbot/core/utils/media_utils.py b/astrbot/core/utils/media_utils.py
index 40f1e6049..df2b34bda 100644
--- a/astrbot/core/utils/media_utils.py
+++ b/astrbot/core/utils/media_utils.py
@@ -30,6 +30,7 @@ async def get_media_duration(file_path: str) -> int | None:
Returns:
时长(毫秒),如果获取失败返回None
+
"""
try:
# 使用ffprobe获取时长
@@ -53,13 +54,12 @@ async def get_media_duration(file_path: str) -> int | None:
duration_ms = int(duration_seconds * 1000)
logger.debug(f"[Media Utils] 获取媒体时长: {duration_ms}ms")
return duration_ms
- else:
- logger.warning(f"[Media Utils] 无法获取媒体文件时长: {file_path}")
- return None
+ logger.warning(f"[Media Utils] 无法获取媒体文件时长: {file_path}")
+ return None
except FileNotFoundError:
logger.warning(
- "[Media Utils] ffprobe未安装或不在PATH中,无法获取媒体时长。请安装ffmpeg: https://ffmpeg.org/"
+ "[Media Utils] ffprobe未安装或不在PATH中,无法获取媒体时长。请安装ffmpeg: https://ffmpeg.org/",
)
return None
except Exception as e:
@@ -79,6 +79,7 @@ async def convert_audio_to_opus(audio_path: str, output_path: str | None = None)
Raises:
Exception: 转换失败时抛出异常
+
"""
# 如果已经是opus格式,直接返回
if audio_path.lower().endswith(".opus"):
@@ -121,7 +122,7 @@ async def convert_audio_to_opus(audio_path: str, output_path: str | None = None)
try:
os.remove(output_path)
logger.debug(
- f"[Media Utils] 已清理失败的opus输出文件: {output_path}"
+ f"[Media Utils] 已清理失败的opus输出文件: {output_path}",
)
except OSError as e:
logger.warning(f"[Media Utils] 清理失败的opus输出文件时出错: {e}")
@@ -135,7 +136,7 @@ async def convert_audio_to_opus(audio_path: str, output_path: str | None = None)
except FileNotFoundError:
logger.error(
- "[Media Utils] ffmpeg未安装或不在PATH中,无法转换音频格式。请安装ffmpeg: https://ffmpeg.org/"
+ "[Media Utils] ffmpeg未安装或不在PATH中,无法转换音频格式。请安装ffmpeg: https://ffmpeg.org/",
)
raise Exception("ffmpeg not found")
except Exception as e:
@@ -144,7 +145,7 @@ async def convert_audio_to_opus(audio_path: str, output_path: str | None = None)
async def convert_video_format(
- video_path: str, output_format: str = "mp4", output_path: str | None = None
+ video_path: str, output_format: str = "mp4", output_path: str | None = None,
) -> str:
"""使用ffmpeg转换视频格式
@@ -158,6 +159,7 @@ async def convert_video_format(
Raises:
Exception: 转换失败时抛出异常
+
"""
# 如果已经是目标格式,直接返回
if video_path.lower().endswith(f".{output_format}"):
@@ -196,11 +198,11 @@ async def convert_video_format(
try:
os.remove(output_path)
logger.debug(
- f"[Media Utils] 已清理失败的{output_format}输出文件: {output_path}"
+ f"[Media Utils] 已清理失败的{output_format}输出文件: {output_path}",
)
except OSError as e:
logger.warning(
- f"[Media Utils] 清理失败的{output_format}输出文件时出错: {e}"
+ f"[Media Utils] 清理失败的{output_format}输出文件时出错: {e}",
)
error_msg = stderr.decode() if stderr else "未知错误"
@@ -212,7 +214,7 @@ async def convert_video_format(
except FileNotFoundError:
logger.error(
- "[Media Utils] ffmpeg未安装或不在PATH中,无法转换视频格式。请安装ffmpeg: https://ffmpeg.org/"
+ "[Media Utils] ffmpeg未安装或不在PATH中,无法转换视频格式。请安装ffmpeg: https://ffmpeg.org/",
)
raise Exception("ffmpeg not found")
except Exception as e:
@@ -234,6 +236,7 @@ async def convert_audio_format(
Returns:
转换后的音频文件路径
+
"""
if audio_path.lower().endswith(f".{output_format}"):
return audio_path
@@ -246,9 +249,7 @@ async def convert_audio_format(
args = ["ffmpeg", "-y", "-i", audio_path]
if output_format == "amr":
args.extend(["-ac", "1", "-ar", "8000", "-ab", "12.2k"])
- elif output_format == "ogg":
- args.extend(["-acodec", "libopus", "-ac", "1", "-ar", "16000"])
- elif output_format == "opus":
+ elif output_format == "ogg" or output_format == "opus":
args.extend(["-acodec", "libopus", "-ac", "1", "-ar", "16000"])
args.append(output_path)
@@ -296,7 +297,6 @@ async def ensure_wav(audio_path: str, output_path: str | None = None) -> str:
If the file appears to already be wav, return it directly to avoid extra conversion.
"""
-
if not audio_path:
return audio_path
@@ -430,6 +430,7 @@ async def compress_image(
Returns:
The compressed image path. Returns the original path if compression
fails or the source does not need compression.
+
"""
max_size = max(int(max_size), 1)
quality = min(max(int(quality), 1), 100)
@@ -439,7 +440,7 @@ async def compress_image(
# Skip compression for remote images and return the original value.
if url_or_path.startswith("http"):
return url_or_path
- elif url_or_path.startswith("data:image"):
+ if url_or_path.startswith("data:image"):
_header, encoded = url_or_path.split(",", 1)
data = base64.b64decode(encoded)
if len(data) < min_file_size_bytes:
diff --git a/astrbot/core/utils/migra_helper.py b/astrbot/core/utils/migra_helper.py
index 40b899620..e1c4c8367 100644
--- a/astrbot/core/utils/migra_helper.py
+++ b/astrbot/core/utils/migra_helper.py
@@ -12,8 +12,7 @@ from astrbot.core.db.migration.migra_webchat_session import migrate_webchat_sess
def _migra_agent_runner_configs(conf: AstrBotConfig, ids_map: dict) -> None:
- """
- Migra agent runner configs from provider configs.
+ """Migra agent runner configs from provider configs.
"""
try:
default_prov_id = conf["provider_settings"]["default_provider_id"]
@@ -43,8 +42,7 @@ def _migra_agent_runner_configs(conf: AstrBotConfig, ids_map: dict) -> None:
def _migra_provider_to_source_structure(conf: AstrBotConfig) -> None:
- """
- Migrate old provider structure to new provider-source separation.
+ """Migrate old provider structure to new provider-source separation.
Provider only keeps: id, provider_source_id, model, modalities, custom_extra_body
All other fields move to provider_sources.
"""
@@ -129,10 +127,9 @@ def _migra_provider_to_source_structure(conf: AstrBotConfig) -> None:
async def migra(
- db, astrbot_config_mgr, umop_config_router, acm: AstrBotConfigManager
+ db, astrbot_config_mgr, umop_config_router, acm: AstrBotConfigManager,
) -> None:
- """
- Stores the migration logic here.
+ """Stores the migration logic here.
btw, i really don't like migration :(
"""
# 4.5 to 4.6 migration for umop_config_router
diff --git a/astrbot/core/utils/network_utils.py b/astrbot/core/utils/network_utils.py
index 727f3762a..dad6465ba 100644
--- a/astrbot/core/utils/network_utils.py
+++ b/astrbot/core/utils/network_utils.py
@@ -16,6 +16,7 @@ def is_connection_error(exc: BaseException) -> bool:
Returns:
True if the exception is a connection/network error
+
"""
# Check for httpx network errors
if isinstance(
@@ -59,6 +60,7 @@ def log_connection_failure(
provider_label: The provider name for log prefix (e.g., "OpenAI", "Gemini")
error: The exception that occurred
proxy: The proxy address if configured, or None/empty string
+
"""
import os
@@ -68,13 +70,13 @@ def log_connection_failure(
effective_proxy = proxy
if not effective_proxy:
effective_proxy = os.environ.get(
- "http_proxy", os.environ.get("https_proxy", "")
+ "http_proxy", os.environ.get("https_proxy", ""),
)
if effective_proxy:
logger.error(
f"[{provider_label}] 网络/代理连接失败 ({error_type})。"
- f"代理地址: {effective_proxy},错误: {error}"
+ f"代理地址: {effective_proxy},错误: {error}",
)
else:
logger.error(f"[{provider_label}] 网络连接失败 ({error_type})。错误: {error}")
@@ -95,6 +97,7 @@ def create_proxy_client(
Returns:
An httpx.AsyncClient configured with the proxy, or None if no proxy
+
"""
if proxy:
logger.info(f"[{provider_label}] 使用代理: {proxy}")
diff --git a/astrbot/core/utils/pip_installer.py b/astrbot/core/utils/pip_installer.py
index e5f713820..fd3197eb4 100644
--- a/astrbot/core/utils/pip_installer.py
+++ b/astrbot/core/utils/pip_installer.py
@@ -45,7 +45,7 @@ _PIP_FAILURE_PATTERNS = {
"dependency_detail": re.compile(r"\bdepends on\b", re.IGNORECASE),
}
_SENSITIVE_PIP_VALUE_KEYS = frozenset(
- {"password", "passwd", "pass", "api_token", "token", "auth_token"}
+ {"password", "passwd", "pass", "api_token", "token", "auth_token"},
)
_MAX_PIP_OUTPUT_LINES = 200
@@ -54,7 +54,7 @@ class DependencyConflictError(Exception):
"""Raised when pip encounters a dependency conflict."""
def __init__(
- self, message: str, errors: list[str], *, is_core_conflict: bool
+ self, message: str, errors: list[str], *, is_core_conflict: bool,
) -> None:
super().__init__(message)
self.errors = errors
@@ -90,7 +90,7 @@ def _get_pip_main():
"pip module is unavailable "
f"(sys.executable={sys.executable}, "
f"frozen={getattr(sys, 'frozen', False)}, "
- f"ASTRBOT_DESKTOP_CLIENT={os.environ.get('ASTRBOT_DESKTOP_CLIENT')})"
+ f"ASTRBOT_DESKTOP_CLIENT={os.environ.get('ASTRBOT_DESKTOP_CLIENT')})",
) from exc
return pip_main
@@ -330,7 +330,7 @@ def _build_packaged_windows_runtime_build_env(
return {}
include_dir = _normalize_windows_native_build_path(
- ntpath.join(runtime_dir, "include")
+ ntpath.join(runtime_dir, "include"),
)
libs_dir = _normalize_windows_native_build_path(ntpath.join(runtime_dir, "libs"))
include_exists = os.path.isdir(include_dir)
@@ -559,7 +559,7 @@ def _ensure_preferred_modules(
site_packages_path: str,
) -> None:
unresolved_prefer_reasons = _prefer_modules_from_site_packages(
- module_names, site_packages_path
+ module_names, site_packages_path,
)
unresolved_modules: list[str] = []
@@ -619,7 +619,7 @@ def _is_module_loaded_from_site_packages(
def _prefer_module_from_site_packages(
- module_name: str, site_packages_path: str
+ module_name: str, site_packages_path: str,
) -> bool:
with _SITE_PACKAGES_IMPORT_LOCK:
base_path = os.path.join(site_packages_path, *module_name.split("."))
@@ -886,12 +886,12 @@ def _patch_distlib_finder_for_frozen_runtime() -> None:
if not isinstance(finder_registry, dict):
logger.warning(
- "Skip patching distlib finder because _finder_registry is unavailable."
+ "Skip patching distlib finder because _finder_registry is unavailable.",
)
return
if not callable(register_finder) or resource_finder is None:
logger.warning(
- "Skip patching distlib finder because register API is unavailable."
+ "Skip patching distlib finder because register API is unavailable.",
)
return
@@ -914,7 +914,7 @@ def _patch_distlib_finder_for_frozen_runtime() -> None:
package_name,
):
finder_registry = getattr(
- distlib_resources, "_finder_registry", finder_registry
+ distlib_resources, "_finder_registry", finder_registry,
)
@@ -944,7 +944,7 @@ class PipInstaller:
if package_name and normalized_requirements_path:
raise ValueError(
- "package_name and requirements_path cannot be used together"
+ "package_name and requirements_path cannot be used together",
)
if package_name:
@@ -955,7 +955,7 @@ class PipInstaller:
elif normalized_requirements_path:
args = ["install", "-r", normalized_requirements_path]
requested_requirements = extract_requirement_names(
- normalized_requirements_path
+ normalized_requirements_path,
)
if not args:
@@ -985,7 +985,7 @@ class PipInstaller:
allow_target_upgrade: bool = True,
) -> None:
args, requested_requirements = self._build_pip_args(
- package_name, requirements_path, mirror
+ package_name, requirements_path, mirror,
)
if not args:
logger.info("Pip 包管理器跳过安装:未提供有效的包名或 requirements 文件。")
@@ -1005,7 +1005,7 @@ class PipInstaller:
"--upgrade",
"--upgrade-strategy",
"only-if-needed",
- ]
+ ],
)
with self._core_constraints.constraints_file() as constraints_file_path:
diff --git a/astrbot/core/utils/quoted_message/__init__.py b/astrbot/core/utils/quoted_message/__init__.py
index 8421898fd..a9e24391c 100644
--- a/astrbot/core/utils/quoted_message/__init__.py
+++ b/astrbot/core/utils/quoted_message/__init__.py
@@ -3,6 +3,6 @@ from __future__ import annotations
from .extractor import extract_quoted_message_images, extract_quoted_message_text
__all__ = [
- "extract_quoted_message_text",
"extract_quoted_message_images",
+ "extract_quoted_message_text",
]
diff --git a/astrbot/core/utils/quoted_message/chain_parser.py b/astrbot/core/utils/quoted_message/chain_parser.py
index 528ce14b8..7847061bd 100644
--- a/astrbot/core/utils/quoted_message/chain_parser.py
+++ b/astrbot/core/utils/quoted_message/chain_parser.py
@@ -89,7 +89,7 @@ def _extract_image_refs_from_component_chain(
seg,
depth=depth + 1,
settings=settings,
- )
+ ),
)
elif isinstance(seg, Node):
image_refs.extend(
@@ -97,7 +97,7 @@ def _extract_image_refs_from_component_chain(
seg.content,
depth=depth + 1,
settings=settings,
- )
+ ),
)
elif isinstance(seg, Nodes):
for node in seg.nodes:
@@ -106,7 +106,7 @@ def _extract_image_refs_from_component_chain(
node.content,
depth=depth + 1,
settings=settings,
- )
+ ),
)
return normalize_and_dedupe_strings(image_refs)
@@ -308,7 +308,7 @@ def _parse_onebot_segments(
isinstance(candidate_file, str)
and candidate_file.strip()
and looks_like_image_file_name(
- seg_data.get("name") or seg_data.get("file_name") or candidate_file
+ seg_data.get("name") or seg_data.get("file_name") or candidate_file,
)
):
image_refs.append(candidate_file.strip())
diff --git a/astrbot/core/utils/quoted_message/extractor.py b/astrbot/core/utils/quoted_message/extractor.py
index 83570d66c..3a3ad5e69 100644
--- a/astrbot/core/utils/quoted_message/extractor.py
+++ b/astrbot/core/utils/quoted_message/extractor.py
@@ -93,14 +93,14 @@ class QuotedMessageExtractor:
fetch_remote: bool,
) -> QuotedMessageContent | None:
reply = reply_component or self._reply_parser.find_first_reply_component(
- self._event
+ self._event,
)
if not reply:
return None
embedded_text = self._reply_parser.extract_text_from_reply_component(reply)
embedded_image_refs = list(
- self._reply_parser.extract_image_refs_from_reply_component(reply)
+ self._reply_parser.extract_image_refs_from_reply_component(reply),
)
reply_id = getattr(reply, "id", None)
@@ -156,7 +156,7 @@ class QuotedMessageExtractor:
if (
embedded_content.embedded_text
and not self._reply_parser.is_forward_placeholder_only_text(
- embedded_content.embedded_text
+ embedded_content.embedded_text,
)
):
return embedded_content.embedded_text
@@ -197,7 +197,7 @@ async def extract_quoted_message_text(
settings: QuotedMessageParserSettings | None = None,
) -> str | None:
return await QuotedMessageExtractor(event, settings=settings or SETTINGS).text(
- reply_component
+ reply_component,
)
@@ -207,5 +207,5 @@ async def extract_quoted_message_images(
settings: QuotedMessageParserSettings | None = None,
) -> list[str]:
return await QuotedMessageExtractor(event, settings=settings or SETTINGS).images(
- reply_component
+ reply_component,
)
diff --git a/astrbot/core/utils/quoted_message/image_resolver.py b/astrbot/core/utils/quoted_message/image_resolver.py
index 5a4c21fb2..b777cd2dd 100644
--- a/astrbot/core/utils/quoted_message/image_resolver.py
+++ b/astrbot/core/utils/quoted_message/image_resolver.py
@@ -36,7 +36,7 @@ def _build_image_resolve_actions(
("get_image", {"image": candidate}),
("get_file", {"file_id": candidate}),
("get_file", {"file": candidate}),
- ]
+ ],
)
try:
@@ -53,7 +53,7 @@ def _build_image_resolve_actions(
(
"get_group_file_url",
{"group_id": group_id_value, "file_id": candidate},
- )
+ ),
)
for candidate in candidates:
actions.append(("get_private_file_url", {"file_id": candidate}))
diff --git a/astrbot/core/utils/quoted_message_parser.py b/astrbot/core/utils/quoted_message_parser.py
index fa6ac18dd..c14e7f884 100644
--- a/astrbot/core/utils/quoted_message_parser.py
+++ b/astrbot/core/utils/quoted_message_parser.py
@@ -6,6 +6,6 @@ from astrbot.core.utils.quoted_message.extractor import (
)
__all__ = [
- "extract_quoted_message_text",
"extract_quoted_message_images",
+ "extract_quoted_message_text",
]
diff --git a/astrbot/core/utils/requirements_utils.py b/astrbot/core/utils/requirements_utils.py
index 969976a4f..2a0f821cd 100644
--- a/astrbot/core/utils/requirements_utils.py
+++ b/astrbot/core/utils/requirements_utils.py
@@ -20,7 +20,6 @@ logger = logging.getLogger("astrbot")
class RequirementsPrecheckFailed(Exception):
"""Raised when the pre-check of requirements fails."""
- pass
@dataclass(frozen=True)
@@ -66,7 +65,7 @@ def _looks_like_local_path_reference(token: str) -> bool:
if not candidate:
return False
return candidate in {".", ".."} or candidate.startswith(
- ("./", "../", "/", "~/", ".\\", "..\\", "\\")
+ ("./", "../", "/", "~/", ".\\", "..\\", "\\"),
)
@@ -196,7 +195,7 @@ def _extract_requirement_names_from_package_tokens(tokens: list[str]) -> frozens
"--trusted-host=",
"--requirement=",
"--constraint=",
- )
+ ),
):
continue
@@ -237,7 +236,7 @@ def parse_package_install_input(raw_input: str) -> ParsedPackageInput:
continue
specs.extend(tokens)
requirement_names.update(
- _extract_requirement_names_from_package_tokens(tokens)
+ _extract_requirement_names_from_package_tokens(tokens),
)
continue
@@ -260,7 +259,7 @@ def _iter_requirement_lines(
resolved_path = os.path.realpath(requirements_path)
if resolved_path in visited:
logger.warning(
- "检测到循环依赖的 requirements 包含: %s,将跳过该文件", resolved_path
+ "检测到循环依赖的 requirements 包含: %s,将跳过该文件", resolved_path,
)
return
visited.add(resolved_path)
@@ -390,7 +389,7 @@ def _load_requirement_lines_for_precheck(
def find_missing_requirements(requirements_path: str) -> set[str] | None:
can_precheck, requirement_lines = _load_requirement_lines_for_precheck(
- requirements_path
+ requirements_path,
)
if not can_precheck or requirement_lines is None:
return None
@@ -466,7 +465,7 @@ def plan_missing_requirements_install(
requirements_path: str,
) -> MissingRequirementsPlan | None:
can_precheck, requirement_lines = _load_requirement_lines_for_precheck(
- requirements_path
+ requirements_path,
)
if not can_precheck or requirement_lines is None:
return None
diff --git a/astrbot/core/utils/session_lock.py b/astrbot/core/utils/session_lock.py
index 732a29b72..684fc0240 100644
--- a/astrbot/core/utils/session_lock.py
+++ b/astrbot/core/utils/session_lock.py
@@ -36,7 +36,7 @@ class SessionLockManager:
def __init__(self) -> None:
self._state_guard = threading.Lock()
self._loop_managers: weakref.WeakKeyDictionary[
- asyncio.AbstractEventLoop, _PerLoopSessionLockManager
+ asyncio.AbstractEventLoop, _PerLoopSessionLockManager,
] = weakref.WeakKeyDictionary()
def _get_loop_manager(self) -> _PerLoopSessionLockManager:
diff --git a/astrbot/core/utils/shared_preferences.py b/astrbot/core/utils/shared_preferences.py
index 344808cbd..d8b8226f7 100644
--- a/astrbot/core/utils/shared_preferences.py
+++ b/astrbot/core/utils/shared_preferences.py
@@ -32,7 +32,7 @@ class SharedPreferences:
self._scheduler = BackgroundScheduler()
self._scheduler.add_job(
- self._clear_temporary_cache, "interval", hours=24, id="clear_sp_temp_cache"
+ self._clear_temporary_cache, "interval", hours=24, id="clear_sp_temp_cache",
)
self._scheduler.start()
@@ -203,7 +203,7 @@ class SharedPreferences:
return result
def put(
- self, key, value, scope: str | None = None, scope_id: str | None = None
+ self, key, value, scope: str | None = None, scope_id: str | None = None,
) -> None:
"""设置偏好设置(已弃用)"""
asyncio.run_coroutine_threadsafe(
@@ -212,7 +212,7 @@ class SharedPreferences:
).result()
def remove(
- self, key, scope: str | None = None, scope_id: str | None = None
+ self, key, scope: str | None = None, scope_id: str | None = None,
) -> None:
"""删除偏好设置(已弃用)"""
asyncio.run_coroutine_threadsafe(
diff --git a/astrbot/core/utils/temp_dir_cleaner.py b/astrbot/core/utils/temp_dir_cleaner.py
index c0c060098..5b9a39515 100644
--- a/astrbot/core/utils/temp_dir_cleaner.py
+++ b/astrbot/core/utils/temp_dir_cleaner.py
@@ -7,7 +7,7 @@ from astrbot import logger
from astrbot.core.utils.astrbot_path import get_astrbot_temp_path
-def parse_size_to_bytes(value: str | int | float | None) -> int:
+def parse_size_to_bytes(value: str | float | None) -> int:
"""Parse size in MB to bytes."""
if value is None:
return 0
@@ -72,7 +72,7 @@ class TempDirCleaner:
continue
total_size += stat.st_size
files.append(
- TempFileInfo(path=path, size=stat.st_size, mtime=stat.st_mtime)
+ TempFileInfo(path=path, size=stat.st_size, mtime=stat.st_mtime),
)
return total_size, files
@@ -81,7 +81,7 @@ class TempDirCleaner:
if not self._temp_dir.exists():
return
for path in sorted(
- self._temp_dir.rglob("*"), key=lambda p: len(p.parts), reverse=True
+ self._temp_dir.rglob("*"), key=lambda p: len(p.parts), reverse=True,
):
if not path.is_dir():
continue
diff --git a/astrbot/core/utils/webhook_utils.py b/astrbot/core/utils/webhook_utils.py
index 40dada3cb..cac96b4c3 100644
--- a/astrbot/core/utils/webhook_utils.py
+++ b/astrbot/core/utils/webhook_utils.py
@@ -23,7 +23,7 @@ def _get_dashboard_port() -> int:
def _is_dashboard_ssl_enabled() -> bool:
env_ssl = os.environ.get("DASHBOARD_SSL_ENABLE") or os.environ.get(
- "ASTRBOT_DASHBOARD_SSL_ENABLE"
+ "ASTRBOT_DASHBOARD_SSL_ENABLE",
)
if env_ssl is not None:
return env_ssl.strip().lower() in {"1", "true", "yes", "on"}
@@ -41,8 +41,8 @@ def log_webhook_info(platform_name: str, webhook_uuid: str) -> None:
Args:
platform_name: 平台名称
webhook_uuid: webhook 的 UUID
- """
+ """
callback_base = _get_callback_api_base()
if not callback_base:
@@ -74,6 +74,7 @@ def ensure_platform_webhook_config(platform_cfg: dict) -> bool:
Returns:
bool: 如果生成了 webhook_uuid 则返回 True,否则返回 False
+
"""
pt = platform_cfg.get("type", "")
if pt in WEBHOOK_SUPPORTED_PLATFORMS and not platform_cfg.get("webhook_uuid"):
diff --git a/astrbot/core/zip_updator.py b/astrbot/core/zip_updator.py
index 6cea6b38d..65b5c4b86 100644
--- a/astrbot/core/zip_updator.py
+++ b/astrbot/core/zip_updator.py
@@ -150,7 +150,7 @@ class RepoZipUpdator:
)
async def download_from_repo_url(
- self, target_path: str, repo_url: str, proxy=""
+ self, target_path: str, repo_url: str, proxy="",
) -> None:
author, repo, branch = self.parse_github_url(repo_url)
diff --git a/astrbot/dashboard/routes/__init__.py b/astrbot/dashboard/routes/__init__.py
index fbbd0c7a0..4c2b9211e 100644
--- a/astrbot/dashboard/routes/__init__.py
+++ b/astrbot/dashboard/routes/__init__.py
@@ -40,10 +40,10 @@ __all__ = [
"PlatformRoute",
"PluginRoute",
"SessionManagementRoute",
+ "SkillsRoute",
"StatRoute",
"StaticFileRoute",
"SubAgentRoute",
"ToolsRoute",
- "SkillsRoute",
"UpdateRoute",
]
diff --git a/astrbot/dashboard/routes/api_key.py b/astrbot/dashboard/routes/api_key.py
index 4b957fe8e..e5d5e6a20 100644
--- a/astrbot/dashboard/routes/api_key.py
+++ b/astrbot/dashboard/routes/api_key.py
@@ -99,7 +99,7 @@ class ApiKeyRoute(Route):
Response().error("expires_in_days must be greater than 0").__dict__
)
expires_at = datetime.now(timezone.utc) + timedelta(
- days=expires_in_days_int
+ days=expires_in_days_int,
)
raw_key = f"abk_{secrets.token_urlsafe(32)}"
diff --git a/astrbot/dashboard/routes/backup.py b/astrbot/dashboard/routes/backup.py
index ecc5dbfc8..ca08d7faa 100644
--- a/astrbot/dashboard/routes/backup.py
+++ b/astrbot/dashboard/routes/backup.py
@@ -40,6 +40,7 @@ def secure_filename(filename: str) -> str:
Returns:
安全的文件名
+
"""
# 跨平台处理:先将反斜杠替换为正斜杠,再取文件名
filename = filename.replace("\\", "/")
@@ -70,6 +71,7 @@ def generate_unique_filename(original_filename: str) -> str:
Returns:
添加了时间戳后缀的唯一文件名,格式为 {原文件名}_{YYYYMMDD_HHMMSS}.{扩展名}
+
"""
name, ext = os.path.splitext(original_filename)
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
@@ -184,7 +186,7 @@ class BackupRoute(Route):
"""创建进度回调函数"""
async def _callback(
- stage: str, current: int, total: int, message: str = ""
+ stage: str, current: int, total: int, message: str = "",
) -> None:
self._update_progress(
task_id,
@@ -202,7 +204,7 @@ class BackupRoute(Route):
if self._cleanup_task is None or self._cleanup_task.done():
try:
self._cleanup_task = asyncio.create_task(
- self._cleanup_expired_uploads()
+ self._cleanup_expired_uploads(),
)
except RuntimeError:
# 如果没有运行中的事件循环,跳过(等待下次异步调用时启动)
@@ -255,15 +257,15 @@ class BackupRoute(Route):
Returns:
dict | None: manifest 内容,如果不是有效备份则返回 None
+
"""
try:
with zipfile.ZipFile(zip_path, "r") as zf:
if "manifest.json" in zf.namelist():
manifest_data = zf.read("manifest.json")
return json.loads(manifest_data.decode("utf-8"))
- else:
- # 没有 manifest.json,不是有效的 AstrBot 备份
- return None
+ # 没有 manifest.json,不是有效的 AstrBot 备份
+ return None
except Exception as e:
logger.debug(f"读取备份 manifest 失败: {e}")
return None # 无法读取,不是有效备份
@@ -310,11 +312,11 @@ class BackupRoute(Route):
"size": stat.st_size,
"created_at": stat.st_mtime,
"type": manifest.get(
- "origin", "exported"
+ "origin", "exported",
), # 老版本没有 origin 默认为 exported
"astrbot_version": manifest.get("astrbot_version", "未知"),
"exported_at": manifest.get("exported_at"),
- }
+ },
)
# 按创建时间倒序排序
@@ -333,7 +335,7 @@ class BackupRoute(Route):
"total": len(backup_files),
"page": page,
"page_size": page_size,
- }
+ },
)
.__dict__
)
@@ -364,7 +366,7 @@ class BackupRoute(Route):
{
"task_id": task_id,
"message": "export task created, processing in background",
- }
+ },
)
.__dict__
)
@@ -442,7 +444,7 @@ class BackupRoute(Route):
await file.save(zip_path)
logger.info(
- f"上传的备份文件已保存: {unique_filename} (原始名称: {file.filename})"
+ f"上传的备份文件已保存: {unique_filename} (原始名称: {file.filename})",
)
return (
@@ -452,7 +454,7 @@ class BackupRoute(Route):
"filename": unique_filename,
"original_filename": file.filename,
"size": os.path.getsize(zip_path),
- }
+ },
)
.__dict__
)
@@ -520,7 +522,7 @@ class BackupRoute(Route):
logger.info(
f"初始化分片上传: upload_id={upload_id}, "
- f"filename={unique_filename}, total_chunks={total_chunks}"
+ f"filename={unique_filename}, total_chunks={total_chunks}",
)
return (
@@ -531,7 +533,7 @@ class BackupRoute(Route):
"chunk_size": CHUNK_SIZE,
"total_chunks": total_chunks,
"filename": unique_filename,
- }
+ },
)
.__dict__
)
@@ -596,7 +598,7 @@ class BackupRoute(Route):
logger.debug(
f"接收分片: upload_id={upload_id}, "
- f"chunk={chunk_index + 1}/{total_chunks}"
+ f"chunk={chunk_index + 1}/{total_chunks}",
)
return (
@@ -606,7 +608,7 @@ class BackupRoute(Route):
"received": received_count,
"total": total_chunks,
"chunk_index": chunk_index,
- }
+ },
)
.__dict__
)
@@ -623,6 +625,7 @@ class BackupRoute(Route):
Args:
zip_path: ZIP 文件路径
+
"""
try:
# 读取原有 manifest
@@ -706,7 +709,7 @@ class BackupRoute(Route):
self._mark_backup_as_uploaded(output_path)
logger.info(
- f"分片上传完成: {filename}, size={file_size}, chunks={total}"
+ f"分片上传完成: {filename}, size={file_size}, chunks={total}",
)
# 清理分片目录
@@ -719,7 +722,7 @@ class BackupRoute(Route):
"filename": filename,
"original_filename": session["original_filename"],
"size": file_size,
- }
+ },
)
.__dict__
)
@@ -859,7 +862,7 @@ class BackupRoute(Route):
{
"task_id": task_id,
"message": "import task created, processing in background",
- }
+ },
)
.__dict__
)
@@ -1065,8 +1068,7 @@ class BackupRoute(Route):
new_name = secure_filename(new_name)
# 移除新文件名中的扩展名(如果有的话)
- if new_name.endswith(".zip"):
- new_name = new_name[:-4]
+ new_name = new_name.removesuffix(".zip")
# 验证新文件名不为空
if not new_name or new_name.replace("_", "") == "":
@@ -1096,7 +1098,7 @@ class BackupRoute(Route):
{
"old_filename": filename,
"new_filename": new_filename,
- }
+ },
)
.__dict__
)
diff --git a/astrbot/dashboard/routes/chat.py b/astrbot/dashboard/routes/chat.py
index 5e6f77db9..cf975d8fb 100644
--- a/astrbot/dashboard/routes/chat.py
+++ b/astrbot/dashboard/routes/chat.py
@@ -107,7 +107,7 @@ class ChatRoute(Route):
if not os.path.exists(real_file_path):
# try legacy
file_path = os.path.join(
- self.legacy_img_dir, os.path.basename(filename)
+ self.legacy_img_dir, os.path.basename(filename),
)
if os.path.exists(file_path):
real_file_path = os.path.realpath(file_path)
@@ -187,7 +187,7 @@ class ChatRoute(Route):
"attachment_id": attachment.attachment_id,
"filename": filename,
"type": attach_type,
- }
+ },
)
.__dict__
)
@@ -201,7 +201,7 @@ class ChatRoute(Route):
)
async def _create_attachment_from_file(
- self, filename: str, attach_type: str
+ self, filename: str, attach_type: str,
) -> dict | None:
"""从本地文件创建 attachment 并返回消息部分。"""
return await create_attachment_part_from_existing_file(
@@ -213,7 +213,7 @@ class ChatRoute(Route):
)
def _extract_web_search_refs(
- self, accumulated_text: str, accumulated_parts: list
+ self, accumulated_text: str, accumulated_parts: list,
) -> dict:
"""从消息中提取 web_search_tavily 的引用
@@ -223,6 +223,7 @@ class ChatRoute(Route):
Returns:
包含 used 列表的字典,记录被引用的搜索结果
+
"""
supported = [
"web_search_baidu",
@@ -241,7 +242,7 @@ class ChatRoute(Route):
for part in tool_call_parts:
for tool_call in part["tool_calls"]:
if tool_call.get("name") not in supported or not tool_call.get(
- "result"
+ "result",
):
continue
try:
@@ -369,7 +370,7 @@ class ChatRoute(Route):
async with track_conversation(self.running_convs, webchat_conv_id):
while True:
result, should_break = await _poll_webchat_stream_result(
- back_queue, username
+ back_queue, username,
)
if should_break:
client_disconnected = True
@@ -410,7 +411,7 @@ class ChatRoute(Route):
except Exception as e:
if not client_disconnected:
logger.debug(
- f"[WebChat] 用户 {username} 断开聊天长连接。 {e}"
+ f"[WebChat] 用户 {username} 断开聊天长连接。 {e}",
)
client_disconnected = True
@@ -430,7 +431,7 @@ class ChatRoute(Route):
if accumulated_text:
# 如果累积了文本,则先保存文本
accumulated_parts.append(
- {"type": "plain", "text": accumulated_text}
+ {"type": "plain", "text": accumulated_text},
)
accumulated_text = ""
elif chain_type == "tool_call_result":
@@ -443,7 +444,7 @@ class ChatRoute(Route):
{
"type": "tool_call",
"tool_calls": [tool_calls[tc_id]],
- }
+ },
)
tool_calls.pop(tc_id, None)
elif chain_type == "reasoning":
@@ -455,14 +456,14 @@ class ChatRoute(Route):
elif msg_type == "image":
filename = result_text.replace("[IMAGE]", "")
part = await self._create_attachment_from_file(
- filename, "image"
+ filename, "image",
)
if part:
accumulated_parts.append(part)
elif msg_type == "record":
filename = result_text.replace("[RECORD]", "")
part = await self._create_attachment_from_file(
- filename, "record"
+ filename, "record",
)
if part:
accumulated_parts.append(part)
@@ -470,7 +471,7 @@ class ChatRoute(Route):
# 格式: [FILE]filename
filename = result_text.replace("[FILE]", "")
part = await self._create_attachment_from_file(
- filename, "file"
+ filename, "file",
)
if part:
accumulated_parts.append(part)
@@ -515,7 +516,7 @@ class ChatRoute(Route):
"data": {
"id": saved_record.id,
"created_at": to_utc_isoformat(
- saved_record.created_at
+ saved_record.created_at,
),
},
}
@@ -561,7 +562,7 @@ class ChatRoute(Route):
)
response = cast(
- QuartResponse,
+ "QuartResponse",
await make_response(
stream(),
{
@@ -708,7 +709,7 @@ class ChatRoute(Route):
"deleted_count": deleted_count,
"failed_count": len(failed_items),
"failed_items": failed_items,
- }
+ },
)
.__dict__
)
@@ -737,7 +738,7 @@ class ChatRoute(Route):
os.remove(attachment.path)
except OSError as e:
logger.warning(
- f"Failed to delete attachment file {attachment.path}: {e}"
+ f"Failed to delete attachment file {attachment.path}: {e}",
)
except Exception as e:
logger.warning(f"Failed to get attachments: {e}")
@@ -768,7 +769,7 @@ class ChatRoute(Route):
data={
"session_id": session.session_id,
"platform_id": session.platform_id,
- }
+ },
)
.__dict__
)
@@ -802,7 +803,7 @@ class ChatRoute(Route):
"is_group": session.is_group,
"created_at": to_utc_isoformat(session.created_at),
"updated_at": to_utc_isoformat(session.updated_at),
- }
+ },
)
return Response().ok(data=sessions_data).__dict__
@@ -820,7 +821,7 @@ class ChatRoute(Route):
# 获取项目信息(如果会话属于某个项目)
username = g.get("username", "guest")
project_info = await self.db.get_project_by_session(
- session_id=session_id, creator=username
+ session_id=session_id, creator=username,
)
# Get platform message history using session_id
diff --git a/astrbot/dashboard/routes/chatui_project.py b/astrbot/dashboard/routes/chatui_project.py
index 6ba570f55..487fab53d 100644
--- a/astrbot/dashboard/routes/chatui_project.py
+++ b/astrbot/dashboard/routes/chatui_project.py
@@ -54,7 +54,7 @@ class ChatUIProjectRoute(Route):
"description": project.description,
"created_at": to_utc_isoformat(project.created_at),
"updated_at": to_utc_isoformat(project.updated_at),
- }
+ },
)
.__dict__
)
@@ -105,7 +105,7 @@ class ChatUIProjectRoute(Route):
"description": project.description,
"created_at": to_utc_isoformat(project.created_at),
"updated_at": to_utc_isoformat(project.updated_at),
- }
+ },
)
.__dict__
)
diff --git a/astrbot/dashboard/routes/config.py b/astrbot/dashboard/routes/config.py
index bcd7e075c..5d5e606a5 100644
--- a/astrbot/dashboard/routes/config.py
+++ b/astrbot/dashboard/routes/config.py
@@ -63,7 +63,7 @@ def _expect_type(value, expected_type, path_key, errors, expected_name=None) ->
if not isinstance(value, expected_type):
errors.append(
f"错误的类型 {path_key}: 期望是 {expected_name or expected_type.__name__}, "
- f"得到了 {type(value).__name__}"
+ f"得到了 {type(value).__name__}",
)
return False
return True
@@ -283,16 +283,15 @@ async def _validate_neo_connectivity(
health_url = f"{endpoint}/health"
try:
- async with aiohttp.ClientSession() as session:
- async with session.get(
- health_url,
- timeout=aiohttp.ClientTimeout(total=5),
- ) as resp:
- if resp.status != 200:
- return (
- f"⚠️ Bay 健康检查失败 (HTTP {resp.status}),"
- f"请确认 Bay 正在运行: {endpoint}"
- )
+ async with aiohttp.ClientSession() as session, session.get(
+ health_url,
+ timeout=aiohttp.ClientTimeout(total=5),
+ ) as resp:
+ if resp.status != 200:
+ return (
+ f"⚠️ Bay 健康检查失败 (HTTP {resp.status}),"
+ f"请确认 Bay 正在运行: {endpoint}"
+ )
except Exception:
return f"⚠️ 无法连接 Bay ({endpoint}),请确认 Bay 已启动。"
@@ -300,7 +299,7 @@ async def _validate_neo_connectivity(
def save_config(
- post_config: dict, config: AstrBotConfig, is_core: bool = False
+ post_config: dict, config: AstrBotConfig, is_core: bool = False,
) -> None:
"""验证并保存配置"""
errors = None
@@ -319,7 +318,7 @@ def save_config(
)
else:
errors, post_config = validate_config(
- post_config, getattr(config, "schema", {}), is_core
+ post_config, getattr(config, "schema", {}), is_core,
)
except BaseException as e:
logger.error(traceback.format_exc())
@@ -418,7 +417,7 @@ class ConfigRoute(Route):
# 删除引用了该 provider_source 的 providers
await self.core_lifecycle.provider_manager.delete_provider(
- provider_source_id=provider_source_id
+ provider_source_id=provider_source_id,
)
try:
@@ -514,13 +513,13 @@ class ConfigRoute(Route):
"metadata": {
"provider": CONFIG_METADATA_2["provider_group"]["metadata"][
"provider"
- ]
- }
- }
- }
+ ],
+ },
+ },
+ },
)
config_schema = {
- "provider": provider_metadata["provider_group"]["metadata"]["provider"]
+ "provider": provider_metadata["provider_group"]["metadata"]["provider"],
}
data = {
"config_schema": config_schema,
@@ -626,7 +625,7 @@ class ConfigRoute(Route):
if system_config:
abconf = self.acm.confs["default"]
metadata = ConfigMetadataI18n.convert_to_i18n_keys(
- CONFIG_METADATA_3_SYSTEM
+ CONFIG_METADATA_3_SYSTEM,
)
return Response().ok({"config": abconf, "metadata": metadata}).__dict__
if abconf_id is None:
@@ -792,7 +791,7 @@ class ConfigRoute(Route):
):
# chat
prov = self.core_lifecycle.provider_manager.get_merged_provider_config(
- provider
+ provider,
)
provider_list.append(prov)
elif not ps_id and provider.get("provider_type", "") in provider_type_ls:
@@ -865,7 +864,7 @@ class ConfigRoute(Route):
return (
Response()
.error(
- "提供商适配器加载失败,请检查提供商类型配置或查看服务端日志"
+ "提供商适配器加载失败,请检查提供商类型配置或查看服务端日志",
)
.__dict__
)
@@ -942,7 +941,7 @@ class ConfigRoute(Route):
try:
self.core_lifecycle.provider_manager.dynamic_import_provider(
- provider_type
+ provider_type,
)
except ImportError as e:
logger.error(traceback.format_exc())
@@ -1066,7 +1065,6 @@ class ConfigRoute(Route):
当前支持的 scope:
- scope=plugin:name=,key=
"""
-
scope = request.args.get("scope") or "plugin"
name = request.args.get("name")
key_path = request.args.get("key")
@@ -1084,7 +1082,6 @@ class ConfigRoute(Route):
async def upload_config_file(self):
"""上传文件到插件数据目录(用于某个 file 类型配置项)。"""
-
try:
scope, name, key_path, md, config = self._resolve_config_file_scope()
except ValueError as e:
@@ -1163,7 +1160,6 @@ class ConfigRoute(Route):
async def delete_config_file(self):
"""删除插件数据目录中的文件。"""
-
scope = request.args.get("scope") or "plugin"
name = request.args.get("name")
if not name:
@@ -1199,7 +1195,6 @@ class ConfigRoute(Route):
async def get_config_file_list(self):
"""获取配置项对应目录下的文件列表。"""
-
try:
_, name, key_path, _, config = self._resolve_config_file_scope()
except ValueError as e:
@@ -1260,7 +1255,7 @@ class ConfigRoute(Route):
try:
await self.core_lifecycle.provider_manager.create_provider(
- new_provider_config
+ new_provider_config,
)
except Exception as e:
return Response().error(str(e)).__dict__
@@ -1302,7 +1297,7 @@ class ConfigRoute(Route):
try:
await self.core_lifecycle.provider_manager.update_provider(
- origin_provider_id, new_config
+ origin_provider_id, new_config,
)
except Exception as e:
return Response().error(str(e)).__dict__
@@ -1332,7 +1327,7 @@ class ConfigRoute(Route):
try:
await self.core_lifecycle.provider_manager.delete_provider(
- provider_id=provider_id
+ provider_id=provider_id,
)
except Exception as e:
return Response().error(str(e)).__dict__
@@ -1356,7 +1351,7 @@ class ConfigRoute(Route):
cached_token = self._logo_token_cache[cache_key]
# 确保platform_default_tmpl[platform.name]存在且为字典
if platform.name not in platform_default_tmpl or not isinstance(
- platform_default_tmpl[platform.name], dict
+ platform_default_tmpl[platform.name], dict,
):
platform_default_tmpl[platform.name] = {}
platform_default_tmpl[platform.name]["logo_token"] = cached_token
@@ -1385,7 +1380,7 @@ class ConfigRoute(Route):
# 确保platform_default_tmpl[platform.name]存在且为字典
if platform.name not in platform_default_tmpl or not isinstance(
- platform_default_tmpl[platform.name], dict
+ platform_default_tmpl[platform.name], dict,
):
platform_default_tmpl[platform.name] = {}
@@ -1412,7 +1407,7 @@ class ConfigRoute(Route):
)
def _inject_platform_metadata_with_i18n(
- self, platform, metadata, platform_i18n_translations: dict
+ self, platform, metadata, platform_i18n_translations: dict,
):
"""将配置元数据注入到 metadata 中并处理国际化键转换。"""
metadata["platform_group"]["metadata"]["platform"].setdefault("items", {})
@@ -1423,7 +1418,7 @@ class ConfigRoute(Route):
for lang, lang_data in platform.i18n_resources.items():
platform_i18n_translations.setdefault(lang, {}).setdefault(
- "platform_group", {}
+ "platform_group", {},
).setdefault("platform", {})[platform.name] = lang_data
for field_key, field_value in platform_items_to_inject.items():
@@ -1432,7 +1427,7 @@ class ConfigRoute(Route):
field_value[key] = f"{i18n_prefix}.{field_key}.{key}"
metadata["platform_group"]["metadata"]["platform"]["items"].update(
- platform_items_to_inject
+ platform_items_to_inject,
)
async def _get_astrbot_config(self):
@@ -1442,10 +1437,10 @@ class ConfigRoute(Route):
{
"platform_group": {
"metadata": {
- "platform": metadata["platform_group"]["metadata"]["platform"]
- }
- }
- }
+ "platform": metadata["platform_group"]["metadata"]["platform"],
+ },
+ },
+ },
)
metadata["platform_group"]["metadata"]["platform"] = platform_i18n[
"platform_group"
@@ -1464,13 +1459,13 @@ class ConfigRoute(Route):
for platform in platform_registry:
if platform.default_config_tmpl:
platform_default_tmpl[platform.name] = copy.deepcopy(
- platform.default_config_tmpl
+ platform.default_config_tmpl,
)
# 注入配置元数据(在 convert_to_i18n_keys 之后,使用国际化键)
if platform.config_metadata:
self._inject_platform_metadata_with_i18n(
- platform, metadata, platform_i18n_translations
+ platform, metadata, platform_i18n_translations,
)
# 收集logo注册任务
@@ -1519,7 +1514,7 @@ class ConfigRoute(Route):
return ret
async def _save_astrbot_configs(
- self, post_configs: dict, conf_id: str | None = None
+ self, post_configs: dict, conf_id: str | None = None,
) -> None:
try:
if conf_id not in self.acm.confs:
@@ -1550,7 +1545,7 @@ class ConfigRoute(Route):
try:
errors, post_configs = validate_config(
- post_configs, getattr(md.config, "schema", {}), is_core=False
+ post_configs, getattr(md.config, "schema", {}), is_core=False,
)
if errors:
raise ValueError(f"格式校验未通过: {errors}")
diff --git a/astrbot/dashboard/routes/conversation.py b/astrbot/dashboard/routes/conversation.py
index 68eed7ef1..0dda96725 100644
--- a/astrbot/dashboard/routes/conversation.py
+++ b/astrbot/dashboard/routes/conversation.py
@@ -321,7 +321,7 @@ class ConversationRoute(Route):
if not conversation:
failed_items.append(
- f"user_id:{user_id}, cid:{cid} - 对话不存在"
+ f"user_id:{user_id}, cid:{cid} - 对话不存在",
)
continue
@@ -347,7 +347,7 @@ class ConversationRoute(Route):
except Exception as e:
failed_items.append(f"user_id:{user_id}, cid:{cid} - {e!s}")
logger.error(
- f"导出对话失败: user_id={user_id}, cid={cid}, error={e!s}"
+ f"导出对话失败: user_id={user_id}, cid={cid}, error={e!s}",
)
if exported_count == 0:
diff --git a/astrbot/dashboard/routes/cron.py b/astrbot/dashboard/routes/cron.py
index af26c6525..64f499b95 100644
--- a/astrbot/dashboard/routes/cron.py
+++ b/astrbot/dashboard/routes/cron.py
@@ -11,7 +11,7 @@ from .route import Response, Route, RouteContext
class CronRoute(Route):
def __init__(
- self, context: RouteContext, core_lifecycle: AstrBotCoreLifecycle
+ self, context: RouteContext, core_lifecycle: AstrBotCoreLifecycle,
) -> None:
super().__init__(context)
self.core_lifecycle = core_lifecycle
@@ -42,7 +42,7 @@ class CronRoute(Route):
cron_mgr = self.core_lifecycle.cron_manager
if cron_mgr is None:
return jsonify(
- Response().error("Cron manager not initialized").__dict__
+ Response().error("Cron manager not initialized").__dict__,
)
job_type = request.args.get("type")
jobs = await cron_mgr.list_jobs(job_type)
@@ -57,7 +57,7 @@ class CronRoute(Route):
cron_mgr = self.core_lifecycle.cron_manager
if cron_mgr is None:
return jsonify(
- Response().error("Cron manager not initialized").__dict__
+ Response().error("Cron manager not initialized").__dict__,
)
payload = await request.json
@@ -79,13 +79,13 @@ class CronRoute(Route):
return jsonify(Response().error("session is required").__dict__)
if run_once and not run_at:
return jsonify(
- Response().error("run_at is required when run_once=true").__dict__
+ Response().error("run_at is required when run_once=true").__dict__,
)
if (not run_once) and not cron_expression:
return jsonify(
Response()
.error("cron_expression is required when run_once=false")
- .__dict__
+ .__dict__,
)
if run_once and cron_expression:
cron_expression = None # ignore cron when run_once specified
@@ -95,7 +95,7 @@ class CronRoute(Route):
run_at_dt = datetime.fromisoformat(str(run_at))
except Exception:
return jsonify(
- Response().error("run_at must be ISO datetime").__dict__
+ Response().error("run_at must be ISO datetime").__dict__,
)
job_payload = {
@@ -128,7 +128,7 @@ class CronRoute(Route):
cron_mgr = self.core_lifecycle.cron_manager
if cron_mgr is None:
return jsonify(
- Response().error("Cron manager not initialized").__dict__
+ Response().error("Cron manager not initialized").__dict__,
)
payload = await request.json
@@ -170,7 +170,7 @@ class CronRoute(Route):
session = str(payload.get("session") or "").strip()
if not session:
return jsonify(
- Response().error("session cannot be empty").__dict__
+ Response().error("session cannot be empty").__dict__,
)
merged_payload["session"] = session
@@ -179,7 +179,7 @@ class CronRoute(Route):
note = str(payload.get("note") or "").strip()
if not note:
return jsonify(
- Response().error("note cannot be empty").__dict__
+ Response().error("note cannot be empty").__dict__,
)
merged_payload["note"] = note
updates["description"] = note
@@ -188,7 +188,7 @@ class CronRoute(Route):
description = str(payload.get("description") or "").strip()
if not description:
return jsonify(
- Response().error("description cannot be empty").__dict__
+ Response().error("description cannot be empty").__dict__,
)
updates["description"] = description
merged_payload["note"] = description
@@ -196,7 +196,7 @@ class CronRoute(Route):
if not note_updated and updates.get("description") is None:
existing_note = str(
- merged_payload.get("note") or job.description or ""
+ merged_payload.get("note") or job.description or "",
).strip()
if existing_note:
merged_payload["note"] = existing_note
@@ -220,7 +220,7 @@ class CronRoute(Route):
run_at_iso = datetime.fromisoformat(str(run_at_raw)).isoformat()
except Exception:
return jsonify(
- Response().error("run_at must be ISO datetime").__dict__
+ Response().error("run_at must be ISO datetime").__dict__,
)
if next_run_once:
@@ -228,7 +228,7 @@ class CronRoute(Route):
return jsonify(
Response()
.error("run_at is required when run_once=true")
- .__dict__
+ .__dict__,
)
next_cron_expression = None
merged_payload["run_at"] = run_at_iso
@@ -237,7 +237,7 @@ class CronRoute(Route):
return jsonify(
Response()
.error("cron_expression is required when run_once=false")
- .__dict__
+ .__dict__,
)
merged_payload.pop("run_at", None)
@@ -249,7 +249,7 @@ class CronRoute(Route):
cron_expression = str(payload.get("cron_expression") or "").strip()
if not cron_expression:
return jsonify(
- Response().error("cron_expression cannot be empty").__dict__
+ Response().error("cron_expression cannot be empty").__dict__,
)
updates["cron_expression"] = cron_expression
@@ -270,7 +270,7 @@ class CronRoute(Route):
cron_mgr = self.core_lifecycle.cron_manager
if cron_mgr is None:
return jsonify(
- Response().error("Cron manager not initialized").__dict__
+ Response().error("Cron manager not initialized").__dict__,
)
await cron_mgr.delete_job(job_id)
return jsonify(Response().ok(message="deleted").__dict__)
diff --git a/astrbot/dashboard/routes/knowledge_base.py b/astrbot/dashboard/routes/knowledge_base.py
index b1b154238..eebb87ffb 100644
--- a/astrbot/dashboard/routes/knowledge_base.py
+++ b/astrbot/dashboard/routes/knowledge_base.py
@@ -77,7 +77,7 @@ class KnowledgeBaseRoute(Route):
}
def _set_task_result(
- self, task_id: str, status: str, result: Any = None, error: str | None = None
+ self, task_id: str, status: str, result: Any = None, error: str | None = None,
) -> None:
self.upload_tasks[task_id] = {
"status": status,
@@ -170,7 +170,7 @@ class KnowledgeBaseRoute(Route):
# 创建进度回调函数
progress_callback = self._make_progress_callback(
- task_id, file_idx, file_info["file_name"]
+ task_id, file_idx, file_info["file_name"],
)
doc = await kb_helper.upload_document(
@@ -252,7 +252,7 @@ class KnowledgeBaseRoute(Route):
# 创建进度回调函数
progress_callback = self._make_progress_callback(
- task_id, file_idx, file_name
+ task_id, file_idx, file_name,
)
# 调用 upload_document,传入 pre_chunked_text
diff --git a/astrbot/dashboard/routes/live_chat.py b/astrbot/dashboard/routes/live_chat.py
index dafb3c2f8..2d6fd6c62 100644
--- a/astrbot/dashboard/routes/live_chat.py
+++ b/astrbot/dashboard/routes/live_chat.py
@@ -60,7 +60,7 @@ class LiveChatSession:
start_time = time.time()
if not self.is_speaking or stamp != self.current_stamp:
logger.warning(
- f"[Live Chat] stamp 不匹配或未在说话状态: {stamp} vs {self.current_stamp}"
+ f"[Live Chat] stamp 不匹配或未在说话状态: {stamp} vs {self.current_stamp}",
)
return None, 0.0
@@ -86,7 +86,7 @@ class LiveChatSession:
self.temp_audio_path = audio_path
logger.info(
- f"[Live Chat] 音频文件已保存: {audio_path}, 大小: {os.path.getsize(audio_path)} bytes"
+ f"[Live Chat] 音频文件已保存: {audio_path}, 大小: {os.path.getsize(audio_path)} bytes",
)
return audio_path, time.time() - start_time
@@ -183,7 +183,7 @@ class LiveChatRoute(Route):
logger.info(f"[Live Chat] WebSocket 连接关闭: {username}")
async def _create_attachment_from_file(
- self, filename: str, attach_type: str
+ self, filename: str, attach_type: str,
) -> dict | None:
"""从本地文件创建 attachment 并返回消息部分。"""
return await create_attachment_part_from_existing_file(
@@ -195,7 +195,7 @@ class LiveChatRoute(Route):
)
def _extract_web_search_refs(
- self, accumulated_text: str, accumulated_parts: list
+ self, accumulated_text: str, accumulated_parts: list,
) -> dict:
"""从消息中提取 web_search 引用。"""
supported = [
@@ -214,7 +214,7 @@ class LiveChatRoute(Route):
for part in tool_call_parts:
for tool_call in part["tool_calls"]:
if tool_call.get("name") not in supported or not tool_call.get(
- "result"
+ "result",
):
continue
try:
@@ -289,7 +289,7 @@ class LiveChatRoute(Route):
request_id: str,
) -> None:
back_queue = webchat_queue_mgr.get_or_create_back_queue(
- request_id, chat_session_id
+ request_id, chat_session_id,
)
try:
while True:
@@ -342,7 +342,7 @@ class LiveChatRoute(Route):
session.chat_subscription_tasks.clear()
async def _handle_chat_message(
- self, session: LiveChatSession, message: dict
+ self, session: LiveChatSession, message: dict,
) -> None:
"""处理 Chat Mode 消息(ct=chat)"""
msg_type = message.get("t")
@@ -534,7 +534,7 @@ class LiveChatRoute(Route):
tool_calls[tool_call.get("id")] = tool_call
if accumulated_text:
accumulated_parts.append(
- {"type": "plain", "text": accumulated_text}
+ {"type": "plain", "text": accumulated_text},
)
accumulated_text = ""
except Exception:
@@ -550,7 +550,7 @@ class LiveChatRoute(Route):
{
"type": "tool_call",
"tool_calls": [tool_calls[tc_id]],
- }
+ },
)
tool_calls.pop(tc_id, None)
except Exception:
@@ -589,7 +589,7 @@ class LiveChatRoute(Route):
or accumulated_text
or accumulated_reasoning
or refs
- or agent_stats
+ or agent_stats,
)
elif (streaming and msg_type == "complete") or not streaming:
if chain_type not in (
@@ -628,7 +628,7 @@ class LiveChatRoute(Route):
"data": {
"id": saved_record.id,
"created_at": to_utc_isoformat(
- saved_record.created_at
+ saved_record.created_at,
),
},
},
@@ -650,7 +650,7 @@ class LiveChatRoute(Route):
{
"ct": "chat",
"t": "error",
- "data": f"处理失败: {str(e)}",
+ "data": f"处理失败: {e!s}",
"code": "PROCESSING_ERROR",
},
)
@@ -714,13 +714,13 @@ class LiveChatRoute(Route):
logger.info(f"[Live Chat] 用户打断: {session.username}")
async def _process_audio(
- self, session: LiveChatSession, audio_path: str, assemble_duration: float
+ self, session: LiveChatSession, audio_path: str, assemble_duration: float,
) -> None:
"""处理音频:STT -> LLM -> 流式 TTS"""
try:
# 发送 WAV 组装耗时
await websocket.send_json(
- {"t": "metrics", "data": {"wav_assemble_time": assemble_duration}}
+ {"t": "metrics", "data": {"wav_assemble_time": assemble_duration}},
)
wav_assembly_finish_time = time.time()
@@ -737,7 +737,7 @@ class LiveChatRoute(Route):
return
await websocket.send_json(
- {"t": "metrics", "data": {"stt": stt_provider.meta().type}}
+ {"t": "metrics", "data": {"stt": stt_provider.meta().type}},
)
user_text = await stt_provider.get_text(audio_path)
@@ -751,7 +751,7 @@ class LiveChatRoute(Route):
{
"t": "user_msg",
"data": {"text": user_text, "ts": int(time.time() * 1000)},
- }
+ },
)
# 2. 构造消息事件并发送到 pipeline
@@ -783,7 +783,7 @@ class LiveChatRoute(Route):
await websocket.send_json({"t": "stop_play"})
# 保存消息并标记为被打断
await self._save_interrupted_message(
- session, user_text, bot_text
+ session, user_text, bot_text,
)
# 清空队列中未处理的消息
while not back_queue.empty():
@@ -804,7 +804,7 @@ class LiveChatRoute(Route):
result_message_id = result.get("message_id")
if result_message_id != message_id:
logger.warning(
- f"[Live Chat] 消息 ID 不匹配: {result_message_id} != {message_id}"
+ f"[Live Chat] 消息 ID 不匹配: {result_message_id} != {message_id}",
)
continue
@@ -823,7 +823,7 @@ class LiveChatRoute(Route):
"llm_total_time": stats.get("end_time", 0)
- stats.get("start_time", 0),
},
- }
+ },
)
except Exception as e:
logger.error(f"[Live Chat] 解析 AgentStats 失败: {e}")
@@ -836,7 +836,7 @@ class LiveChatRoute(Route):
{
"t": "metrics",
"data": stats,
- }
+ },
)
except Exception as e:
logger.error(f"[Live Chat] 解析 TTSStats 失败: {e}")
@@ -860,9 +860,9 @@ class LiveChatRoute(Route):
{
"t": "metrics",
"data": {
- "speak_to_first_frame": speak_to_first_frame_latency
+ "speak_to_first_frame": speak_to_first_frame_latency,
},
- }
+ },
)
text = result.get("text")
@@ -871,7 +871,7 @@ class LiveChatRoute(Route):
{
"t": "bot_text_chunk",
"data": {"text": text},
- }
+ },
)
# 发送音频数据给前端
@@ -879,7 +879,7 @@ class LiveChatRoute(Route):
{
"t": "response",
"data": data, # base64 编码的音频数据
- }
+ },
)
elif result_type in ["complete", "end"]:
@@ -895,7 +895,7 @@ class LiveChatRoute(Route):
"text": bot_text,
"ts": int(time.time() * 1000),
},
- }
+ },
)
# 发送结束标记
@@ -907,7 +907,7 @@ class LiveChatRoute(Route):
{
"t": "metrics",
"data": {"wav_to_tts_total_time": wav_to_tts_duration},
- }
+ },
)
break
finally:
@@ -915,14 +915,14 @@ class LiveChatRoute(Route):
except Exception as e:
logger.error(f"[Live Chat] 处理音频失败: {e}", exc_info=True)
- await websocket.send_json({"t": "error", "data": f"处理失败: {str(e)}"})
+ await websocket.send_json({"t": "error", "data": f"处理失败: {e!s}"})
finally:
session.is_processing = False
session.should_interrupt = False
async def _save_interrupted_message(
- self, session: LiveChatSession, user_text: str, bot_text: str
+ self, session: LiveChatSession, user_text: str, bot_text: str,
) -> None:
"""保存被打断的消息"""
interrupted_text = bot_text + " [用户打断]"
@@ -932,11 +932,11 @@ class LiveChatRoute(Route):
try:
timestamp = int(time.time() * 1000)
logger.info(
- f"[Live Chat] 用户消息: {user_text} (session: {session.session_id}, ts: {timestamp})"
+ f"[Live Chat] 用户消息: {user_text} (session: {session.session_id}, ts: {timestamp})",
)
if bot_text:
logger.info(
- f"[Live Chat] Bot 消息(打断): {interrupted_text} (session: {session.session_id}, ts: {timestamp})"
+ f"[Live Chat] Bot 消息(打断): {interrupted_text} (session: {session.session_id}, ts: {timestamp})",
)
except Exception as e:
logger.error(f"[Live Chat] 记录消息失败: {e}", exc_info=True)
diff --git a/astrbot/dashboard/routes/log.py b/astrbot/dashboard/routes/log.py
index e7eebef6e..a6317f853 100644
--- a/astrbot/dashboard/routes/log.py
+++ b/astrbot/dashboard/routes/log.py
@@ -43,7 +43,7 @@ class LogRoute(Route):
)
async def _replay_cached_logs(
- self, last_event_id: str
+ self, last_event_id: str,
) -> AsyncGenerator[str, None]:
"""辅助生成器:重放缓存的日志"""
try:
@@ -86,7 +86,7 @@ class LogRoute(Route):
self.log_broker.unregister(queue)
response = cast(
- QuartResponse,
+ "QuartResponse",
await make_response(
stream(),
{
diff --git a/astrbot/dashboard/routes/open_api.py b/astrbot/dashboard/routes/open_api.py
index 8f2047326..58e38888a 100644
--- a/astrbot/dashboard/routes/open_api.py
+++ b/astrbot/dashboard/routes/open_api.py
@@ -73,7 +73,7 @@ class OpenApiRoute(Route):
"name": str(conf_info.get("name", "")).strip(),
"path": str(conf_info.get("path", "")).strip(),
"is_default": conf_id == "default",
- }
+ },
)
return result
@@ -141,7 +141,7 @@ class OpenApiRoute(Route):
async def chat_send(self):
post_data = await request.get_json(silent=True) or {}
effective_username, username_err = self._resolve_open_username(
- post_data.get("username")
+ post_data.get("username"),
)
if username_err:
return Response().error(username_err).__dict__
@@ -173,7 +173,7 @@ class OpenApiRoute(Route):
await self.core_lifecycle.umop_config_router.delete_route(umo)
else:
await self.core_lifecycle.umop_config_router.update_route(
- umo, config_id
+ umo, config_id,
)
except Exception as e:
logger.error(
@@ -241,7 +241,7 @@ class OpenApiRoute(Route):
"type": "error",
"code": code,
"data": message,
- }
+ },
)
async def _update_session_config_route(
@@ -260,7 +260,7 @@ class OpenApiRoute(Route):
await self.core_lifecycle.umop_config_router.delete_route(umo)
else:
await self.core_lifecycle.umop_config_router.update_route(
- umo, config_id
+ umo, config_id,
)
except Exception as e:
logger.error(
@@ -275,11 +275,11 @@ class OpenApiRoute(Route):
async def _handle_chat_ws_send(self, post_data: dict) -> None:
effective_username, username_err = self._resolve_open_username(
- post_data.get("username")
+ post_data.get("username"),
)
if username_err or not effective_username:
await self._send_chat_ws_error(
- username_err or "Invalid username", "BAD_USER"
+ username_err or "Invalid username", "BAD_USER",
)
return
@@ -342,7 +342,7 @@ class OpenApiRoute(Route):
"enable_streaming": enable_streaming,
"message_id": message_id,
},
- )
+ ),
)
message_parts_for_storage = strip_message_parts_path_fields(message_parts)
@@ -360,7 +360,7 @@ class OpenApiRoute(Route):
"data": None,
"session_id": session_id,
"message_id": message_id,
- }
+ },
)
accumulated_parts = []
@@ -407,7 +407,7 @@ class OpenApiRoute(Route):
tool_calls[tool_call.get("id")] = tool_call
if accumulated_text:
accumulated_parts.append(
- {"type": "plain", "text": accumulated_text}
+ {"type": "plain", "text": accumulated_text},
)
accumulated_text = ""
elif chain_type == "tool_call_result":
@@ -417,7 +417,7 @@ class OpenApiRoute(Route):
tool_calls[tc_id]["result"] = tcr.get("result")
tool_calls[tc_id]["finished_ts"] = tcr.get("ts")
accumulated_parts.append(
- {"type": "tool_call", "tool_calls": [tool_calls[tc_id]]}
+ {"type": "tool_call", "tool_calls": [tool_calls[tc_id]]},
)
tool_calls.pop(tc_id, None)
elif chain_type == "reasoning":
@@ -429,28 +429,28 @@ class OpenApiRoute(Route):
elif msg_type == "image":
filename = str(result_text).replace("[IMAGE]", "")
part = await self.chat_route._create_attachment_from_file(
- filename, "image"
+ filename, "image",
)
if part:
accumulated_parts.append(part)
elif msg_type == "record":
filename = str(result_text).replace("[RECORD]", "")
part = await self.chat_route._create_attachment_from_file(
- filename, "record"
+ filename, "record",
)
if part:
accumulated_parts.append(part)
elif msg_type == "file":
filename = str(result_text).replace("[FILE]", "")
part = await self.chat_route._create_attachment_from_file(
- filename, "file"
+ filename, "file",
)
if part:
accumulated_parts.append(part)
elif msg_type == "video":
filename = str(result_text).replace("[VIDEO]", "")
part = await self.chat_route._create_attachment_from_file(
- filename, "video"
+ filename, "video",
)
if part:
accumulated_parts.append(part)
@@ -486,11 +486,11 @@ class OpenApiRoute(Route):
"data": {
"id": saved_record.id,
"created_at": to_utc_isoformat(
- saved_record.created_at
+ saved_record.created_at,
),
},
"session_id": session_id,
- }
+ },
)
accumulated_parts = []
accumulated_text = ""
@@ -500,7 +500,7 @@ class OpenApiRoute(Route):
except Exception as e:
logger.exception(f"Open API WS chat failed: {e}", exc_info=True)
await self._send_chat_ws_error(
- f"Failed to process message: {e}", "PROCESSING_ERROR"
+ f"Failed to process message: {e}", "PROCESSING_ERROR",
)
finally:
webchat_queue_mgr.remove_back_queue(message_id)
@@ -545,7 +545,7 @@ class OpenApiRoute(Route):
async def get_chat_sessions(self):
username, username_err = self._resolve_open_username(
- request.args.get("username")
+ request.args.get("username"),
)
if username_err:
return Response().error(username_err).__dict__
@@ -558,12 +558,9 @@ class OpenApiRoute(Route):
except ValueError:
return Response().error("page and page_size must be integers").__dict__
- if page < 1:
- page = 1
- if page_size < 1:
- page_size = 1
- if page_size > 100:
- page_size = 100
+ page = max(page, 1)
+ page_size = max(page_size, 1)
+ page_size = min(page_size, 100)
platform_id = request.args.get("platform_id")
@@ -590,7 +587,7 @@ class OpenApiRoute(Route):
"is_group": session.is_group,
"created_at": to_utc_isoformat(session.created_at),
"updated_at": to_utc_isoformat(session.updated_at),
- }
+ },
)
return (
@@ -601,7 +598,7 @@ class OpenApiRoute(Route):
"page": page,
"page_size": page_size,
"total": total,
- }
+ },
)
.__dict__
)
@@ -653,7 +650,7 @@ class OpenApiRoute(Route):
try:
message_chain = await self._build_message_chain_from_payload(
- message_payload
+ message_payload,
)
await platform_inst.send_by_session(session, message_chain)
return Response().ok().__dict__
diff --git a/astrbot/dashboard/routes/persona.py b/astrbot/dashboard/routes/persona.py
index 56c14fe61..835375734 100644
--- a/astrbot/dashboard/routes/persona.py
+++ b/astrbot/dashboard/routes/persona.py
@@ -44,7 +44,7 @@ class PersonaRoute(Route):
folder_id = request.args.get("folder_id")
if folder_id is not None:
personas = await self.persona_mgr.get_personas_by_folder(
- folder_id if folder_id else None
+ folder_id or None,
)
else:
personas = await self.persona_mgr.get_all_personas()
@@ -151,9 +151,9 @@ class PersonaRoute(Route):
persona = await self.persona_mgr.create_persona(
persona_id=persona_id,
system_prompt=system_prompt,
- begin_dialogs=begin_dialogs if begin_dialogs else None,
- tools=tools if tools else None,
- skills=skills if skills else None,
+ begin_dialogs=begin_dialogs or None,
+ tools=tools or None,
+ skills=skills or None,
custom_error_message=custom_error_message,
folder_id=folder_id,
sort_order=sort_order,
@@ -209,7 +209,7 @@ class PersonaRoute(Route):
if has_custom_error_message:
if custom_error_message is not None and not isinstance(
- custom_error_message, str
+ custom_error_message, str,
):
return Response().error("自定义报错回复信息必须是字符串").__dict__
if isinstance(custom_error_message, str):
diff --git a/astrbot/dashboard/routes/platform.py b/astrbot/dashboard/routes/platform.py
index 874bc19db..85f44f7c5 100644
--- a/astrbot/dashboard/routes/platform.py
+++ b/astrbot/dashboard/routes/platform.py
@@ -50,6 +50,7 @@ class PlatformRoute(Route):
Returns:
根据平台适配器返回相应的响应
+
"""
# 根据 webhook_uuid 查找对应的平台
platform_adapter = self._find_platform_by_uuid(webhook_uuid)
@@ -64,7 +65,7 @@ class PlatformRoute(Route):
return result
except NotImplementedError:
logger.error(
- f"平台 {platform_adapter.meta().name} 未实现 webhook_callback 方法"
+ f"平台 {platform_adapter.meta().name} 未实现 webhook_callback 方法",
)
return Response().error("平台未支持统一 Webhook 模式").__dict__, 500
except Exception as e:
@@ -79,6 +80,7 @@ class PlatformRoute(Route):
Returns:
平台适配器实例,未找到则返回 None
+
"""
for platform in self.platform_manager.platform_insts:
if platform.config.get("webhook_uuid") == webhook_uuid:
@@ -91,6 +93,7 @@ class PlatformRoute(Route):
Returns:
包含平台统计信息的响应
+
"""
try:
stats = self.platform_manager.get_all_stats()
diff --git a/astrbot/dashboard/routes/plugin.py b/astrbot/dashboard/routes/plugin.py
index d151bbe6f..31a832ee9 100644
--- a/astrbot/dashboard/routes/plugin.py
+++ b/astrbot/dashboard/routes/plugin.py
@@ -92,7 +92,7 @@ class PluginRoute(Route):
data = await request.get_json()
version_spec = data.get("astrbot_version", "")
is_valid, message = self.plugin_manager._validate_astrbot_version_specifier(
- version_spec
+ version_spec,
)
return (
Response()
@@ -101,7 +101,7 @@ class PluginRoute(Route):
"compatible": is_valid,
"message": message,
"astrbot_version": version_spec,
- }
+ },
)
.__dict__
)
@@ -128,8 +128,7 @@ class PluginRoute(Route):
if success:
return Response().ok(None, f"插件 {dir_name} 重载成功。").__dict__
- else:
- return Response().error(f"重载失败: {err}").__dict__
+ return Response().error(f"重载失败: {err}").__dict__
except Exception as e:
logger.error(f"/api/plugin/reload-failed: {traceback.format_exc()}")
@@ -200,7 +199,7 @@ class PluginRoute(Route):
continue # 继续尝试其他URL或使用缓存
logger.info(
- f"成功获取远程插件市场数据,包含 {len(remote_data)} 个插件"
+ f"成功获取远程插件市场数据,包含 {len(remote_data)} 个插件",
)
# 获取最新的MD5并保存到缓存
current_md5 = await self._fetch_remote_md5(source.md5_url)
@@ -360,7 +359,7 @@ class PluginRoute(Route):
base_dir = Path(
self.plugin_manager.reserved_plugin_path
if plugin.reserved
- else self.plugin_manager.plugin_store_path
+ else self.plugin_manager.plugin_store_path,
)
plugin_dir = base_dir / plugin.root_dir_name
if not plugin_dir.is_dir():
@@ -416,7 +415,7 @@ class PluginRoute(Route):
plugin.desc,
plugin.version,
plugin.display_name,
- ]
+ ],
):
continue
_plugin_resp.append(_t)
@@ -685,7 +684,7 @@ class PluginRoute(Route):
raise result
if isinstance(result, BaseException):
results.append(
- {"name": name, "status": "error", "message": str(result)}
+ {"name": name, "status": "error", "message": str(result)},
)
else:
results.append(result)
diff --git a/astrbot/dashboard/routes/session_management.py b/astrbot/dashboard/routes/session_management.py
index fc632d1f5..7c3d4b0ce 100644
--- a/astrbot/dashboard/routes/session_management.py
+++ b/astrbot/dashboard/routes/session_management.py
@@ -49,7 +49,7 @@ class SessionManagementRoute(Route):
self.register_routes()
async def _get_umo_rules(
- self, page: int = 1, page_size: int = 10, search: str = ""
+ self, page: int = 1, page_size: int = 10, search: str = "",
) -> tuple[dict, int]:
"""获取所有带有自定义规则的 umo 及其规则内容(支持分页和搜索)。
@@ -67,6 +67,7 @@ class SessionManagementRoute(Route):
Returns:
tuple[dict, int]: (umo_rules, total) - 分页后的 umo 规则和总数
+
"""
umo_rules = {}
async with self.db_helper.get_db() as session:
@@ -75,7 +76,7 @@ class SessionManagementRoute(Route):
select(Preference).where(
col(Preference.scope) == "umo",
col(Preference.key).in_(AVAILABLE_SESSION_RULE_KEYS),
- )
+ ),
)
prefs = result.scalars().all()
for pref in prefs:
@@ -134,15 +135,13 @@ class SessionManagementRoute(Route):
search = request.args.get("search", "", type=str).strip()
# 参数校验
- if page < 1:
- page = 1
+ page = max(page, 1)
if page_size < 1:
page_size = 10
- if page_size > 100:
- page_size = 100
+ page_size = min(page_size, 100)
umo_rules, total = await self._get_umo_rules(
- page=page, page_size=page_size, search=search
+ page=page, page_size=page_size, search=search,
)
# 构建规则列表
@@ -240,7 +239,7 @@ class SessionManagementRoute(Route):
"available_plugins": available_plugins,
"available_kbs": available_kbs,
"available_rule_keys": AVAILABLE_SESSION_RULE_KEYS,
- }
+ },
)
.__dict__
)
@@ -315,10 +314,9 @@ class SessionManagementRoute(Route):
.ok({"message": f"规则 {rule_key} 已删除", "umo": umo})
.__dict__
)
- else:
- # 删除该 umo 的所有规则
- await sp.clear_async("umo", umo)
- return Response().ok({"message": "所有规则已删除", "umo": umo}).__dict__
+ # 删除该 umo 的所有规则
+ await sp.clear_async("umo", umo)
+ return Response().ok({"message": "所有规则已删除", "umo": umo}).__dict__
except Exception as e:
logger.error(f"删除会话规则失败: {e!s}")
return Response().error(f"删除会话规则失败: {e!s}").__dict__
@@ -334,7 +332,6 @@ class SessionManagementRoute(Route):
"rule_key": "session_service_config" | ... (可选,不传则删除所有规则)
}
"""
-
try:
data = await request.get_json()
umos = data.get("umos", [])
@@ -356,7 +353,7 @@ class SessionManagementRoute(Route):
async with self.db_helper.get_db() as session:
session: AsyncSession
result = await session.execute(
- select(ConversationV2.user_id).distinct()
+ select(ConversationV2.user_id).distinct(),
)
all_umos = [row[0] for row in result.fetchall()]
@@ -410,21 +407,20 @@ class SessionManagementRoute(Route):
"message": f"{message},{len(failed_umos)} 条删除失败",
"success_count": success_count,
"failed_umos": failed_umos,
- }
+ },
)
.__dict__
)
- else:
- return (
- Response()
- .ok(
- {
- "message": message,
- "success_count": success_count,
- }
- )
- .__dict__
+ return (
+ Response()
+ .ok(
+ {
+ "message": message,
+ "success_count": success_count,
+ },
)
+ .__dict__
+ )
except Exception as e:
logger.error(f"批量删除会话规则失败: {e!s}")
return Response().error(f"批量删除会话规则失败: {e!s}").__dict__
@@ -441,7 +437,7 @@ class SessionManagementRoute(Route):
result = await session.execute(
select(ConversationV2.user_id)
.distinct()
- .order_by(ConversationV2.user_id)
+ .order_by(ConversationV2.user_id),
)
umos = [row[0] for row in result.fetchall()]
@@ -467,12 +463,10 @@ class SessionManagementRoute(Route):
message_type = request.args.get("message_type", "all", type=str)
platform = request.args.get("platform", "", type=str)
- if page < 1:
- page = 1
+ page = max(page, 1)
if page_size < 1:
page_size = 20
- if page_size > 100:
- page_size = 100
+ page_size = min(page_size, 100)
# 从 Conversation 表获取所有 distinct user_id (即 umo)
async with self.db_helper.get_db() as session:
@@ -480,7 +474,7 @@ class SessionManagementRoute(Route):
result = await session.execute(
select(ConversationV2.user_id)
.distinct()
- .order_by(ConversationV2.user_id)
+ .order_by(ConversationV2.user_id),
)
all_umos = [row[0] for row in result.fetchall()]
@@ -558,7 +552,7 @@ class SessionManagementRoute(Route):
"chat_provider": rules.get(chat_provider_key),
"tts_provider": rules.get(tts_provider_key),
"stt_provider": rules.get(stt_provider_key),
- }
+ },
)
# 分页
@@ -597,7 +591,7 @@ class SessionManagementRoute(Route):
"available_chat_providers": available_chat_providers,
"available_tts_providers": available_tts_providers,
"available_stt_providers": available_stt_providers,
- }
+ },
)
.__dict__
)
@@ -645,7 +639,7 @@ class SessionManagementRoute(Route):
async with self.db_helper.get_db() as session:
session: AsyncSession
result = await session.execute(
- select(ConversationV2.user_id).distinct()
+ select(ConversationV2.user_id).distinct(),
)
all_umos = [row[0] for row in result.fetchall()]
@@ -715,7 +709,7 @@ class SessionManagementRoute(Route):
"success_count": success_count,
"failed_count": len(failed_umos),
"failed_umos": failed_umos,
- }
+ },
)
.__dict__
)
@@ -778,7 +772,7 @@ class SessionManagementRoute(Route):
async with self.db_helper.get_db() as session:
session: AsyncSession
result = await session.execute(
- select(ConversationV2.user_id).distinct()
+ select(ConversationV2.user_id).distinct(),
)
all_umos = [row[0] for row in result.fetchall()]
@@ -825,7 +819,7 @@ class SessionManagementRoute(Route):
"success_count": success_count,
"failed_count": len(failed_umos),
"failed_umos": failed_umos,
- }
+ },
)
.__dict__
)
@@ -856,7 +850,7 @@ class SessionManagementRoute(Route):
"name": group_data.get("name", ""),
"umos": group_data.get("umos", []),
"umo_count": len(group_data.get("umos", [])),
- }
+ },
)
return Response().ok({"groups": groups_list}).__dict__
except Exception as e:
@@ -898,7 +892,7 @@ class SessionManagementRoute(Route):
"umos": umos,
"umo_count": len(umos),
},
- }
+ },
)
.__dict__
)
@@ -955,7 +949,7 @@ class SessionManagementRoute(Route):
"umos": group["umos"],
"umo_count": len(group["umos"]),
},
- }
+ },
)
.__dict__
)
diff --git a/astrbot/dashboard/routes/skills.py b/astrbot/dashboard/routes/skills.py
index abae13e33..fa0ae7c03 100644
--- a/astrbot/dashboard/routes/skills.py
+++ b/astrbot/dashboard/routes/skills.py
@@ -93,17 +93,17 @@ class SkillsRoute(Route):
if not endpoint or not access_token:
raise ValueError(
"Shipyard Neo endpoint or access token not configured. "
- "Set them in Dashboard or ensure Bay's credentials.json is accessible."
+ "Set them in Dashboard or ensure Bay's credentials.json is accessible.",
)
return endpoint, access_token
async def _delete_neo_release(
- self, client: Any, release_id: str, reason: str | None
+ self, client: Any, release_id: str, reason: str | None,
):
return await client.skills.delete_release(release_id, reason=reason)
async def _delete_neo_candidate(
- self, client: Any, candidate_id: str, reason: str | None
+ self, client: Any, candidate_id: str, reason: str | None,
):
return await client.skills.delete_candidate(candidate_id, reason=reason)
@@ -132,12 +132,12 @@ class SkillsRoute(Route):
async def get_skills(self):
try:
provider_settings = self.core_lifecycle.astrbot_config.get(
- "provider_settings", {}
+ "provider_settings", {},
)
runtime = provider_settings.get("computer_use_runtime", "local")
skill_mgr = SkillManager()
skills = skill_mgr.list_skills(
- active_only=False, runtime=runtime, show_sandbox_path=False
+ active_only=False, runtime=runtime, show_sandbox_path=False,
)
return (
Response()
@@ -146,7 +146,7 @@ class SkillsRoute(Route):
"skills": [skill.__dict__ for skill in skills],
"runtime": runtime,
"sandbox_cache": skill_mgr.get_sandbox_skills_cache_status(),
- }
+ },
)
.__dict__
)
@@ -181,12 +181,12 @@ class SkillsRoute(Route):
try:
try:
skill_name = skill_mgr.install_skill_from_zip(
- temp_path, overwrite=False, skill_name_hint=Path(filename).stem
+ temp_path, overwrite=False, skill_name_hint=Path(filename).stem,
)
except TypeError:
# Backward compatibility for callers that do not accept skill_name_hint
skill_name = skill_mgr.install_skill_from_zip(
- temp_path, overwrite=False
+ temp_path, overwrite=False,
)
except Exception:
# Keep behavior consistent with previous implementation
@@ -246,7 +246,7 @@ class SkillsRoute(Route):
{
"filename": filename,
"error": "Only .zip files are supported",
- }
+ },
)
continue
@@ -263,7 +263,7 @@ class SkillsRoute(Route):
# Backward compatibility for monkeypatched implementations in tests
try:
skill_name = skill_mgr.install_skill_from_zip(
- temp_path, overwrite=False
+ temp_path, overwrite=False,
)
except FileExistsError:
skipped.append(
@@ -271,7 +271,7 @@ class SkillsRoute(Route):
"filename": filename,
"name": Path(filename).stem,
"error": "Skill already exists.",
- }
+ },
)
skill_name = None
except FileExistsError:
@@ -280,7 +280,7 @@ class SkillsRoute(Route):
"filename": filename,
"name": Path(filename).stem,
"error": "Skill already exists.",
- }
+ },
)
skill_name = None
@@ -302,7 +302,7 @@ class SkillsRoute(Route):
await sync_skills_to_active_sandboxes()
except Exception:
logger.warning(
- "Failed to sync uploaded skills to active sandboxes."
+ "Failed to sync uploaded skills to active sandboxes.",
)
total = len(file_list)
@@ -383,7 +383,7 @@ class SkillsRoute(Route):
return (
Response()
.error(
- "Sandbox preset skill cannot be downloaded from local skill files."
+ "Sandbox preset skill cannot be downloaded from local skill files.",
)
.__dict__
)
@@ -539,7 +539,7 @@ class SkillsRoute(Route):
report=data.get("report"),
)
logger.info(
- f"[Neo] Candidate evaluated: id={candidate_id}, passed={passed}"
+ f"[Neo] Candidate evaluated: id={candidate_id}, passed={passed}",
)
return Response().ok(_to_jsonable(result)).__dict__
@@ -577,13 +577,13 @@ class SkillsRoute(Route):
did_sync_to_local = bool(sync_json)
if did_sync_to_local:
logger.info(
- f"[Neo] Stable release synced to local: skill={sync_json.get('local_skill_name', '')}"
+ f"[Neo] Stable release synced to local: skill={sync_json.get('local_skill_name', '')}",
)
if result.get("sync_error"):
resp = Response().error(
"Stable promote synced failed and has been rolled back. "
- f"sync_error={result['sync_error']}"
+ f"sync_error={result['sync_error']}",
)
resp.data = {
"release": release_json,
@@ -647,7 +647,7 @@ class SkillsRoute(Route):
)
logger.info(
f"[Neo] Release synced to local: skill={result.local_skill_name}, "
- f"release_id={result.release_id}"
+ f"release_id={result.release_id}",
)
return (
Response()
@@ -660,7 +660,7 @@ class SkillsRoute(Route):
"payload_ref": result.payload_ref,
"map_path": result.map_path,
"synced_at": result.synced_at,
- }
+ },
)
.__dict__
)
diff --git a/astrbot/dashboard/routes/stat.py b/astrbot/dashboard/routes/stat.py
index 2eb3cd400..0ce3408ee 100644
--- a/astrbot/dashboard/routes/stat.py
+++ b/astrbot/dashboard/routes/stat.py
@@ -211,10 +211,10 @@ class StatRoute(Route):
local_tz = datetime.now().astimezone().tzinfo or timezone.utc
now_local = datetime.now(local_tz)
range_start_local = (now_local - timedelta(days=days)).replace(
- minute=0, second=0, microsecond=0
+ minute=0, second=0, microsecond=0,
)
today_start_local = now_local.replace(
- hour=0, minute=0, second=0, microsecond=0
+ hour=0, minute=0, second=0, microsecond=0,
)
query_start_local = min(range_start_local, today_start_local)
query_start_utc = query_start_local.astimezone(timezone.utc)
@@ -226,7 +226,7 @@ class StatRoute(Route):
ProviderStat.agent_type == "internal",
ProviderStat.created_at >= query_start_utc,
)
- .order_by(ProviderStat.created_at.asc())
+ .order_by(ProviderStat.created_at.asc()),
)
records = result.scalars().all()
@@ -237,7 +237,7 @@ class StatRoute(Route):
bucket_cursor += timedelta(hours=1)
trend_by_provider: dict[str, dict[int, int]] = defaultdict(
- lambda: defaultdict(int)
+ lambda: defaultdict(int),
)
total_by_provider: dict[str, int] = defaultdict(int)
total_by_umo: dict[str, int] = defaultdict(int)
@@ -267,7 +267,7 @@ class StatRoute(Route):
if created_at_local >= range_start_local:
bucket_local = created_at_local.replace(
- minute=0, second=0, microsecond=0
+ minute=0, second=0, microsecond=0,
)
bucket_ts = int(bucket_local.timestamp() * 1000)
trend_by_provider[provider_id][bucket_ts] += token_total
@@ -386,7 +386,7 @@ class StatRoute(Route):
"today_total_calls": today_total_calls,
"today_by_model": today_by_model_data,
"today_by_provider": today_by_provider_data,
- }
+ },
)
.__dict__
)
diff --git a/astrbot/dashboard/routes/subagent.py b/astrbot/dashboard/routes/subagent.py
index e3d77f73a..92cb2ae3e 100644
--- a/astrbot/dashboard/routes/subagent.py
+++ b/astrbot/dashboard/routes/subagent.py
@@ -109,7 +109,7 @@ class SubAgentRoute(Route):
"parameters": tool.parameters,
"active": tool.active,
"handler_module_path": tool.handler_module_path,
- }
+ },
)
return jsonify(Response().ok(data=tools_dict).__dict__)
except Exception as e:
diff --git a/astrbot/dashboard/routes/t2i.py b/astrbot/dashboard/routes/t2i.py
index 634828e95..2a84b58ab 100644
--- a/astrbot/dashboard/routes/t2i.py
+++ b/astrbot/dashboard/routes/t2i.py
@@ -13,7 +13,7 @@ from .route import Response, Route, RouteContext
class T2iRoute(Route):
def __init__(
- self, context: RouteContext, core_lifecycle: AstrBotCoreLifecycle
+ self, context: RouteContext, core_lifecycle: AstrBotCoreLifecycle,
) -> None:
super().__init__(context)
self.core_lifecycle = core_lifecycle
diff --git a/astrbot/dashboard/routes/tools.py b/astrbot/dashboard/routes/tools.py
index 33b74deff..45a082c83 100644
--- a/astrbot/dashboard/routes/tools.py
+++ b/astrbot/dashboard/routes/tools.py
@@ -15,7 +15,6 @@ DEFAULT_MCP_CONFIG = {"mcpServers": {}}
class EmptyMcpServersError(ValueError):
"""Raised when mcpServers is empty."""
- pass
def _extract_mcp_server_config(mcp_servers_value: object) -> dict:
@@ -23,6 +22,7 @@ def _extract_mcp_server_config(mcp_servers_value: object) -> dict:
Raises:
ValueError: Invalid configuration
+
"""
if not isinstance(mcp_servers_value, dict):
raise ValueError("mcpServers must be a JSON object")
@@ -33,7 +33,7 @@ def _extract_mcp_server_config(mcp_servers_value: object) -> dict:
if not isinstance(extracted, dict):
raise ValueError(
"Invalid mcpServers format. Ensure each key in mcpServers is a server name, "
- "and each value is an object containing fields like command/url."
+ "and each value is an object containing fields like command/url.",
)
return extracted
@@ -78,7 +78,7 @@ class ToolsRoute(Route):
if not isinstance(mcp_servers, dict):
logger.warning(
- f"Invalid MCP server config type: {type(mcp_servers).__name__}. Expected object/dict; skipped all MCP servers."
+ f"Invalid MCP server config type: {type(mcp_servers).__name__}. Expected object/dict; skipped all MCP servers.",
)
mcp_servers = {}
@@ -86,7 +86,7 @@ class ToolsRoute(Route):
for name, server_config in mcp_servers.items():
if not isinstance(server_config, dict):
logger.warning(
- f"Invalid config for MCP server '{name}' (type: {type(server_config).__name__}); skipped."
+ f"Invalid config for MCP server '{name}' (type: {type(server_config).__name__}); skipped.",
)
continue
@@ -137,7 +137,7 @@ class ToolsRoute(Route):
if key == "mcpServers":
try:
server_config = _extract_mcp_server_config(
- server_data["mcpServers"]
+ server_data["mcpServers"],
)
except ValueError as e:
return Response().error(f"{e!s}").__dict__
@@ -241,7 +241,7 @@ class ToolsRoute(Route):
if key == "mcpServers":
try:
server_config = _extract_mcp_server_config(
- server_data["mcpServers"]
+ server_data["mcpServers"],
)
except ValueError as e:
return Response().error(f"{e!s}").__dict__
@@ -276,7 +276,7 @@ class ToolsRoute(Route):
return (
Response()
.error(
- f"Timed out while disabling MCP server {old_name} before enabling: {e!s}"
+ f"Timed out while disabling MCP server {old_name} before enabling: {e!s}",
)
.__dict__
)
@@ -285,7 +285,7 @@ class ToolsRoute(Route):
return (
Response()
.error(
- f"Failed to disable MCP server {old_name} before enabling: {e!s}"
+ f"Failed to disable MCP server {old_name} before enabling: {e!s}",
)
.__dict__
)
@@ -393,7 +393,7 @@ class ToolsRoute(Route):
return (
Response()
.error(
- "Only one MCP server configuration can be tested at a time"
+ "Only one MCP server configuration can be tested at a time",
)
.__dict__
)
@@ -445,7 +445,7 @@ class ToolsRoute(Route):
origin = "mcp"
origin_name = tool.mcp_server_name
elif tool.handler_module_path and star_map.get(
- tool.handler_module_path
+ tool.handler_module_path,
):
star = star_map[tool.handler_module_path]
origin = "plugin"
diff --git a/astrbot/dashboard/routes/util.py b/astrbot/dashboard/routes/util.py
index 105619815..bd60e6e61 100644
--- a/astrbot/dashboard/routes/util.py
+++ b/astrbot/dashboard/routes/util.py
@@ -17,7 +17,6 @@ def get_schema_item(schema: dict | None, key_path: str) -> dict | None:
- 扁平 schema(直接 key 命中)
- 嵌套 object schema({type: "object", items: {...}})
"""
-
if not isinstance(schema, dict) or not key_path:
return None
if key_path in schema:
@@ -44,7 +43,6 @@ def sanitize_filename(name: str) -> str:
- 将路径分隔符替换为下划线
- 拒绝空字符串 / "." / ".."
"""
-
cleaned = os.path.basename(name).strip()
if not cleaned or cleaned in {".", ".."}:
return ""
@@ -59,7 +57,6 @@ def sanitize_path_segment(segment: str) -> str:
仅保留 [A-Za-z0-9_-],其余替换为 "_"
"""
-
cleaned = []
for ch in segment:
if (
@@ -81,14 +78,12 @@ def sanitize_path_segment(segment: str) -> str:
def config_key_to_folder(key_path: str) -> str:
"""将 dot-path 的配置 key 转成稳定的文件夹路径。"""
-
parts = [sanitize_path_segment(p) for p in key_path.split(".") if p]
return "/".join(parts) if parts else "_"
def normalize_rel_path(rel_path: str | None) -> str | None:
"""规范化用户传入的相对路径,并阻止路径穿越。"""
-
if not isinstance(rel_path, str):
return None
rel = rel_path.replace("\\", "/").lstrip("/")
diff --git a/astrbot/dashboard/server.py b/astrbot/dashboard/server.py
index b3dc75945..fce043fc5 100644
--- a/astrbot/dashboard/server.py
+++ b/astrbot/dashboard/server.py
@@ -273,7 +273,7 @@ class AstrBotDashboard:
"""获取占用端口的进程详细信息"""
try:
for conn in psutil.net_connections(kind="inet"):
- if cast(_AddrWithPort, conn.laddr).port == port:
+ if cast("_AddrWithPort", conn.laddr).port == port:
try:
process = psutil.Process(conn.pid)
# 获取详细信息
diff --git a/docs/scripts/sync_docs_to_wiki.py b/docs/scripts/sync_docs_to_wiki.py
index f717f1f96..70f3881b0 100644
--- a/docs/scripts/sync_docs_to_wiki.py
+++ b/docs/scripts/sync_docs_to_wiki.py
@@ -147,7 +147,6 @@ def iter_markdown_links(content: str):
This scanner intentionally handles inline `[]()` links used in the docs tree.
It does not parse reference-style links or arbitrary HTML.
"""
-
index = 0
while index < len(content):
label_start = content.find("[", index)
@@ -292,7 +291,7 @@ class LinkResolver:
self.source_pages = discover_source_pages(str(self.source_root))
def resolve_base_target(
- self, base_target: str, source_path: str
+ self, base_target: str, source_path: str,
) -> ResolutionResult:
return resolve_link_path(
base_target=base_target,
@@ -302,7 +301,7 @@ class LinkResolver:
)
def resolve_markdown_target(
- self, target: str, source_path: str
+ self, target: str, source_path: str,
) -> tuple[str | None, str]:
parsed_target = parse_doc_target(target)
if parsed_target is None:
@@ -386,7 +385,7 @@ def rewrite_links(
segment.text,
source_path=source_path,
resolver=resolver,
- )
+ ),
)
continue
@@ -404,7 +403,7 @@ def find_unresolved_doc_links(source_root: Path) -> list[str]:
content = (root / source_path).read_text(encoding="utf-8")
for link in iter_markdown_links(content):
resolved_path, _ = resolver.resolve_markdown_target(
- link.target, source_path
+ link.target, source_path,
)
if resolved_path is not None:
continue
@@ -523,7 +522,7 @@ def build_sidebar(page_infos: list[PageInfo]) -> str:
def build_page_info(
- source_root: Path, source_path: str, resolver: LinkResolver
+ source_root: Path, source_path: str, resolver: LinkResolver,
) -> PageInfo:
source_file = source_root / source_path
content = source_file.read_text(encoding="utf-8")
@@ -608,7 +607,7 @@ def sync_docs_to_wiki(source_root: Path, wiki_root: Path) -> None:
def main() -> int:
parser = argparse.ArgumentParser(
- description="Sync AstrBot docs content to GitHub wiki pages."
+ description="Sync AstrBot docs content to GitHub wiki pages.",
)
parser.add_argument(
"--source-root",
@@ -635,7 +634,7 @@ def main() -> int:
return 0
sync_docs_to_wiki(
- source_root=Path(args.source_root), wiki_root=Path(args.wiki_root)
+ source_root=Path(args.source_root), wiki_root=Path(args.wiki_root),
)
return 0
diff --git a/docs/scripts/upload_doc_images_to_r2.py b/docs/scripts/upload_doc_images_to_r2.py
index 7db614dc4..c541fc157 100755
--- a/docs/scripts/upload_doc_images_to_r2.py
+++ b/docs/scripts/upload_doc_images_to_r2.py
@@ -27,13 +27,13 @@ IMAGE_EXTS = {
MD_IMAGE_RE = re.compile(r"!\[[^\]]*\]\(([^)]+)\)")
HTML_IMG_RE = re.compile(
- r"
]*\bsrc\s*=\s*([\"'])([^\"']+)\1[^>]*>", re.IGNORECASE
+ r"
]*\bsrc\s*=\s*([\"'])([^\"']+)\1[^>]*>", re.IGNORECASE,
)
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(
- description="Upload all locally referenced images from Markdown docs to Cloudflare R2 using rclone."
+ description="Upload all locally referenced images from Markdown docs to Cloudflare R2 using rclone.",
)
parser.add_argument("--remote", required=True, help="rclone remote name, e.g. r2")
parser.add_argument("--bucket", default="", help="bucket name in remote path")
@@ -48,10 +48,10 @@ def parse_args() -> argparse.Namespace:
help="docs root to scan for .md files (default: current directory)",
)
parser.add_argument(
- "--dry-run", action="store_true", help="preview uploads without sending files"
+ "--dry-run", action="store_true", help="preview uploads without sending files",
)
parser.add_argument(
- "--list-only", action="store_true", help="only print matched image files"
+ "--list-only", action="store_true", help="only print matched image files",
)
parser.add_argument(
"--rewrite-markdown",
@@ -135,7 +135,7 @@ def find_markdown_files(root: Path) -> list[Path]:
def collect_images(
- root: Path, md_files: Sequence[Path]
+ root: Path, md_files: Sequence[Path],
) -> tuple[set[Path], list[tuple[Path, str]]]:
images: set[Path] = set()
missing: list[tuple[Path, str]] = []
@@ -191,7 +191,7 @@ def build_public_url(base: str, object_path: str) -> str:
def run_rclone_upload(
- root: Path, target: str, rel_files: Iterable[str], dry_run: bool
+ root: Path, target: str, rel_files: Iterable[str], dry_run: bool,
) -> None:
if shutil.which("rclone") is None:
raise RuntimeError("rclone not found in PATH")
@@ -262,7 +262,7 @@ def rewrite_markdown_files(
if not url:
return match.group(0)
return match.group(0).replace(
- f"src={quote_ch}{raw}{quote_ch}", f"src={quote_ch}{url}{quote_ch}", 1
+ f"src={quote_ch}{raw}{quote_ch}", f"src={quote_ch}{url}{quote_ch}", 1,
)
updated = MD_IMAGE_RE.sub(md_repl, text)
diff --git a/main.py b/main.py
index 14e0c23a8..3f4b14168 100644
--- a/main.py
+++ b/main.py
@@ -108,7 +108,7 @@ async def main_async(webui_dir_arg: str | None) -> None:
if webui_dir is None:
logger.warning(
"管理面板文件检查失败,WebUI 功能将不可用。"
- "请检查网络连接或手动指定 --webui-dir 参数。"
+ "请检查网络连接或手动指定 --webui-dir 参数。",
)
db = db_helper
diff --git a/scripts/hatch_build.py b/scripts/hatch_build.py
index 431465b54..5e9c1cbf1 100644
--- a/scripts/hatch_build.py
+++ b/scripts/hatch_build.py
@@ -1,5 +1,4 @@
-"""
-Custom Hatchling build hook.
+"""Custom Hatchling build hook.
Only runs when the environment variable ASTRBOT_BUILD_DASHBOARD=1 is set,
so that `uv sync` / editable installs are never affected.
diff --git a/typings/faiss/__init__.pyi b/typings/faiss/__init__.pyi
index 6f2bace36..1ebf7e1b2 100644
--- a/typings/faiss/__init__.pyi
+++ b/typings/faiss/__init__.pyi
@@ -32,17 +32,17 @@ class Index:
@overload
def reconstruct(self, key: int, x: np.ndarray) -> None: ...
def reconstruct(
- self, key: int, x: np.ndarray | None = ...
+ self, key: int, x: np.ndarray | None = ...,
) -> np.ndarray | None: ...
@overload
def reconstruct_n(self, n0: int, ni: int) -> np.ndarray: ...
@overload
def reconstruct_n(self, n0: int, ni: int, x: np.ndarray) -> None: ...
def reconstruct_n(
- self, n0: int = ..., ni: int = ..., x: np.ndarray | None = ...
+ self, n0: int = ..., ni: int = ..., x: np.ndarray | None = ...,
) -> np.ndarray | None: ...
def range_search(
- self, x: np.ndarray, thresh: float, *, params: Any = ...
+ self, x: np.ndarray, thresh: float, *, params: Any = ...,
) -> tuple[np.ndarray, np.ndarray, np.ndarray]: ...
def add_sa_codes(self, codes: np.ndarray, ids: np.ndarray | None = ...) -> None: ...
def sa_encode(self, x: np.ndarray) -> np.ndarray: ...
@@ -70,13 +70,13 @@ class InvertedLists:
def __len__(self) -> int: ...
class AdditiveQuantizer:
- pass
+ ...
class Quantizer:
- pass
+ ...
class VectorTransform:
- pass
+ ...
# SWIG-provided downcast helpers (present in some faiss Python builds).
def downcast_IndexBinary(obj: Any) -> IndexBinary: ...