diff --git a/astrbot/builtin_stars/astrbot/long_term_memory.py b/astrbot/builtin_stars/astrbot/long_term_memory.py index ed97e408a..ee9a8f286 100644 --- a/astrbot/builtin_stars/astrbot/long_term_memory.py +++ b/astrbot/builtin_stars/astrbot/long_term_memory.py @@ -172,7 +172,9 @@ 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 diff --git a/astrbot/builtin_stars/astrbot/main.py b/astrbot/builtin_stars/astrbot/main.py index b5b793ca4..e8189b09c 100644 --- a/astrbot/builtin_stars/astrbot/main.py +++ b/astrbot/builtin_stars/astrbot/main.py @@ -86,7 +86,9 @@ 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 +99,9 @@ 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/help.py b/astrbot/builtin_stars/builtin_commands/commands/help.py index f147af160..93ab8f726 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() except BaseException: diff --git a/astrbot/builtin_stars/builtin_commands/commands/provider.py b/astrbot/builtin_stars/builtin_commands/commands/provider.py index 166bf6907..9db5e886a 100644 --- a/astrbot/builtin_stars/builtin_commands/commands/provider.py +++ b/astrbot/builtin_stars/builtin_commands/commands/provider.py @@ -48,7 +48,11 @@ 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 +71,10 @@ 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 +113,10 @@ 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) @@ -205,7 +215,11 @@ class ProviderCommands: 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 +301,10 @@ 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 @@ -558,7 +575,10 @@ 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: diff --git a/astrbot/builtin_stars/builtin_commands/main.py b/astrbot/builtin_stars/builtin_commands/main.py index 3e4b911b3..943aac5c0 100644 --- a/astrbot/builtin_stars/builtin_commands/main.py +++ b/astrbot/builtin_stars/builtin_commands/main.py @@ -170,7 +170,9 @@ 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_plug.py b/astrbot/cli/commands/cmd_plug.py index 9b7a69f06..a11d711d0 100644 --- a/astrbot/cli/commands/cmd_plug.py +++ b/astrbot/cli/commands/cmd_plug.py @@ -180,7 +180,9 @@ 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: diff --git a/astrbot/cli/utils/version_comparator.py b/astrbot/cli/utils/version_comparator.py index 2bac3a824..a8267475c 100644 --- a/astrbot/cli/utils/version_comparator.py +++ b/astrbot/cli/utils/version_comparator.py @@ -62,7 +62,9 @@ class VersionComparator: return -1 if isinstance(p1, str) and isinstance(p2, int): return 1 - if (isinstance(p1, int) and isinstance(p2, int)) or (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 6f6e6e2d7..b00340a7d 100644 --- a/astrbot/core/agent/context/compressor.py +++ b/astrbot/core/agent/context/compressor.py @@ -25,7 +25,10 @@ class ContextCompressor(Protocol): """ 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. @@ -59,7 +62,9 @@ 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. @@ -72,7 +77,10 @@ class TruncateByTurnsCompressor: 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. @@ -100,7 +108,8 @@ 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. @@ -181,7 +190,10 @@ 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. @@ -211,7 +223,8 @@ 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: diff --git a/astrbot/core/agent/context/manager.py b/astrbot/core/agent/context/manager.py index 49550b423..98246a671 100644 --- a/astrbot/core/agent/context/manager.py +++ b/astrbot/core/agent/context/manager.py @@ -43,7 +43,9 @@ class ContextManager: ) 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. @@ -68,11 +70,14 @@ 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) @@ -82,7 +87,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. @@ -111,7 +118,9 @@ 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...", diff --git a/astrbot/core/agent/context/token_counter.py b/astrbot/core/agent/context/token_counter.py index ec9e53da7..79e105fa0 100644 --- a/astrbot/core/agent/context/token_counter.py +++ b/astrbot/core/agent/context/token_counter.py @@ -11,7 +11,9 @@ class TokenCounter(Protocol): """ 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. @@ -44,7 +46,9 @@ 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 bc1538216..97ce721a6 100644 --- a/astrbot/core/agent/context/truncator.py +++ b/astrbot/core/agent/context/truncator.py @@ -140,7 +140,9 @@ 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) @@ -169,7 +171,9 @@ 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) @@ -198,6 +202,8 @@ 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 a5ef336f6..cc75aed5b 100644 --- a/astrbot/core/agent/mcp_client.py +++ b/astrbot/core/agent/mcp_client.py @@ -409,7 +409,11 @@ 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, @@ -421,7 +425,9 @@ 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 e51ff96ab..c41267763 100644 --- a/astrbot/core/agent/message.py +++ b/astrbot/core/agent/message.py @@ -33,7 +33,9 @@ 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": diff --git a/astrbot/core/agent/runners/base.py b/astrbot/core/agent/runners/base.py index ac9eae8f1..efc039b21 100644 --- a/astrbot/core/agent/runners/base.py +++ b/astrbot/core/agent/runners/base.py @@ -39,7 +39,8 @@ 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 4ab4c9267..8e49e081e 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.") @@ -93,7 +92,8 @@ class CozeAgentRunner(BaseAgentRunner[TContext]): logger.error(f"Coze 请求失败:{e!s}") self._transition_state(AgentState.ERROR) self.final_llm_resp = LLMResponse( - role="err", completion_text=f"Coze 请求失败:{e!s}", + role="err", + completion_text=f"Coze 请求失败:{e!s}", ) yield AgentResponse( type="err", @@ -106,7 +106,8 @@ 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(): @@ -165,7 +166,8 @@ class CozeAgentRunner(BaseAgentRunner[TContext]): if url: file_id = ( await self._download_and_upload_image( - url, session_id, + url, + session_id, ) ) processed_content.append( diff --git a/astrbot/core/agent/runners/dashscope/dashscope_agent_runner.py b/astrbot/core/agent/runners/dashscope/dashscope_agent_runner.py index 14b7201ab..009220b8d 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.") @@ -105,7 +104,8 @@ class DashscopeAgentRunner(BaseAgentRunner[TContext]): logger.error(f"阿里云百炼请求失败:{e!s}") self._transition_state(AgentState.ERROR) self.final_llm_resp = LLMResponse( - role="err", completion_text=f"阿里云百炼请求失败:{e!s}", + role="err", + completion_text=f"阿里云百炼请求失败:{e!s}", ) yield AgentResponse( type="err", @@ -116,14 +116,17 @@ class DashscopeAgentRunner(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(): yield resp def _consume_sync_generator( - self, response: T.Any, response_queue: queue.Queue, + self, + response: T.Any, + response_queue: queue.Queue, ) -> None: """在线程中消费同步generator,将结果放入队列 @@ -144,7 +147,9 @@ 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 @@ -216,7 +221,11 @@ 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 @@ -276,7 +285,9 @@ class DashscopeAgentRunner(BaseAgentRunner[TContext]): 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]: """处理流式响应 @@ -301,7 +312,10 @@ 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 @@ -378,7 +392,10 @@ 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 58740fe2e..05f88e8d1 100644 --- a/astrbot/core/agent/runners/deerflow/deerflow_agent_runner.py +++ b/astrbot/core/agent/runners/deerflow/deerflow_agent_runner.py @@ -130,7 +130,9 @@ 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, @@ -304,7 +306,8 @@ 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") diff --git a/astrbot/core/agent/runners/deerflow/deerflow_api_client.py b/astrbot/core/agent/runners/deerflow/deerflow_api_client.py index 3d7a557ef..16b06e6e3 100644 --- a/astrbot/core/agent/runners/deerflow/deerflow_api_client.py +++ b/astrbot/core/agent/runners/deerflow/deerflow_api_client.py @@ -168,7 +168,8 @@ 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. diff --git a/astrbot/core/agent/runners/deerflow/deerflow_content_mapper.py b/astrbot/core/agent/runners/deerflow/deerflow_content_mapper.py index 87cb691f4..dbb7e893e 100644 --- a/astrbot/core/agent/runners/deerflow/deerflow_content_mapper.py +++ b/astrbot/core/agent/runners/deerflow/deerflow_content_mapper.py @@ -164,14 +164,18 @@ 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 b438c182c..1e4fbf1e6 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.") @@ -86,7 +85,8 @@ class DifyAgentRunner(BaseAgentRunner[TContext]): logger.error(f"Dify 请求失败:{e!s}") self._transition_state(AgentState.ERROR) self.final_llm_resp = LLMResponse( - role="err", completion_text=f"Dify 请求失败:{e!s}", + role="err", + completion_text=f"Dify 请求失败:{e!s}", ) yield AgentResponse( type="err", @@ -99,7 +99,8 @@ 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(): diff --git a/astrbot/core/agent/runners/tool_loop_agent_runner.py b/astrbot/core/agent/runners/tool_loop_agent_runner.py index a47f4ff4b..2b44b6435 100644 --- a/astrbot/core/agent/runners/tool_loop_agent_runner.py +++ b/astrbot/core/agent/runners/tool_loop_agent_runner.py @@ -72,7 +72,8 @@ 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) @@ -299,7 +300,9 @@ 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 = { @@ -522,7 +525,8 @@ 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]") @@ -705,7 +709,8 @@ 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 @@ -733,7 +738,8 @@ class ToolLoopAgentRunner(BaseAgentRunner[TContext]): 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 @@ -937,7 +943,8 @@ class ToolLoopAgentRunner(BaseAgentRunner[TContext]): 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, ), ) @@ -954,7 +961,8 @@ 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 +974,8 @@ 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 +996,8 @@ 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, ), ) @@ -1072,7 +1082,8 @@ 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) diff --git a/astrbot/core/agent/tool.py b/astrbot/core/agent/tool.py index d5a8f6338..4eb986b1c 100644 --- a/astrbot/core/agent/tool.py +++ b/astrbot/core/agent/tool.py @@ -32,7 +32,8 @@ 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 diff --git a/astrbot/core/agent/tool_image_cache.py b/astrbot/core/agent/tool_image_cache.py index 0bcf44791..ff79165d7 100644 --- a/astrbot/core/agent/tool_image_cache.py +++ b/astrbot/core/agent/tool_image_cache.py @@ -110,7 +110,9 @@ 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. diff --git a/astrbot/core/astr_agent_hooks.py b/astrbot/core/astr_agent_hooks.py index 852f8f6c1..0b679d2ab 100644 --- a/astrbot/core/astr_agent_hooks.py +++ b/astrbot/core/astr_agent_hooks.py @@ -17,7 +17,8 @@ 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 02c29a925..154e7d445 100644 --- a/astrbot/core/astr_agent_run_util.py +++ b/astrbot/core/astr_agent_run_util.py @@ -47,7 +47,8 @@ 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 +66,8 @@ 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 = "" @@ -157,7 +159,8 @@ 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), @@ -261,7 +264,8 @@ 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") diff --git a/astrbot/core/astr_agent_tool_exec.py b/astrbot/core/astr_agent_tool_exec.py index 40becfa45..34d92a2ae 100644 --- a/astrbot/core/astr_agent_tool_exec.py +++ b/astrbot/core/astr_agent_tool_exec.py @@ -53,7 +53,8 @@ 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 +66,8 @@ 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 +135,9 @@ 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 +277,9 @@ 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 @@ -412,7 +418,10 @@ 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): @@ -520,7 +529,10 @@ class FunctionToolExecutor(BaseFunctionToolExecutor[AstrAgentContext]): ) 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}.") diff --git a/astrbot/core/astr_main_agent.py b/astrbot/core/astr_main_agent.py index 9126c0ded..218248ee1 100644 --- a/astrbot/core/astr_main_agent.py +++ b/astrbot/core/astr_main_agent.py @@ -161,7 +161,8 @@ 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") @@ -171,7 +172,8 @@ 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 @@ -183,7 +185,8 @@ 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 @@ -337,7 +340,8 @@ 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: @@ -773,7 +777,8 @@ 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) @@ -786,7 +791,8 @@ 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) @@ -799,7 +805,8 @@ 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 @@ -928,7 +935,9 @@ 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 @@ -963,7 +972,9 @@ 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, @@ -982,7 +993,9 @@ 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() @@ -1093,7 +1106,8 @@ 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 @@ -1116,7 +1130,9 @@ 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): @@ -1401,7 +1417,9 @@ 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/exporter.py b/astrbot/core/backup/exporter.py index cbae0b8af..3e615a6fe 100644 --- a/astrbot/core/backup/exporter.py +++ b/astrbot/core/backup/exporter.py @@ -99,7 +99,10 @@ 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) @@ -115,17 +118,26 @@ 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, + "知识库元数据导出完成", ) # 导出每个知识库的文档数据 @@ -141,7 +153,10 @@ 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) @@ -155,7 +170,10 @@ class AstrBotExporter: if progress_callback: await progress_callback( - "kb_documents", total_kbs, total_kbs, "知识库文档导出完成", + "kb_documents", + total_kbs, + total_kbs, + "知识库文档导出完成", ) # 3. 导出配置文件 @@ -179,7 +197,10 @@ 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: @@ -287,7 +308,10 @@ 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: @@ -306,7 +330,8 @@ class AstrBotExporter: logger.warning(f"导出知识库媒体文件失败: {e}") async def _export_directories( - self, zf: zipfile.ZipFile, + self, + zf: zipfile.ZipFile, ) -> dict[str, dict[str, int]]: """导出插件和其他数据目录 @@ -358,7 +383,9 @@ 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 182c28a1f..d455e3020 100644 --- a/astrbot/core/backup/importer.py +++ b/astrbot/core/backup/importer.py @@ -468,7 +468,8 @@ 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 @@ -478,7 +479,10 @@ class AstrBotImporter: # 6. 导入插件和其他目录 if progress_callback: await progress_callback( - "directories", 0, 100, "正在导入插件和数据目录...", + "directories", + 0, + 100, + "正在导入插件和数据目录...", ) dir_stats = await self._import_directories(zf, manifest, result) @@ -556,7 +560,8 @@ 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] = {} @@ -586,7 +591,9 @@ class AstrBotImporter: 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) @@ -601,7 +608,8 @@ 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. diff --git a/astrbot/core/computer/booters/bay_manager.py b/astrbot/core/computer/booters/bay_manager.py index 4fecda157..42cc7ec98 100644 --- a/astrbot/core/computer/booters/bay_manager.py +++ b/astrbot/core/computer/booters/bay_manager.py @@ -110,7 +110,8 @@ 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) @@ -128,7 +129,8 @@ 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") @@ -201,7 +203,8 @@ 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/local.py b/astrbot/core/computer/booters/local.py index be2ba56da..77675c78c 100644 --- a/astrbot/core/computer/booters/local.py +++ b/astrbot/core/computer/booters/local.py @@ -183,7 +183,10 @@ 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 +212,11 @@ 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 +239,9 @@ 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) diff --git a/astrbot/core/computer/booters/shipyard.py b/astrbot/core/computer/booters/shipyard.py index f403f8575..44818b5c4 100644 --- a/astrbot/core/computer/booters/shipyard.py +++ b/astrbot/core/computer/booters/shipyard.py @@ -15,7 +15,8 @@ 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 c7af0f55f..c4183a43f 100644 --- a/astrbot/core/computer/booters/shipyard_neo.py +++ b/astrbot/core/computer/booters/shipyard_neo.py @@ -428,7 +428,8 @@ 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 diff --git a/astrbot/core/computer/computer_client.py b/astrbot/core/computer/computer_client.py index bd06f346e..c1133880e 100644 --- a/astrbot/core/computer/computer_client.py +++ b/astrbot/core/computer/computer_client.py @@ -462,7 +462,10 @@ 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 @@ -509,7 +512,8 @@ 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/browser.py b/astrbot/core/computer/olayer/browser.py index 942bd9aa3..e876b5bc2 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 c50173803..86d2b4b36 100644 --- a/astrbot/core/computer/olayer/filesystem.py +++ b/astrbot/core/computer/olayer/filesystem.py @@ -1,12 +1,14 @@ -"""File system component -""" +"""File system component""" 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""" ... @@ -16,7 +18,11 @@ 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""" ... @@ -26,7 +32,9 @@ 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 fbcf397c4..78bdad0bf 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 82eb9845c..fb9ccdcae 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/fs.py b/astrbot/core/computer/tools/fs.py index 50e16ff77..235e6d168 100644 --- a/astrbot/core/computer/tools/fs.py +++ b/astrbot/core/computer/tools/fs.py @@ -183,7 +183,8 @@ 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 diff --git a/astrbot/core/computer/tools/permissions.py b/astrbot/core/computer/tools/permissions.py index e8287e423..bd1aef4c9 100644 --- a/astrbot/core/computer/tools/permissions.py +++ b/astrbot/core/computer/tools/permissions.py @@ -3,7 +3,8 @@ 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, diff --git a/astrbot/core/computer/tools/python.py b/astrbot/core/computer/tools/python.py index 2d92034d0..a6158f3a8 100644 --- a/astrbot/core/computer/tools/python.py +++ b/astrbot/core/computer/tools/python.py @@ -46,7 +46,9 @@ 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", ), ) @@ -68,7 +70,10 @@ 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 @@ -94,7 +99,10 @@ 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 diff --git a/astrbot/core/config/i18n_utils.py b/astrbot/core/config/i18n_utils.py index 429a1fdf9..63a637153 100644 --- a/astrbot/core/config/i18n_utils.py +++ b/astrbot/core/config/i18n_utils.py @@ -41,7 +41,10 @@ class ConfigMetadataI18n: 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] = {} @@ -72,11 +75,15 @@ 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, @@ -108,7 +115,9 @@ 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 25c7af57e..d17cff808 100644 --- a/astrbot/core/conversation_mgr.py +++ b/astrbot/core/conversation_mgr.py @@ -110,7 +110,9 @@ 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: """切换会话的对话 diff --git a/astrbot/core/core_lifecycle.py b/astrbot/core/core_lifecycle.py index 1c1369b9a..f319f940f 100644 --- a/astrbot/core/core_lifecycle.py +++ b/astrbot/core/core_lifecycle.py @@ -106,7 +106,9 @@ 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: diff --git a/astrbot/core/cron/manager.py b/astrbot/core/cron/manager.py index 477a63ff1..09065ed50 100644 --- a/astrbot/core/cron/manager.py +++ b/astrbot/core/cron/manager.py @@ -178,7 +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: @@ -194,7 +195,10 @@ 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 +312,8 @@ 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, @@ -347,7 +352,10 @@ class CronJobManager: ) 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 6eae9462d..8199d0bdb 100644 --- a/astrbot/core/db/__init__.py +++ b/astrbot/core/db/__init__.py @@ -399,7 +399,8 @@ 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.""" ... @@ -428,14 +429,17 @@ 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.""" ... @@ -668,14 +672,16 @@ 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.""" ... @@ -797,7 +803,9 @@ 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 920171ff9..9da178e00 100644 --- a/astrbot/core/db/migration/migra_token_usage.py +++ b/astrbot/core/db/migration/migra_token_usage.py @@ -19,7 +19,9 @@ 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,7 +40,10 @@ 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 diff --git a/astrbot/core/db/migration/migra_webchat_session.py b/astrbot/core/db/migration/migra_webchat_session.py index ddfd20e5d..85c1b5e6c 100644 --- a/astrbot/core/db/migration/migra_webchat_session.py +++ b/astrbot/core/db/migration/migra_webchat_session.py @@ -25,7 +25,9 @@ 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 +55,10 @@ 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 +76,8 @@ 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 的映射字典 diff --git a/astrbot/core/db/migration/sqlite_v3.py b/astrbot/core/db/migration/sqlite_v3.py index cd48795d3..0d0d1583a 100644 --- a/astrbot/core/db/migration/sqlite_v3.py +++ b/astrbot/core/db/migration/sqlite_v3.py @@ -227,7 +227,9 @@ 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 +308,10 @@ 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 126f3ddb5..1165f2a0e 100644 --- a/astrbot/core/db/po.py +++ b/astrbot/core/db/po.py @@ -438,7 +438,9 @@ 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 b14a4c5be..5a7b2d230 100644 --- a/astrbot/core/db/sqlite.py +++ b/astrbot/core/db/sqlite.py @@ -340,7 +340,12 @@ 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 @@ -557,7 +562,8 @@ 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: @@ -846,7 +852,8 @@ 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. @@ -878,7 +885,8 @@ 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()) @@ -936,7 +944,9 @@ class SQLiteDatabase(BaseDatabase): ) 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: @@ -950,7 +960,8 @@ class SQLiteDatabase(BaseDatabase): 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. @@ -1455,7 +1466,8 @@ 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: @@ -1467,7 +1479,8 @@ 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: @@ -1779,7 +1792,9 @@ 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: diff --git a/astrbot/core/knowledge_base/kb_db_sqlite.py b/astrbot/core/knowledge_base/kb_db_sqlite.py index efb594d90..73db1d2eb 100644 --- a/astrbot/core/knowledge_base/kb_db_sqlite.py +++ b/astrbot/core/knowledge_base/kb_db_sqlite.py @@ -260,7 +260,8 @@ 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 9b75132cd..102372554 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. @@ -75,7 +74,8 @@ 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 @@ -514,7 +514,8 @@ 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( @@ -583,15 +584,16 @@ 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}", ) 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: @@ -625,7 +627,9 @@ 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 ] diff --git a/astrbot/core/log.py b/astrbot/core/log.py index bbac91f86..21f223280 100644 --- a/astrbot/core/log.py +++ b/astrbot/core/log.py @@ -111,7 +111,9 @@ 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"), diff --git a/astrbot/core/message/components.py b/astrbot/core/message/components.py index 9dcd65a63..3c4e3ae72 100644 --- a/astrbot/core/message/components.py +++ b/astrbot/core/message/components.py @@ -157,7 +157,8 @@ 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 +247,8 @@ 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 +444,8 @@ 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/persona_error_reply.py b/astrbot/core/persona_error_reply.py index e6ea1bb10..82244f517 100644 --- a/astrbot/core/persona_error_reply.py +++ b/astrbot/core/persona_error_reply.py @@ -35,7 +35,8 @@ 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,7 +71,8 @@ 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( @@ -79,7 +81,8 @@ async def resolve_event_conversation_persona_id( 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 eb57183de..046e08e1c 100644 --- a/astrbot/core/persona_mgr.py +++ b/astrbot/core/persona_mgr.py @@ -175,7 +175,8 @@ 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 @@ -186,7 +187,9 @@ class PersonaManager: 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 到指定文件夹 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 08167b3b8..31933fe66 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,23 @@ 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 +105,8 @@ 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 +144,9 @@ 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 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 91321acb4..e5b82182c 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 @@ -186,7 +186,8 @@ 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( @@ -286,7 +287,9 @@ 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 diff --git a/astrbot/core/pipeline/result_decorate/stage.py b/astrbot/core/pipeline/result_decorate/stage.py index 3e0a4d3c8..b75a892bb 100644 --- a/astrbot/core/pipeline/result_decorate/stage.py +++ b/astrbot/core/pipeline/result_decorate/stage.py @@ -76,10 +76,13 @@ 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 diff --git a/astrbot/core/pipeline/waking_check/stage.py b/astrbot/core/pipeline/waking_check/stage.py index 26134bdb2..8504f7f1c 100644 --- a/astrbot/core/pipeline/waking_check/stage.py +++ b/astrbot/core/pipeline/waking_check/stage.py @@ -69,7 +69,8 @@ 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/astrbot_message.py b/astrbot/core/platform/astrbot_message.py index 05da0b8c5..26a62aba6 100644 --- a/astrbot/core/platform/astrbot_message.py +++ b/astrbot/core/platform/astrbot_message.py @@ -13,10 +13,7 @@ class MessageMember: def __str__(self) -> str: # 使用 f-string 来构建返回的字符串表示形式 - return ( - f"User ID: {self.user_id}," - f"Nickname: {self.nickname or 'N/A'}" - ) + return f"User ID: {self.user_id},Nickname: {self.nickname or 'N/A'}" @dataclass diff --git a/astrbot/core/platform/manager.py b/astrbot/core/platform/manager.py index cff2bfe95..5529293ff 100644 --- a/astrbot/core/platform/manager.py +++ b/astrbot/core/platform/manager.py @@ -228,7 +228,9 @@ 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: diff --git a/astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py b/astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py index d023d7421..e540f7ac7 100644 --- a/astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py +++ b/astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py @@ -145,7 +145,8 @@ 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 +171,8 @@ 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"): diff --git a/astrbot/core/platform/sources/dingtalk/dingtalk_adapter.py b/astrbot/core/platform/sources/dingtalk/dingtalk_adapter.py index d35dc6489..be88de737 100644 --- a/astrbot/core/platform/sources/dingtalk/dingtalk_adapter.py +++ b/astrbot/core/platform/sources/dingtalk/dingtalk_adapter.py @@ -47,7 +47,9 @@ 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__( @@ -196,7 +198,8 @@ class DingtalkPlatformAdapter(Platform): 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,7 +215,8 @@ 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) plain_parts: list[str] = [] @@ -425,11 +429,14 @@ class DingtalkPlatformAdapter(Platform): "Content-Type": "application/json", "x-acs-dingtalk-access-token": access_token, } - async with aiohttp.ClientSession() as session, session.post( - "https://api.dingtalk.com/v1.0/robot/groupMessages/send", - headers=headers, - json=payload, - ) as resp: + 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()}", @@ -457,11 +464,14 @@ class DingtalkPlatformAdapter(Platform): "Content-Type": "application/json", "x-acs-dingtalk-access-token": access_token, } - async with aiohttp.ClientSession() as session, session.post( - "https://api.dingtalk.com/v1.0/robot/oToMessages/batchSend", - headers=headers, - json=payload, - ) as resp: + 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()}", diff --git a/astrbot/core/platform/sources/discord/discord_platform_adapter.py b/astrbot/core/platform/sources/discord/discord_platform_adapter.py index e1268420e..c752b1c56 100644 --- a/astrbot/core/platform/sources/discord/discord_platform_adapter.py +++ b/astrbot/core/platform/sources/discord/discord_platform_adapter.py @@ -35,7 +35,9 @@ else: # 注册平台适配器 @register_platform_adapter( - "discord", "Discord 适配器 (基于 Pycord)", support_streaming_message=False, + "discord", + "Discord 适配器 (基于 Pycord)", + support_streaming_message=False, ) class DiscordPlatformAdapter(Platform): def __init__( @@ -156,7 +158,8 @@ 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 @@ -189,7 +192,8 @@ 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)) @@ -433,7 +437,8 @@ 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}") diff --git a/astrbot/core/platform/sources/discord/discord_platform_event.py b/astrbot/core/platform/sources/discord/discord_platform_event.py index 4b8ea6f9f..e6ff71a46 100644 --- a/astrbot/core/platform/sources/discord/discord_platform_event.py +++ b/astrbot/core/platform/sources/discord/discord_platform_event.py @@ -98,7 +98,9 @@ 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,7 +276,9 @@ class DiscordPlatformEvent(AstrMessageEvent): self.message_obj.raw_message, "add_reaction", ): - await cast("discord.Message", self.message_obj.raw_message).add_reaction( + await cast( + "discord.Message", self.message_obj.raw_message + ).add_reaction( emoji, ) except Exception as e: @@ -319,7 +323,8 @@ 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 diff --git a/astrbot/core/platform/sources/kook/kook_adapter.py b/astrbot/core/platform/sources/kook/kook_adapter.py index 010d0d97e..b039b1e6c 100644 --- a/astrbot/core/platform/sources/kook/kook_adapter.py +++ b/astrbot/core/platform/sources/kook/kook_adapter.py @@ -43,7 +43,10 @@ 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 +58,9 @@ 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 +76,9 @@ 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: @@ -136,7 +143,8 @@ 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 检查 @@ -157,7 +165,8 @@ 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,7 +201,9 @@ 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 "" @@ -341,7 +352,8 @@ 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 b9213b80b..71e517c1b 100644 --- a/astrbot/core/platform/sources/kook/kook_client.py +++ b/astrbot/core/platform/sources/kook/kook_client.py @@ -125,7 +125,9 @@ class KookClient: return None gateway_url: str = resp_content.data.url - logger.info(f"[KOOK] 获取gateway成功: {gateway_url.split('?', maxsplit=1)[0]}") + logger.info( + f"[KOOK] 获取gateway成功: {gateway_url.split('?', maxsplit=1)[0]}" + ) return gateway_url except pydantic.ValidationError as e: @@ -149,7 +151,9 @@ 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() @@ -293,7 +297,8 @@ 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) diff --git a/astrbot/core/platform/sources/kook/kook_event.py b/astrbot/core/platform/sources/kook/kook_event.py index bbe757eb5..48f3b8085 100644 --- a/astrbot/core/platform/sources/kook/kook_event.py +++ b/astrbot/core/platform/sources/kook/kook_event.py @@ -48,10 +48,14 @@ 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 +97,9 @@ 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 diff --git a/astrbot/core/platform/sources/kook/kook_types.py b/astrbot/core/platform/sources/kook/kook_types.py index d7a7c9fc9..f3b1b9caa 100644 --- a/astrbot/core/platform/sources/kook/kook_types.py +++ b/astrbot/core/platform/sources/kook/kook_types.py @@ -57,7 +57,14 @@ 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 +283,9 @@ 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 @@ -351,7 +360,8 @@ class KookExtra(KookBaseDataClass): class KookMessageEventData(KookBaseDataClass): signal: Literal[KookMessageSignal.MESSAGE] = Field( - KookMessageSignal.MESSAGE, exclude=True, + KookMessageSignal.MESSAGE, + exclude=True, ) """only for type hint""" @@ -369,7 +379,8 @@ class KookMessageEventData(KookBaseDataClass): class KookHelloEventData(KookBaseDataClass): signal: Literal[KookMessageSignal.HELLO] = Field( - KookMessageSignal.HELLO, exclude=True, + KookMessageSignal.HELLO, + exclude=True, ) """only for type hint""" @@ -379,28 +390,32 @@ 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""" @@ -410,7 +425,8 @@ 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""" @@ -421,7 +437,9 @@ 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 15bb93a2f..5223e93e5 100644 --- a/astrbot/core/platform/sources/lark/lark_adapter.py +++ b/astrbot/core/platform/sources/lark/lark_adapter.py @@ -34,7 +34,9 @@ from .server import LarkWebhookServer @register_platform_adapter( - "lark", "飞书机器人官方 API 适配器", support_streaming_message=True, + "lark", + "飞书机器人官方 API 适配器", + support_streaming_message=True, ) class LarkPlatformAdapter(Platform): def __init__( diff --git a/astrbot/core/platform/sources/lark/lark_event.py b/astrbot/core/platform/sources/lark/lark_event.py index f10ca7cc6..448dc0553 100644 --- a/astrbot/core/platform/sources/lark/lark_event.py +++ b/astrbot/core/platform/sources/lark/lark_event.py @@ -557,17 +557,29 @@ 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: @@ -599,7 +611,9 @@ class LarkMessageEvent(AstrMessageEvent): """ 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 @@ -1082,7 +1096,8 @@ 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 a81a92893..f30c53813 100644 --- a/astrbot/core/platform/sources/lark/server.py +++ b/astrbot/core/platform/sources/lark/server.py @@ -163,7 +163,11 @@ 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 diff --git a/astrbot/core/platform/sources/mattermost/client.py b/astrbot/core/platform/sources/mattermost/client.py index 1e3a1744d..37ba33474 100644 --- a/astrbot/core/platform/sources/mattermost/client.py +++ b/astrbot/core/platform/sources/mattermost/client.py @@ -139,7 +139,9 @@ 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) @@ -218,7 +220,9 @@ 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/misskey/misskey_adapter.py b/astrbot/core/platform/sources/misskey/misskey_adapter.py index c6ee19fb7..c0041e455 100644 --- a/astrbot/core/platform/sources/misskey/misskey_adapter.py +++ b/astrbot/core/platform/sources/misskey/misskey_adapter.py @@ -45,7 +45,9 @@ DEFAULT_UPLOAD_CONCURRENCY = 3 @register_platform_adapter( - "misskey", "Misskey 平台适配器", support_streaming_message=False, + "misskey", + "Misskey 平台适配器", + support_streaming_message=False, ) class MisskeyPlatformAdapter(Platform): def __init__( diff --git a/astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py b/astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py index 9ed31888a..867c4bde0 100644 --- a/astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py +++ b/astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py @@ -145,7 +145,6 @@ class QQOfficialMessageEvent(AstrMessageEvent): # 如需兜底,应该只发送未发送 delta(后续可继续优化) self.send_buffer = None - @staticmethod def _extract_response_message_id(ret) -> str | None: """兼容 qq-botpy 返回 Message 对象或 dict 两种形态。""" diff --git a/astrbot/core/platform/sources/qqofficial/qqofficial_platform_adapter.py b/astrbot/core/platform/sources/qqofficial/qqofficial_platform_adapter.py index 6821d7382..13e134737 100644 --- a/astrbot/core/platform/sources/qqofficial/qqofficial_platform_adapter.py +++ b/astrbot/core/platform/sources/qqofficial/qqofficial_platform_adapter.py @@ -71,7 +71,8 @@ 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, @@ -95,7 +96,8 @@ 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, @@ -548,7 +550,8 @@ class QQOfficialPlatformAdapter(Platform): 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 @@ -569,7 +572,8 @@ class QQOfficialPlatformAdapter(Platform): ) 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 b117751a5..1ed2649a7 100644 --- a/astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_adapter.py +++ b/astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_adapter.py @@ -29,7 +29,8 @@ 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, @@ -53,7 +54,8 @@ 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, 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 7671c9af6..de1388cac 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,10 @@ 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"] diff --git a/astrbot/core/platform/sources/satori/satori_adapter.py b/astrbot/core/platform/sources/satori/satori_adapter.py index c1bf6b150..5a38d3394 100644 --- a/astrbot/core/platform/sources/satori/satori_adapter.py +++ b/astrbot/core/platform/sources/satori/satori_adapter.py @@ -29,7 +29,9 @@ 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 5f0b7af22..ff366bdfc 100644 --- a/astrbot/core/platform/sources/slack/client.py +++ b/astrbot/core/platform/sources/slack/client.py @@ -143,7 +143,9 @@ 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/telegram/tg_adapter.py b/astrbot/core/platform/sources/telegram/tg_adapter.py index 9a2cedca7..9495a6587 100644 --- a/astrbot/core/platform/sources/telegram/tg_adapter.py +++ b/astrbot/core/platform/sources/telegram/tg_adapter.py @@ -118,10 +118,12 @@ 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 @@ -307,7 +309,9 @@ 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}") @@ -510,7 +514,9 @@ 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). diff --git a/astrbot/core/platform/sources/telegram/tg_event.py b/astrbot/core/platform/sources/telegram/tg_event.py index db4440a86..99eb3efc5 100644 --- a/astrbot/core/platform/sources/telegram/tg_event.py +++ b/astrbot/core/platform/sources/telegram/tg_event.py @@ -167,17 +167,24 @@ 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 @@ -250,7 +257,10 @@ 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: @@ -321,7 +331,9 @@ class TelegramPlatformEvent(AstrMessageEvent): 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() @@ -514,12 +526,18 @@ 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) @@ -614,7 +632,11 @@ 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 @@ -675,7 +697,11 @@ 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, ) # 编辑或发送消息 @@ -705,7 +731,8 @@ 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/webchat_adapter.py b/astrbot/core/platform/sources/webchat/webchat_adapter.py index e0ca40a0a..164ec8211 100644 --- a/astrbot/core/platform/sources/webchat/webchat_adapter.py +++ b/astrbot/core/platform/sources/webchat/webchat_adapter.py @@ -154,7 +154,8 @@ 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) @@ -248,7 +249,8 @@ 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/wecom/wecom_event.py b/astrbot/core/platform/sources/wecom/wecom_event.py index 1589775ae..140d9110a 100644 --- a/astrbot/core/platform/sources/wecom/wecom_event.py +++ b/astrbot/core/platform/sources/wecom/wecom_event.py @@ -105,7 +105,9 @@ class WecomPlatformEvent(AstrMessageEvent): 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 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 952d07d2e..2f52d86b6 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,13 @@ 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 +105,12 @@ 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", @@ -372,7 +376,10 @@ 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, @@ -592,7 +599,8 @@ 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(), @@ -603,7 +611,8 @@ 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() @@ -611,7 +620,9 @@ 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( 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 f1b700a48..c60957d27 100644 --- a/astrbot/core/platform/sources/wecom_ai_bot/wecomai_event.py +++ b/astrbot/core/platform/sources/wecom_ai_bot/wecomai_event.py @@ -314,7 +314,8 @@ 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 82663aca0..3803edca5 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,9 @@ 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() 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 aa701609d..1b7df1c9c 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 @@ -125,7 +125,9 @@ 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_webhook.py b/astrbot/core/platform/sources/wecom_ai_bot/wecomai_webhook.py index 797b22a43..6ebb5ac97 100644 --- a/astrbot/core/platform/sources/wecom_ai_bot/wecomai_webhook.py +++ b/astrbot/core/platform/sources/wecom_ai_bot/wecomai_webhook.py @@ -101,7 +101,9 @@ class WecomAIBotWebhookClient: ) 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}") @@ -214,7 +216,9 @@ 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 9ad469d88..e128ae9ad 100644 --- a/astrbot/core/platform/sources/weixin_oc/weixin_oc_adapter.py +++ b/astrbot/core/platform/sources/weixin_oc/weixin_oc_adapter.py @@ -498,7 +498,8 @@ 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 @@ -742,7 +743,8 @@ 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): @@ -814,7 +816,8 @@ 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) @@ -958,7 +961,8 @@ 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 "" @@ -992,7 +996,8 @@ 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 [] @@ -1113,7 +1118,10 @@ 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_official_account/weixin_offacc_adapter.py b/astrbot/core/platform/sources/weixin_official_account/weixin_offacc_adapter.py index 6be122e6c..87f8cf565 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 @@ -185,11 +185,12 @@ class WeixinOfficialAccountServer: self.user_buffer.pop(from_user, None) return _reply_text(cached_xml) return _reply_text( - cached_xml - + "\n【后续消息还在缓冲中,回复任意文字继续获取】", + 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,回复任意文字尝试获取回复】" @@ -230,7 +231,8 @@ class WeixinOfficialAccountServer: 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("处理消息失败,请稍后再试。") @@ -311,7 +313,9 @@ class WeixinOfficialAccountServer: @register_platform_adapter( - "weixin_official_account", "微信公众平台 适配器", support_streaming_message=False, + "weixin_official_account", + "微信公众平台 适配器", + support_streaming_message=False, ) class WeixinOfficialAccountPlatformAdapter(Platform): def __init__( @@ -341,7 +345,9 @@ 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( @@ -445,7 +451,9 @@ class WeixinOfficialAccountPlatformAdapter(Platform): 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), 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 f0053933e..ecd89beeb 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 @@ -83,7 +83,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", + False, ) for comp in message.chain: if isinstance(comp, Plain): @@ -120,7 +121,9 @@ 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"] @@ -158,7 +161,9 @@ class WeixinOfficialAccountPlatformEvent(AstrMessageEvent): ], ) 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: diff --git a/astrbot/core/platform_message_history_mgr.py b/astrbot/core/platform_message_history_mgr.py index 37a6feb3d..b3ac69ac3 100644 --- a/astrbot/core/platform_message_history_mgr.py +++ b/astrbot/core/platform_message_history_mgr.py @@ -41,7 +41,10 @@ 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/func_tool_manager.py b/astrbot/core/provider/func_tool_manager.py index 67f4c6987..54cbc78d9 100644 --- a/astrbot/core/provider/func_tool_manager.py +++ b/astrbot/core/provider/func_tool_manager.py @@ -415,7 +415,8 @@ 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 服务列表。文件格式如下: ``` @@ -506,7 +507,9 @@ class FunctionToolManager: ) 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.", @@ -635,7 +638,9 @@ class FunctionToolManager: return [] async def _cleanup_mcp_client_safely( - self, mcp_client: MCPClient, name: str, + self, + mcp_client: MCPClient, + name: str, ) -> None: """安全清理单个 MCP 客户端,避免清理异常中断主流程。""" try: diff --git a/astrbot/core/provider/manager.py b/astrbot/core/provider/manager.py index 3e5bf8385..03e12e4aa 100644 --- a/astrbot/core/provider/manager.py +++ b/astrbot/core/provider/manager.py @@ -211,7 +211,9 @@ 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: """获取正在使用的提供商实例。 @@ -772,7 +774,9 @@ 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: diff --git a/astrbot/core/provider/sources/anthropic_source.py b/astrbot/core/provider/sources/anthropic_source.py index ffdfd40e1..ce6036987 100644 --- a/astrbot/core/provider/sources/anthropic_source.py +++ b/astrbot/core/provider/sources/anthropic_source.py @@ -298,7 +298,9 @@ 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", "") @@ -393,7 +395,8 @@ 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 57ea9fbb4..fb441ceb3 100644 --- a/astrbot/core/provider/sources/azure_tts_source.py +++ b/astrbot/core/provider/sources/azure_tts_source.py @@ -47,7 +47,8 @@ class OTTSProvider: async def __aenter__(self): self._client = AsyncClient( - timeout=self.timeout, proxy=self.proxy or None, + timeout=self.timeout, + proxy=self.proxy or None, ) return self @@ -213,7 +214,9 @@ 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 17ec5bbdf..4f2631a63 100644 --- a/astrbot/core/provider/sources/bailian_rerank_source.py +++ b/astrbot/core/provider/sources/bailian_rerank_source.py @@ -13,19 +13,18 @@ class BailianRerankError(Exception): """百炼重排序服务异常基类""" - class BailianAPIError(BailianRerankError): """百炼API返回错误""" - class BailianNetworkError(BailianRerankError): """百炼网络请求错误""" - @register_provider_adapter( - "bailian_rerank", "阿里云百炼文本排序适配器", provider_type=ProviderType.RERANK, + "bailian_rerank", + "阿里云百炼文本排序适配器", + provider_type=ProviderType.RERANK, ) class BailianRerankProvider(RerankProvider): """阿里云百炼文本重排序适配器.""" @@ -39,7 +38,8 @@ 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 不能为空。") @@ -61,7 +61,8 @@ class BailianRerankProvider(RerankProvider): } self.client = aiohttp.ClientSession( - headers=headers, timeout=aiohttp.ClientTimeout(total=self.timeout), + headers=headers, + timeout=aiohttp.ClientTimeout(total=self.timeout), ) # 设置模型名称 @@ -70,7 +71,10 @@ 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: """构建请求载荷 @@ -159,7 +163,8 @@ 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: diff --git a/astrbot/core/provider/sources/fishaudio_tts_api_source.py b/astrbot/core/provider/sources/fishaudio_tts_api_source.py index 645cd050b..e9a00a884 100644 --- a/astrbot/core/provider/sources/fishaudio_tts_api_source.py +++ b/astrbot/core/provider/sources/fishaudio_tts_api_source.py @@ -157,7 +157,8 @@ 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(): diff --git a/astrbot/core/provider/sources/gemini_source.py b/astrbot/core/provider/sources/gemini_source.py index da8c047f5..8638f48d7 100644 --- a/astrbot/core/provider/sources/gemini_source.py +++ b/astrbot/core/provider/sources/gemini_source.py @@ -241,7 +241,8 @@ 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,7 +254,8 @@ 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() @@ -445,7 +447,8 @@ 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( diff --git a/astrbot/core/provider/sources/groq_source.py b/astrbot/core/provider/sources/groq_source.py index fb8b77c03..e77e286fb 100644 --- a/astrbot/core/provider/sources/groq_source.py +++ b/astrbot/core/provider/sources/groq_source.py @@ -3,7 +3,8 @@ 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 21d8043b9..be46fb883 100644 --- a/astrbot/core/provider/sources/longcat_source.py +++ b/astrbot/core/provider/sources/longcat_source.py @@ -3,7 +3,8 @@ 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_tts_api_source.py b/astrbot/core/provider/sources/mimo_tts_api_source.py index 88c1ee742..6d1f17b46 100644 --- a/astrbot/core/provider/sources/mimo_tts_api_source.py +++ b/astrbot/core/provider/sources/mimo_tts_api_source.py @@ -39,7 +39,8 @@ 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) diff --git a/astrbot/core/provider/sources/nvidia_rerank_source.py b/astrbot/core/provider/sources/nvidia_rerank_source.py index 0c76e73b9..826997f58 100644 --- a/astrbot/core/provider/sources/nvidia_rerank_source.py +++ b/astrbot/core/provider/sources/nvidia_rerank_source.py @@ -8,21 +8,26 @@ 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,7 +42,8 @@ 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 @@ -74,7 +80,9 @@ 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", []) @@ -133,7 +141,8 @@ 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 60da20c74..4e36f637e 100644 --- a/astrbot/core/provider/sources/oai_aihubmix_source.py +++ b/astrbot/core/provider/sources/oai_aihubmix_source.py @@ -3,7 +3,8 @@ 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_source.py b/astrbot/core/provider/sources/openai_source.py index f1ebd9411..070c47e97 100644 --- a/astrbot/core/provider/sources/openai_source.py +++ b/astrbot/core/provider/sources/openai_source.py @@ -374,7 +374,8 @@ 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 +405,8 @@ 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 +497,8 @@ 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 @@ -806,7 +809,9 @@ 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") @@ -963,7 +968,8 @@ 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 @@ -987,7 +993,8 @@ 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( diff --git a/astrbot/core/provider/sources/openrouter_source.py b/astrbot/core/provider/sources/openrouter_source.py index 3cbdd3eee..4399fc7d7 100644 --- a/astrbot/core/provider/sources/openrouter_source.py +++ b/astrbot/core/provider/sources/openrouter_source.py @@ -3,7 +3,8 @@ 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 d3018df83..54c969abd 100644 --- a/astrbot/core/provider/sources/sensevoice_selfhosted_source.py +++ b/astrbot/core/provider/sources/sensevoice_selfhosted_source.py @@ -92,7 +92,9 @@ class ProviderSenseVoiceSTTSelfHost(STTProvider): res = await loop.run_in_executor( None, # 使用默认的线程池 lambda: cast("SenseVoiceSmall", self.model)( - audio_url, language="auto", use_itn=True, + audio_url, + language="auto", + use_itn=True, ), ) diff --git a/astrbot/core/provider/sources/xai_source.py b/astrbot/core/provider/sources/xai_source.py index 9dea66f20..fc6ce9c6c 100644 --- a/astrbot/core/provider/sources/xai_source.py +++ b/astrbot/core/provider/sources/xai_source.py @@ -3,7 +3,8 @@ 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/skills/neo_skill_sync.py b/astrbot/core/skills/neo_skill_sync.py index fa7098c44..e70993e58 100644 --- a/astrbot/core/skills/neo_skill_sync.py +++ b/astrbot/core/skills/neo_skill_sync.py @@ -242,7 +242,8 @@ 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.") diff --git a/astrbot/core/skills/skill_manager.py b/astrbot/core/skills/skill_manager.py index 4a9e858f9..546a9cb39 100644 --- a/astrbot/core/skills/skill_manager.py +++ b/astrbot/core/skills/skill_manager.py @@ -331,7 +331,8 @@ 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 +382,8 @@ 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 diff --git a/astrbot/core/star/command_management.py b/astrbot/core/star/command_management.py index 3f2b31a1f..f4ed077e3 100644 --- a/astrbot/core/star/command_management.py +++ b/astrbot/core/star/command_management.py @@ -285,18 +285,23 @@ 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) diff --git a/astrbot/core/star/filter/permission.py b/astrbot/core/star/filter/permission.py index 4870025d6..23edc8bd9 100644 --- a/astrbot/core/star/filter/permission.py +++ b/astrbot/core/star/filter/permission.py @@ -15,7 +15,9 @@ 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/star_handler.py b/astrbot/core/star/register/star_handler.py index 808cb0999..2f0c4957d 100644 --- a/astrbot/core/star/register/star_handler.py +++ b/astrbot/core/star/register/star_handler.py @@ -407,7 +407,9 @@ def register_on_waiting_llm_request(**kwargs): def decorator(awaitable): _ = get_handler_or_create( - awaitable, EventType.OnWaitingLLMRequestEvent, **kwargs, + awaitable, + EventType.OnWaitingLLMRequestEvent, + **kwargs, ) return awaitable diff --git a/astrbot/core/star/star_manager.py b/astrbot/core/star/star_manager.py index ea8dfc640..c624b346d 100644 --- a/astrbot/core/star/star_manager.py +++ b/astrbot/core/star/star_manager.py @@ -189,7 +189,9 @@ 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 +309,8 @@ 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,则检查所有插件的依赖 @@ -1303,7 +1306,10 @@ 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 安装插件 @@ -1722,11 +1728,14 @@ 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 diff --git a/astrbot/core/subagent_orchestrator.py b/astrbot/core/subagent_orchestrator.py index b54cb393d..2fc0e6a3a 100644 --- a/astrbot/core/subagent_orchestrator.py +++ b/astrbot/core/subagent_orchestrator.py @@ -20,7 +20,9 @@ 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 diff --git a/astrbot/core/tools/cron_tools.py b/astrbot/core/tools/cron_tools.py index eea6b0c4d..6c0de97f4 100644 --- a/astrbot/core/tools/cron_tools.py +++ b/astrbot/core/tools/cron_tools.py @@ -74,7 +74,9 @@ class FutureTaskTool(FunctionTool[AstrAgentContext]): ) 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: diff --git a/astrbot/core/tools/knowledge_base_tools.py b/astrbot/core/tools/knowledge_base_tools.py index a3b7fb837..94da77404 100644 --- a/astrbot/core/tools/knowledge_base_tools.py +++ b/astrbot/core/tools/knowledge_base_tools.py @@ -107,7 +107,9 @@ class KnowledgeBaseQueryTool(FunctionTool[AstrAgentContext]): ) 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 9c4e6ca71..3789677e0 100644 --- a/astrbot/core/tools/message_tools.py +++ b/astrbot/core/tools/message_tools.py @@ -75,7 +75,9 @@ class SendMessageToUserTool(FunctionTool[AstrAgentContext]): ) 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 +92,8 @@ 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 +104,9 @@ 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 +133,8 @@ 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 +146,8 @@ 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 +159,8 @@ 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 +178,8 @@ 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 ee94f4b63..f2d595245 100644 --- a/astrbot/core/tools/web_search_tools.py +++ b/astrbot/core/tools/web_search_tools.py @@ -124,11 +124,14 @@ async def _tavily_search( "Authorization": f"Bearer {tavily_key}", "Content-Type": "application/json", } - async with aiohttp.ClientSession(trust_env=True) as session, session.post( - "https://api.tavily.com/search", - json=payload, - headers=header, - ) as response: + 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( @@ -152,11 +155,14 @@ 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, session.post( - "https://api.tavily.com/extract", - json=payload, - headers=header, - ) as response: + 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( @@ -180,11 +186,14 @@ async def _bocha_search( "Authorization": f"Bearer {bocha_key}", "Content-Type": "application/json", } - async with aiohttp.ClientSession(trust_env=True) as session, session.post( - "https://api.bochaai.com/v1/web-search", - json=payload, - headers=header, - ) as response: + 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( @@ -212,11 +221,14 @@ async def _brave_search( "Accept": "application/json", "X-Subscription-Token": brave_key, } - 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: + 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( @@ -247,11 +259,14 @@ async def _baidu_search( "X-Appbuilder-Authorization": f"Bearer {api_key}", "Content-Type": "application/json", } - 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: + 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( diff --git a/astrbot/core/utils/core_constraints.py b/astrbot/core/utils/core_constraints.py index 4b0c36daf..bde84c205 100644 --- a/astrbot/core/utils/core_constraints.py +++ b/astrbot/core/utils/core_constraints.py @@ -103,7 +103,10 @@ 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/llm_metadata.py b/astrbot/core/utils/llm_metadata.py index 254446bc9..54e45fc48 100644 --- a/astrbot/core/utils/llm_metadata.py +++ b/astrbot/core/utils/llm_metadata.py @@ -33,9 +33,13 @@ LLM_METADATAS: dict[str, LLMMetadata] = {} 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, session.get(url) as response: + async with ( + aiohttp.ClientSession( + trust_env=True, + connector=build_tls_connector(), + ) as session, + session.get(url) as response, + ): data = await response.json() global LLM_METADATAS models = {} @@ -51,7 +55,8 @@ async def update_llm_metadata() -> None: knowledge=model.get("knowledge", "none"), release_date=model.get("release_date", ""), modalities=model.get( - "modalities", {"input": [], "output": []}, + "modalities", + {"input": [], "output": []}, ), open_weights=model.get("open_weights", False), limit=model.get("limit", {"context": 0, "output": 0}), diff --git a/astrbot/core/utils/media_utils.py b/astrbot/core/utils/media_utils.py index df2b34bda..6e374faf8 100644 --- a/astrbot/core/utils/media_utils.py +++ b/astrbot/core/utils/media_utils.py @@ -145,7 +145,9 @@ 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转换视频格式 diff --git a/astrbot/core/utils/migra_helper.py b/astrbot/core/utils/migra_helper.py index e1c4c8367..3cfc0a77d 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"] if default_prov_id in ids_map: @@ -127,7 +126,10 @@ 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. btw, i really don't like migration :( diff --git a/astrbot/core/utils/network_utils.py b/astrbot/core/utils/network_utils.py index dad6465ba..21893b6f5 100644 --- a/astrbot/core/utils/network_utils.py +++ b/astrbot/core/utils/network_utils.py @@ -70,7 +70,8 @@ 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: diff --git a/astrbot/core/utils/pip_installer.py b/astrbot/core/utils/pip_installer.py index fd3197eb4..3bfd212a4 100644 --- a/astrbot/core/utils/pip_installer.py +++ b/astrbot/core/utils/pip_installer.py @@ -54,7 +54,11 @@ 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 @@ -559,7 +563,8 @@ 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 +624,8 @@ 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(".")) @@ -914,7 +920,9 @@ 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, ) @@ -985,7 +993,9 @@ 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 文件。") diff --git a/astrbot/core/utils/requirements_utils.py b/astrbot/core/utils/requirements_utils.py index 2a0f821cd..5d6d35b55 100644 --- a/astrbot/core/utils/requirements_utils.py +++ b/astrbot/core/utils/requirements_utils.py @@ -21,7 +21,6 @@ class RequirementsPrecheckFailed(Exception): """Raised when the pre-check of requirements fails.""" - @dataclass(frozen=True) class ParsedPackageInput: specs: tuple[str, ...] @@ -259,7 +258,8 @@ 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) diff --git a/astrbot/core/utils/session_lock.py b/astrbot/core/utils/session_lock.py index 684fc0240..eb4e8fe1e 100644 --- a/astrbot/core/utils/session_lock.py +++ b/astrbot/core/utils/session_lock.py @@ -36,7 +36,8 @@ 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 d8b8226f7..d5f502ff2 100644 --- a/astrbot/core/utils/shared_preferences.py +++ b/astrbot/core/utils/shared_preferences.py @@ -32,7 +32,10 @@ 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 +206,11 @@ 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 +219,10 @@ 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 5b9a39515..d25282a47 100644 --- a/astrbot/core/utils/temp_dir_cleaner.py +++ b/astrbot/core/utils/temp_dir_cleaner.py @@ -81,7 +81,9 @@ 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/zip_updator.py b/astrbot/core/zip_updator.py index 65b5c4b86..2d6a1b483 100644 --- a/astrbot/core/zip_updator.py +++ b/astrbot/core/zip_updator.py @@ -150,7 +150,10 @@ 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/backup.py b/astrbot/dashboard/routes/backup.py index ca08d7faa..79b4957c1 100644 --- a/astrbot/dashboard/routes/backup.py +++ b/astrbot/dashboard/routes/backup.py @@ -186,7 +186,10 @@ 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, @@ -312,7 +315,8 @@ 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"), diff --git a/astrbot/dashboard/routes/chat.py b/astrbot/dashboard/routes/chat.py index cf975d8fb..d05aedf90 100644 --- a/astrbot/dashboard/routes/chat.py +++ b/astrbot/dashboard/routes/chat.py @@ -107,7 +107,8 @@ 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) @@ -201,7 +202,9 @@ 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 +216,9 @@ 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 的引用 @@ -370,7 +375,8 @@ 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 @@ -456,14 +462,16 @@ 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) @@ -471,7 +479,8 @@ 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) @@ -821,7 +830,8 @@ 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/config.py b/astrbot/dashboard/routes/config.py index 5d5e606a5..fefab3ea5 100644 --- a/astrbot/dashboard/routes/config.py +++ b/astrbot/dashboard/routes/config.py @@ -283,10 +283,13 @@ async def _validate_neo_connectivity( health_url = f"{endpoint}/health" try: - async with aiohttp.ClientSession() as session, session.get( - health_url, - timeout=aiohttp.ClientTimeout(total=5), - ) as resp: + 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})," @@ -299,7 +302,9 @@ 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 @@ -318,7 +323,9 @@ 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()) @@ -1297,7 +1304,8 @@ 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__ @@ -1351,7 +1359,8 @@ 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 @@ -1380,7 +1389,8 @@ 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] = {} @@ -1407,7 +1417,10 @@ 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", {}) @@ -1418,7 +1431,8 @@ 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(): @@ -1465,7 +1479,9 @@ class ConfigRoute(Route): # 注入配置元数据(在 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注册任务 @@ -1514,7 +1530,9 @@ 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: @@ -1545,7 +1563,9 @@ 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/cron.py b/astrbot/dashboard/routes/cron.py index 64f499b95..af98dedb0 100644 --- a/astrbot/dashboard/routes/cron.py +++ b/astrbot/dashboard/routes/cron.py @@ -11,7 +11,9 @@ 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 @@ -249,7 +251,9 @@ 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 diff --git a/astrbot/dashboard/routes/knowledge_base.py b/astrbot/dashboard/routes/knowledge_base.py index eebb87ffb..1d8e985a6 100644 --- a/astrbot/dashboard/routes/knowledge_base.py +++ b/astrbot/dashboard/routes/knowledge_base.py @@ -77,7 +77,11 @@ 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 +174,9 @@ 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 +258,9 @@ 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 2d6fd6c62..6da08957a 100644 --- a/astrbot/dashboard/routes/live_chat.py +++ b/astrbot/dashboard/routes/live_chat.py @@ -183,7 +183,9 @@ 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 +197,9 @@ 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 = [ @@ -289,7 +293,8 @@ 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 +347,9 @@ 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") @@ -714,7 +721,10 @@ 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: @@ -783,7 +793,9 @@ 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(): @@ -922,7 +934,10 @@ class LiveChatRoute(Route): 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 + " [用户打断]" diff --git a/astrbot/dashboard/routes/log.py b/astrbot/dashboard/routes/log.py index a6317f853..6484c0c54 100644 --- a/astrbot/dashboard/routes/log.py +++ b/astrbot/dashboard/routes/log.py @@ -43,7 +43,8 @@ class LogRoute(Route): ) async def _replay_cached_logs( - self, last_event_id: str, + self, + last_event_id: str, ) -> AsyncGenerator[str, None]: """辅助生成器:重放缓存的日志""" try: diff --git a/astrbot/dashboard/routes/open_api.py b/astrbot/dashboard/routes/open_api.py index 58e38888a..b128fc9b8 100644 --- a/astrbot/dashboard/routes/open_api.py +++ b/astrbot/dashboard/routes/open_api.py @@ -173,7 +173,8 @@ 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( @@ -260,7 +261,8 @@ 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( @@ -279,7 +281,8 @@ class OpenApiRoute(Route): ) 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 @@ -417,7 +420,10 @@ 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 +435,32 @@ 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) @@ -500,7 +510,8 @@ 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) diff --git a/astrbot/dashboard/routes/persona.py b/astrbot/dashboard/routes/persona.py index 835375734..a663a0648 100644 --- a/astrbot/dashboard/routes/persona.py +++ b/astrbot/dashboard/routes/persona.py @@ -209,7 +209,8 @@ 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/session_management.py b/astrbot/dashboard/routes/session_management.py index 7c3d4b0ce..1d1ac804c 100644 --- a/astrbot/dashboard/routes/session_management.py +++ b/astrbot/dashboard/routes/session_management.py @@ -49,7 +49,10 @@ 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 及其规则内容(支持分页和搜索)。 @@ -141,7 +144,9 @@ class SessionManagementRoute(Route): 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, ) # 构建规则列表 diff --git a/astrbot/dashboard/routes/skills.py b/astrbot/dashboard/routes/skills.py index fa0ae7c03..373a57be9 100644 --- a/astrbot/dashboard/routes/skills.py +++ b/astrbot/dashboard/routes/skills.py @@ -98,12 +98,18 @@ class SkillsRoute(Route): 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 +138,15 @@ 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() @@ -181,12 +190,15 @@ 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 @@ -263,7 +275,8 @@ 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( diff --git a/astrbot/dashboard/routes/stat.py b/astrbot/dashboard/routes/stat.py index 0ce3408ee..d1c853919 100644 --- a/astrbot/dashboard/routes/stat.py +++ b/astrbot/dashboard/routes/stat.py @@ -211,10 +211,15 @@ 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) @@ -267,7 +272,9 @@ 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 diff --git a/astrbot/dashboard/routes/t2i.py b/astrbot/dashboard/routes/t2i.py index 2a84b58ab..8495fad2d 100644 --- a/astrbot/dashboard/routes/t2i.py +++ b/astrbot/dashboard/routes/t2i.py @@ -13,7 +13,9 @@ 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 45a082c83..083c80182 100644 --- a/astrbot/dashboard/routes/tools.py +++ b/astrbot/dashboard/routes/tools.py @@ -16,7 +16,6 @@ class EmptyMcpServersError(ValueError): """Raised when mcpServers is empty.""" - def _extract_mcp_server_config(mcp_servers_value: object) -> dict: """Extract server configuration from user-submitted mcpServers field. diff --git a/docs/scripts/sync_docs_to_wiki.py b/docs/scripts/sync_docs_to_wiki.py index 70f3881b0..613072163 100644 --- a/docs/scripts/sync_docs_to_wiki.py +++ b/docs/scripts/sync_docs_to_wiki.py @@ -291,7 +291,9 @@ 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, @@ -301,7 +303,9 @@ 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: @@ -403,7 +407,8 @@ 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 @@ -522,7 +527,9 @@ 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") @@ -634,7 +641,8 @@ 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 c541fc157..f8283e1c4 100755 --- a/docs/scripts/upload_doc_images_to_r2.py +++ b/docs/scripts/upload_doc_images_to_r2.py @@ -27,7 +27,8 @@ 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, ) @@ -48,10 +49,14 @@ 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 +140,8 @@ 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 +197,10 @@ 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 +271,9 @@ 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/typings/faiss/__init__.pyi b/typings/faiss/__init__.pyi index 1ebf7e1b2..2ae665564 100644 --- a/typings/faiss/__init__.pyi +++ b/typings/faiss/__init__.pyi @@ -32,17 +32,26 @@ 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: ... @@ -69,14 +78,9 @@ class IndexBinary(Index): class InvertedLists: def __len__(self) -> int: ... -class AdditiveQuantizer: - ... - -class Quantizer: - ... - -class VectorTransform: - ... +class AdditiveQuantizer: ... +class Quantizer: ... +class VectorTransform: ... # SWIG-provided downcast helpers (present in some faiss Python builds). def downcast_IndexBinary(obj: Any) -> IndexBinary: ...