feat: unify download task tool names

This commit is contained in:
jxxghp
2026-06-15 14:28:18 +08:00
parent 47f6389424
commit bae820a11d
7 changed files with 62 additions and 26 deletions

View File

@@ -64,7 +64,7 @@ You act as a proactive agent. Your goal is to fully resolve the user's media-rel
- For fuzzy torrent names, filenames, or manually provided paths, prefer `recognize_media` before asking the user for a cleaner title.
- If `search_media` fails, fall back to `search_web` or `recognize_media`. Only ask the user when automated paths are exhausted.
- If torrent search yields no useful result, check site scope, site health, and recognition quality before concluding that the resource is unavailable.
- Reuse the latest torrent search cache for `get_search_results` and `add_download` instead of re-running the same search unnecessarily.
- Reuse the latest torrent search cache for `get_search_results` and `add_download_tasks` instead of re-running the same search unnecessarily.
- Use `execute_command` only for diagnostics, read-only inspection, or commands the user explicitly asked to run. Its default `action=start` starts a managed background session and returns `session_id`, `status`, `last_seq`, and `output_until_seq`; call the same tool again with `action=read`, `action=wait`, `action=write`, or `action=kill` to poll output, wait in short segments, send stdin, or stop the process.
</tool_strategy>

View File

@@ -1,6 +1,6 @@
from typing import List, Callable
from app.agent.tools.impl.add_download import AddDownloadTool
from app.agent.tools.impl.add_download_tasks import AddDownloadTasksTool
from app.agent.tools.impl.add_subscribe import AddSubscribeTool
from app.agent.tools.impl.update_subscribe import UpdateSubscribeTool
from app.agent.tools.impl.search_subscribe import SearchSubscribeTool
@@ -50,7 +50,7 @@ from app.agent.tools.impl.query_personas import QueryPersonasTool
from app.agent.tools.impl.switch_persona import SwitchPersonaTool
from app.agent.tools.impl.update_persona_definition import UpdatePersonaDefinitionTool
from app.agent.tools.impl.update_site_cookie import UpdateSiteCookieTool
from app.agent.tools.impl.delete_download import DeleteDownloadTool
from app.agent.tools.impl.delete_download_tasks import DeleteDownloadTasksTool
from app.agent.tools.impl.delete_download_history import DeleteDownloadHistoryTool
from app.agent.tools.impl.delete_transfer_history import DeleteTransferHistoryTool
from app.agent.tools.impl.update_download_tasks import UpdateDownloadTasksTool
@@ -167,7 +167,7 @@ class MoviePilotToolFactory:
GetSearchResultsTool,
SearchWebTool,
RecognizeCaptchaTool,
AddDownloadTool,
AddDownloadTasksTool,
QuerySubscribesTool,
QuerySubscribeSharesTool,
QueryPopularSubscribesTool,
@@ -183,7 +183,7 @@ class MoviePilotToolFactory:
QuerySubscribeHistoryTool,
DeleteSubscribeTool,
QueryDownloadTasksTool,
DeleteDownloadTool,
DeleteDownloadTasksTool,
DeleteDownloadHistoryTool,
DeleteTransferHistoryTool,
UpdateDownloadTasksTool,

View File

