feat: add Python version check requiring 3.12 or 3.13

- Add version check at startup in both __main__.py and cmd_run.py
- Suggest using `uv run -m astrbot` or reinstalling with uv
- Add ABC base class and abstract methods to ComputerBooter
- Improve type annotations in OpenAIAgentsRunner
This commit is contained in:
LIghtJUNction
2026-03-23 17:32:11 +08:00
parent 99c66c2410
commit cfdb4ef651
4 changed files with 80 additions and 20 deletions

View File

@@ -44,9 +44,14 @@ logo_tmpl = r"""
def check_env() -> None:
if not (sys.version_info.major == 3 and sys.version_info.minor >= 10):
logger.error("请使用 Python3.10+ 运行本项目。")
exit()
# Python version check: require 3.12 or 3.13
if not (sys.version_info.major == 3 and sys.version_info.minor in (12, 13)):
print(
f"❌ Python 版本错误: 当前使用 Python {sys.version_info.major}.{sys.version_info.minor}\n"
"请使用 uv tool install -e . --force -p 3.12 或 -p 3.13 重新安装",
file=sys.stderr,
)
sys.exit(1)
astrbot_root = get_astrbot_root()
if astrbot_root not in sys.path:

View File

@@ -6,18 +6,29 @@ and the openai-agents library from OpenAI.
from __future__ import annotations
import sys
from collections.abc import AsyncGenerator
from typing import Any
if sys.version_info >= (3, 12):
from typing import override
else:
from typing_extensions import override
from agents import Agent as OpenAIAgent
from agents import Model, Runner, RunResult
from agents.run_config import RunConfig
from astrbot.core.agent.hooks import BaseAgentRunHooks
from astrbot.core.agent.response import AgentResponse, AgentResponseData
from astrbot.core.agent.run_context import ContextWrapper, TContext
from astrbot.core.agent.runners.base import AgentState, BaseAgentRunner
from astrbot.core.agent.tool_executor import BaseFunctionToolExecutor
from astrbot.core.agent.tool import FunctionTool
from astrbot.core.provider.entities import LLMResponse
from astrbot.core.message.message_event_result import MessageChain
from astrbot.core.provider.entities import LLMResponse, ProviderRequest
from astrbot.core.provider.func_tool_manager import FunctionToolManager
from astrbot.core.provider.provider import Provider
from .tool_adapter import astrbot_tool_to_agents_tool
@@ -82,10 +93,25 @@ class OpenAIAgentsRunner(BaseAgentRunner[TContext]):
return tool.handler
raise ValueError(f"Tool handler not found for: {tool_name}")
@override
async def reset(
self,
provider: Provider,
request: ProviderRequest,
run_context: ContextWrapper[TContext],
tool_executor: BaseFunctionToolExecutor[TContext],
agent_hooks: BaseAgentRunHooks[TContext],
streaming: bool = False,
enforce_max_turns: int = -1,
llm_compress_instruction: str | None = None,
llm_compress_keep_recent: int = 0,
llm_compress_provider: Provider | None = None,
truncate_turns: int = 1,
custom_token_counter: Any = None,
custom_compressor: Any = None,
tool_schema_mode: str | None = "full",
fallback_providers: list[Provider] | None = None,
provider_config: dict | None = None,
**kwargs: Any,
) -> None:
"""Reset the agent to its initial state."""
@@ -103,13 +129,17 @@ class OpenAIAgentsRunner(BaseAgentRunner[TContext]):
**self._openai_agents_kwargs,
)
async def step(self) -> Any:
@override
async def step(self) -> AsyncGenerator[AgentResponse, None]:
"""Process a single step of the agent (not directly supported)."""
raise NotImplementedError(
"step() is not directly supported. Use step_until_done() instead."
)
async def step_until_done(self, max_step: int = 50) -> Any:
@override
async def step_until_done(
self, max_step: int
) -> AsyncGenerator[AgentResponse, None]:
"""Run the agent until completion or max steps."""
if not self._agent or not self._run_context:
raise RuntimeError("Agent not initialized. Call reset() first.")
@@ -125,12 +155,17 @@ class OpenAIAgentsRunner(BaseAgentRunner[TContext]):
run_config=run_config,
)
self._state = AgentState.DONE
if self._run_result:
chain = MessageChain().message(self._run_result.final_output)
yield AgentResponse(
type="llm_result",
data=AgentResponseData(chain=chain),
)
except Exception:
self._state = AgentState.ERROR
raise
return self._run_result
def done(self) -> bool:
"""Check if the agent has completed its task."""
return self._state == AgentState.DONE
@@ -140,7 +175,6 @@ class OpenAIAgentsRunner(BaseAgentRunner[TContext]):
if not self._run_result:
return None
return LLMResponse(
content=self._run_result.final_output,
model=self._run_result.model,
usage=self._run_result.usage,
role="assistant",
completion_text=self._run_result.final_output,
)

View File

@@ -57,6 +57,15 @@ from filelock import FileLock, Timeout
from astrbot.cli.utils import DashboardManager
from astrbot.runtime_bootstrap import initialize_runtime_bootstrap
# Python version check: require 3.12 or 3.13
if not (sys.version_info.major == 3 and sys.version_info.minor in (12, 13)):
print(
f"❌ Python 版本错误: 当前使用 Python {sys.version_info.major}.{sys.version_info.minor}\n"
"请使用 uv tool install -e . --force -p 3.12 或 -p 3.13 重新安装",
file=sys.stderr,
)
sys.exit(1)
initialize_runtime_bootstrap()
# Regular expression to find bash-like parameter expansions:
# ${VAR:-default} or ${VAR}

View File

@@ -1,5 +1,6 @@
from __future__ import annotations
import abc
from typing import TYPE_CHECKING
from ..olayer import (
@@ -13,15 +14,21 @@ if TYPE_CHECKING:
from astrbot.core.agent.tool import FunctionTool
class ComputerBooter:
class ComputerBooter(abc.ABC):
@property
def fs(self) -> FileSystemComponent: ...
@abc.abstractmethod
def fs(self) -> FileSystemComponent:
raise NotImplementedError("Subclass must implement fs property")
@property
def python(self) -> PythonComponent: ...
@abc.abstractmethod
def python(self) -> PythonComponent:
raise NotImplementedError("Subclass must implement python property")
@property
def shell(self) -> ShellComponent: ...
@abc.abstractmethod
def shell(self) -> ShellComponent:
raise NotImplementedError("Subclass must implement shell property")
@property
def capabilities(self) -> tuple[str, ...] | None:
@@ -36,24 +43,29 @@ class ComputerBooter:
def browser(self) -> BrowserComponent | None:
return None
async def boot(self, session_id: str) -> None: ...
@abc.abstractmethod
async def boot(self, session_id: str) -> None:
raise NotImplementedError("Subclass must implement boot method")
async def shutdown(self) -> None: ...
@abc.abstractmethod
async def shutdown(self) -> None:
raise NotImplementedError("Subclass must implement shutdown method")
async def upload_file(self, path: str, file_name: str) -> dict:
"""Upload file to the computer.
Should return a dict with `success` (bool) and `file_path` (str) keys.
"""
...
raise NotImplementedError("Subclass must implement upload_file method")
async def download_file(self, remote_path: str, local_path: str) -> None:
"""Download file from the computer."""
...
raise NotImplementedError("Subclass must implement download_file method")
@abc.abstractmethod
async def available(self) -> bool:
"""Check if the computer is available."""
...
raise NotImplementedError("Subclass must implement available method")
@classmethod
def get_default_tools(cls) -> list[FunctionTool]: