refactor(config): retire RuntimeSettingsCompat host usage

This commit is contained in:
jxxghp
2026-08-26 15:55:21 +08:00
parent cdab54254d
commit 9dbe424c3d
162 changed files with 1966 additions and 1745 deletions
+3 -6
View File
@@ -17,10 +17,7 @@ from app.runtime.capabilities.model import (
SelectorSchema,
)
from app.runtime.capabilities.registry import CapabilityRegistry
from app.runtime.settings import RuntimeSettingsCompat
settings = RuntimeSettingsCompat()
from app.runtime.settings import get_runtime_setting, has_runtime_setting
_DEFAULT_CAPABILITY_ROOT = Path(__file__).resolve().parent
_SETTING_SELECTOR = "setting_truthy"
@@ -29,7 +26,7 @@ _SETTING_SELECTOR = "setting_truthy"
def _validate_setting_selector(config: Mapping[str, Any]) -> None:
"""限制 selector 只能读取已声明的应用设置。"""
key = config["key"]
if not isinstance(key, str) or not key or not hasattr(settings, key):
if not isinstance(key, str) or not key or not has_runtime_setting(key):
raise ValueError(f"未知应用设置:{key!r}")
@@ -221,4 +218,4 @@ def should_run_agent_service(spec: CapabilitySpec) -> bool:
or selector.kind != _SETTING_SELECTOR
):
raise ValueError(f"{spec.source}: 不是可协调的 Agent Service 声明")
return bool(getattr(settings, selector.config["key"]))
return bool(get_runtime_setting(selector.config["key"]))
+27 -28
View File
@@ -13,9 +13,8 @@ from typing import Any, Dict, Optional
from uuid import uuid4
from app.application.notification import get_notification_configs
from app.runtime.settings import RuntimeSettingsCompat
from app.runtime.settings import get_runtime_setting
settings = RuntimeSettingsCompat()
from app.runtime.log import logger
from app.adapters.network.http import RequestUtils
@@ -61,11 +60,11 @@ class OpenAIAudioProvider(AudioCapabilityProvider):
@staticmethod
def _input_credentials() -> tuple[Optional[str], Optional[str]]:
return settings.AUDIO_INPUT_API_KEY, settings.AUDIO_INPUT_BASE_URL
return get_runtime_setting('AUDIO_INPUT_API_KEY'), get_runtime_setting('AUDIO_INPUT_BASE_URL')
@staticmethod
def _output_credentials() -> tuple[Optional[str], Optional[str]]:
return settings.AUDIO_OUTPUT_API_KEY, settings.AUDIO_OUTPUT_BASE_URL
return get_runtime_setting('AUDIO_OUTPUT_API_KEY'), get_runtime_setting('AUDIO_OUTPUT_BASE_URL')
def is_available_for_audio_input(self) -> bool:
api_key, _ = self._input_credentials()
@@ -89,9 +88,9 @@ class OpenAIAudioProvider(AudioCapabilityProvider):
audio_file = BytesIO(content)
audio_file.name = filename
response = client.audio.transcriptions.create(
model=settings.AUDIO_INPUT_MODEL,
model=get_runtime_setting('AUDIO_INPUT_MODEL'),
file=audio_file,
language=settings.AUDIO_INPUT_LANGUAGE or "zh",
language=get_runtime_setting('AUDIO_INPUT_LANGUAGE') or "zh",
response_format="verbose_json",
)
text = getattr(response, "text", None)
@@ -109,12 +108,12 @@ class OpenAIAudioProvider(AudioCapabilityProvider):
if not api_key:
raise ValueError("音频输出 provider 未配置 API Key")
client = self._build_client(api_key=api_key, base_url=base_url)
voice_dir = settings.TEMP_PATH / "voice"
voice_dir = get_runtime_setting('TEMP_PATH') / "voice"
voice_dir.mkdir(parents=True, exist_ok=True)
output_path = voice_dir / f"{uuid4().hex}.opus"
response = client.audio.speech.create(
model=settings.AUDIO_OUTPUT_MODEL,
voice=settings.AUDIO_OUTPUT_VOICE,
model=get_runtime_setting('AUDIO_OUTPUT_MODEL'),
voice=get_runtime_setting('AUDIO_OUTPUT_VOICE'),
input=text,
response_format="opus",
)
@@ -163,22 +162,22 @@ class OpenAIChatAudioProvider(AudioCapabilityProvider):
@staticmethod
def _input_credentials() -> tuple[Optional[str], Optional[str]]:
return settings.AUDIO_INPUT_API_KEY, settings.AUDIO_INPUT_BASE_URL
return get_runtime_setting('AUDIO_INPUT_API_KEY'), get_runtime_setting('AUDIO_INPUT_BASE_URL')
@staticmethod
def _output_credentials() -> tuple[Optional[str], Optional[str]]:
return settings.AUDIO_OUTPUT_API_KEY, settings.AUDIO_OUTPUT_BASE_URL
return get_runtime_setting('AUDIO_OUTPUT_API_KEY'), get_runtime_setting('AUDIO_OUTPUT_BASE_URL')
def _normalize_stt_model(self) -> str:
return self._normalize_model(
model=settings.AUDIO_INPUT_MODEL,
model=get_runtime_setting('AUDIO_INPUT_MODEL'),
supported_models=self.SUPPORTED_STT_MODELS,
default_model=self.DEFAULT_STT_MODEL,
)
def _normalize_tts_model(self) -> str:
return self._normalize_model(
model=settings.AUDIO_OUTPUT_MODEL,
model=get_runtime_setting('AUDIO_OUTPUT_MODEL'),
supported_models=self.SUPPORTED_TTS_MODELS,
default_model=self.DEFAULT_TTS_MODEL,
)
@@ -268,7 +267,7 @@ class OpenAIChatAudioProvider(AudioCapabilityProvider):
return None
suffix = Path(filename or "").suffix.lower() or ".audio"
voice_dir = settings.TEMP_PATH / "voice"
voice_dir = get_runtime_setting('TEMP_PATH') / "voice"
voice_dir.mkdir(parents=True, exist_ok=True)
input_path = voice_dir / f"{uuid4().hex}{suffix}"
output_path = input_path.with_suffix(self.TRANSCODED_STT_SUFFIX)
@@ -391,7 +390,7 @@ class OpenAIChatAudioProvider(AudioCapabilityProvider):
if not normalized_audio:
return None
content, filename = normalized_audio
language = (settings.AUDIO_INPUT_LANGUAGE or "").strip()
language = (get_runtime_setting('AUDIO_INPUT_LANGUAGE') or "").strip()
prompt = "请将这段音频完整转写为文字,只输出转写结果,不要添加解释。"
if language:
prompt += f"音频主要语言是 {language}"
@@ -426,7 +425,7 @@ class OpenAIChatAudioProvider(AudioCapabilityProvider):
logger.error(
"%s TTS 当前不支持该模型或模型未配置: %s",
self.DISPLAY_NAME,
settings.AUDIO_OUTPUT_MODEL,
get_runtime_setting('AUDIO_OUTPUT_MODEL'),
)
return None
@@ -435,7 +434,7 @@ class OpenAIChatAudioProvider(AudioCapabilityProvider):
if not api_key:
raise ValueError("音频输出 provider 未配置 API Key")
client = self._build_client(api_key=api_key, base_url=base_url)
voice_dir = settings.TEMP_PATH / "voice"
voice_dir = get_runtime_setting('TEMP_PATH') / "voice"
voice_dir.mkdir(parents=True, exist_ok=True)
wav_path = voice_dir / f"{uuid4().hex}.wav"
request = {
@@ -448,7 +447,7 @@ class OpenAIChatAudioProvider(AudioCapabilityProvider):
],
"audio": {
"format": self.AUDIO_RESPONSE_FORMAT,
"voice": settings.AUDIO_OUTPUT_VOICE or self.DEFAULT_VOICE,
"voice": get_runtime_setting('AUDIO_OUTPUT_VOICE') or self.DEFAULT_VOICE,
},
}
if self.INCLUDE_AUDIO_MODALITIES:
@@ -487,7 +486,7 @@ class MiMoAudioProvider(OpenAIChatAudioProvider):
)
def _normalize_tts_model(self) -> str:
model = (settings.AUDIO_OUTPUT_MODEL or "").strip().lower()
model = (get_runtime_setting('AUDIO_OUTPUT_MODEL') or "").strip().lower()
if not model or not model.startswith("mimo-"):
return self.DEFAULT_TTS_MODEL
return model
@@ -546,21 +545,21 @@ class MiniMaxAudioProvider(OpenAIChatAudioProvider):
def _normalize_stt_model(self) -> str:
"""将非 MiniMax 的默认转写模型名兜底为 MiniMax 对话模型。"""
model = (settings.AUDIO_INPUT_MODEL or "").strip()
model = (get_runtime_setting('AUDIO_INPUT_MODEL') or "").strip()
if not model or model.lower().startswith(("gpt-", "mimo-")):
return self.DEFAULT_STT_MODEL
return model
def _normalize_tts_model(self) -> str:
"""将非 MiniMax 语音模型兜底为官方 T2A 模型。"""
model = (settings.AUDIO_OUTPUT_MODEL or "").strip().lower()
model = (get_runtime_setting('AUDIO_OUTPUT_MODEL') or "").strip().lower()
if model in self.SUPPORTED_TTS_MODELS:
return model
return self.DEFAULT_TTS_MODEL
def _normalize_voice_id(self) -> str:
"""将其他 provider 的默认音色兜底为 MiniMax 中文系统音色。"""
voice_id = (settings.AUDIO_OUTPUT_VOICE or "").strip()
voice_id = (get_runtime_setting('AUDIO_OUTPUT_VOICE') or "").strip()
if not voice_id or voice_id in {"alloy", "mimo_default"}:
return self.DEFAULT_VOICE
return voice_id
@@ -599,7 +598,7 @@ class MiniMaxAudioProvider(OpenAIChatAudioProvider):
"Content-Type": "application/json",
"Accept": "application/json",
},
proxies=settings.PROXY or {},
proxies=get_runtime_setting('PROXY') or {},
timeout=60,
).post_res(
url=self._build_t2a_url(base_url),
@@ -637,7 +636,7 @@ class MiniMaxAudioProvider(OpenAIChatAudioProvider):
if not audio_data:
raise ValueError("MiniMax T2A 响应中没有音频数据")
voice_dir = settings.TEMP_PATH / "voice"
voice_dir = get_runtime_setting('TEMP_PATH') / "voice"
voice_dir.mkdir(parents=True, exist_ok=True)
output_path = voice_dir / f"{uuid4().hex}.opus"
output_path.write_bytes(self._decode_audio_payload(audio_data))
@@ -681,9 +680,9 @@ class AgentCapabilityManager:
@classmethod
def get_audio_provider(cls, mode: str) -> Optional[AudioCapabilityProvider]:
provider_name = cls._normalize_provider_name(
settings.AUDIO_INPUT_PROVIDER
get_runtime_setting('AUDIO_INPUT_PROVIDER')
if (mode or "").lower() == "input"
else settings.AUDIO_OUTPUT_PROVIDER
else get_runtime_setting('AUDIO_OUTPUT_PROVIDER')
)
provider = cls._audio_providers.get(provider_name)
if provider:
@@ -701,12 +700,12 @@ class AgentCapabilityManager:
@staticmethod
def supports_audio_input() -> bool:
"""当前 Agent 是否启用音频输入能力。"""
return bool(settings.LLM_SUPPORT_AUDIO_INPUT)
return bool(get_runtime_setting('LLM_SUPPORT_AUDIO_INPUT'))
@staticmethod
def supports_audio_output() -> bool:
"""当前 Agent 是否启用音频输出能力。"""
return bool(settings.LLM_SUPPORT_AUDIO_OUTPUT)
return bool(get_runtime_setting('LLM_SUPPORT_AUDIO_OUTPUT'))
@classmethod
def is_audio_input_available(cls) -> bool:
+24 -26
View File
@@ -11,10 +11,8 @@ from urllib.parse import urlsplit
from langchain_core.messages import AIMessage, AIMessageChunk
from app.agent.llm.gateway import resolve_llm_provider_runtime
from app.runtime.settings import RuntimeSettingsCompat
settings = RuntimeSettingsCompat()
from app.runtime.log import logger
from app.runtime.settings import get_runtime_setting
if TYPE_CHECKING:
from app.agent.llm.server_tools import ServerToolResolution
@@ -148,8 +146,8 @@ def _resolve_llm_proxy(use_proxy: bool | None = None) -> str | None:
"""
解析本次 LLM 调用应使用的系统代理地址。
"""
should_use_proxy = settings.LLM_USE_PROXY if use_proxy is None else use_proxy
return settings.PROXY_HOST if should_use_proxy and settings.PROXY_HOST else None
should_use_proxy = get_runtime_setting('LLM_USE_PROXY') if use_proxy is None else use_proxy
return get_runtime_setting('PROXY_HOST') if should_use_proxy and get_runtime_setting('PROXY_HOST') else None
def _build_httpx_proxy_kwargs(proxy_url: str | None) -> dict[str, str]:
@@ -539,7 +537,7 @@ class LLMHelper:
record_input = cls._source_input_limit(model_record)
metadata_input = cls._source_input_limit(metadata_source)
profile_input = cls._positive_token_limit(profile.get("max_input_tokens"))
configured_k = cls._positive_token_limit(settings.LLM_MAX_CONTEXT_TOKENS)
configured_k = cls._positive_token_limit(get_runtime_setting('LLM_MAX_CONTEXT_TOKENS'))
configured_input = configured_k * 1000 if configured_k else None
endpoint_matched = runtime.get("model_profile_endpoint_matched") is True
@@ -790,8 +788,8 @@ class LLMHelper:
base_url_preset: Optional[str] = None,
) -> Optional[bool]:
"""复用 provider 目录缓存解析当前模型是否支持图片输入。"""
provider_name = str(provider if provider is not None else settings.LLM_PROVIDER).strip()
model_name = str(model if model is not None else settings.LLM_MODEL).strip()
provider_name = str(provider if provider is not None else get_runtime_setting('LLM_PROVIDER')).strip()
model_name = str(model if model is not None else get_runtime_setting('LLM_MODEL')).strip()
if not provider_name or not model_name:
return None
@@ -799,11 +797,11 @@ class LLMHelper:
metadata = resolve_llm_provider_runtime().resolve_cached_model_metadata(
provider_id=provider_name,
model_id=model_name,
base_url=base_url if base_url is not None else settings.LLM_BASE_URL,
base_url=base_url if base_url is not None else get_runtime_setting('LLM_BASE_URL'),
base_url_preset_id=(
base_url_preset
if base_url_preset is not None
else settings.LLM_BASE_URL_PRESET
else get_runtime_setting('LLM_BASE_URL_PRESET')
),
)
except Exception as err:
@@ -828,7 +826,7 @@ class LLMHelper:
被兼容端点以 400 拒绝。无参调用保持旧版“只读总开关”语义,
未知自定义模型也保持原有开关语义。
"""
if not settings.LLM_SUPPORT_IMAGE_INPUT:
if not get_runtime_setting('LLM_SUPPORT_IMAGE_INPUT'):
return False
if provider is None and model is None:
return True
@@ -857,8 +855,8 @@ class LLMHelper:
这主要用于单测 stub 环境以及极端的最小运行环境,正常生产路径仍优先
走 `LLMProviderManager.resolve_runtime()`。
"""
api_key_value = api_key if api_key is not None else settings.LLM_API_KEY
base_url_value = base_url if base_url is not None else settings.LLM_BASE_URL
api_key_value = api_key if api_key is not None else get_runtime_setting('LLM_API_KEY')
base_url_value = base_url if base_url is not None else get_runtime_setting('LLM_BASE_URL')
if not api_key_value:
raise ValueError("未配置LLM API Key")
@@ -1037,7 +1035,7 @@ class LLMHelper:
"""
规范化 API 协议配置,未知值统一回退为 ``auto`` 以保持兼容。
"""
normalized = str(api_protocol or settings.LLM_API_PROTOCOL or "").strip().lower()
normalized = str(api_protocol or get_runtime_setting('LLM_API_PROTOCOL') or "").strip().lower()
if normalized in {"auto", "chat_completions", "responses"}:
return normalized
if normalized:
@@ -1185,15 +1183,15 @@ class LLMHelper:
:param prompt_cache_key: 同一 Agent 会话内稳定且脱敏的提示词缓存路由键。
:return: LLM实例
"""
provider_name = str(provider if provider is not None else settings.LLM_PROVIDER).lower()
model_name = model if model is not None else settings.LLM_MODEL
api_key_value = api_key if api_key is not None else settings.LLM_API_KEY
base_url_value = base_url if base_url is not None else settings.LLM_BASE_URL
provider_name = str(provider if provider is not None else get_runtime_setting('LLM_PROVIDER')).lower()
model_name = model if model is not None else get_runtime_setting('LLM_MODEL')
api_key_value = api_key if api_key is not None else get_runtime_setting('LLM_API_KEY')
base_url_value = base_url if base_url is not None else get_runtime_setting('LLM_BASE_URL')
base_url_preset_value = (
base_url_preset if base_url_preset is not None else settings.LLM_BASE_URL_PRESET
base_url_preset if base_url_preset is not None else get_runtime_setting('LLM_BASE_URL_PRESET')
)
user_agent_value = user_agent if user_agent is not None else settings.LLM_USER_AGENT
temperature_value = temperature if temperature is not None else settings.LLM_TEMPERATURE
user_agent_value = user_agent if user_agent is not None else get_runtime_setting('LLM_USER_AGENT')
temperature_value = temperature if temperature is not None else get_runtime_setting('LLM_TEMPERATURE')
normalized_thinking_level = cls._resolve_thinking_level(
thinking_level=thinking_level,
)
@@ -1228,12 +1226,12 @@ class LLMHelper:
mode=(
web_search_mode
if web_search_mode is not None
else getattr(settings, "LLM_WEB_SEARCH_MODE", "local")
else get_runtime_setting("LLM_WEB_SEARCH_MODE", "local")
),
api_protocol=(
api_protocol
if api_protocol is not None
else settings.LLM_API_PROTOCOL
else get_runtime_setting('LLM_API_PROTOCOL')
),
base_url=runtime.get("base_url"),
)
@@ -1345,7 +1343,7 @@ class LLMHelper:
credentials=aws_auth,
base_url=runtime.get("base_url"),
use_proxy=use_proxy,
read_timeout=settings.LLM_TOOL_TIMEOUT,
read_timeout=get_runtime_setting('LLM_TOOL_TIMEOUT'),
)
model = bedrock_model_cls(
model_id=model_name,
@@ -1492,8 +1490,8 @@ class LLMHelper:
:param api_protocol: OpenAI 兼容接口 API 协议,未显式传入时沿用已保存配置。
:param web_search_mode: 联网搜索模式,未显式传入时沿用已保存配置。
"""
provider_name = provider if provider is not None else settings.LLM_PROVIDER
model_name = model if model is not None else settings.LLM_MODEL
provider_name = provider if provider is not None else get_runtime_setting('LLM_PROVIDER')
model_name = model if model is not None else get_runtime_setting('LLM_MODEL')
start = time.perf_counter()
llm_kwargs = {
"streaming": False,
+21 -22
View File
@@ -20,9 +20,8 @@ import aiofiles
import httpx
import jwt
from app.runtime.settings import RuntimeSettingsCompat
from app.runtime.settings import get_runtime_setting
settings = RuntimeSettingsCompat()
from app.application.configuration import get_configured_system_config
from app.runtime.log import logger
from app.schemas.types import LlmProviderAction, SystemConfigKey
@@ -268,7 +267,7 @@ class LLMProviderManager(metaclass=Singleton):
self._models_dev_data: dict[str, Any] | None = None
self._models_dev_loaded_at: float = 0
self._models_dev_cache_path = (
Path(settings.TEMP_PATH) / "llm_provider_models_dev_cache.json"
Path(get_runtime_setting('TEMP_PATH')) / "llm_provider_models_dev_cache.json"
)
def _cleanup_auth_sessions_locked(self, now: Optional[float] = None) -> None:
@@ -1498,13 +1497,13 @@ class LLMProviderManager(metaclass=Singleton):
def _build_httpx_kwargs(self, use_proxy: Optional[bool] = None) -> dict[str, Any]:
"""构造用于 httpx 客户端的参数,如代理等。"""
should_use_proxy = settings.LLM_USE_PROXY if use_proxy is None else use_proxy
should_use_proxy = get_runtime_setting('LLM_USE_PROXY') if use_proxy is None else use_proxy
kwargs: dict[str, Any] = {
"timeout": self._DEFAULT_TIMEOUT,
"trust_env": False,
}
if should_use_proxy and settings.PROXY_HOST:
kwargs[self._httpx_proxy_key()] = settings.PROXY_HOST
if should_use_proxy and get_runtime_setting('PROXY_HOST'):
kwargs[self._httpx_proxy_key()] = get_runtime_setting('PROXY_HOST')
return kwargs
@staticmethod
@@ -1616,7 +1615,7 @@ class LLMProviderManager(metaclass=Singleton):
async def _fetch_models_dev(self, use_proxy: Optional[bool] = None) -> dict[str, Any]:
"""通过网络请求获取最新 models.dev 数据。"""
headers = {"User-Agent": settings.USER_AGENT}
headers = {"User-Agent": get_runtime_setting('USER_AGENT')}
async with httpx.AsyncClient(**self._build_httpx_kwargs(use_proxy)) as client:
response = await client.get(self._MODELS_DEV_URL, headers=headers)
response.raise_for_status()
@@ -2043,10 +2042,10 @@ class LLMProviderManager(metaclass=Singleton):
from google import genai
from google.genai.types import HttpOptions
should_use_proxy = settings.LLM_USE_PROXY if use_proxy is None else use_proxy
should_use_proxy = get_runtime_setting('LLM_USE_PROXY') if use_proxy is None else use_proxy
client_args: dict[str, Any] = {"trust_env": False}
if should_use_proxy and settings.PROXY_HOST:
client_args[self._httpx_proxy_key()] = settings.PROXY_HOST
if should_use_proxy and get_runtime_setting('PROXY_HOST'):
client_args[self._httpx_proxy_key()] = get_runtime_setting('PROXY_HOST')
http_options = HttpOptions(
client_args=client_args,
async_client_args=client_args,
@@ -2160,10 +2159,10 @@ class LLMProviderManager(metaclass=Singleton):
"""
from botocore.config import Config
should_use_proxy = settings.LLM_USE_PROXY if use_proxy is None else use_proxy
should_use_proxy = get_runtime_setting('LLM_USE_PROXY') if use_proxy is None else use_proxy
proxies = None
if should_use_proxy and settings.PROXY_HOST:
proxies = {"http": settings.PROXY_HOST, "https": settings.PROXY_HOST}
if should_use_proxy and get_runtime_setting('PROXY_HOST'):
proxies = {"http": get_runtime_setting('PROXY_HOST'), "https": get_runtime_setting('PROXY_HOST')}
return Config(
connect_timeout=10,
read_timeout=60,
@@ -2388,7 +2387,7 @@ class LLMProviderManager(metaclass=Singleton):
仅补充 Copilot 必需的意图头,避免重复覆盖。
"""
headers = {
"User-Agent": settings.USER_AGENT,
"User-Agent": get_runtime_setting('USER_AGENT'),
"Openai-Intent": "conversation-edits",
"x-initiator": "user",
}
@@ -2769,7 +2768,7 @@ class LLMProviderManager(metaclass=Singleton):
f"{self._CHATGPT_ISSUER}/api/accounts/deviceauth/usercode",
headers={
"Content-Type": "application/json",
"User-Agent": settings.USER_AGENT,
"User-Agent": get_runtime_setting('USER_AGENT'),
},
json={"client_id": self._CHATGPT_CLIENT_ID},
)
@@ -2806,7 +2805,7 @@ class LLMProviderManager(metaclass=Singleton):
headers={
"Accept": "application/json",
"Content-Type": "application/json",
"User-Agent": settings.USER_AGENT,
"User-Agent": get_runtime_setting('USER_AGENT'),
},
json={
"client_id": self._COPILOT_CLIENT_ID,
@@ -3036,11 +3035,11 @@ class LLMProviderManager(metaclass=Singleton):
"""管理动作:使用传入配置或当前已保存配置执行一次最小 LLM 调用。"""
from app.agent.llm.helper import LLMHelper, LLMTestTimeout
provider_name = provider or settings.LLM_PROVIDER
model = params.get("model") if params.get("model") is not None else settings.LLM_MODEL
provider_name = provider or get_runtime_setting('LLM_PROVIDER')
model = params.get("model") if params.get("model") is not None else get_runtime_setting('LLM_MODEL')
enabled = params.get("enabled")
enabled = bool(enabled) if enabled is not None else bool(settings.AI_AGENT_ENABLE)
api_key = params.get("api_key") if params.get("api_key") is not None else settings.LLM_API_KEY
enabled = bool(enabled) if enabled is not None else bool(get_runtime_setting('AI_AGENT_ENABLE'))
api_key = params.get("api_key") if params.get("api_key") is not None else get_runtime_setting('LLM_API_KEY')
data = {"provider": provider_name, "model": model}
if not provider_name:
@@ -3151,7 +3150,7 @@ class LLMProviderManager(metaclass=Singleton):
f"{self._CHATGPT_ISSUER}/api/accounts/deviceauth/token",
headers={
"Content-Type": "application/json",
"User-Agent": settings.USER_AGENT,
"User-Agent": get_runtime_setting('USER_AGENT'),
},
json={
"device_auth_id": session.context["device_auth_id"],
@@ -3196,7 +3195,7 @@ class LLMProviderManager(metaclass=Singleton):
headers={
"Accept": "application/json",
"Content-Type": "application/json",
"User-Agent": settings.USER_AGENT,
"User-Agent": get_runtime_setting('USER_AGENT'),
},
json={
"client_id": self._COPILOT_CLIENT_ID,
+2 -4
View File
@@ -6,15 +6,13 @@ from typing import Dict, List, Optional
from langchain_core.messages import BaseMessage, messages_from_dict, messages_to_dict
from app.runtime.settings import RuntimeSettingsCompat
settings = RuntimeSettingsCompat()
from app.application.agentdata import get_agent_chat_port
from app.application.messaging.chat import (
get_configured_agent_chat_persistence,
get_configured_agent_chat_service,
)
from app.runtime.log import logger
from app.runtime.settings import get_runtime_setting
from app.schemas.agent import ConversationMemory
@@ -228,7 +226,7 @@ class MemoryManager:
for cache_key, memory in self.memory_cache.items():
if (
current_time - memory.updated_at
).days > settings.LLM_MEMORY_RETENTION_DAYS:
).days > get_runtime_setting('LLM_MEMORY_RETENTION_DAYS'):
expired_sessions.append(cache_key)
# 只清理内存缓存,不删除Redis中的键(Redis会自动过期)
+48 -49
View File
@@ -68,9 +68,8 @@ from app.agent.tools.impl.mcp import (
)
from app.agent.tools.impl.query_system_settings import QuerySystemSettingsTool
from app.chain.agent import AgentChain
from app.runtime.settings import RuntimeSettingsCompat
from app.runtime.settings import get_runtime_setting
settings = RuntimeSettingsCompat()
from app.runtime.events import eventmanager
from app.runtime.observability import record_metric
from app.application.plugin.runtime import get_plugin_manager
@@ -622,7 +621,7 @@ class MoviePilotAgent:
def _get_recursion_limit() -> int:
"""读取 LangGraph 递归上限,防止模型持续循环调用工具。"""
try:
limit = int(settings.LLM_MAX_ITERATIONS or 0)
limit = int(get_runtime_setting('LLM_MAX_ITERATIONS') or 0)
except (TypeError, ValueError):
limit = 0
return limit if limit > 0 else 128
@@ -747,7 +746,7 @@ class MoviePilotAgent:
self._session_usage.cache_usage_available |= cache_usage_available
provider_type = _agent_provider_metric_type(
(self._llm_provider_selection or {}).get("provider")
or settings.LLM_PROVIDER
or get_runtime_setting('LLM_PROVIDER')
)
if input_tokens:
record_metric(
@@ -872,14 +871,14 @@ class MoviePilotAgent:
not self._session_usage.model
and self._session_usage.last_request_sequence == 0
):
self._session_usage.model = settings.LLM_MODEL
self._session_usage.model = get_runtime_setting('LLM_MODEL')
if (
not self._session_usage.context_window_tokens
and self._session_usage.last_request_sequence == 0
):
self._session_usage.context_window_tokens = (
settings.LLM_MAX_CONTEXT_TOKENS * 1000
if settings.LLM_MAX_CONTEXT_TOKENS
get_runtime_setting('LLM_MAX_CONTEXT_TOKENS') * 1000
if get_runtime_setting('LLM_MAX_CONTEXT_TOKENS')
else None
)
return self._session_usage.to_dict(self.session_id)
@@ -899,9 +898,9 @@ class MoviePilotAgent:
session_id=self.session_id,
selected_provider_id=selection.get("selected_provider_id"),
selected_provider_name=selection.get("selected_provider_name"),
provider=selection.get("provider") or settings.LLM_PROVIDER,
base_url=selection.get("base_url") or settings.LLM_BASE_URL,
model=self._session_usage.model or selection.get("model") or settings.LLM_MODEL,
provider=selection.get("provider") or get_runtime_setting('LLM_PROVIDER'),
base_url=selection.get("base_url") or get_runtime_setting('LLM_BASE_URL'),
model=self._session_usage.model or selection.get("model") or get_runtime_setting('LLM_MODEL'),
input_tokens=self._session_usage.total_input_tokens,
output_tokens=self._session_usage.total_output_tokens,
total_tokens=self._session_usage.total_tokens,
@@ -1272,7 +1271,7 @@ class MoviePilotAgent:
if self.is_background:
return False
# 啰嗦模式下始终需要流式输出来捕获工具调用前的 Agent 文字
if settings.AI_AGENT_VERBOSE:
if get_runtime_setting('AI_AGENT_VERBOSE'):
return True
try:
channel_enum = NotificationChannel(self.channel)
@@ -1321,16 +1320,16 @@ class MoviePilotAgent:
return self._llm_runtime_config
event_data = AgentLLMProviderEventData(
provider=settings.LLM_PROVIDER,
model=settings.LLM_MODEL,
api_key=settings.LLM_API_KEY,
base_url=settings.LLM_BASE_URL,
base_url_preset=settings.LLM_BASE_URL_PRESET,
user_agent=settings.LLM_USER_AGENT,
use_proxy=settings.LLM_USE_PROXY,
thinking_level=settings.LLM_THINKING_LEVEL,
api_protocol=settings.LLM_API_PROTOCOL,
web_search_mode=settings.LLM_WEB_SEARCH_MODE,
provider=get_runtime_setting('LLM_PROVIDER'),
model=get_runtime_setting('LLM_MODEL'),
api_key=get_runtime_setting('LLM_API_KEY'),
base_url=get_runtime_setting('LLM_BASE_URL'),
base_url_preset=get_runtime_setting('LLM_BASE_URL_PRESET'),
user_agent=get_runtime_setting('LLM_USER_AGENT'),
use_proxy=get_runtime_setting('LLM_USE_PROXY'),
thinking_level=get_runtime_setting('LLM_THINKING_LEVEL'),
api_protocol=get_runtime_setting('LLM_API_PROTOCOL'),
web_search_mode=get_runtime_setting('LLM_WEB_SEARCH_MODE'),
)
selected_event = await eventmanager.async_send_event(
ChainEventType.AgentLLMProvider,
@@ -1340,43 +1339,43 @@ class MoviePilotAgent:
provider = (
self._clean_optional_text(self._get_event_value(resolved_data, "provider"))
or settings.LLM_PROVIDER
or get_runtime_setting('LLM_PROVIDER')
)
model = (
self._clean_optional_text(self._get_event_value(resolved_data, "model"))
or settings.LLM_MODEL
or get_runtime_setting('LLM_MODEL')
)
api_key = (
self._clean_optional_text(self._get_event_value(resolved_data, "api_key"))
or settings.LLM_API_KEY
or get_runtime_setting('LLM_API_KEY')
)
base_url = (
self._clean_optional_text(self._get_event_value(resolved_data, "base_url"))
or settings.LLM_BASE_URL
or get_runtime_setting('LLM_BASE_URL')
)
base_url_preset = (
self._clean_optional_text(self._get_event_value(resolved_data, "base_url_preset"))
or settings.LLM_BASE_URL_PRESET
or get_runtime_setting('LLM_BASE_URL_PRESET')
)
user_agent = (
self._clean_optional_text(self._get_event_value(resolved_data, "user_agent"))
or settings.LLM_USER_AGENT
or get_runtime_setting('LLM_USER_AGENT')
)
use_proxy = self._get_event_value(resolved_data, "use_proxy")
if use_proxy is None:
use_proxy = settings.LLM_USE_PROXY
use_proxy = get_runtime_setting('LLM_USE_PROXY')
thinking_level = (
self._clean_optional_text(
self._get_event_value(resolved_data, "thinking_level")
)
or settings.LLM_THINKING_LEVEL
or get_runtime_setting('LLM_THINKING_LEVEL')
)
api_protocol = self._clean_optional_text(
self._get_event_value(resolved_data, "api_protocol")
) or settings.LLM_API_PROTOCOL
) or get_runtime_setting('LLM_API_PROTOCOL')
web_search_mode = self._clean_optional_text(
self._get_event_value(resolved_data, "web_search_mode")
) or settings.LLM_WEB_SEARCH_MODE
) or get_runtime_setting('LLM_WEB_SEARCH_MODE')
selected_provider_id = self._clean_optional_text(
self._get_event_value(resolved_data, "selected_provider_id")
)
@@ -1519,8 +1518,8 @@ class MoviePilotAgent:
清理执行错误中的密钥和尾部长说明,避免把敏感字段或 SDK 调参文档直接发给用户。
"""
sanitized = re.sub(r"\s+", " ", str(message or "")).strip()
if settings.LLM_API_KEY:
sanitized = sanitized.replace(settings.LLM_API_KEY, "***")
if get_runtime_setting('LLM_API_KEY'):
sanitized = sanitized.replace(get_runtime_setting('LLM_API_KEY'), "***")
sanitized = re.sub(
r"(?i)(api[_-]?key\s*[:=]\s*)([^\s,;]+)",
r"\1***",
@@ -1713,11 +1712,11 @@ class MoviePilotAgent:
bool(self._tool_context.get("is_admin")),
self.has_message_context,
self.is_background,
settings.AI_AGENT_VERBOSE,
settings.LLM_TEMPERATURE,
settings.LLM_MAX_CONTEXT_TOKENS,
settings.LLM_MAX_TOOLS,
settings.LLM_MAX_ITERATIONS,
get_runtime_setting('AI_AGENT_VERBOSE'),
get_runtime_setting('LLM_TEMPERATURE'),
get_runtime_setting('LLM_MAX_CONTEXT_TOKENS'),
get_runtime_setting('LLM_MAX_TOOLS'),
get_runtime_setting('LLM_MAX_ITERATIONS'),
self._public_runtime_config_signature(runtime_config),
agent_runtime_manager.current_signature(),
agent_mcp_manager.config_signature(),
@@ -2004,7 +2003,7 @@ class MoviePilotAgent:
)
skills_middleware = SkillsMiddleware(
sources=[str(agent_runtime_manager.skills_dir)],
bundled_skills_dir=str(settings.ROOT_PATH / "skills"),
bundled_skills_dir=str(get_runtime_setting('ROOT_PATH') / "skills"),
stream_handler=self.stream_handler,
)
skill_tools = list(getattr(skills_middleware, "tools", []) or [])
@@ -2060,7 +2059,7 @@ class MoviePilotAgent:
temporary_subagent_middlewares = ()
logger.debug(f"复用会话内 Agent 图: session_id={self.session_id}")
return cached_agent
max_tools = settings.LLM_MAX_TOOLS
max_tools = get_runtime_setting('LLM_MAX_TOOLS')
from app.agent.runtime_loader import get_tool_factory
always_include_tools = (
@@ -2558,7 +2557,7 @@ class MoviePilotAgent:
"agent.provider.duration",
time.perf_counter() - metric_started_at,
provider_type=_agent_provider_metric_type(
selection.get("provider") or settings.LLM_PROVIDER
selection.get("provider") or get_runtime_setting('LLM_PROVIDER')
),
outcome="success" if execution_success else "error",
)
@@ -2588,7 +2587,7 @@ class MoviePilotAgent:
source=None if broadcast else self.source,
mtype=MessageType.Agent,
userid=None if broadcast else self.user_id,
username=self.username or (settings.SUPERUSER if broadcast else None),
username=self.username or (get_runtime_setting('SUPERUSER') if broadcast else None),
original_message_id=None if broadcast else self.original_message_id,
original_chat_id=None if broadcast else self.original_chat_id,
title=title,
@@ -2704,10 +2703,10 @@ class AgentManager:
status = agent.get_session_status()
else:
status = _SessionUsageSnapshot(
model=settings.LLM_MODEL,
model=get_runtime_setting('LLM_MODEL'),
context_window_tokens=(
settings.LLM_MAX_CONTEXT_TOKENS * 1000
if settings.LLM_MAX_CONTEXT_TOKENS
get_runtime_setting('LLM_MAX_CONTEXT_TOKENS') * 1000
if get_runtime_setting('LLM_MAX_CONTEXT_TOKENS')
else None
),
).to_dict(session_id)
@@ -3515,7 +3514,7 @@ class AgentManager:
message=message,
channel=None,
source=None,
username=settings.SUPERUSER,
username=get_runtime_setting('SUPERUSER'),
reply_mode=reply_mode,
output_callback=output_callback,
allow_message_tools=allow_message_tools,
@@ -3538,7 +3537,7 @@ class AgentManager:
:param trigger_source: 触发入口,scheduled-自动调度,manual-显式立即执行
:return: 执行是否成功及结果摘要
"""
if not settings.AI_AGENT_ENABLE:
if not get_runtime_setting('AI_AGENT_ENABLE'):
return False, "AI Agent 未启用"
accepting_before_claim = self._accepting_tasks
task_service = get_agent_task_execution_service()
@@ -3564,7 +3563,7 @@ class AgentManager:
)
success = True
result = ""
notification_username = run.username or settings.SUPERUSER
notification_username = run.username or get_runtime_setting('SUPERUSER')
try:
result = await self.process_message(
session_id=run.session_id,
@@ -3650,7 +3649,7 @@ class AgentManager:
message=heartbeat_message,
channel=None,
source=None,
username=settings.SUPERUSER,
username=get_runtime_setting('SUPERUSER'),
reply_mode=ReplyMode.CAPTURE_ONLY,
allow_message_tools=True,
)
+4 -5
View File
@@ -10,9 +10,8 @@ from typing import Any, Dict, Optional
import yaml
from app.agent.llm.capability import AgentCapabilityManager
from app.runtime.settings import RuntimeSettingsCompat
from app.runtime.settings import get_runtime_setting
settings = RuntimeSettingsCompat()
from app.runtime.log import logger
from app.schemas.notification import ChannelCapability
from app.schemas.notification import ChannelCapabilities
@@ -301,9 +300,9 @@ class PromptManager:
def _get_runtime_path_lines() -> list[str]:
"""返回基础系统提示词需要常驻注入的全局运行路径。"""
paths = {
"项目根目录": settings.ROOT_PATH,
"配置目录": settings.CONFIG_PATH,
"临时目录": settings.TEMP_PATH,
"项目根目录": get_runtime_setting('ROOT_PATH'),
"配置目录": get_runtime_setting('CONFIG_PATH'),
"临时目录": get_runtime_setting('TEMP_PATH'),
}
return [f" - {label}: `{path}`" for label, path in paths.items()]
+22 -22
View File
@@ -9,15 +9,15 @@ from pathlib import Path
from typing import Dict, List, Optional, Tuple
from urllib.parse import urlencode, urljoin, urlparse
from app.agent.skills.metadata import parse_skill_metadata
from app.runtime.cache import cached, fresh
from app.runtime.settings import RuntimeSettingsCompat
settings = RuntimeSettingsCompat()
from app.runtime.log import logger
from app.adapters.network.http import RequestUtils
from app.agent.skills.metadata import parse_skill_metadata
from app.application.configuration import get_runtime_settings
from app.foundation.singleton import WeakSingleton
from app.foundation.url import UrlUtils
from app.runtime.cache import cached, fresh
from app.runtime.config import Settings
from app.runtime.log import logger
from app.runtime.settings import get_runtime_setting
_SOURCE_META_FILENAME = ".moviepilot-skill-source.json"
_DEFAULT_BRANCHES = ("main", "master")
@@ -77,30 +77,30 @@ class SkillHelper(metaclass=WeakSingleton):
"""
返回用户技能目录所有市场安装的技能都落在这里
"""
return settings.CONFIG_PATH / "agent" / "skills"
return get_runtime_setting('CONFIG_PATH') / "agent" / "skills"
@staticmethod
def get_bundled_skills_dir() -> Path:
"""
返回仓库内置技能目录
"""
return settings.ROOT_PATH / "skills"
return get_runtime_setting('ROOT_PATH') / "skills"
@staticmethod
def get_market_sources() -> List[str]:
"""
解析配置中的技能市场列表
"""
if not settings.SKILL_MARKET:
if not get_runtime_setting('SKILL_MARKET'):
return []
return [item.strip() for item in settings.SKILL_MARKET.split(",") if item.strip()]
return [item.strip() for item in get_runtime_setting('SKILL_MARKET').split(",") if item.strip()]
@staticmethod
def get_default_market_sources() -> List[str]:
"""
返回系统默认的技能市场列表用于区分内置源和用户追加源
"""
skill_market_field = type(settings).model_fields.get("SKILL_MARKET")
skill_market_field = Settings.model_fields.get("SKILL_MARKET")
default_value = skill_market_field.default if skill_market_field else None
if not default_value:
return []
@@ -199,10 +199,10 @@ class SkillHelper(metaclass=WeakSingleton):
@staticmethod
def _persist_market_sources(sources: List[str]) -> Tuple[bool, str]:
"""
将技能源列表写回配置文件同步更新内存中的 settings
将技能源列表写回配置服务让后续读取立即看到新值
"""
filtered_sources = [item.strip() for item in sources if item and item.strip()]
success, message = settings.update_setting(
success, message = get_runtime_settings().update(
key="SKILL_MARKET",
value=",".join(filtered_sources),
)
@@ -1093,8 +1093,8 @@ class SkillHelper(metaclass=WeakSingleton):
}
strategies = []
if settings.PROXY_HOST:
strategies.append({"proxies": settings.PROXY, "timeout": timeout})
if get_runtime_setting('PROXY_HOST'):
strategies.append({"proxies": get_runtime_setting('PROXY'), "timeout": timeout})
strategies.append({"timeout": timeout})
for kwargs in strategies:
@@ -1124,8 +1124,8 @@ class SkillHelper(metaclass=WeakSingleton):
请求注册表 API兼容代理和直连场景
"""
strategies = []
if settings.PROXY_HOST:
strategies.append(({"proxies": settings.PROXY, "timeout": timeout}, url))
if get_runtime_setting('PROXY_HOST'):
strategies.append(({"proxies": get_runtime_setting('PROXY'), "timeout": timeout}, url))
strategies.append(({"timeout": timeout}, url))
for kwargs, target_url in strategies:
@@ -1152,17 +1152,17 @@ class SkillHelper(metaclass=WeakSingleton):
按代理优先级顺序请求 GitHub 资源兼容代理和直连场景
"""
strategies = []
headers = settings.REPO_GITHUB_HEADERS(repo=repo_name)
if not is_api and settings.GITHUB_PROXY:
proxy_url = f"{UrlUtils.standardize_base_url(settings.GITHUB_PROXY)}{url}"
headers = get_runtime_setting('REPO_GITHUB_HEADERS')(repo=repo_name)
if not is_api and get_runtime_setting('GITHUB_PROXY'):
proxy_url = f"{UrlUtils.standardize_base_url(get_runtime_setting('GITHUB_PROXY'))}{url}"
strategies.append((proxy_url, {"headers": headers, "timeout": timeout}))
if settings.PROXY_HOST:
if get_runtime_setting('PROXY_HOST'):
strategies.append(
(
url,
{
"headers": headers,
"proxies": settings.PROXY,
"proxies": get_runtime_setting('PROXY'),
"timeout": timeout,
},
)
+5 -6
View File
@@ -19,9 +19,8 @@ from app.agent.policy.sanitizer import (
)
from app.agent.tools.tags import ToolTag
from app.chain import ChainBase
from app.runtime.settings import RuntimeSettingsCompat
from app.runtime.settings import get_runtime_setting
settings = RuntimeSettingsCompat()
from app.application.messaging.agent import matches_channel_admin
from app.application.notification import get_notification_configs
from app.runtime.log import logger
@@ -301,7 +300,7 @@ class ToolExecutionTimeoutError(TimeoutError):
def _get_tool_timeout_seconds() -> Optional[float]:
"""读取工具执行超时时间,配置为 0 或负数时表示不限制。"""
try:
timeout = float(settings.LLM_TOOL_TIMEOUT or 0)
timeout = float(get_runtime_setting('LLM_TOOL_TIMEOUT') or 0)
except (TypeError, ValueError):
timeout = 0
return timeout if timeout > 0 else None
@@ -414,7 +413,7 @@ class MoviePilotTool(BaseTool, metaclass=ABCMeta):
# 发送工具执行过程消息(流式传输且非最后终结工具时)
if self._stream_handler and self._stream_handler.is_streaming and not self.return_direct:
if settings.AI_AGENT_VERBOSE:
if get_runtime_setting('AI_AGENT_VERBOSE'):
if self._stream_handler.is_auto_flushing:
# 渠道支持编辑:工具消息追加到 buffer,由定时刷新推送
if tool_message:
@@ -614,7 +613,7 @@ class MoviePilotTool(BaseTool, metaclass=ABCMeta):
:return: 普通用户允许读写的本地目录列表
"""
roots = [
settings.CONFIG_PATH / "agent",
get_runtime_setting('CONFIG_PATH') / "agent",
]
resolved_roots = []
for root in roots:
@@ -758,7 +757,7 @@ class MoviePilotTool(BaseTool, metaclass=ABCMeta):
"userid": None,
"username": message.username
or self._username
or settings.SUPERUSER,
or get_runtime_setting('SUPERUSER'),
"original_message_id": None,
"original_chat_id": None,
}
+2 -3
View File
@@ -9,11 +9,10 @@ from app.adapters.external.market import PluginHelper
from app.application.configuration import get_configured_system_config
from app.application.plugin.gateway import get_plugin_install_service
from app.application.plugin.runtime import get_plugin_manager
from app.runtime.settings import RuntimeSettingsCompat
from app.runtime.settings import get_runtime_setting
from app.schemas.plugin import PluginRuntimeStatus
from app.schemas.types import SystemConfigKey
settings = RuntimeSettingsCompat()
# 默认只向智能体返回一个可读预览,避免超大插件数据挤爆上下文窗口。
DEFAULT_PLUGIN_DATA_PREVIEW_CHARS = 12_000
@@ -395,7 +394,7 @@ async def uninstall_plugin_runtime(plugin_id: str) -> dict[str, Any]:
elif was_clone:
plugin_manager.delete_plugin_config(plugin_id)
plugin_manager.delete_plugin_data(plugin_id)
plugin_base_dir = settings.ROOT_PATH / "app" / "plugins" / plugin_id.lower()
plugin_base_dir = get_runtime_setting('ROOT_PATH') / "app" / "plugins" / plugin_id.lower()
try:
clone_files_removed = await run_agent_blocking(
"plugin",
+3 -5
View File
@@ -14,10 +14,8 @@ from pathlib import Path
from typing import Any, Optional
from app.agent.tools.impl._command_safety import validate_command_safety
from app.runtime.settings import RuntimeSettingsCompat
settings = RuntimeSettingsCompat()
from app.runtime.log import logger
from app.runtime.settings import get_runtime_setting
if os.name == "posix":
import fcntl as _fcntl
@@ -151,10 +149,10 @@ class _TerminalSessionManager:
def _normalize_cwd(cwd: Optional[str]) -> str:
"""解析工作目录,未传入时默认使用 MoviePilot 项目根目录。"""
if not cwd:
return str(settings.ROOT_PATH)
return str(get_runtime_setting('ROOT_PATH'))
path = Path(cwd).expanduser()
if not path.is_absolute():
path = (settings.ROOT_PATH / path).resolve()
path = (get_runtime_setting('ROOT_PATH') / path).resolve()
else:
path = path.resolve()
if not path.exists():
+2 -3
View File
@@ -12,9 +12,8 @@ from app.agent.tools.tags import ToolTag
from app.chain.download import DownloadChain
from app.chain.media import MediaChain
from app.chain.search import SearchChain
from app.runtime.settings import RuntimeSettingsCompat
from app.runtime.settings import get_runtime_setting
settings = RuntimeSettingsCompat()
from app.domain.context import Context
from app.domain.metainfo import MetaInfo
from app.application.agentdata import get_agent_site_port
@@ -142,7 +141,7 @@ class AddDownloadTasksTool(MoviePilotTool):
@staticmethod
def _merge_labels_with_system_tag(labels: Optional[str]) -> Optional[str]:
"""合并用户标签与系统默认标签,确保任务可被系统管理"""
system_tag = (settings.TORRENT_TAG or "").strip()
system_tag = (get_runtime_setting('TORRENT_TAG') or "").strip()
user_labels = [item.strip() for item in (labels or "").split(",") if item.strip()]
if system_tag and system_tag not in user_labels:
+6 -7
View File
@@ -7,9 +7,8 @@ from pydantic import BaseModel, Field, model_validator
from app.agent.tools.base import MoviePilotTool
from app.agent.tools.tags import ToolTag
from app.runtime.settings import RuntimeSettingsCompat
from app.runtime.settings import get_runtime_setting
settings = RuntimeSettingsCompat()
from app.application.agentdata import get_agent_chat_port
from app.application.agentdata import get_agent_task_port
from app.runtime.scheduling import TimerUtils
@@ -73,7 +72,7 @@ class CreateAgentTaskInput(BaseModel):
self.trigger_type, self.trigger = TimerUtils.normalize_schedule_trigger(
trigger_type=self.trigger_type,
trigger_value=self.trigger,
timezone_name=settings.TZ,
timezone_name=get_runtime_setting('TZ'),
require_future=True,
)
return self
@@ -105,14 +104,14 @@ class CreateAgentTaskTool(MoviePilotTool):
trigger_value = payload.trigger
if payload.trigger_type == "date" and payload.delay_minutes is not None:
timezone = pytz.timezone(settings.TZ)
timezone = pytz.timezone(get_runtime_setting('TZ'))
trigger_value = (
datetime.now(timezone) + timedelta(minutes=payload.delay_minutes)
).isoformat(timespec="seconds")
_, trigger_value = TimerUtils.normalize_schedule_trigger(
trigger_type=payload.trigger_type,
trigger_value=trigger_value,
timezone_name=settings.TZ,
timezone_name=get_runtime_setting('TZ'),
require_future=True,
)
chat = get_agent_chat_port().get(
@@ -136,7 +135,7 @@ class CreateAgentTaskTool(MoviePilotTool):
return get_agent_task_port().to_dict(
task,
next_run_at=next_run_at,
timezone=settings.TZ,
timezone=get_runtime_setting('TZ'),
)
async def run(
@@ -149,7 +148,7 @@ class CreateAgentTaskTool(MoviePilotTool):
**kwargs: object,
) -> str:
"""创建 Agent 自主定时任务。"""
if not settings.AI_AGENT_ENABLE:
if not get_runtime_setting('AI_AGENT_ENABLE'):
return "AI Agent 未启用,无法创建自主定时任务"
payload = CreateAgentTaskInput(
name=name,
+2 -4
View File
@@ -5,10 +5,8 @@ from pydantic import BaseModel, Field
from app.agent.tools.base import MoviePilotTool
from app.agent.tools.tags import ToolTag
from app.runtime.settings import RuntimeSettingsCompat
settings = RuntimeSettingsCompat()
from app.application.agentdata import get_agent_task_port
from app.runtime.settings import get_runtime_setting
class QueryAgentTasksInput(BaseModel):
@@ -63,7 +61,7 @@ class QueryAgentTasksTool(MoviePilotTool):
data = oper.to_dict(
task,
next_run_at=get_agent_task_next_run(task.id),
timezone=settings.TZ,
timezone=get_runtime_setting('TZ'),
)
if task_id:
data["recent_runs"] = [
+2 -3
View File
@@ -9,9 +9,8 @@ from pydantic import BaseModel, Field
from app.agent.tools.base import MoviePilotTool
from app.agent.tools.tags import ToolTag
from app.chain.media import MediaChain
from app.runtime.settings import RuntimeSettingsCompat
from app.runtime.settings import get_runtime_setting
settings = RuntimeSettingsCompat()
from app.domain.context import Context
from app.domain.meta.metamusic import MetaMusic
from app.domain.metainfo import MetaInfo
@@ -104,7 +103,7 @@ class RecognizeMediaTool(MoviePilotTool):
}, ensure_ascii=False)
is_audio_path = bool(
path and Path(path).suffix.lower() in settings.RMT_AUDIOEXT
path and Path(path).suffix.lower() in get_runtime_setting('RMT_AUDIOEXT')
)
recognize_music = media_type_enum == MediaType.MUSIC or (
media_type_enum is None and is_audio_path
+2 -3
View File
@@ -10,9 +10,8 @@ from app.agent.tools.base import MoviePilotTool
from app.agent.tools.tags import ToolTag
from app.chain.media import MediaChain
from app.chain.scraping import ScrapingChain
from app.runtime.settings import RuntimeSettingsCompat
from app.runtime.settings import get_runtime_setting
settings = RuntimeSettingsCompat()
from app.runtime.log import logger
from app.schemas.workflow import FileItem
from app.schemas.types import (
@@ -184,7 +183,7 @@ class ScrapeMetadataTool(MoviePilotTool):
scraping_chain = ScrapingChain()
is_audio_file = (
fileitem.type == "file"
and Path(path).suffix.lower() in settings.RMT_AUDIOEXT
and Path(path).suffix.lower() in get_runtime_setting('RMT_AUDIOEXT')
)
scrape_music = media_type_enum == MediaType.MUSIC or (
media_type_enum is None and is_audio_file
+2 -4
View File
@@ -9,10 +9,8 @@ from pydantic import BaseModel, Field
from app.agent.tools.base import MoviePilotTool
from app.agent.tools.tags import ToolTag
from app.runtime.settings import RuntimeSettingsCompat
settings = RuntimeSettingsCompat()
from app.runtime.log import logger
from app.runtime.settings import get_runtime_setting
# 搜索超时时间(秒)
SEARCH_TIMEOUT = 20
@@ -412,7 +410,7 @@ class SearchWebTool(MoviePilotTool):
"""在线程中执行同步搜索"""
results = []
ddgs_kwargs = {"timeout": SEARCH_TIMEOUT}
proxy_url = self._get_proxy_url(settings.PROXY)
proxy_url = self._get_proxy_url(get_runtime_setting('PROXY'))
if proxy_url:
ddgs_kwargs["proxy"] = proxy_url
+2 -3
View File
@@ -6,9 +6,8 @@ from pydantic import BaseModel, Field
from app.agent.llm.capability import AgentCapabilityManager
from app.agent.tools.base import MoviePilotTool
from app.agent.tools.tags import ToolTag
from app.runtime.settings import RuntimeSettingsCompat
from app.runtime.settings import get_runtime_setting
settings = RuntimeSettingsCompat()
from app.runtime.log import logger
from app.schemas.message import Message
from app.schemas.message import MessageType
@@ -96,7 +95,7 @@ class SendVoiceMessageTool(MoviePilotTool):
voice_path=voice_path,
voice_caption=(
message
if voice_path and settings.AUDIO_OUTPUT_INCLUDE_TEXT
if voice_path and get_runtime_setting('AUDIO_OUTPUT_INCLUDE_TEXT')
else None
),
save_history=False,
+4 -6
View File
@@ -7,11 +7,9 @@ from pydantic import BaseModel, Field, model_validator
from app.agent.tools.base import MoviePilotTool
from app.agent.tools.tags import ToolTag
from app.runtime.settings import RuntimeSettingsCompat
settings = RuntimeSettingsCompat()
from app.application.agentdata import get_agent_task_port
from app.runtime.scheduling import TimerUtils
from app.runtime.settings import get_runtime_setting
class UpdateAgentTaskInput(BaseModel):
@@ -115,7 +113,7 @@ class UpdateAgentTaskTool(MoviePilotTool):
trigger_type = payload.trigger_type or task.trigger_type
trigger_value = payload.trigger
if trigger_type == "date" and payload.delay_minutes is not None:
timezone = pytz.timezone(settings.TZ)
timezone = pytz.timezone(get_runtime_setting('TZ'))
trigger_value = (
datetime.now(timezone) + timedelta(minutes=payload.delay_minutes)
).isoformat(timespec="seconds")
@@ -134,7 +132,7 @@ class UpdateAgentTaskTool(MoviePilotTool):
normalized_type, normalized_trigger = TimerUtils.normalize_schedule_trigger(
trigger_type=trigger_type,
trigger_value=trigger_value,
timezone_name=settings.TZ,
timezone_name=get_runtime_setting('TZ'),
require_future=bool(
trigger_type == "date"
and (
@@ -181,7 +179,7 @@ class UpdateAgentTaskTool(MoviePilotTool):
return oper.to_dict(
updated_task,
next_run_at=next_run_at,
timezone=settings.TZ,
timezone=get_runtime_setting('TZ'),
)
async def run(