@@ -1,28 +1,29 @@
"""添加下载工具"""
"""添加下载任务工具"""
import re
from pathlib import Path
from typing import List, Optional, Type
from typing import List, Optional, Type, Union
from pydantic import BaseModel, Field
from app.agent.tools.base import MoviePilotTool
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.chain.download import DownloadChain
from app.core.config import settings
from app.core.context import Context
from app.core.metainfo import MetaInfo
from app.db.site_oper import SiteOper
from app.helper.directory import DirectoryHelper
from app.log import logger
from app.schemas import TorrentInfo, FileURI
from app.schemas import FileURI, TorrentInfo
from app.utils.crypto import HashUtils
class AddDownloadInput(BaseModel):
"""添加下载工具的输入参数模型"""
class AddDownloadTasksInput(BaseModel):
"""添加下载任务工具的输入参数模型"""
explanation: Optional[str] = Field(None, description="Clear explanation of why this tool is being used in the current context")
torrent_url: List[str] = Field(
...,
@@ -36,15 +37,17 @@ class AddDownloadInput(BaseModel):
description="Comma-separated list of labels/tags to assign to the download (optional, e.g., 'movie,hd,bluray')")
class AddDownloadTool(MoviePilotTool):
name: str = "add_download"
class AddDownloadTasksTool(MoviePilotTool):
"""添加下载任务工具"""
name: str = "add_download_tasks"
tags: list[str] = [
ToolTag.Write,
ToolTag.Download,
ToolTag.Resource,
]
description: str = "Add torrent download tasks using refs from get_search_results or magnet links."
args_schema: Type[BaseModel] = AddDownloadInput
args_schema: Type[BaseModel] = AddDownloadTasksInput
def get_tool_message(self, **kwargs) -> Optional[str]:
"""根据下载参数生成友好的提示消息"""
@@ -157,16 +160,16 @@ class AddDownloadTool(MoviePilotTool):
prefix = "添加种子任务失败:"
if normalized_error.startswith(prefix):
normalized_error = normalized_error[len(prefix):].lstrip()
if AddDownloadTool._is_magnet_link_input(normalized_error):
if AddDownloadTasksTool._is_magnet_link_input(normalized_error):
normalized_error = ""
if normalized_error:
return f"{torrent_ref} {normalized_error}"
if AddDownloadTool._is_torrent_ref(torrent_ref):
if AddDownloadTasksTool._is_torrent_ref(torrent_ref):
return torrent_ref
return ""
@classmethod
def _normalize_torrent_urls(cls, torrent_url: Optional[List[str] | str]) -> List[str]:
def _normalize_torrent_urls(cls, torrent_url: Optional[Union[List[str], str]]) -> List[str]:
"""统一规范 torrent_url 输入,保留所有非空值"""
if torrent_url is None:
return []
@@ -234,6 +237,7 @@ class AddDownloadTool(MoviePilotTool):
async def run(self, torrent_url: Optional[List[str]] = None,
downloader: Optional[str] = None, save_path: Optional[str] = None,
labels: Optional[str] = None, **kwargs) -> str:
"""执行添加下载任务。"""
logger.info(
f"执行工具: {self.name}, 参数: torrent_url={torrent_url}, downloader={downloader}, save_path={save_path}, labels={labels}")

View File

@@ -10,7 +10,7 @@ from app.chain.download import DownloadChain
from app.log import logger
class DeleteDownloadInput(BaseModel):
class DeleteDownloadTasksInput(BaseModel):
"""删除下载任务工具的输入参数模型"""
explanation: Optional[str] = Field(None,
@@ -28,15 +28,17 @@ class DeleteDownloadInput(BaseModel):
)
class DeleteDownloadTool(MoviePilotTool):
name: str = "delete_download"
class DeleteDownloadTasksTool(MoviePilotTool):
"""删除下载任务工具"""
name: str = "delete_download_tasks"
tags: list[str] = [
ToolTag.Write,
ToolTag.Download,
ToolTag.Admin,
]
description: str = "Delete a download task from the downloader by task hash only. Optionally specify the downloader name and whether to delete downloaded files."
args_schema: Type[BaseModel] = DeleteDownloadInput
args_schema: Type[BaseModel] = DeleteDownloadTasksInput
require_admin: bool = True
def get_tool_message(self, **kwargs) -> Optional[str]:
@@ -69,6 +71,7 @@ class DeleteDownloadTool(MoviePilotTool):
delete_files: Optional[bool] = False,
**kwargs,
) -> str:
"""执行删除下载任务。"""
logger.info(
f"执行工具: {self.name}, 参数: hash={hash}, downloader={downloader}, delete_files={delete_files}"
)

View File

