fix: standardize dashboard env vars to ASTRBOT_* prefix only

- Remove all DASHBOARD_* and ASTRBOT_DASHBOARD_* fallback chains
- server.py now only checks ASTRBOT_HOST, ASTRBOT_PORT, ASTRBOT_SSL_*
- cmd_run.py no longer sets legacy DASHBOARD_* environment variables
- Clean up import paths for agent runners
This commit is contained in:
LIghtJUNction
2026-03-23 13:18:23 +08:00
parent 93f92dc366
commit 19e0952a6f
5 changed files with 80 additions and 67 deletions

View File

@@ -196,24 +196,6 @@ def run(
# after this point and will take precedence.
load_dotenv(dotenv_path=str(svc_path), override=True)
# Normalize environment variables for backward compatibility
# If legacy env vars are set but the preferred new ones aren't, copy them over.
env_map = {
# Dashboard legacy -> standardized dashboard-prefixed
"DASHBOARD_ENABLE": "ASTRBOT_DASHBOARD_ENABLE",
"DASHBOARD_HOST": "ASTRBOT_HOST",
"DASHBOARD_PORT": "ASTRBOT_PORT",
"DASHBOARD_SSL_ENABLE": "ASTRBOT_SSL_ENABLE",
"DASHBOARD_SSL_CERT": "ASTRBOT_SSL_CERT",
"DASHBOARD_SSL_KEY": "ASTRBOT_SSL_KEY",
"DASHBOARD_SSL_CA_CERTS": "ASTRBOT_SSL_CA_CERTS",
# Some packages used alternate names
"ASTRBOT_DASHBOARD_SSL_CERT": "ASTRBOT_SSL_CERT",
}
for legacy, new in env_map.items():
if legacy in os.environ and new not in os.environ:
os.environ[new] = os.environ[legacy]
# Mark CLI execution
os.environ["ASTRBOT_CLI"] = "1"
@@ -243,29 +225,20 @@ def run(
# Host/Port precedence: CLI args > parsed service config/env/.env > defaults.
if port is not None:
os.environ["ASTRBOT_PORT"] = port
os.environ["DASHBOARD_PORT"] = port # legacy
# If CLI didn't provide port but env/.env provided ASTRBOT_DASHBOARD_PORT, leave it as-is.
if host is not None:
os.environ["ASTRBOT_HOST"] = host
os.environ["DASHBOARD_HOST"] = host # legacy
# If CLI didn't provide host but env/.env provided ASTRBOT_DASHBOARD_HOST, leave it as-is.
# CLI-provided SSL paths should set backend-standard env names (preferred),
# and also set legacy/dashboard names for compatibility.
# CLI-provided SSL paths should set backend-standard env names.
if ssl_cert is not None:
os.environ["ASTRBOT_SSL_CERT"] = ssl_cert
os.environ["DASHBOARD_SSL_CERT"] = ssl_cert
if ssl_key is not None:
os.environ["ASTRBOT_SSL_KEY"] = ssl_key
os.environ["DASHBOARD_SSL_KEY"] = ssl_key
if ssl_ca is not None:
os.environ["ASTRBOT_SSL_CA_CERTS"] = ssl_ca
os.environ["DASHBOARD_SSL_CA_CERTS"] = ssl_ca
# Dashboard enable is derived from CLI flag (--backend-only). CLI decision should win.
os.environ["ASTRBOT_DASHBOARD_ENABLE"] = str(not backend_only)
os.environ["DASHBOARD_ENABLE"] = str(not backend_only) # legacy
os.environ["ASTRBOT_LOG_LEVEL"] = log_level

View File

@@ -1,13 +1,15 @@
import abc
import typing as T
from collections.abc import AsyncGenerator
from enum import Enum, auto
from typing import Any, Generic
from astrbot import logger
from astrbot.core.provider.entities import LLMResponse
from ..hooks import BaseAgentRunHooks
from ..response import AgentResponse
from ..run_context import ContextWrapper, TContext
from astrbot.core.agent.hooks import BaseAgentRunHooks
from astrbot.core.agent.response import AgentResponse
from astrbot.core.agent.run_context import ContextWrapper, TContext
from astrbot.core.agent.tool_executor import BaseFunctionToolExecutor
from astrbot.core.provider.entities import LLMResponse, ProviderRequest
from astrbot.core.provider.provider import Provider
class AgentState(Enum):
@@ -19,7 +21,7 @@ class AgentState(Enum):
ERROR = auto() # Error state
class BaseAgentRunner(T.Generic[TContext]):
class BaseAgentRunner(Generic[TContext]):
def __init__(
self,
):
@@ -28,9 +30,23 @@ class BaseAgentRunner(T.Generic[TContext]):
@abc.abstractmethod
async def reset(
self,
provider: Provider,
request: ProviderRequest,
run_context: ContextWrapper[TContext],
tool_executor: BaseFunctionToolExecutor[TContext],
agent_hooks: BaseAgentRunHooks[TContext],
**kwargs: T.Any,
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.
This method should be called before starting a new run.
@@ -38,14 +54,14 @@ class BaseAgentRunner(T.Generic[TContext]):
...
@abc.abstractmethod
async def step(self) -> T.AsyncGenerator[AgentResponse, None]:
async def step(self) -> AsyncGenerator[AgentResponse, None]:
"""Process a single step of the agent."""
...
@abc.abstractmethod
async def step_until_done(
self, max_step: int
) -> T.AsyncGenerator[AgentResponse, None]:
) -> AsyncGenerator[AgentResponse, None]:
"""Process steps until the agent is done."""
...

View File

@@ -1,21 +1,24 @@
import base64
import json
import sys
import typing as T
from collections.abc import AsyncGenerator
from typing import Any
import astrbot.core.message.components as Comp
from astrbot import logger
from astrbot.core import sp
from astrbot.core.agent.hooks import BaseAgentRunHooks
from astrbot.core.agent.response import AgentResponseData
from astrbot.core.agent.run_context import ContextWrapper, TContext
from astrbot.core.agent.runners.base import AgentResponse, AgentState, BaseAgentRunner
from astrbot.core.agent.tool_executor import BaseFunctionToolExecutor
from astrbot.core.message.message_event_result import MessageChain
from astrbot.core.provider.entities import (
LLMResponse,
ProviderRequest,
)
from astrbot.core.provider.provider import Provider
from ...hooks import BaseAgentRunHooks
from ...response import AgentResponseData
from ...run_context import ContextWrapper, TContext
from ..base import AgentResponse, AgentState, BaseAgentRunner
from .coze_api_client import CozeAPIClient
if sys.version_info >= (3, 12):
@@ -30,19 +33,32 @@ class CozeAgentRunner(BaseAgentRunner[TContext]):
@override
async def reset(
self,
provider: Provider,
request: ProviderRequest,
run_context: ContextWrapper[TContext],
tool_executor: BaseFunctionToolExecutor[TContext],
agent_hooks: BaseAgentRunHooks[TContext],
provider_config: dict,
**kwargs: T.Any,
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:
self.req = request
self.streaming = kwargs.get("streaming", False)
self.streaming = streaming
self.final_llm_resp = None
self._state = AgentState.IDLE
self.agent_hooks = agent_hooks
self.run_context = run_context
provider_config = provider_config or {}
self.api_key = provider_config.get("coze_api_key", "")
if not self.api_key:
raise Exception("Coze API Key 不能为空。")
@@ -107,8 +123,8 @@ class CozeAgentRunner(BaseAgentRunner[TContext]):
@override
async def step_until_done(
self, max_step: int = 3
) -> T.AsyncGenerator[AgentResponse, None]:
self, max_step: int
) -> AsyncGenerator[AgentResponse, None]:
while not self.done():
async for resp in self.step():
yield resp

View File

@@ -4,7 +4,8 @@ import queue
import re
import sys
import threading
import typing as T
from collections.abc import AsyncGenerator
from typing import Any
from dashscope import Application
from dashscope.app.application_response import ApplicationResponse
@@ -16,11 +17,13 @@ from astrbot.core.provider.entities import (
LLMResponse,
ProviderRequest,
)
from astrbot.core.provider.provider import Provider
from ...hooks import BaseAgentRunHooks
from ...response import AgentResponseData
from ...run_context import ContextWrapper, TContext
from ..base import AgentResponse, AgentState, BaseAgentRunner
from ..tool_executor import BaseFunctionToolExecutor
if sys.version_info >= (3, 12):
from typing import override
@@ -34,19 +37,32 @@ class DashscopeAgentRunner(BaseAgentRunner[TContext]):
@override
async def reset(
self,
provider: Provider,
request: ProviderRequest,
run_context: ContextWrapper[TContext],
tool_executor: BaseFunctionToolExecutor[TContext],
agent_hooks: BaseAgentRunHooks[TContext],
provider_config: dict,
**kwargs: T.Any,
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:
self.req = request
self.streaming = kwargs.get("streaming", False)
self.streaming = streaming
self.final_llm_resp = None
self._state = AgentState.IDLE
self.agent_hooks = agent_hooks
self.run_context = run_context
provider_config = provider_config or {}
self.api_key = provider_config.get("dashscope_api_key", "")
if not self.api_key:
raise Exception("阿里云百炼 API Key 不能为空。")
@@ -117,14 +133,14 @@ class DashscopeAgentRunner(BaseAgentRunner[TContext]):
@override
async def step_until_done(
self, max_step: int = 3
) -> T.AsyncGenerator[AgentResponse, None]:
self, max_step: int
) -> 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: Any, response_queue: queue.Queue
) -> None:
"""在线程中消费同步generator,将结果放入队列
@@ -278,7 +294,7 @@ class DashscopeAgentRunner(BaseAgentRunner[TContext]):
return payload
async def _handle_streaming_response(
self, response: T.Any, session_id: str
self, response: Any, session_id: str
) -> T.AsyncGenerator[AgentResponse, None]:
"""处理流式响应

View File

@@ -153,9 +153,7 @@ class AstrBotDashboard:
def _check_webui_enabled(self) -> bool:
cfg = self.config.get("dashboard", {})
_env = os.environ.get(
"ASTRBOT_DASHBOARD_ENABLE", os.environ.get("DASHBOARD_ENABLE")
)
_env = os.environ.get("ASTRBOT_DASHBOARD_ENABLE")
if _env is not None:
return _env.lower() in ("true", "1", "yes")
return cfg.get("enable", True)
@@ -477,13 +475,12 @@ class AstrBotDashboard:
"""Run dashboard server (blocking)"""
if not self.enable_webui:
logger.warning(
"WebUI 已禁用 (dashboard.enable=false or DASHBOARD_ENABLE=false)"
"WebUI 已禁用 (dashboard.enable=false or ASTRBOT_DASHBOARD_ENABLE=false)"
)
dashboard_config = self.config.get("dashboard", {})
host_value = (
os.environ.get("ASTRBOT_HOST")
or os.environ.get("DASHBOARD_HOST")
or dashboard_config.get("host", "0.0.0.0")
)
host = _resolve_dashboard_value(host_value, field_name="host")
@@ -492,7 +489,6 @@ class AstrBotDashboard:
port_value = (
os.environ.get("ASTRBOT_PORT")
or os.environ.get("DASHBOARD_PORT")
or dashboard_config.get("port", 6185)
)
resolved_port = _resolve_dashboard_value(port_value, field_name="port")
@@ -501,8 +497,7 @@ class AstrBotDashboard:
port = int(resolved_port)
ssl_config = dashboard_config.get("ssl", {})
ssl_enable = _parse_env_bool(
os.environ.get("ASTRBOT_SSL_ENABLE")
or os.environ.get("DASHBOARD_SSL_ENABLE"),
os.environ.get("ASTRBOT_SSL_ENABLE"),
ssl_config.get("enable", False),
)
@@ -539,19 +534,16 @@ class AstrBotDashboard:
if ssl_enable:
cert_file = (
os.environ.get("ASTRBOT_SSL_CERT")
or os.environ.get("DASHBOARD_SSL_CERT")
or ssl_config.get("cert_file", "")
)
cert_file = _resolve_dashboard_value(cert_file, field_name="ssl.cert_file")
key_file = (
os.environ.get("ASTRBOT_SSL_KEY")
or os.environ.get("DASHBOARD_SSL_KEY")
or ssl_config.get("key_file", "")
)
key_file = _resolve_dashboard_value(key_file, field_name="ssl.key_file")
ca_certs = (
os.environ.get("ASTRBOT_SSL_CA_CERTS")
or os.environ.get("DASHBOARD_SSL_CA_CERTS")
or ssl_config.get("ca_certs", "")
)
ca_certs = _resolve_dashboard_value(ca_certs, field_name="ssl.ca_certs")