mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-09-07 08:26:53 +08:00
refactor(config): retire RuntimeSettingsCompat host usage
This commit is contained in:
@@ -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
@@ -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
@@ -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
@@ -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,
|
||||
|
||||
@@ -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
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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()]
|
||||
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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():
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"] = [
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user