mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-06-02 06:00:56 +08:00
feat(system-settings): add unified tools for querying and updating system settings
This commit is contained in:
186
app/agent/tools/impl/query_system_settings.py
Normal file
186
app/agent/tools/impl/query_system_settings.py
Normal file
@@ -0,0 +1,186 @@
|
||||
"""统一查询系统设置工具。"""
|
||||
|
||||
import json
|
||||
from typing import Optional, Type
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from app.agent.tools.base import MoviePilotTool
|
||||
from app.agent.tools.impl._system_setting_utils import (
|
||||
SettingSpec,
|
||||
list_setting_specs,
|
||||
resolve_setting_spec,
|
||||
)
|
||||
from app.core.config import settings
|
||||
from app.db.systemconfig_oper import SystemConfigOper
|
||||
from app.log import logger
|
||||
|
||||
|
||||
class QuerySystemSettingsInput(BaseModel):
|
||||
"""查询系统设置工具的输入参数模型。"""
|
||||
|
||||
explanation: str = Field(
|
||||
...,
|
||||
description="Clear explanation of why this tool is being used in the current context",
|
||||
)
|
||||
setting_key: Optional[str] = Field(
|
||||
None,
|
||||
description=(
|
||||
"Exact setting key to query. Supports Settings field names like 'APP_DOMAIN' or 'TMDB_API_KEY', "
|
||||
"SystemConfigKey values like 'Downloaders' or 'MediaServers', enum names, and some single-key aliases "
|
||||
"such as 'downloaders', 'directories', 'search_sites', 'subscribe_sites', 'site_auth', 'ai_agent', "
|
||||
"and 'custom_identifiers'."
|
||||
),
|
||||
)
|
||||
group: Optional[str] = Field(
|
||||
"all",
|
||||
description=(
|
||||
"Optional group filter when setting_key is not provided. Supports 'all', 'settings', 'systemconfig', "
|
||||
"and category aliases such as 'downloaders', 'media_servers', 'notifications', 'notification_switches', "
|
||||
"'storages', 'directories', 'search_sites', 'subscribe_sites', 'site_auth', 'ai_agent', 'filter_rules', "
|
||||
"'subscribe_defaults', 'plugins', and 'custom_identifiers'. Chinese aliases are also accepted."
|
||||
),
|
||||
)
|
||||
keyword: Optional[str] = Field(
|
||||
None,
|
||||
description=(
|
||||
"Optional keyword used to fuzzy match setting keys, group names, or labels when listing settings."
|
||||
),
|
||||
)
|
||||
include_values: Optional[bool] = Field(
|
||||
None,
|
||||
description=(
|
||||
"Whether to include full setting values. Default behavior: when a single setting is matched it returns the full value; "
|
||||
"when multiple settings are matched it returns summaries only unless this is explicitly set to true."
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class QuerySystemSettingsTool(MoviePilotTool):
|
||||
name: str = "query_system_settings"
|
||||
description: str = (
|
||||
"Query system settings across both the basic Settings module and all SystemConfig-backed categories. "
|
||||
"Use this tool to inspect downloaders, media servers, notification channels, storages, directories, search-site ranges, "
|
||||
"subscribe-site ranges, site auth params, AI agent config, and any other system setting before making changes."
|
||||
)
|
||||
require_admin: bool = True
|
||||
args_schema: Type[BaseModel] = QuerySystemSettingsInput
|
||||
|
||||
def get_tool_message(self, **kwargs) -> Optional[str]:
|
||||
"""根据查询参数生成友好的提示消息。"""
|
||||
|
||||
setting_key = kwargs.get("setting_key")
|
||||
group = kwargs.get("group", "all")
|
||||
keyword = kwargs.get("keyword")
|
||||
if setting_key:
|
||||
return f"查询系统设置: {setting_key}"
|
||||
if keyword:
|
||||
return f"筛选系统设置: {group} / {keyword}"
|
||||
return f"查询系统设置分组: {group}"
|
||||
|
||||
@staticmethod
|
||||
def _load_setting_value(spec: SettingSpec):
|
||||
if spec.source == "settings":
|
||||
return getattr(settings, spec.key)
|
||||
return SystemConfigOper().get(spec.key)
|
||||
|
||||
@staticmethod
|
||||
def _summarize_value(value) -> dict:
|
||||
summary = {
|
||||
"has_value": value is not None,
|
||||
"value_type": type(value).__name__,
|
||||
}
|
||||
if isinstance(value, list):
|
||||
summary["item_count"] = len(value)
|
||||
if value:
|
||||
summary["item_type"] = type(value[0]).__name__
|
||||
elif isinstance(value, dict):
|
||||
keys = list(value.keys())
|
||||
summary["item_count"] = len(keys)
|
||||
summary["keys_preview"] = keys[:10]
|
||||
if len(keys) > 10:
|
||||
summary["keys_truncated"] = True
|
||||
elif isinstance(value, str):
|
||||
summary["length"] = len(value)
|
||||
preview = value[:200]
|
||||
if preview:
|
||||
summary["value_preview"] = preview
|
||||
if len(value) > len(preview):
|
||||
summary["value_truncated"] = True
|
||||
elif value is not None:
|
||||
summary["value_preview"] = value
|
||||
return summary
|
||||
|
||||
async def run(
|
||||
self,
|
||||
setting_key: Optional[str] = None,
|
||||
group: Optional[str] = "all",
|
||||
keyword: Optional[str] = None,
|
||||
include_values: Optional[bool] = None,
|
||||
**kwargs,
|
||||
) -> str:
|
||||
logger.info(
|
||||
"执行工具: %s, setting_key=%s, group=%s, keyword=%s",
|
||||
self.name,
|
||||
setting_key,
|
||||
group,
|
||||
keyword,
|
||||
)
|
||||
|
||||
try:
|
||||
if setting_key:
|
||||
spec = resolve_setting_spec(setting_key)
|
||||
if not spec:
|
||||
return json.dumps(
|
||||
{
|
||||
"success": False,
|
||||
"message": f"系统设置项 '{setting_key}' 不存在",
|
||||
},
|
||||
ensure_ascii=False,
|
||||
)
|
||||
specs = [spec]
|
||||
else:
|
||||
specs = list_setting_specs(group=group, keyword=keyword)
|
||||
if not specs:
|
||||
return json.dumps(
|
||||
{
|
||||
"success": False,
|
||||
"message": "没有找到匹配的系统设置项",
|
||||
},
|
||||
ensure_ascii=False,
|
||||
)
|
||||
|
||||
should_include_values = (
|
||||
include_values if include_values is not None else len(specs) == 1
|
||||
)
|
||||
settings_payload = []
|
||||
for spec in specs:
|
||||
value = self._load_setting_value(spec)
|
||||
item = {
|
||||
"setting_key": spec.key,
|
||||
"source": spec.source,
|
||||
"group": spec.group,
|
||||
"label": spec.label,
|
||||
}
|
||||
item.update(self._summarize_value(value))
|
||||
if should_include_values:
|
||||
item["value"] = value
|
||||
settings_payload.append(item)
|
||||
|
||||
return json.dumps(
|
||||
{
|
||||
"success": True,
|
||||
"matched_count": len(settings_payload),
|
||||
"include_values": should_include_values,
|
||||
"settings": settings_payload,
|
||||
},
|
||||
ensure_ascii=False,
|
||||
indent=2,
|
||||
default=str,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"查询系统设置失败: {e}", exc_info=True)
|
||||
return json.dumps(
|
||||
{"success": False, "message": f"查询系统设置时发生错误: {str(e)}"},
|
||||
ensure_ascii=False,
|
||||
)
|
||||
Reference in New Issue
Block a user