@@ -238,8 +238,10 @@ MoviePilot 也提供普通 REST API 给前端和自动化客户端使用。所
**下载任务工具说明**
- `add_download_tasks` 用于添加下载任务,支持 `get_search_results` 返回的 `hash:id` 引用和磁力链接,可指定下载器、保存目录和标签。
- `query_download_tasks` 用于查询下载任务支持按下载器、状态、Hash、标题、标签过滤返回保存目录、内容路径、上传/下载速度、上传/下载限速、分类、分享率、做种时间等下载器可提供的字段。按 `hash` 查询或传入 `include_trackers=true` 时,会尽量返回 Tracker URL 列表。
- `update_download_tasks` 用于修改下载任务,统一支持 `start`/`stop`、标签、上传/下载限速、Tracker、保存目录、分类、分享率、做种时间等字段具体字段是否成功取决于下载器能力返回结果会按操作项逐条标记成功或失败。
- `delete_download_tasks` 用于删除下载任务,按任务 Hash 操作,可指定下载器,并可选择是否同时删除已下载文件。
### 3. 获取工具详情

View File

@@ -24,7 +24,7 @@ Always run `show <command>` before calling a command — parameter names are not
|---|---|
| Media Search | search_media, recognize_media, query_media_detail, get_recommendations, search_person, search_person_credits |
| Torrent | search_torrents, get_search_results |
| Download | add_download, query_download_tasks, update_download_tasks, delete_download, query_downloaders |
| Download | add_download_tasks, query_download_tasks, update_download_tasks, delete_download_tasks, query_downloaders |
| Subscription | add_subscribe, query_subscribes, update_subscribe, delete_subscribe, search_subscribe, query_subscribe_history, query_popular_subscribes, query_subscribe_shares |
| Library | query_library_exists, query_library_latest, transfer_file, scrape_metadata, query_transfer_history |
| Files | list_directory, query_directory_settings |
@@ -95,7 +95,7 @@ If the media already exists in the library or is already subscribed, **stop** an
#### 6. Add download
Download one or more torrents (`torrent_url` comes from `get_search_results` output):
`node scripts/mp-cli.js add_download torrent_url="abc1234:1,def5678:2"`
`node scripts/mp-cli.js add_download_tasks torrent_url="abc1234:1,def5678:2"`
#### Error handling
@@ -104,7 +104,7 @@ Download one or more torrents (`torrent_url` comes from `get_search_results` out
| `search_media` empty | Retry with alternative title (English/original), inform user. Still empty → ask for title or TMDB ID. |
| `search_torrents` empty | Inform user, ask whether to retry with different sites. |
| `get_search_results` empty | Do not silently broaden filters. Suggest which filter to relax, ask before retrying. |
| `add_download` fails | Run `query_downloaders` + `query_download_tasks` to diagnose, then report to user. |
| `add_download_tasks` fails | Run `query_downloaders` + `query_download_tasks` to diagnose, then report to user. |
### Add Subscription
@@ -135,10 +135,10 @@ Add trackers to a download task:
`node scripts/mp-cli.js update_download_tasks hash=<hash> trackers='https://tracker.example/announce,udp://tracker.example:80/announce'`
Delete a download task (confirm with user first — irreversible):
`node scripts/mp-cli.js delete_download hash=<hash>`
`node scripts/mp-cli.js delete_download_tasks hash=<hash>`
Delete a download task and also remove its files (confirm with user first — irreversible):
`node scripts/mp-cli.js delete_download hash=<hash> delete_files=true`
`node scripts/mp-cli.js delete_download_tasks hash=<hash> delete_files=true`
### Manage Subscriptions

View File

@@ -0,0 +1,27 @@
from unittest.mock import patch
from app.agent.tools.factory import MoviePilotToolFactory
def test_factory_registers_plural_download_task_tool_names():
"""
下载任务工具应统一使用 *_download_tasks 命名。
"""
with patch(
"app.agent.tools.factory.PluginManager.get_plugin_agent_tools",
return_value=[],
):
tools = MoviePilotToolFactory.create_tools(
session_id="download-task-names",
user_id="10001",
)
tool_names = {tool.name for tool in tools}
assert {
"add_download_tasks",
"query_download_tasks",
"update_download_tasks",
"delete_download_tasks",
} <= tool_names
assert "add_download" not in tool_names
assert "delete_download" not in tool_names