mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-09-04 23:17:20 +08:00
add: query_doctor_report agent tool
This commit is contained in:
@@ -58,6 +58,7 @@ You act as a proactive agent. Your goal is to fully resolve the user's media-rel
|
||||
- Use parallel tool calls by default for independent read-only or diagnostic work. In one assistant turn, issue all tool calls that can run without waiting for each other's results, such as checking enabled sites, library existence, recent history, downloader status, and scheduler or configuration state.
|
||||
- Keep tools sequential only when later arguments depend on earlier output, when a tool mutates state, when confirmation is required, or when concurrent writes could conflict.
|
||||
- When planning a multi-step investigation, group the first wave of safe state-gathering calls together, then continue with dependent actions after those results return.
|
||||
- For system startup, Docker, dependency, database, frontend asset, port, safe-mode, or unclear runtime failures, use `query_doctor_report` early to collect the read-only Doctor diagnostic report before falling back to generic command execution.
|
||||
- Prefer site-aware tool paths when the task is about torrents, subscriptions, or download failures. `query_sites`, `test_site`, and `query_site_userdata` are part of the main operating flow, not edge-case tools.
|
||||
- If search results are ambiguous, use `query_media_detail` or `recognize_media` to clarify before proceeding.
|
||||
- For fuzzy torrent names, filenames, or manually provided paths, prefer `recognize_media` before asking the user for a cleaner title.
|
||||
|
||||
@@ -74,6 +74,7 @@ from app.agent.tools.impl.uninstall_plugin import UninstallPluginTool
|
||||
from app.agent.tools.impl.run_slash_command import RunSlashCommandTool
|
||||
from app.agent.tools.impl.list_slash_commands import ListSlashCommandsTool
|
||||
from app.agent.tools.impl.query_custom_identifiers import QueryCustomIdentifiersTool
|
||||
from app.agent.tools.impl.query_doctor_report import QueryDoctorReportTool
|
||||
from app.agent.tools.impl.update_custom_identifiers import UpdateCustomIdentifiersTool
|
||||
from app.agent.tools.impl.query_system_settings import QuerySystemSettingsTool
|
||||
from app.agent.tools.impl.update_system_settings import UpdateSystemSettingsTool
|
||||
@@ -99,6 +100,7 @@ class MoviePilotToolFactory:
|
||||
"read_file",
|
||||
"edit_file",
|
||||
"execute_command",
|
||||
"query_doctor_report",
|
||||
"send_message",
|
||||
"ask_user_choice",
|
||||
)
|
||||
@@ -220,6 +222,7 @@ class MoviePilotToolFactory:
|
||||
UninstallPluginTool,
|
||||
RunSlashCommandTool,
|
||||
ListSlashCommandsTool,
|
||||
QueryDoctorReportTool,
|
||||
QueryCustomIdentifiersTool,
|
||||
UpdateCustomIdentifiersTool,
|
||||
QuerySystemSettingsTool,
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
"""查询 MoviePilot Doctor 诊断报告工具。"""
|
||||
|
||||
import json
|
||||
from typing import Any, Optional, Type
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from app.agent.tools.base import MoviePilotTool
|
||||
from app.agent.tools.tags import ToolTag
|
||||
from app.doctor import run_doctor
|
||||
from app.log import logger
|
||||
|
||||
|
||||
class QueryDoctorReportInput(BaseModel):
|
||||
"""查询 Doctor 诊断报告工具的输入参数模型。"""
|
||||
|
||||
explanation: Optional[str] = Field(
|
||||
None,
|
||||
description="Clear explanation of why this tool is being used in the current context",
|
||||
)
|
||||
deep: Optional[bool] = Field(
|
||||
False,
|
||||
description=(
|
||||
"Whether to run deeper checks. When true, doctor may perform slower environment probes "
|
||||
"such as PostgreSQL TCP connectivity checks."
|
||||
),
|
||||
)
|
||||
include_details: Optional[bool] = Field(
|
||||
True,
|
||||
description=(
|
||||
"Whether to include full doctor findings with details and context. Set false for a compact "
|
||||
"summary when only overall status and finding titles are needed."
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class QueryDoctorReportTool(MoviePilotTool):
|
||||
"""
|
||||
Doctor 离线诊断报告查询工具。
|
||||
"""
|
||||
|
||||
name: str = "query_doctor_report"
|
||||
tags: list[str] = [
|
||||
ToolTag.Read,
|
||||
ToolTag.System,
|
||||
ToolTag.Admin,
|
||||
]
|
||||
description: str = (
|
||||
"Run MoviePilot Doctor in read-only mode and return a structured diagnostic report for troubleshooting. "
|
||||
"Use this tool when analyzing startup failures, Docker/runtime issues, port conflicts, dependency problems, "
|
||||
"database health, frontend assets, safe mode, or recent log error clues. This tool never applies fixes."
|
||||
)
|
||||
require_admin: bool = True
|
||||
args_schema: Type[BaseModel] = QueryDoctorReportInput
|
||||
|
||||
def get_tool_message(self, **kwargs) -> Optional[str]:
|
||||
"""根据查询参数生成友好的提示消息。"""
|
||||
if kwargs.get("deep"):
|
||||
return "运行 Doctor 深度诊断"
|
||||
return "运行 Doctor 诊断"
|
||||
|
||||
@staticmethod
|
||||
def _compact_report(report: dict[str, Any]) -> dict[str, Any]:
|
||||
"""压缩诊断报告,保留 Agent 判断问题所需的核心字段。"""
|
||||
return {
|
||||
"schema_version": report.get("schema_version"),
|
||||
"status": report.get("status"),
|
||||
"generated_at": report.get("generated_at"),
|
||||
"version": report.get("version"),
|
||||
"environment": report.get("environment"),
|
||||
"summary": report.get("summary"),
|
||||
"findings": [
|
||||
{
|
||||
"id": item.get("id"),
|
||||
"severity": item.get("severity"),
|
||||
"status": item.get("status"),
|
||||
"title": item.get("title"),
|
||||
"fixable": item.get("fixable"),
|
||||
"fixed": item.get("fixed"),
|
||||
}
|
||||
for item in report.get("findings") or []
|
||||
if isinstance(item, dict)
|
||||
],
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _run_doctor_report(deep: bool = False) -> dict[str, Any]:
|
||||
"""在线程池中运行只读 Doctor 诊断。"""
|
||||
return run_doctor(deep=bool(deep)).to_dict()
|
||||
|
||||
async def run(
|
||||
self,
|
||||
deep: Optional[bool] = False,
|
||||
include_details: Optional[bool] = True,
|
||||
**kwargs,
|
||||
) -> str:
|
||||
"""
|
||||
运行只读 Doctor 诊断并返回 JSON 字符串。
|
||||
"""
|
||||
logger.info(
|
||||
f"执行工具: {self.name}, deep={bool(deep)}, include_details={bool(include_details)}"
|
||||
)
|
||||
try:
|
||||
report = await self.run_blocking("default", self._run_doctor_report, bool(deep))
|
||||
if not include_details:
|
||||
report = self._compact_report(report)
|
||||
return json.dumps(
|
||||
{
|
||||
"success": True,
|
||||
"deep": bool(deep),
|
||||
"include_details": bool(include_details),
|
||||
"report": report,
|
||||
},
|
||||
ensure_ascii=False,
|
||||
indent=2,
|
||||
default=str,
|
||||
)
|
||||
except Exception as err:
|
||||
logger.error(f"查询 Doctor 诊断报告失败: {err}", exc_info=True)
|
||||
return json.dumps(
|
||||
{
|
||||
"success": False,
|
||||
"message": f"查询 Doctor 诊断报告时发生错误: {str(err)}",
|
||||
},
|
||||
ensure_ascii=False,
|
||||
)
|
||||
Reference in New Issue
Block a user