mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-09-08 08:57:09 +08:00
Refactor agent tool inputs and background activity logging
This commit is contained in:
@@ -6,6 +6,7 @@
|
|||||||
并在每次 Agent 启动时注入轻量索引,完整日志由工具按需查询。
|
并在每次 Agent 启动时注入轻量索引,完整日志由工具按需查询。
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import asyncio
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
@@ -88,10 +89,6 @@ ACTIVITY_ENTRY_PATTERN = re.compile(r"^-\s+\*\*(?P<time>\d{2}:\d{2})\*\*\s+(?P<s
|
|||||||
class QueryActivityLogInput(BaseModel):
|
class QueryActivityLogInput(BaseModel):
|
||||||
"""查询活动日志工具的输入参数模型。"""
|
"""查询活动日志工具的输入参数模型。"""
|
||||||
|
|
||||||
explanation: Optional[str] = Field(
|
|
||||||
None,
|
|
||||||
description="Clear explanation of why this tool is being used in the current context",
|
|
||||||
)
|
|
||||||
keyword: Optional[str] = Field(
|
keyword: Optional[str] = Field(
|
||||||
None,
|
None,
|
||||||
description=(
|
description=(
|
||||||
@@ -288,17 +285,15 @@ class _ActivityLogToolProvider:
|
|||||||
date: Optional[str] = None,
|
date: Optional[str] = None,
|
||||||
days: Optional[int] = DEFAULT_QUERY_DAYS,
|
days: Optional[int] = DEFAULT_QUERY_DAYS,
|
||||||
limit: Optional[int] = DEFAULT_QUERY_LIMIT,
|
limit: Optional[int] = DEFAULT_QUERY_LIMIT,
|
||||||
explanation: Optional[str] = None,
|
|
||||||
) -> str:
|
) -> str:
|
||||||
"""查询活动日志并返回 JSON 字符串。"""
|
"""查询活动日志并返回 JSON 字符串。"""
|
||||||
logger.info(
|
logger.info(
|
||||||
"查询活动日志: keyword=%s, use_regex=%s, date=%s, days=%s, limit=%s, explanation=%s",
|
"查询活动日志: keyword=%s, use_regex=%s, date=%s, days=%s, limit=%s",
|
||||||
keyword,
|
keyword,
|
||||||
use_regex,
|
use_regex,
|
||||||
date,
|
date,
|
||||||
days,
|
days,
|
||||||
limit,
|
limit,
|
||||||
explanation or "-",
|
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
payload = query_activity_logs(
|
payload = query_activity_logs(
|
||||||
@@ -505,6 +500,7 @@ class ActivityLogMiddleware(AgentMiddleware[ActivityLogState, ContextT, Response
|
|||||||
self.retention_days = retention_days
|
self.retention_days = retention_days
|
||||||
self.prompt_load_days = prompt_load_days
|
self.prompt_load_days = prompt_load_days
|
||||||
self.stream_handler = stream_handler
|
self.stream_handler = stream_handler
|
||||||
|
self._background_tasks: set[asyncio.Task[None]] = set()
|
||||||
self._tool_provider = _ActivityLogToolProvider(activity_dir=activity_dir)
|
self._tool_provider = _ActivityLogToolProvider(activity_dir=activity_dir)
|
||||||
self.tools = [
|
self.tools = [
|
||||||
StructuredTool.from_function(
|
StructuredTool.from_function(
|
||||||
@@ -631,6 +627,44 @@ class ActivityLogMiddleware(AgentMiddleware[ActivityLogState, ContextT, Response
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"Failed to cleanup old activity logs: {e}")
|
logger.warning(f"Failed to cleanup old activity logs: {e}")
|
||||||
|
|
||||||
|
def _schedule_activity_recording(self, messages: list) -> None:
|
||||||
|
"""提交后台活动记录任务,不阻塞当前 Agent 会话结束。"""
|
||||||
|
task = asyncio.create_task(self._record_activity(messages))
|
||||||
|
self._background_tasks.add(task)
|
||||||
|
task.add_done_callback(self._on_activity_recording_done)
|
||||||
|
|
||||||
|
def _on_activity_recording_done(self, task: asyncio.Task[None]) -> None:
|
||||||
|
"""清理已完成的后台任务并记录未捕获异常。"""
|
||||||
|
self._background_tasks.discard(task)
|
||||||
|
try:
|
||||||
|
task.result()
|
||||||
|
except asyncio.CancelledError:
|
||||||
|
logger.debug("活动日志后台记录任务已取消")
|
||||||
|
except Exception as err:
|
||||||
|
logger.warning(f"活动日志后台记录任务失败: {err}")
|
||||||
|
|
||||||
|
async def _record_activity(self, messages: list) -> None:
|
||||||
|
"""在后台生成本轮活动摘要并写入活动日志。"""
|
||||||
|
try:
|
||||||
|
# 提取本轮交互
|
||||||
|
round_messages = _extract_last_round(messages)
|
||||||
|
if not round_messages:
|
||||||
|
return
|
||||||
|
if _should_skip_activity_summary(round_messages):
|
||||||
|
return
|
||||||
|
|
||||||
|
# 格式化对话文本
|
||||||
|
conversation_text = _format_conversation_for_summary(round_messages)
|
||||||
|
if not conversation_text:
|
||||||
|
return
|
||||||
|
|
||||||
|
# 调用 LLM 生成摘要
|
||||||
|
summary = await _summarize_with_llm(conversation_text)
|
||||||
|
if summary:
|
||||||
|
await self._append_activity(summary)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Failed to record activity: {e}")
|
||||||
|
|
||||||
async def abefore_agent(
|
async def abefore_agent(
|
||||||
self, state: ActivityLogState, runtime: Runtime
|
self, state: ActivityLogState, runtime: Runtime
|
||||||
) -> Optional[ActivityLogStateUpdate]:
|
) -> Optional[ActivityLogStateUpdate]:
|
||||||
@@ -699,28 +733,12 @@ class ActivityLogMiddleware(AgentMiddleware[ActivityLogState, ContextT, Response
|
|||||||
async def aafter_agent(
|
async def aafter_agent(
|
||||||
self, state: ActivityLogState, runtime: Runtime
|
self, state: ActivityLogState, runtime: Runtime
|
||||||
) -> Optional[dict[str, Any]]:
|
) -> Optional[dict[str, Any]]:
|
||||||
"""Agent 执行完毕后,调用 LLM 对本轮对话生成摘要并追加到当日活动日志。"""
|
"""Agent 执行完毕后,异步提交活动日志记录任务。"""
|
||||||
try:
|
try:
|
||||||
messages = state.get("messages", [])
|
messages = state.get("messages", [])
|
||||||
if not messages:
|
if not messages:
|
||||||
return None
|
return None
|
||||||
|
self._schedule_activity_recording(list(messages))
|
||||||
# 提取本轮交互
|
|
||||||
round_messages = _extract_last_round(messages)
|
|
||||||
if not round_messages:
|
|
||||||
return None
|
|
||||||
if _should_skip_activity_summary(round_messages):
|
|
||||||
return None
|
|
||||||
|
|
||||||
# 格式化对话文本
|
|
||||||
conversation_text = _format_conversation_for_summary(round_messages)
|
|
||||||
if not conversation_text:
|
|
||||||
return None
|
|
||||||
|
|
||||||
# 调用 LLM 生成摘要
|
|
||||||
summary = await _summarize_with_llm(conversation_text)
|
|
||||||
if summary:
|
|
||||||
await self._append_activity(summary)
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"Failed to record activity: {e}")
|
logger.warning(f"Failed to record activity: {e}")
|
||||||
|
|
||||||
|
|||||||
@@ -92,10 +92,6 @@ class SkillsStateUpdate(TypedDict):
|
|||||||
class SkillToolInput(BaseModel):
|
class SkillToolInput(BaseModel):
|
||||||
"""Skill 加载工具的输入参数模型。"""
|
"""Skill 加载工具的输入参数模型。"""
|
||||||
|
|
||||||
explanation: Optional[str] = Field(
|
|
||||||
None,
|
|
||||||
description="Clear explanation of why this skill is needed in the current context",
|
|
||||||
)
|
|
||||||
name: str = Field(
|
name: str = Field(
|
||||||
...,
|
...,
|
||||||
description="Skill name or id from the available skills list.",
|
description="Skill name or id from the available skills list.",
|
||||||
@@ -460,9 +456,9 @@ class _SkillToolProvider:
|
|||||||
raw_content = await handle.read(MAX_SKILL_FILE_SIZE)
|
raw_content = await handle.read(MAX_SKILL_FILE_SIZE)
|
||||||
return raw_content.decode("utf-8", errors="replace"), truncated
|
return raw_content.decode("utf-8", errors="replace"), truncated
|
||||||
|
|
||||||
async def load_skill(self, name: str, explanation: Optional[str] = None) -> str:
|
async def load_skill(self, name: str) -> str:
|
||||||
"""加载指定 Skill 的完整说明并返回 JSON 字符串。"""
|
"""加载指定 Skill 的完整说明并返回 JSON 字符串。"""
|
||||||
logger.info(f"加载 Skill: name={name}, explanation={explanation or '-'}")
|
logger.info(f"加载 Skill: name={name}")
|
||||||
try:
|
try:
|
||||||
skill = await self._find_skill(name)
|
skill = await self._find_skill(name)
|
||||||
if not skill:
|
if not skill:
|
||||||
@@ -674,8 +670,7 @@ class SkillsMiddleware(AgentMiddleware[SkillsState, ContextT, ResponseT]): # no
|
|||||||
if not isinstance(tool_args, dict):
|
if not isinstance(tool_args, dict):
|
||||||
tool_args = {}
|
tool_args = {}
|
||||||
logger.info(
|
logger.info(
|
||||||
f"开始执行 Skill 工具: name={tool_args.get('name') or '-'}, "
|
f"开始执行 Skill 工具: name={tool_args.get('name') or '-'}"
|
||||||
f"explanation={tool_args.get('explanation') or '-'}"
|
|
||||||
)
|
)
|
||||||
if self.stream_handler and getattr(self.stream_handler, "is_streaming", False):
|
if self.stream_handler and getattr(self.stream_handler, "is_streaming", False):
|
||||||
self.stream_handler.record_tool_call(
|
self.stream_handler.record_tool_call(
|
||||||
|
|||||||
+3
-10
@@ -238,10 +238,6 @@ class MoviePilotTool(BaseTool, metaclass=ABCMeta):
|
|||||||
|
|
||||||
# 获取工具执行提示消息
|
# 获取工具执行提示消息
|
||||||
tool_message = self.get_tool_message(**kwargs)
|
tool_message = self.get_tool_message(**kwargs)
|
||||||
if not tool_message:
|
|
||||||
explanation = kwargs.get("explanation")
|
|
||||||
if explanation:
|
|
||||||
tool_message = explanation
|
|
||||||
|
|
||||||
# 发送工具执行过程消息(流式传输且非最后终结工具时)
|
# 发送工具执行过程消息(流式传输且非最后终结工具时)
|
||||||
if self._stream_handler and self._stream_handler.is_streaming and not self.return_direct:
|
if self._stream_handler and self._stream_handler.is_streaming and not self.return_direct:
|
||||||
@@ -325,16 +321,13 @@ class MoviePilotTool(BaseTool, metaclass=ABCMeta):
|
|||||||
获取工具执行时的友好提示消息。
|
获取工具执行时的友好提示消息。
|
||||||
|
|
||||||
子类可以重写此方法,根据实际参数生成个性化的提示消息。
|
子类可以重写此方法,根据实际参数生成个性化的提示消息。
|
||||||
如果返回 None 或空字符串,将回退使用 explanation 参数。
|
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
**kwargs: 工具的所有参数(包括 explanation)
|
**kwargs: 工具的所有参数
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
str: 友好的提示消息,如果返回 None 或空字符串则使用 explanation
|
str: 友好的提示消息
|
||||||
"""
|
"""
|
||||||
explanation = kwargs.get("explanation")
|
return None
|
||||||
return str(explanation) if explanation else None
|
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
async def run(self, **kwargs) -> str:
|
async def run(self, **kwargs) -> str:
|
||||||
|
|||||||
@@ -20,8 +20,6 @@ from app.schemas.types import SystemConfigKey
|
|||||||
class AddCustomFilterRuleInput(BaseModel):
|
class AddCustomFilterRuleInput(BaseModel):
|
||||||
"""新增自定义过滤规则工具的输入参数模型"""
|
"""新增自定义过滤规则工具的输入参数模型"""
|
||||||
|
|
||||||
explanation: Optional[str] = Field(None,
|
|
||||||
description="Clear explanation of why this tool is being used in the current context",)
|
|
||||||
rule_id: str = Field(
|
rule_id: str = Field(
|
||||||
...,
|
...,
|
||||||
description="Unique custom rule ID. Only letters and numbers are allowed.",
|
description="Unique custom rule ID. Only letters and numbers are allowed.",
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ from app.utils.crypto import HashUtils
|
|||||||
class AddDownloadTasksInput(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(
|
torrent_url: List[str] = Field(
|
||||||
...,
|
...,
|
||||||
description="One or more torrent_url values. Supports refs from get_search_results (`hash:id`) and magnet links."
|
description="One or more torrent_url values. Supports refs from get_search_results (`hash:id`) and magnet links."
|
||||||
|
|||||||
@@ -24,8 +24,6 @@ from app.schemas.types import SystemConfigKey
|
|||||||
class AddRuleGroupInput(BaseModel):
|
class AddRuleGroupInput(BaseModel):
|
||||||
"""新增过滤规则组工具的输入参数模型"""
|
"""新增过滤规则组工具的输入参数模型"""
|
||||||
|
|
||||||
explanation: Optional[str] = Field(None,
|
|
||||||
description="Clear explanation of why this tool is being used in the current context",)
|
|
||||||
name: str = Field(..., description="New rule group name.")
|
name: str = Field(..., description="New rule group name.")
|
||||||
rule_string: str = Field(
|
rule_string: str = Field(
|
||||||
...,
|
...,
|
||||||
|
|||||||
@@ -15,8 +15,6 @@ from app.schemas.types import MediaType, MessageChannel
|
|||||||
class AddSubscribeInput(BaseModel):
|
class AddSubscribeInput(BaseModel):
|
||||||
"""添加订阅工具的输入参数模型"""
|
"""添加订阅工具的输入参数模型"""
|
||||||
|
|
||||||
explanation: Optional[str] = Field(None,
|
|
||||||
description="Clear explanation of why this tool is being used in the current context",)
|
|
||||||
title: str = Field(
|
title: str = Field(
|
||||||
...,
|
...,
|
||||||
description="The title of the media to subscribe to (e.g., 'The Matrix', 'Breaking Bad')",
|
description="The title of the media to subscribe to (e.g., 'The Matrix', 'Breaking Bad')",
|
||||||
|
|||||||
@@ -24,10 +24,6 @@ class UserChoiceOptionInput(BaseModel):
|
|||||||
...,
|
...,
|
||||||
description="The exact content that will be sent back to the agent after the user clicks this button",
|
description="The exact content that will be sent back to the agent after the user clicks this button",
|
||||||
)
|
)
|
||||||
description: Optional[str] = Field(
|
|
||||||
None,
|
|
||||||
description="Optional user-facing description stored in chat history after this option is selected",
|
|
||||||
)
|
|
||||||
|
|
||||||
@model_validator(mode="after")
|
@model_validator(mode="after")
|
||||||
def validate_option(self):
|
def validate_option(self):
|
||||||
@@ -44,8 +40,6 @@ class UserChoiceOptionInput(BaseModel):
|
|||||||
class AskUserChoiceInput(BaseModel):
|
class AskUserChoiceInput(BaseModel):
|
||||||
"""按钮选择工具输入。"""
|
"""按钮选择工具输入。"""
|
||||||
|
|
||||||
explanation: Optional[str] = Field(None,
|
|
||||||
description="Clear explanation of why the agent needs the user to choose from buttons",)
|
|
||||||
message: str = Field(
|
message: str = Field(
|
||||||
...,
|
...,
|
||||||
description="Question or prompt shown to the user together with the buttons",
|
description="Question or prompt shown to the user together with the buttons",
|
||||||
@@ -166,7 +160,6 @@ class AskUserChoiceTool(MoviePilotTool):
|
|||||||
AgentInteractionOption(
|
AgentInteractionOption(
|
||||||
label=option.label.strip(),
|
label=option.label.strip(),
|
||||||
value=option.value.strip(),
|
value=option.value.strip(),
|
||||||
description=(option.description.strip() if option.description else None),
|
|
||||||
)
|
)
|
||||||
for option in options
|
for option in options
|
||||||
]
|
]
|
||||||
@@ -190,7 +183,6 @@ class AskUserChoiceTool(MoviePilotTool):
|
|||||||
"callback_data": (
|
"callback_data": (
|
||||||
f"agent_interaction:choice:{request.request_id}:{index}"
|
f"agent_interaction:choice:{request.request_id}:{index}"
|
||||||
),
|
),
|
||||||
"description": option.description or option.label,
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
if len(current_row) >= max_per_row:
|
if len(current_row) >= max_per_row:
|
||||||
|
|||||||
@@ -47,8 +47,6 @@ class BrowserAction(str, Enum):
|
|||||||
class BrowseWebpageInput(BaseModel):
|
class BrowseWebpageInput(BaseModel):
|
||||||
"""浏览器操作工具的输入参数模型"""
|
"""浏览器操作工具的输入参数模型"""
|
||||||
|
|
||||||
explanation: Optional[str] = Field(None,
|
|
||||||
description="Clear explanation of why this browser action is being performed",)
|
|
||||||
action: str = Field(
|
action: str = Field(
|
||||||
...,
|
...,
|
||||||
description=(
|
description=(
|
||||||
|
|||||||
@@ -20,8 +20,6 @@ from app.schemas.types import SystemConfigKey
|
|||||||
class DeleteCustomFilterRuleInput(BaseModel):
|
class DeleteCustomFilterRuleInput(BaseModel):
|
||||||
"""删除自定义过滤规则工具的输入参数模型"""
|
"""删除自定义过滤规则工具的输入参数模型"""
|
||||||
|
|
||||||
explanation: Optional[str] = Field(None,
|
|
||||||
description="Clear explanation of why this tool is being used in the current context",)
|
|
||||||
rule_id: str = Field(..., description="Custom rule ID to delete.")
|
rule_id: str = Field(..., description="Custom rule ID to delete.")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -13,8 +13,6 @@ from app.log import logger
|
|||||||
class DeleteDownloadHistoryInput(BaseModel):
|
class DeleteDownloadHistoryInput(BaseModel):
|
||||||
"""删除下载历史记录工具的输入参数模型"""
|
"""删除下载历史记录工具的输入参数模型"""
|
||||||
|
|
||||||
explanation: Optional[str] = Field(None,
|
|
||||||
description="Clear explanation of why this tool is being used in the current context",)
|
|
||||||
history_id: int = Field(
|
history_id: int = Field(
|
||||||
..., description="The ID of the download history record to delete"
|
..., description="The ID of the download history record to delete"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -13,8 +13,6 @@ from app.log import logger
|
|||||||
class DeleteDownloadTasksInput(BaseModel):
|
class DeleteDownloadTasksInput(BaseModel):
|
||||||
"""删除下载任务工具的输入参数模型"""
|
"""删除下载任务工具的输入参数模型"""
|
||||||
|
|
||||||
explanation: Optional[str] = Field(None,
|
|
||||||
description="Clear explanation of why this tool is being used in the current context",)
|
|
||||||
hash: str = Field(
|
hash: str = Field(
|
||||||
..., description="Task hash (can be obtained from query_download_tasks tool)"
|
..., description="Task hash (can be obtained from query_download_tasks tool)"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -19,8 +19,6 @@ from app.schemas.types import SystemConfigKey
|
|||||||
class DeleteRuleGroupInput(BaseModel):
|
class DeleteRuleGroupInput(BaseModel):
|
||||||
"""删除过滤规则组工具的输入参数模型"""
|
"""删除过滤规则组工具的输入参数模型"""
|
||||||
|
|
||||||
explanation: Optional[str] = Field(None,
|
|
||||||
description="Clear explanation of why this tool is being used in the current context",)
|
|
||||||
name: str = Field(..., description="Rule group name to delete.")
|
name: str = Field(..., description="Rule group name to delete.")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -16,8 +16,6 @@ from app.schemas.types import EventType
|
|||||||
class DeleteSubscribeInput(BaseModel):
|
class DeleteSubscribeInput(BaseModel):
|
||||||
"""删除订阅工具的输入参数模型"""
|
"""删除订阅工具的输入参数模型"""
|
||||||
|
|
||||||
explanation: Optional[str] = Field(None,
|
|
||||||
description="Clear explanation of why this tool is being used in the current context",)
|
|
||||||
subscribe_id: int = Field(
|
subscribe_id: int = Field(
|
||||||
...,
|
...,
|
||||||
description="The ID of the subscription to delete (can be obtained from query_subscribes tool)",
|
description="The ID of the subscription to delete (can be obtained from query_subscribes tool)",
|
||||||
|
|||||||
@@ -15,8 +15,6 @@ from app.schemas import FileItem
|
|||||||
class DeleteTransferHistoryInput(BaseModel):
|
class DeleteTransferHistoryInput(BaseModel):
|
||||||
"""删除整理历史记录工具的输入参数模型"""
|
"""删除整理历史记录工具的输入参数模型"""
|
||||||
|
|
||||||
explanation: Optional[str] = Field(None,
|
|
||||||
description="Clear explanation of why this tool is being used in the current context",)
|
|
||||||
history_id: int = Field(
|
history_id: int = Field(
|
||||||
..., description="The ID of the transfer history record to delete"
|
..., description="The ID of the transfer history record to delete"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -135,7 +135,6 @@ class _CommandOutput:
|
|||||||
class ExecuteCommandInput(BaseModel):
|
class ExecuteCommandInput(BaseModel):
|
||||||
"""执行 Shell 命令工具的输入参数模型。"""
|
"""执行 Shell 命令工具的输入参数模型。"""
|
||||||
|
|
||||||
explanation: Optional[str] = Field(None, description="Clear explanation of why this command action is needed")
|
|
||||||
action: Optional[Literal["start", "read", "wait", "write", "kill", "run"]] = Field(
|
action: Optional[Literal["start", "read", "wait", "write", "kill", "run"]] = Field(
|
||||||
"start",
|
"start",
|
||||||
description=(
|
description=(
|
||||||
|
|||||||
@@ -15,8 +15,6 @@ from app.schemas.types import MediaType, media_type_to_agent
|
|||||||
class GetRecommendationsInput(BaseModel):
|
class GetRecommendationsInput(BaseModel):
|
||||||
"""获取推荐工具的输入参数模型"""
|
"""获取推荐工具的输入参数模型"""
|
||||||
|
|
||||||
explanation: Optional[str] = Field(None,
|
|
||||||
description="Clear explanation of why this tool is being used in the current context",)
|
|
||||||
source: Optional[str] = Field(
|
source: Optional[str] = Field(
|
||||||
"tmdb_trending",
|
"tmdb_trending",
|
||||||
description="Recommendation source: "
|
description="Recommendation source: "
|
||||||
|
|||||||
@@ -21,8 +21,6 @@ from ._torrent_search_utils import (
|
|||||||
class GetSearchResultsInput(BaseModel):
|
class GetSearchResultsInput(BaseModel):
|
||||||
"""获取搜索结果工具的输入参数模型"""
|
"""获取搜索结果工具的输入参数模型"""
|
||||||
|
|
||||||
explanation: Optional[str] = Field(None,
|
|
||||||
description="Clear explanation of why this tool is being used in the current context",)
|
|
||||||
site: Optional[List[str]] = Field(None, description="Site name filters")
|
site: Optional[List[str]] = Field(None, description="Site name filters")
|
||||||
season: Optional[List[str]] = Field(None, description="Season or episode filters")
|
season: Optional[List[str]] = Field(None, description="Season or episode filters")
|
||||||
free_state: Optional[List[str]] = Field(None, description="Promotion state filters")
|
free_state: Optional[List[str]] = Field(None, description="Promotion state filters")
|
||||||
|
|||||||
@@ -19,8 +19,6 @@ from app.log import logger
|
|||||||
class InstallPluginInput(BaseModel):
|
class InstallPluginInput(BaseModel):
|
||||||
"""安装插件工具的输入参数模型"""
|
"""安装插件工具的输入参数模型"""
|
||||||
|
|
||||||
explanation: Optional[str] = Field(None,
|
|
||||||
description="Clear explanation of why this tool is being used in the current context",)
|
|
||||||
plugin_id: str = Field(
|
plugin_id: str = Field(
|
||||||
...,
|
...,
|
||||||
description="Exact plugin ID to install. Use query_market_plugins first to find the correct plugin_id.",
|
description="Exact plugin ID to install. Use query_market_plugins first to find the correct plugin_id.",
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ from app.utils.string import StringUtils
|
|||||||
|
|
||||||
class ListDirectoryInput(BaseModel):
|
class ListDirectoryInput(BaseModel):
|
||||||
"""查询文件系统目录内容工具的输入参数模型"""
|
"""查询文件系统目录内容工具的输入参数模型"""
|
||||||
explanation: Optional[str] = Field(None, description="Clear explanation of why this tool is being used in the current context")
|
|
||||||
path: str = Field(..., description="Directory path to list contents (e.g., '/home/user/downloads' or 'C:/Downloads')")
|
path: str = Field(..., description="Directory path to list contents (e.g., '/home/user/downloads' or 'C:/Downloads')")
|
||||||
storage: Optional[str] = Field("local", description="Storage type (default: 'local' for local file system, can be 'smb', 'alist', etc.)")
|
storage: Optional[str] = Field("local", description="Storage type (default: 'local' for local file system, can be 'smb', 'alist', etc.)")
|
||||||
sort_by: Optional[str] = Field("name", description="Sort order: 'name' for alphabetical sorting, 'time' for modification time sorting (default: 'name')")
|
sort_by: Optional[str] = Field("name", description="Sort order: 'name' for alphabetical sorting, 'time' for modification time sorting (default: 'name')")
|
||||||
|
|||||||
@@ -13,8 +13,6 @@ from app.log import logger
|
|||||||
class ListSlashCommandsInput(BaseModel):
|
class ListSlashCommandsInput(BaseModel):
|
||||||
"""查询所有可用斜杠命令工具的输入参数模型"""
|
"""查询所有可用斜杠命令工具的输入参数模型"""
|
||||||
|
|
||||||
explanation: Optional[str] = Field(None,
|
|
||||||
description="Clear explanation of why this tool is being used in the current context",)
|
|
||||||
|
|
||||||
|
|
||||||
class ListSlashCommandsTool(MoviePilotTool):
|
class ListSlashCommandsTool(MoviePilotTool):
|
||||||
|
|||||||
@@ -18,8 +18,6 @@ from app.log import logger
|
|||||||
class QueryBuiltinFilterRulesInput(BaseModel):
|
class QueryBuiltinFilterRulesInput(BaseModel):
|
||||||
"""查询内置过滤规则工具的输入参数模型"""
|
"""查询内置过滤规则工具的输入参数模型"""
|
||||||
|
|
||||||
explanation: Optional[str] = Field(None,
|
|
||||||
description="Clear explanation of why this tool is being used in the current context",)
|
|
||||||
rule_ids: Optional[List[str]] = Field(
|
rule_ids: Optional[List[str]] = Field(
|
||||||
None,
|
None,
|
||||||
description="Optional list of built-in rule IDs to query. If omitted, return all built-in rules.",
|
description="Optional list of built-in rule IDs to query. If omitted, return all built-in rules.",
|
||||||
|
|||||||
@@ -19,8 +19,6 @@ from app.log import logger
|
|||||||
class QueryCustomFilterRulesInput(BaseModel):
|
class QueryCustomFilterRulesInput(BaseModel):
|
||||||
"""查询自定义过滤规则工具的输入参数模型"""
|
"""查询自定义过滤规则工具的输入参数模型"""
|
||||||
|
|
||||||
explanation: Optional[str] = Field(None,
|
|
||||||
description="Clear explanation of why this tool is being used in the current context",)
|
|
||||||
rule_ids: Optional[List[str]] = Field(
|
rule_ids: Optional[List[str]] = Field(
|
||||||
None,
|
None,
|
||||||
description="Optional list of custom rule IDs to query. If omitted, return all custom rules.",
|
description="Optional list of custom rule IDs to query. If omitted, return all custom rules.",
|
||||||
|
|||||||
@@ -15,8 +15,6 @@ from app.schemas.types import SystemConfigKey
|
|||||||
class QueryCustomIdentifiersInput(BaseModel):
|
class QueryCustomIdentifiersInput(BaseModel):
|
||||||
"""查询自定义识别词工具的输入参数模型"""
|
"""查询自定义识别词工具的输入参数模型"""
|
||||||
|
|
||||||
explanation: Optional[str] = Field(None,
|
|
||||||
description="Clear explanation of why this tool is being used in the current context",)
|
|
||||||
|
|
||||||
|
|
||||||
class QueryCustomIdentifiersTool(MoviePilotTool):
|
class QueryCustomIdentifiersTool(MoviePilotTool):
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ from app.log import logger
|
|||||||
|
|
||||||
class QueryDirectorySettingsInput(BaseModel):
|
class QueryDirectorySettingsInput(BaseModel):
|
||||||
"""查询系统目录设置工具的输入参数模型"""
|
"""查询系统目录设置工具的输入参数模型"""
|
||||||
explanation: Optional[str] = Field(None, description="Clear explanation of why this tool is being used in the current context")
|
|
||||||
directory_type: Optional[str] = Field("all",
|
directory_type: Optional[str] = Field("all",
|
||||||
description="Filter directories by type: 'download' for download directories, 'library' for media library directories, 'all' for all directories")
|
description="Filter directories by type: 'download' for download directories, 'library' for media library directories, 'all' for all directories")
|
||||||
storage_type: Optional[str] = Field("all",
|
storage_type: Optional[str] = Field("all",
|
||||||
|
|||||||
@@ -14,10 +14,6 @@ from app.log import logger
|
|||||||
class QueryDoctorReportInput(BaseModel):
|
class QueryDoctorReportInput(BaseModel):
|
||||||
"""查询 Doctor 诊断报告工具的输入参数模型。"""
|
"""查询 Doctor 诊断报告工具的输入参数模型。"""
|
||||||
|
|
||||||
explanation: Optional[str] = Field(
|
|
||||||
None,
|
|
||||||
description="Clear explanation of why this tool is being used in the current context",
|
|
||||||
)
|
|
||||||
deep: Optional[bool] = Field(
|
deep: Optional[bool] = Field(
|
||||||
False,
|
False,
|
||||||
description=(
|
description=(
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ from app.schemas.types import TorrentQueryStatus, media_type_to_agent
|
|||||||
|
|
||||||
class QueryDownloadTasksInput(BaseModel):
|
class QueryDownloadTasksInput(BaseModel):
|
||||||
"""查询下载工具的输入参数模型"""
|
"""查询下载工具的输入参数模型"""
|
||||||
explanation: Optional[str] = Field(None, description="Clear explanation of why this tool is being used in the current context")
|
|
||||||
downloader: Optional[str] = Field(None,
|
downloader: Optional[str] = Field(None,
|
||||||
description="Name of specific downloader to query (optional, if not provided queries all configured downloaders)")
|
description="Name of specific downloader to query (optional, if not provided queries all configured downloaders)")
|
||||||
status: Optional[str] = Field("all",
|
status: Optional[str] = Field("all",
|
||||||
|
|||||||
@@ -14,9 +14,6 @@ from app.schemas.types import SystemConfigKey
|
|||||||
|
|
||||||
class QueryDownloadersInput(BaseModel):
|
class QueryDownloadersInput(BaseModel):
|
||||||
"""查询下载器工具的输入参数模型"""
|
"""查询下载器工具的输入参数模型"""
|
||||||
explanation: Optional[str] = Field(None, description="Clear explanation of why this tool is being used in the current context")
|
|
||||||
|
|
||||||
|
|
||||||
class QueryDownloadersTool(MoviePilotTool):
|
class QueryDownloadersTool(MoviePilotTool):
|
||||||
name: str = "query_downloaders"
|
name: str = "query_downloaders"
|
||||||
tags: list[str] = [
|
tags: list[str] = [
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ from app.log import logger
|
|||||||
|
|
||||||
class QueryEpisodeScheduleInput(BaseModel):
|
class QueryEpisodeScheduleInput(BaseModel):
|
||||||
"""查询剧集上映时间工具的输入参数模型"""
|
"""查询剧集上映时间工具的输入参数模型"""
|
||||||
explanation: Optional[str] = Field(None, description="Clear explanation of why this tool is being used in the current context")
|
|
||||||
tmdb_id: int = Field(..., description="TMDB ID of the TV series (can be obtained from search_media tool)")
|
tmdb_id: int = Field(..., description="TMDB ID of the TV series (can be obtained from search_media tool)")
|
||||||
season: int = Field(..., description="Season number to query")
|
season: int = Field(..., description="Season number to query")
|
||||||
episode_group: Optional[str] = Field(None, description="Episode group ID (optional)")
|
episode_group: Optional[str] = Field(None, description="Episode group ID (optional)")
|
||||||
|
|||||||
@@ -22,8 +22,6 @@ from app.log import logger
|
|||||||
class QueryInstalledPluginsInput(BaseModel):
|
class QueryInstalledPluginsInput(BaseModel):
|
||||||
"""查询已安装插件工具的输入参数模型"""
|
"""查询已安装插件工具的输入参数模型"""
|
||||||
|
|
||||||
explanation: Optional[str] = Field(None,
|
|
||||||
description="Clear explanation of why this tool is being used in the current context",)
|
|
||||||
query: Optional[str] = Field(
|
query: Optional[str] = Field(
|
||||||
None,
|
None,
|
||||||
description="Optional keyword to filter installed plugins by plugin ID, name, description, or author.",
|
description="Optional keyword to filter installed plugins by plugin ID, name, description, or author.",
|
||||||
|
|||||||
@@ -77,7 +77,6 @@ def _build_tv_server_result(existing_seasons: OrderedDict, total_seasons: Ordere
|
|||||||
|
|
||||||
class QueryLibraryExistsInput(BaseModel):
|
class QueryLibraryExistsInput(BaseModel):
|
||||||
"""查询媒体库工具的输入参数模型"""
|
"""查询媒体库工具的输入参数模型"""
|
||||||
explanation: Optional[str] = Field(None, description="Clear explanation of why this tool is being used in the current context")
|
|
||||||
tmdb_id: Optional[int] = Field(None, description="TMDB ID (can be obtained from search_media tool). Either tmdb_id or douban_id must be provided.")
|
tmdb_id: Optional[int] = Field(None, description="TMDB ID (can be obtained from search_media tool). Either tmdb_id or douban_id must be provided.")
|
||||||
douban_id: Optional[str] = Field(None, description="Douban ID (can be obtained from search_media tool). Either tmdb_id or douban_id must be provided.")
|
douban_id: Optional[str] = Field(None, description="Douban ID (can be obtained from search_media tool). Either tmdb_id or douban_id must be provided.")
|
||||||
media_type: Optional[str] = Field(None, description="Allowed values: movie, tv")
|
media_type: Optional[str] = Field(None, description="Allowed values: movie, tv")
|
||||||
|
|||||||
@@ -18,8 +18,6 @@ PAGE_SIZE = 20
|
|||||||
class QueryLibraryLatestInput(BaseModel):
|
class QueryLibraryLatestInput(BaseModel):
|
||||||
"""查询媒体服务器最近入库影片工具的输入参数模型"""
|
"""查询媒体服务器最近入库影片工具的输入参数模型"""
|
||||||
|
|
||||||
explanation: Optional[str] = Field(None,
|
|
||||||
description="Clear explanation of why this tool is being used in the current context",)
|
|
||||||
server: Optional[str] = Field(
|
server: Optional[str] = Field(
|
||||||
None,
|
None,
|
||||||
description="Media server name (optional, if not specified queries all enabled media servers)",
|
description="Media server name (optional, if not specified queries all enabled media servers)",
|
||||||
|
|||||||
@@ -21,8 +21,6 @@ from app.log import logger
|
|||||||
class QueryMarketPluginsInput(BaseModel):
|
class QueryMarketPluginsInput(BaseModel):
|
||||||
"""查询插件市场工具的输入参数模型"""
|
"""查询插件市场工具的输入参数模型"""
|
||||||
|
|
||||||
explanation: Optional[str] = Field(None,
|
|
||||||
description="Clear explanation of why this tool is being used in the current context",)
|
|
||||||
query: Optional[str] = Field(
|
query: Optional[str] = Field(
|
||||||
None,
|
None,
|
||||||
description="Optional keyword to filter plugin market results by plugin ID, name, description, or author.",
|
description="Optional keyword to filter plugin market results by plugin ID, name, description, or author.",
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ SEASON_PREVIEW_LIMIT = 100
|
|||||||
|
|
||||||
class QueryMediaDetailInput(BaseModel):
|
class QueryMediaDetailInput(BaseModel):
|
||||||
"""查询媒体详情工具的输入参数模型"""
|
"""查询媒体详情工具的输入参数模型"""
|
||||||
explanation: Optional[str] = Field(None, description="Clear explanation of why this tool is being used in the current context")
|
|
||||||
tmdb_id: Optional[int] = Field(None, description="TMDB ID of the media (movie or TV series, can be obtained from search_media tool)")
|
tmdb_id: Optional[int] = Field(None, description="TMDB ID of the media (movie or TV series, can be obtained from search_media tool)")
|
||||||
douban_id: Optional[str] = Field(None, description="Douban ID of the media (alternative to tmdb_id)")
|
douban_id: Optional[str] = Field(None, description="Douban ID of the media (alternative to tmdb_id)")
|
||||||
media_type: str = Field(..., description="Allowed values: movie, tv")
|
media_type: str = Field(..., description="Allowed values: movie, tv")
|
||||||
|
|||||||
@@ -14,8 +14,6 @@ from app.log import logger
|
|||||||
class QueryPersonasInput(BaseModel):
|
class QueryPersonasInput(BaseModel):
|
||||||
"""查询人格工具的输入参数模型。"""
|
"""查询人格工具的输入参数模型。"""
|
||||||
|
|
||||||
explanation: Optional[str] = Field(None,
|
|
||||||
description="Clear explanation of why this tool is being used in the current context",)
|
|
||||||
query: Optional[str] = Field(
|
query: Optional[str] = Field(
|
||||||
None,
|
None,
|
||||||
description=(
|
description=(
|
||||||
|
|||||||
@@ -14,8 +14,6 @@ from app.log import logger
|
|||||||
class QueryPluginCapabilitiesInput(BaseModel):
|
class QueryPluginCapabilitiesInput(BaseModel):
|
||||||
"""查询插件能力工具的输入参数模型"""
|
"""查询插件能力工具的输入参数模型"""
|
||||||
|
|
||||||
explanation: Optional[str] = Field(None,
|
|
||||||
description="Clear explanation of why this tool is being used in the current context",)
|
|
||||||
plugin_id: Optional[str] = Field(
|
plugin_id: Optional[str] = Field(
|
||||||
None,
|
None,
|
||||||
description="Optional plugin ID to query capabilities for a specific plugin. "
|
description="Optional plugin ID to query capabilities for a specific plugin. "
|
||||||
|
|||||||
@@ -15,8 +15,6 @@ from app.log import logger
|
|||||||
class QueryPluginConfigInput(BaseModel):
|
class QueryPluginConfigInput(BaseModel):
|
||||||
"""查询插件配置工具的输入参数模型"""
|
"""查询插件配置工具的输入参数模型"""
|
||||||
|
|
||||||
explanation: Optional[str] = Field(None,
|
|
||||||
description="Clear explanation of why this tool is being used in the current context",)
|
|
||||||
plugin_id: str = Field(
|
plugin_id: str = Field(
|
||||||
...,
|
...,
|
||||||
description="The plugin ID to query. Use query_installed_plugins first to discover valid plugin IDs.",
|
description="The plugin ID to query. Use query_installed_plugins first to discover valid plugin IDs.",
|
||||||
|
|||||||
@@ -19,8 +19,6 @@ from app.log import logger
|
|||||||
class QueryPluginDataInput(BaseModel):
|
class QueryPluginDataInput(BaseModel):
|
||||||
"""查询插件数据工具的输入参数模型"""
|
"""查询插件数据工具的输入参数模型"""
|
||||||
|
|
||||||
explanation: Optional[str] = Field(None,
|
|
||||||
description="Clear explanation of why this tool is being used in the current context",)
|
|
||||||
plugin_id: str = Field(
|
plugin_id: str = Field(
|
||||||
...,
|
...,
|
||||||
description="The plugin ID to query. Use query_installed_plugins first to discover valid plugin IDs.",
|
description="The plugin ID to query. Use query_installed_plugins first to discover valid plugin IDs.",
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ MAX_PAGE_SIZE = 50
|
|||||||
|
|
||||||
class QueryPopularSubscribesInput(BaseModel):
|
class QueryPopularSubscribesInput(BaseModel):
|
||||||
"""查询热门订阅工具的输入参数模型"""
|
"""查询热门订阅工具的输入参数模型"""
|
||||||
explanation: Optional[str] = Field(None, description="Clear explanation of why this tool is being used in the current context")
|
|
||||||
media_type: str = Field(..., description="Allowed values: movie, tv")
|
media_type: str = Field(..., description="Allowed values: movie, tv")
|
||||||
page: Optional[int] = Field(1, description="Page number for pagination (default: 1)")
|
page: Optional[int] = Field(1, description="Page number for pagination (default: 1)")
|
||||||
count: Optional[int] = Field(30, description="Number of items per page (default: 30, max: 50)")
|
count: Optional[int] = Field(30, description="Number of items per page (default: 30, max: 50)")
|
||||||
|
|||||||
@@ -19,8 +19,6 @@ from app.log import logger
|
|||||||
class QueryRuleGroupsInput(BaseModel):
|
class QueryRuleGroupsInput(BaseModel):
|
||||||
"""查询规则组工具的输入参数模型"""
|
"""查询规则组工具的输入参数模型"""
|
||||||
|
|
||||||
explanation: Optional[str] = Field(None,
|
|
||||||
description="Clear explanation of why this tool is being used in the current context",)
|
|
||||||
group_names: Optional[List[str]] = Field(
|
group_names: Optional[List[str]] = Field(
|
||||||
None,
|
None,
|
||||||
description="Optional list of rule group names to query. If omitted, return all rule groups.",
|
description="Optional list of rule group names to query. If omitted, return all rule groups.",
|
||||||
|
|||||||
@@ -12,9 +12,6 @@ from app.log import logger
|
|||||||
|
|
||||||
class QuerySchedulersInput(BaseModel):
|
class QuerySchedulersInput(BaseModel):
|
||||||
"""查询定时服务工具的输入参数模型"""
|
"""查询定时服务工具的输入参数模型"""
|
||||||
explanation: Optional[str] = Field(None, description="Clear explanation of why this tool is being used in the current context")
|
|
||||||
|
|
||||||
|
|
||||||
class QuerySchedulersTool(MoviePilotTool):
|
class QuerySchedulersTool(MoviePilotTool):
|
||||||
name: str = "query_schedulers"
|
name: str = "query_schedulers"
|
||||||
tags: list[str] = [
|
tags: list[str] = [
|
||||||
|
|||||||
@@ -22,8 +22,6 @@ def _preview_list(value, limit: int = SITE_USERDATA_DETAIL_PREVIEW_LIMIT) -> tup
|
|||||||
class QuerySiteUserdataInput(BaseModel):
|
class QuerySiteUserdataInput(BaseModel):
|
||||||
"""查询站点用户数据工具的输入参数模型"""
|
"""查询站点用户数据工具的输入参数模型"""
|
||||||
|
|
||||||
explanation: Optional[str] = Field(None,
|
|
||||||
description="Clear explanation of why this tool is being used in the current context",)
|
|
||||||
site_id: int = Field(
|
site_id: int = Field(
|
||||||
...,
|
...,
|
||||||
description="The ID of the site to query user data for (can be obtained from query_sites tool)",
|
description="The ID of the site to query user data for (can be obtained from query_sites tool)",
|
||||||
|
|||||||
@@ -14,8 +14,6 @@ from app.log import logger
|
|||||||
class QuerySitesInput(BaseModel):
|
class QuerySitesInput(BaseModel):
|
||||||
"""查询站点工具的输入参数模型"""
|
"""查询站点工具的输入参数模型"""
|
||||||
|
|
||||||
explanation: Optional[str] = Field(None,
|
|
||||||
description="Clear explanation of why this tool is being used in the current context",)
|
|
||||||
status: Optional[str] = Field(
|
status: Optional[str] = Field(
|
||||||
"all",
|
"all",
|
||||||
description="Filter sites by status: 'active' for enabled sites, 'inactive' for disabled sites, 'all' for all sites",
|
description="Filter sites by status: 'active' for enabled sites, 'inactive' for disabled sites, 'all' for all sites",
|
||||||
|
|||||||
@@ -17,8 +17,6 @@ PAGE_SIZE = 20
|
|||||||
class QuerySubscribeHistoryInput(BaseModel):
|
class QuerySubscribeHistoryInput(BaseModel):
|
||||||
"""查询订阅历史工具的输入参数模型"""
|
"""查询订阅历史工具的输入参数模型"""
|
||||||
|
|
||||||
explanation: Optional[str] = Field(None,
|
|
||||||
description="Clear explanation of why this tool is being used in the current context",)
|
|
||||||
media_type: Optional[str] = Field(
|
media_type: Optional[str] = Field(
|
||||||
"all", description="Allowed values: movie, tv, all"
|
"all", description="Allowed values: movie, tv, all"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ MAX_PAGE_SIZE = 50
|
|||||||
|
|
||||||
class QuerySubscribeSharesInput(BaseModel):
|
class QuerySubscribeSharesInput(BaseModel):
|
||||||
"""查询订阅分享工具的输入参数模型"""
|
"""查询订阅分享工具的输入参数模型"""
|
||||||
explanation: Optional[str] = Field(None, description="Clear explanation of why this tool is being used in the current context")
|
|
||||||
name: Optional[str] = Field(None, description="Filter shares by media name (partial match, optional)")
|
name: Optional[str] = Field(None, description="Filter shares by media name (partial match, optional)")
|
||||||
page: Optional[int] = Field(1, description="Page number for pagination (default: 1)")
|
page: Optional[int] = Field(1, description="Page number for pagination (default: 1)")
|
||||||
count: Optional[int] = Field(30, description="Number of items per page (default: 30, max: 50)")
|
count: Optional[int] = Field(30, description="Number of items per page (default: 30, max: 50)")
|
||||||
|
|||||||
@@ -48,8 +48,6 @@ QUERY_SUBSCRIBE_OUTPUT_FIELDS = [
|
|||||||
class QuerySubscribesInput(BaseModel):
|
class QuerySubscribesInput(BaseModel):
|
||||||
"""查询订阅工具的输入参数模型"""
|
"""查询订阅工具的输入参数模型"""
|
||||||
|
|
||||||
explanation: Optional[str] = Field(None,
|
|
||||||
description="Clear explanation of why this tool is being used in the current context",)
|
|
||||||
status: Optional[str] = Field(
|
status: Optional[str] = Field(
|
||||||
"all",
|
"all",
|
||||||
description="Filter subscriptions by status: 'R' for enabled subscriptions, 'S' for paused ones, 'all' for all subscriptions",
|
description="Filter subscriptions by status: 'R' for enabled subscriptions, 'S' for paused ones, 'all' for all subscriptions",
|
||||||
|
|||||||
@@ -23,8 +23,6 @@ from app.log import logger
|
|||||||
class QuerySystemSettingsInput(BaseModel):
|
class QuerySystemSettingsInput(BaseModel):
|
||||||
"""查询系统设置工具的输入参数模型。"""
|
"""查询系统设置工具的输入参数模型。"""
|
||||||
|
|
||||||
explanation: Optional[str] = Field(None,
|
|
||||||
description="Clear explanation of why this tool is being used in the current context",)
|
|
||||||
setting_key: Optional[str] = Field(
|
setting_key: Optional[str] = Field(
|
||||||
None,
|
None,
|
||||||
description=(
|
description=(
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ from app.utils.jieba import cut as jieba_cut
|
|||||||
|
|
||||||
class QueryTransferHistoryInput(BaseModel):
|
class QueryTransferHistoryInput(BaseModel):
|
||||||
"""查询整理历史记录工具的输入参数模型"""
|
"""查询整理历史记录工具的输入参数模型"""
|
||||||
explanation: Optional[str] = Field(None, description="Clear explanation of why this tool is being used in the current context")
|
|
||||||
title: Optional[str] = Field(None, description="Search by title (optional, supports partial match)")
|
title: Optional[str] = Field(None, description="Search by title (optional, supports partial match)")
|
||||||
status: Optional[str] = Field("all",
|
status: Optional[str] = Field("all",
|
||||||
description="Filter by status: 'success' for successful transfers, 'failed' for failed transfers, 'all' for all records (default: 'all')")
|
description="Filter by status: 'success' for successful transfers, 'failed' for failed transfers, 'all' for all records (default: 'all')")
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ from app.log import logger
|
|||||||
|
|
||||||
class QueryWorkflowsInput(BaseModel):
|
class QueryWorkflowsInput(BaseModel):
|
||||||
"""查询工作流工具的输入参数模型"""
|
"""查询工作流工具的输入参数模型"""
|
||||||
explanation: Optional[str] = Field(None, description="Clear explanation of why this tool is being used in the current context")
|
|
||||||
state: Optional[str] = Field("all", description="Filter workflows by state: 'W' for waiting, 'R' for running, 'P' for paused, 'S' for success, 'F' for failed, 'all' for all workflows (default: 'all')")
|
state: Optional[str] = Field("all", description="Filter workflows by state: 'W' for waiting, 'R' for running, 'P' for paused, 'S' for success, 'F' for failed, 'all' for all workflows (default: 'all')")
|
||||||
name: Optional[str] = Field(None, description="Filter workflows by name (partial match, optional)")
|
name: Optional[str] = Field(None, description="Filter workflows by name (partial match, optional)")
|
||||||
trigger_type: Optional[str] = Field("all", description="Filter workflows by trigger type: 'timer' for scheduled, 'event' for event-triggered, 'manual' for manual, 'all' for all types (default: 'all')")
|
trigger_type: Optional[str] = Field("all", description="Filter workflows by trigger type: 'timer' for scheduled, 'event' for event-triggered, 'manual' for manual, 'all' for all types (default: 'all')")
|
||||||
|
|||||||
@@ -15,10 +15,6 @@ from app.log import logger
|
|||||||
class RecognizeCaptchaInput(BaseModel):
|
class RecognizeCaptchaInput(BaseModel):
|
||||||
"""识别图形验证码工具的输入参数模型。"""
|
"""识别图形验证码工具的输入参数模型。"""
|
||||||
|
|
||||||
explanation: Optional[str] = Field(
|
|
||||||
None,
|
|
||||||
description="Clear explanation of why this captcha image needs to be recognized",
|
|
||||||
)
|
|
||||||
image_url: str = Field(
|
image_url: str = Field(
|
||||||
...,
|
...,
|
||||||
description=(
|
description=(
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ from app.schemas.types import media_type_to_agent
|
|||||||
|
|
||||||
class RecognizeMediaInput(BaseModel):
|
class RecognizeMediaInput(BaseModel):
|
||||||
"""识别媒体信息工具的输入参数模型"""
|
"""识别媒体信息工具的输入参数模型"""
|
||||||
explanation: Optional[str] = Field(None, description="Clear explanation of why this tool is being used in the current context")
|
|
||||||
title: Optional[str] = Field(None, description="The title of the torrent/media to recognize (required for torrent recognition)")
|
title: Optional[str] = Field(None, description="The title of the torrent/media to recognize (required for torrent recognition)")
|
||||||
subtitle: Optional[str] = Field(None, description="The subtitle or description of the torrent (optional, helps improve recognition accuracy)")
|
subtitle: Optional[str] = Field(None, description="The subtitle or description of the torrent (optional, helps improve recognition accuracy)")
|
||||||
path: Optional[str] = Field(None, description="The file path to recognize (required for file recognition, mutually exclusive with title)")
|
path: Optional[str] = Field(None, description="The file path to recognize (required for file recognition, mutually exclusive with title)")
|
||||||
|
|||||||
@@ -17,8 +17,6 @@ from app.log import logger
|
|||||||
class ReloadPluginInput(BaseModel):
|
class ReloadPluginInput(BaseModel):
|
||||||
"""重载插件工具的输入参数模型"""
|
"""重载插件工具的输入参数模型"""
|
||||||
|
|
||||||
explanation: Optional[str] = Field(None,
|
|
||||||
description="Clear explanation of why this tool is being used in the current context",)
|
|
||||||
plugin_id: str = Field(
|
plugin_id: str = Field(
|
||||||
...,
|
...,
|
||||||
description="The plugin ID to reload so the latest saved config takes effect.",
|
description="The plugin ID to reload so the latest saved config takes effect.",
|
||||||
|
|||||||
@@ -12,8 +12,6 @@ from app.log import logger
|
|||||||
class RunSchedulerInput(BaseModel):
|
class RunSchedulerInput(BaseModel):
|
||||||
"""运行定时服务工具的输入参数模型"""
|
"""运行定时服务工具的输入参数模型"""
|
||||||
|
|
||||||
explanation: Optional[str] = Field(None,
|
|
||||||
description="Clear explanation of why this tool is being used in the current context",)
|
|
||||||
job_id: str = Field(
|
job_id: str = Field(
|
||||||
...,
|
...,
|
||||||
description="The ID of the scheduled job to run (can be obtained from query_schedulers tool)",
|
description="The ID of the scheduled job to run (can be obtained from query_schedulers tool)",
|
||||||
|
|||||||
@@ -15,8 +15,6 @@ from app.schemas.types import EventType, MessageChannel
|
|||||||
class RunSlashCommandInput(BaseModel):
|
class RunSlashCommandInput(BaseModel):
|
||||||
"""运行斜杠命令工具的输入参数模型"""
|
"""运行斜杠命令工具的输入参数模型"""
|
||||||
|
|
||||||
explanation: Optional[str] = Field(None,
|
|
||||||
description="Clear explanation of why this tool is being used in the current context",)
|
|
||||||
command: str = Field(
|
command: str = Field(
|
||||||
...,
|
...,
|
||||||
description="The slash command to execute, e.g. '/cookiecloud'. "
|
description="The slash command to execute, e.g. '/cookiecloud'. "
|
||||||
|
|||||||
@@ -14,8 +14,6 @@ from app.log import logger
|
|||||||
class RunWorkflowInput(BaseModel):
|
class RunWorkflowInput(BaseModel):
|
||||||
"""执行工作流工具的输入参数模型"""
|
"""执行工作流工具的输入参数模型"""
|
||||||
|
|
||||||
explanation: Optional[str] = Field(None,
|
|
||||||
description="Clear explanation of why this tool is being used in the current context",)
|
|
||||||
workflow_id: int = Field(
|
workflow_id: int = Field(
|
||||||
..., description="Workflow ID (can be obtained from query_workflows tool)"
|
..., description="Workflow ID (can be obtained from query_workflows tool)"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -16,8 +16,6 @@ from app.schemas import FileItem
|
|||||||
class ScrapeMetadataInput(BaseModel):
|
class ScrapeMetadataInput(BaseModel):
|
||||||
"""刮削媒体元数据工具的输入参数模型"""
|
"""刮削媒体元数据工具的输入参数模型"""
|
||||||
|
|
||||||
explanation: Optional[str] = Field(None,
|
|
||||||
description="Clear explanation of why this tool is being used in the current context",)
|
|
||||||
path: str = Field(
|
path: str = Field(
|
||||||
...,
|
...,
|
||||||
description="Path to the file or directory to scrape metadata for (e.g., '/path/to/file.mkv' or '/path/to/directory')",
|
description="Path to the file or directory to scrape metadata for (e.g., '/path/to/file.mkv' or '/path/to/directory')",
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ from app.schemas.types import MediaType, media_type_to_agent
|
|||||||
|
|
||||||
class SearchMediaInput(BaseModel):
|
class SearchMediaInput(BaseModel):
|
||||||
"""搜索媒体工具的输入参数模型"""
|
"""搜索媒体工具的输入参数模型"""
|
||||||
explanation: Optional[str] = Field(None, description="Clear explanation of why this tool is being used in the current context")
|
|
||||||
title: str = Field(..., description="The title of the media to search for (e.g., 'The Matrix', 'Breaking Bad')")
|
title: str = Field(..., description="The title of the media to search for (e.g., 'The Matrix', 'Breaking Bad')")
|
||||||
year: Optional[str] = Field(None, description="Release year of the media (optional, helps narrow down results)")
|
year: Optional[str] = Field(None, description="Release year of the media (optional, helps narrow down results)")
|
||||||
media_type: Optional[str] = Field(None,
|
media_type: Optional[str] = Field(None,
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ from app.log import logger
|
|||||||
|
|
||||||
class SearchPersonInput(BaseModel):
|
class SearchPersonInput(BaseModel):
|
||||||
"""搜索人物工具的输入参数模型"""
|
"""搜索人物工具的输入参数模型"""
|
||||||
explanation: Optional[str] = Field(None, description="Clear explanation of why this tool is being used in the current context")
|
|
||||||
name: str = Field(..., description="The name of the person to search for (e.g., 'Tom Hanks', '周杰伦')")
|
name: str = Field(..., description="The name of the person to search for (e.g., 'Tom Hanks', '周杰伦')")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ from app.log import logger
|
|||||||
|
|
||||||
class SearchPersonCreditsInput(BaseModel):
|
class SearchPersonCreditsInput(BaseModel):
|
||||||
"""搜索演员参演作品工具的输入参数模型"""
|
"""搜索演员参演作品工具的输入参数模型"""
|
||||||
explanation: Optional[str] = Field(None, description="Clear explanation of why this tool is being used in the current context")
|
|
||||||
person_id: int = Field(..., description="The ID of the person/actor to search for credits (e.g., 31 for Tom Hanks in TMDB)")
|
person_id: int = Field(..., description="The ID of the person/actor to search for credits (e.g., 31 for Tom Hanks in TMDB)")
|
||||||
source: str = Field(..., description="The data source: 'tmdb' for TheMovieDB, 'douban' for Douban, 'bangumi' for Bangumi")
|
source: str = Field(..., description="The data source: 'tmdb' for TheMovieDB, 'douban' for Douban, 'bangumi' for Bangumi")
|
||||||
page: Optional[int] = Field(1, description="Page number for pagination (default: 1)")
|
page: Optional[int] = Field(1, description="Page number for pagination (default: 1)")
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ from app.schemas.types import media_type_to_agent
|
|||||||
|
|
||||||
class SearchSubscribeInput(BaseModel):
|
class SearchSubscribeInput(BaseModel):
|
||||||
"""搜索订阅缺失剧集工具的输入参数模型"""
|
"""搜索订阅缺失剧集工具的输入参数模型"""
|
||||||
explanation: Optional[str] = Field(None, description="Clear explanation of why this tool is being used in the current context")
|
|
||||||
subscribe_id: int = Field(..., description="The ID of the subscription to search for missing episodes (can be obtained from query_subscribes tool)")
|
subscribe_id: int = Field(..., description="The ID of the subscription to search for missing episodes (can be obtained from query_subscribes tool)")
|
||||||
manual: Optional[bool] = Field(False, description="Whether this is a manual search (default: False)")
|
manual: Optional[bool] = Field(False, description="Whether this is a manual search (default: False)")
|
||||||
filter_groups: Optional[List[str]] = Field(None,
|
filter_groups: Optional[List[str]] = Field(None,
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ from ._torrent_search_utils import (
|
|||||||
|
|
||||||
class SearchTorrentsInput(BaseModel):
|
class SearchTorrentsInput(BaseModel):
|
||||||
"""搜索种子工具的输入参数模型"""
|
"""搜索种子工具的输入参数模型"""
|
||||||
explanation: Optional[str] = Field(None, description="Clear explanation of why this tool is being used in the current context")
|
|
||||||
tmdb_id: Optional[int] = Field(None, description="TMDB ID (can be obtained from search_media tool). Either tmdb_id or douban_id must be provided.")
|
tmdb_id: Optional[int] = Field(None, description="TMDB ID (can be obtained from search_media tool). Either tmdb_id or douban_id must be provided.")
|
||||||
douban_id: Optional[str] = Field(None, description="Douban ID (can be obtained from search_media tool). Either tmdb_id or douban_id must be provided.")
|
douban_id: Optional[str] = Field(None, description="Douban ID (can be obtained from search_media tool). Either tmdb_id or douban_id must be provided.")
|
||||||
media_type: Optional[str] = Field(None, description="Allowed values: movie, tv")
|
media_type: Optional[str] = Field(None, description="Allowed values: movie, tv")
|
||||||
|
|||||||
@@ -48,10 +48,6 @@ class _SearchSiteFilter:
|
|||||||
class SearchWebInput(BaseModel):
|
class SearchWebInput(BaseModel):
|
||||||
"""搜索网络内容工具的输入参数模型"""
|
"""搜索网络内容工具的输入参数模型"""
|
||||||
|
|
||||||
explanation: Optional[str] = Field(
|
|
||||||
None,
|
|
||||||
description="Clear explanation of why this tool is being used in the current context",
|
|
||||||
)
|
|
||||||
query: str = Field(
|
query: str = Field(
|
||||||
..., description="The search query string to search for on the web"
|
..., description="The search query string to search for on the web"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -16,8 +16,6 @@ from app.schemas.types import MessageChannel
|
|||||||
class SendLocalFileInput(BaseModel):
|
class SendLocalFileInput(BaseModel):
|
||||||
"""发送本地附件工具输入。"""
|
"""发送本地附件工具输入。"""
|
||||||
|
|
||||||
explanation: Optional[str] = Field(None,
|
|
||||||
description="Clear explanation of why sending this local file helps the user",)
|
|
||||||
file_path: str = Field(
|
file_path: str = Field(
|
||||||
...,
|
...,
|
||||||
description="Absolute path to the local image or file to send to the user",
|
description="Absolute path to the local image or file to send to the user",
|
||||||
|
|||||||
@@ -14,10 +14,6 @@ from app.schemas.types import NotificationType
|
|||||||
class SendMessageInput(BaseModel):
|
class SendMessageInput(BaseModel):
|
||||||
"""发送消息工具的输入参数模型"""
|
"""发送消息工具的输入参数模型"""
|
||||||
|
|
||||||
explanation: Optional[str] = Field(
|
|
||||||
None,
|
|
||||||
description="Clear explanation of why this tool is being used in the current context",
|
|
||||||
)
|
|
||||||
message: Optional[str] = Field(
|
message: Optional[str] = Field(
|
||||||
None,
|
None,
|
||||||
description="The message content to send to the user (should be clear and informative)",
|
description="The message content to send to the user (should be clear and informative)",
|
||||||
|
|||||||
@@ -14,10 +14,6 @@ from app.schemas import Notification, NotificationType
|
|||||||
class SendVoiceMessageInput(BaseModel):
|
class SendVoiceMessageInput(BaseModel):
|
||||||
"""发送语音消息工具输入。"""
|
"""发送语音消息工具输入。"""
|
||||||
|
|
||||||
explanation: Optional[str] = Field(
|
|
||||||
None,
|
|
||||||
description="Clear explanation of why a voice reply is the best fit in the current context",
|
|
||||||
)
|
|
||||||
message: str = Field(
|
message: str = Field(
|
||||||
...,
|
...,
|
||||||
description="The spoken content to send back to the user",
|
description="The spoken content to send back to the user",
|
||||||
|
|||||||
@@ -14,8 +14,6 @@ from app.log import logger
|
|||||||
class SwitchPersonaInput(BaseModel):
|
class SwitchPersonaInput(BaseModel):
|
||||||
"""切换人格工具的输入参数模型。"""
|
"""切换人格工具的输入参数模型。"""
|
||||||
|
|
||||||
explanation: Optional[str] = Field(None,
|
|
||||||
description="Clear explanation of why this tool is being used in the current context",)
|
|
||||||
persona_id: str = Field(
|
persona_id: str = Field(
|
||||||
...,
|
...,
|
||||||
description=(
|
description=(
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ from app.log import logger
|
|||||||
|
|
||||||
class TestSiteInput(BaseModel):
|
class TestSiteInput(BaseModel):
|
||||||
"""测试站点连通性工具的输入参数模型"""
|
"""测试站点连通性工具的输入参数模型"""
|
||||||
explanation: Optional[str] = Field(None, description="Clear explanation of why this tool is being used in the current context")
|
|
||||||
site_identifier: int = Field(..., description="Site ID to test (can be obtained from query_sites tool)")
|
site_identifier: int = Field(..., description="Site ID to test (can be obtained from query_sites tool)")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -14,8 +14,6 @@ from app.schemas import FileItem, MediaType
|
|||||||
class TransferFileInput(BaseModel):
|
class TransferFileInput(BaseModel):
|
||||||
"""整理文件或目录工具的输入参数模型"""
|
"""整理文件或目录工具的输入参数模型"""
|
||||||
|
|
||||||
explanation: Optional[str] = Field(None,
|
|
||||||
description="Clear explanation of why this tool is being used in the current context",)
|
|
||||||
file_path: str = Field(
|
file_path: str = Field(
|
||||||
...,
|
...,
|
||||||
description="Path to the file or directory to transfer (e.g., '/path/to/file.mkv' or '/path/to/directory')",
|
description="Path to the file or directory to transfer (e.g., '/path/to/file.mkv' or '/path/to/directory')",
|
||||||
|
|||||||
@@ -18,8 +18,6 @@ from app.log import logger
|
|||||||
class UninstallPluginInput(BaseModel):
|
class UninstallPluginInput(BaseModel):
|
||||||
"""卸载插件工具的输入参数模型"""
|
"""卸载插件工具的输入参数模型"""
|
||||||
|
|
||||||
explanation: Optional[str] = Field(None,
|
|
||||||
description="Clear explanation of why this tool is being used in the current context",)
|
|
||||||
plugin_id: str = Field(
|
plugin_id: str = Field(
|
||||||
...,
|
...,
|
||||||
description="Exact plugin ID to uninstall. Use query_installed_plugins first to find the correct plugin_id.",
|
description="Exact plugin ID to uninstall. Use query_installed_plugins first to find the correct plugin_id.",
|
||||||
|
|||||||
@@ -23,8 +23,6 @@ from app.schemas.types import SystemConfigKey
|
|||||||
class UpdateCustomFilterRuleInput(BaseModel):
|
class UpdateCustomFilterRuleInput(BaseModel):
|
||||||
"""更新自定义过滤规则工具的输入参数模型"""
|
"""更新自定义过滤规则工具的输入参数模型"""
|
||||||
|
|
||||||
explanation: Optional[str] = Field(None,
|
|
||||||
description="Clear explanation of why this tool is being used in the current context",)
|
|
||||||
current_rule_id: str = Field(
|
current_rule_id: str = Field(
|
||||||
..., description="Existing custom rule ID to update."
|
..., description="Existing custom rule ID to update."
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -15,8 +15,6 @@ from app.schemas.types import SystemConfigKey
|
|||||||
class UpdateCustomIdentifiersInput(BaseModel):
|
class UpdateCustomIdentifiersInput(BaseModel):
|
||||||
"""更新自定义识别词工具的输入参数模型"""
|
"""更新自定义识别词工具的输入参数模型"""
|
||||||
|
|
||||||
explanation: Optional[str] = Field(None,
|
|
||||||
description="Clear explanation of why this tool is being used in the current context",)
|
|
||||||
identifiers: List[str] = Field(
|
identifiers: List[str] = Field(
|
||||||
...,
|
...,
|
||||||
description=(
|
description=(
|
||||||
|
|||||||
@@ -14,10 +14,6 @@ from app.log import logger
|
|||||||
class UpdateDownloadTasksInput(BaseModel):
|
class UpdateDownloadTasksInput(BaseModel):
|
||||||
"""更新下载任务工具的输入参数模型"""
|
"""更新下载任务工具的输入参数模型"""
|
||||||
|
|
||||||
explanation: Optional[str] = Field(
|
|
||||||
None,
|
|
||||||
description="Clear explanation of why this tool is being used in the current context",
|
|
||||||
)
|
|
||||||
hash: str = Field(
|
hash: str = Field(
|
||||||
..., description="Task hash (can be obtained from query_download_tasks tool)"
|
..., description="Task hash (can be obtained from query_download_tasks tool)"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -14,8 +14,6 @@ from app.log import logger
|
|||||||
class UpdatePersonaDefinitionInput(BaseModel):
|
class UpdatePersonaDefinitionInput(BaseModel):
|
||||||
"""更新人格定义工具的输入参数模型。"""
|
"""更新人格定义工具的输入参数模型。"""
|
||||||
|
|
||||||
explanation: Optional[str] = Field(None,
|
|
||||||
description="Clear explanation of why this tool is being used in the current context",)
|
|
||||||
persona_id: str = Field(
|
persona_id: str = Field(
|
||||||
...,
|
...,
|
||||||
description=(
|
description=(
|
||||||
|
|||||||
@@ -15,8 +15,6 @@ from app.log import logger
|
|||||||
class UpdatePluginConfigInput(BaseModel):
|
class UpdatePluginConfigInput(BaseModel):
|
||||||
"""修改插件配置工具的输入参数模型"""
|
"""修改插件配置工具的输入参数模型"""
|
||||||
|
|
||||||
explanation: Optional[str] = Field(None,
|
|
||||||
description="Clear explanation of why this tool is being used in the current context",)
|
|
||||||
plugin_id: str = Field(
|
plugin_id: str = Field(
|
||||||
...,
|
...,
|
||||||
description="The plugin ID to update. Use query_plugin_config first to inspect the current config.",
|
description="The plugin ID to update. Use query_plugin_config first to inspect the current config.",
|
||||||
|
|||||||
@@ -25,8 +25,6 @@ from app.schemas.types import SystemConfigKey
|
|||||||
class UpdateRuleGroupInput(BaseModel):
|
class UpdateRuleGroupInput(BaseModel):
|
||||||
"""更新过滤规则组工具的输入参数模型"""
|
"""更新过滤规则组工具的输入参数模型"""
|
||||||
|
|
||||||
explanation: Optional[str] = Field(None,
|
|
||||||
description="Clear explanation of why this tool is being used in the current context",)
|
|
||||||
current_name: str = Field(..., description="Existing rule group name to update.")
|
current_name: str = Field(..., description="Existing rule group name to update.")
|
||||||
new_name: Optional[str] = Field(
|
new_name: Optional[str] = Field(
|
||||||
None,
|
None,
|
||||||
|
|||||||
@@ -17,8 +17,6 @@ from app.utils.string import StringUtils
|
|||||||
class UpdateSiteInput(BaseModel):
|
class UpdateSiteInput(BaseModel):
|
||||||
"""更新站点工具的输入参数模型"""
|
"""更新站点工具的输入参数模型"""
|
||||||
|
|
||||||
explanation: Optional[str] = Field(None,
|
|
||||||
description="Clear explanation of why this tool is being used in the current context",)
|
|
||||||
site_id: int = Field(
|
site_id: int = Field(
|
||||||
...,
|
...,
|
||||||
description="The ID of the site to update (can be obtained from query_sites tool)",
|
description="The ID of the site to update (can be obtained from query_sites tool)",
|
||||||
|
|||||||
@@ -14,8 +14,6 @@ from app.log import logger
|
|||||||
class UpdateSiteCookieInput(BaseModel):
|
class UpdateSiteCookieInput(BaseModel):
|
||||||
"""更新站点Cookie和UA工具的输入参数模型"""
|
"""更新站点Cookie和UA工具的输入参数模型"""
|
||||||
|
|
||||||
explanation: Optional[str] = Field(None,
|
|
||||||
description="Clear explanation of why this tool is being used in the current context",)
|
|
||||||
site_identifier: int = Field(
|
site_identifier: int = Field(
|
||||||
...,
|
...,
|
||||||
description="Site ID to update Cookie and User-Agent for (can be obtained from query_sites tool)",
|
description="Site ID to update Cookie and User-Agent for (can be obtained from query_sites tool)",
|
||||||
|
|||||||
@@ -16,8 +16,6 @@ from app.schemas.types import EventType
|
|||||||
class UpdateSubscribeInput(BaseModel):
|
class UpdateSubscribeInput(BaseModel):
|
||||||
"""更新订阅工具的输入参数模型"""
|
"""更新订阅工具的输入参数模型"""
|
||||||
|
|
||||||
explanation: Optional[str] = Field(None,
|
|
||||||
description="Clear explanation of why this tool is being used in the current context",)
|
|
||||||
subscribe_id: int = Field(
|
subscribe_id: int = Field(
|
||||||
...,
|
...,
|
||||||
description="The ID of the subscription to update (can be obtained from query_subscribes tool)",
|
description="The ID of the subscription to update (can be obtained from query_subscribes tool)",
|
||||||
|
|||||||
@@ -29,8 +29,6 @@ SettingValue = Optional[Union[list, dict, bool, int, float, str]]
|
|||||||
class UpdateSystemSettingsInput(BaseModel):
|
class UpdateSystemSettingsInput(BaseModel):
|
||||||
"""更新系统设置工具的输入参数模型。"""
|
"""更新系统设置工具的输入参数模型。"""
|
||||||
|
|
||||||
explanation: Optional[str] = Field(None,
|
|
||||||
description="Clear explanation of why this tool is being used in the current context",)
|
|
||||||
setting_key: str = Field(
|
setting_key: str = Field(
|
||||||
...,
|
...,
|
||||||
description=(
|
description=(
|
||||||
|
|||||||
+3
-9
@@ -559,8 +559,6 @@ def _format_tool_detail(tool: Dict[str, Any]) -> None:
|
|||||||
required = set((tool.get("inputSchema") or {}).get("required") or [])
|
required = set((tool.get("inputSchema") or {}).get("required") or [])
|
||||||
fields = []
|
fields = []
|
||||||
for name, schema in properties.items():
|
for name, schema in properties.items():
|
||||||
if name == "explanation":
|
|
||||||
continue
|
|
||||||
fields.append(
|
fields.append(
|
||||||
(
|
(
|
||||||
f"{name}*" if name in required else name,
|
f"{name}*" if name in required else name,
|
||||||
@@ -1133,8 +1131,7 @@ def tool_show(tool_name: str) -> None:
|
|||||||
@click.argument("args", nargs=-1, type=click.UNPROCESSED)
|
@click.argument("args", nargs=-1, type=click.UNPROCESSED)
|
||||||
def tool_run(tool_name: str, args: tuple[str, ...]) -> None:
|
def tool_run(tool_name: str, args: tuple[str, ...]) -> None:
|
||||||
"""运行指定工具"""
|
"""运行指定工具"""
|
||||||
arguments = {"explanation": "CLI invocation"}
|
arguments = _parse_key_value_pairs(args)
|
||||||
arguments.update(_parse_key_value_pairs(args))
|
|
||||||
result = _call_tool(tool_name, arguments, runtime=_backend_runtime())
|
result = _call_tool(tool_name, arguments, runtime=_backend_runtime())
|
||||||
if isinstance(result, (dict, list)):
|
if isinstance(result, (dict, list)):
|
||||||
_print_json(result)
|
_print_json(result)
|
||||||
@@ -1152,7 +1149,7 @@ def scheduler_list() -> None:
|
|||||||
"""列出调度任务"""
|
"""列出调度任务"""
|
||||||
result = _call_tool(
|
result = _call_tool(
|
||||||
"query_schedulers",
|
"query_schedulers",
|
||||||
{"explanation": "List scheduler jobs from local CLI"},
|
{},
|
||||||
runtime=_backend_runtime(),
|
runtime=_backend_runtime(),
|
||||||
)
|
)
|
||||||
if isinstance(result, list):
|
if isinstance(result, list):
|
||||||
@@ -1168,10 +1165,7 @@ def scheduler_run(job_id: str) -> None:
|
|||||||
"""立即执行某个调度任务"""
|
"""立即执行某个调度任务"""
|
||||||
result = _call_tool(
|
result = _call_tool(
|
||||||
"run_scheduler",
|
"run_scheduler",
|
||||||
{
|
{"job_id": job_id},
|
||||||
"explanation": "Run a scheduler job from local CLI",
|
|
||||||
"job_id": job_id,
|
|
||||||
},
|
|
||||||
runtime=_backend_runtime(),
|
runtime=_backend_runtime(),
|
||||||
)
|
)
|
||||||
if isinstance(result, (dict, list)):
|
if isinstance(result, (dict, list)):
|
||||||
|
|||||||
@@ -128,6 +128,8 @@ MoviePilot 也提供普通 REST API 给前端和自动化客户端使用。所
|
|||||||
|
|
||||||
获取所有可用的MCP工具列表。
|
获取所有可用的MCP工具列表。
|
||||||
|
|
||||||
|
工具的 `inputSchema` 只包含实际执行业务所需的参数,不包含用于解释调用原因的通用 `explanation` 参数,以减少 Agent 上下文消耗。
|
||||||
|
|
||||||
**认证**: 需要API KEY,在请求头中添加 `X-API-KEY: <api_key>` 或在查询参数中添加 `apikey=<api_key>`
|
**认证**: 需要API KEY,在请求头中添加 `X-API-KEY: <api_key>` 或在查询参数中添加 `apikey=<api_key>`
|
||||||
|
|
||||||
**响应示例**:
|
**响应示例**:
|
||||||
|
|||||||
@@ -150,7 +150,7 @@ Write the rule using the appropriate format. Ensure:
|
|||||||
Use the `query_custom_identifiers` tool to get all current rules:
|
Use the `query_custom_identifiers` tool to get all current rules:
|
||||||
|
|
||||||
```
|
```
|
||||||
query_custom_identifiers(explanation="Checking existing identifiers before adding new rules to avoid duplicates")
|
query_custom_identifiers()
|
||||||
```
|
```
|
||||||
|
|
||||||
### Step 4: Check for Duplicates
|
### Step 4: Check for Duplicates
|
||||||
@@ -166,7 +166,6 @@ Merge new non-duplicate rules into the existing list, then use `update_custom_id
|
|||||||
|
|
||||||
```
|
```
|
||||||
update_custom_identifiers(
|
update_custom_identifiers(
|
||||||
explanation="Adding new identifier rules for [description]",
|
|
||||||
identifiers=["existing rule 1", "existing rule 2", "# new comment", "new rule"]
|
identifiers=["existing rule 1", "existing rule 2", "# new comment", "new rule"]
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
@@ -178,7 +177,7 @@ update_custom_identifiers(
|
|||||||
If the user wants to verify the rule works, use `recognize_media` to test:
|
If the user wants to verify the rule works, use `recognize_media` to test:
|
||||||
|
|
||||||
```
|
```
|
||||||
recognize_media(explanation="Testing recognition after adding identifier", title="the torrent title to test")
|
recognize_media(title="the torrent title to test")
|
||||||
```
|
```
|
||||||
|
|
||||||
### Step 7: Report
|
### Step 7: Report
|
||||||
|
|||||||
@@ -28,6 +28,13 @@ def _write_activity_log(activity_dir, date_str: str, lines: list[str]) -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def _wait_activity_log_tasks(middleware: ActivityLogMiddleware) -> None:
|
||||||
|
"""等待活动日志后台任务完成,避免测试与后台写入竞态。"""
|
||||||
|
tasks = list(middleware._background_tasks)
|
||||||
|
if tasks:
|
||||||
|
await asyncio.gather(*tasks)
|
||||||
|
|
||||||
|
|
||||||
def test_activity_log_index_counts_entries_without_body(tmp_path):
|
def test_activity_log_index_counts_entries_without_body(tmp_path):
|
||||||
"""活动日志索引只应包含条目数量,不暴露完整摘要正文。"""
|
"""活动日志索引只应包含条目数量,不暴露完整摘要正文。"""
|
||||||
date_str = datetime.now().strftime("%Y-%m-%d")
|
date_str = datetime.now().strftime("%Y-%m-%d")
|
||||||
@@ -91,6 +98,7 @@ def test_activity_log_abefore_agent_refreshes_existing_state(tmp_path):
|
|||||||
|
|
||||||
def test_activity_log_skips_trivial_greeting_without_llm(tmp_path):
|
def test_activity_log_skips_trivial_greeting_without_llm(tmp_path):
|
||||||
"""无实际任务的寒暄不应调用 LLM,也不应写入活动日志。"""
|
"""无实际任务的寒暄不应调用 LLM,也不应写入活动日志。"""
|
||||||
|
async def _run_test():
|
||||||
middleware = ActivityLogMiddleware(activity_dir=str(tmp_path))
|
middleware = ActivityLogMiddleware(activity_dir=str(tmp_path))
|
||||||
summarize_mock = AsyncMock(return_value="不应写入")
|
summarize_mock = AsyncMock(return_value="不应写入")
|
||||||
append_mock = AsyncMock()
|
append_mock = AsyncMock()
|
||||||
@@ -102,8 +110,7 @@ def test_activity_log_skips_trivial_greeting_without_llm(tmp_path):
|
|||||||
),
|
),
|
||||||
patch.object(middleware, "_append_activity", new=append_mock),
|
patch.object(middleware, "_append_activity", new=append_mock),
|
||||||
):
|
):
|
||||||
asyncio.run(
|
await middleware.aafter_agent(
|
||||||
middleware.aafter_agent(
|
|
||||||
{
|
{
|
||||||
"messages": [
|
"messages": [
|
||||||
HumanMessage(content="你好"),
|
HumanMessage(content="你好"),
|
||||||
@@ -112,7 +119,11 @@ def test_activity_log_skips_trivial_greeting_without_llm(tmp_path):
|
|||||||
},
|
},
|
||||||
runtime=None,
|
runtime=None,
|
||||||
)
|
)
|
||||||
)
|
await _wait_activity_log_tasks(middleware)
|
||||||
|
|
||||||
|
return summarize_mock, append_mock
|
||||||
|
|
||||||
|
summarize_mock, append_mock = asyncio.run(_run_test())
|
||||||
|
|
||||||
summarize_mock.assert_not_awaited()
|
summarize_mock.assert_not_awaited()
|
||||||
append_mock.assert_not_awaited()
|
append_mock.assert_not_awaited()
|
||||||
@@ -137,18 +148,18 @@ def test_summarize_with_llm_ignores_skip_marker():
|
|||||||
|
|
||||||
def test_activity_log_records_detailed_summary(tmp_path):
|
def test_activity_log_records_detailed_summary(tmp_path):
|
||||||
"""有实际工具动作的交互应写入较完整的活动摘要。"""
|
"""有实际工具动作的交互应写入较完整的活动摘要。"""
|
||||||
middleware = ActivityLogMiddleware(activity_dir=str(tmp_path))
|
|
||||||
summary = (
|
summary = (
|
||||||
"用户要求整理 `/downloads/Show`,助手调用 transfer_file 识别并转移剧集,"
|
"用户要求整理 `/downloads/Show`,助手调用 transfer_file 识别并转移剧集,"
|
||||||
"结果成功写入目标媒体库。"
|
"结果成功写入目标媒体库。"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
async def _run_test():
|
||||||
|
middleware = ActivityLogMiddleware(activity_dir=str(tmp_path))
|
||||||
with patch(
|
with patch(
|
||||||
"app.agent.middleware.activity_log._summarize_with_llm",
|
"app.agent.middleware.activity_log._summarize_with_llm",
|
||||||
new=AsyncMock(return_value=summary),
|
new=AsyncMock(return_value=summary),
|
||||||
):
|
):
|
||||||
asyncio.run(
|
await middleware.aafter_agent(
|
||||||
middleware.aafter_agent(
|
|
||||||
{
|
{
|
||||||
"messages": [
|
"messages": [
|
||||||
HumanMessage(content="帮我整理 /downloads/Show"),
|
HumanMessage(content="帮我整理 /downloads/Show"),
|
||||||
@@ -170,7 +181,9 @@ def test_activity_log_records_detailed_summary(tmp_path):
|
|||||||
},
|
},
|
||||||
runtime=None,
|
runtime=None,
|
||||||
)
|
)
|
||||||
)
|
await _wait_activity_log_tasks(middleware)
|
||||||
|
|
||||||
|
asyncio.run(_run_test())
|
||||||
|
|
||||||
log_files = list(tmp_path.glob("*.md"))
|
log_files = list(tmp_path.glob("*.md"))
|
||||||
assert len(log_files) == 1
|
assert len(log_files) == 1
|
||||||
@@ -179,6 +192,61 @@ def test_activity_log_records_detailed_summary(tmp_path):
|
|||||||
assert "- **" in content
|
assert "- **" in content
|
||||||
|
|
||||||
|
|
||||||
|
def test_activity_log_after_agent_does_not_wait_for_summary(tmp_path):
|
||||||
|
"""活动日志摘要生成应在后台执行,不阻塞当前 Agent 会话结束。"""
|
||||||
|
|
||||||
|
async def _slow_summarize(_conversation_text: str) -> str:
|
||||||
|
"""模拟较慢的活动摘要生成。"""
|
||||||
|
await asyncio.sleep(0.05)
|
||||||
|
return "用户要求检查下载任务,助手调用工具完成检查。"
|
||||||
|
|
||||||
|
async def _run_test():
|
||||||
|
middleware = ActivityLogMiddleware(activity_dir=str(tmp_path))
|
||||||
|
append_mock = AsyncMock()
|
||||||
|
with (
|
||||||
|
patch(
|
||||||
|
"app.agent.middleware.activity_log._summarize_with_llm",
|
||||||
|
side_effect=_slow_summarize,
|
||||||
|
) as summarize_mock,
|
||||||
|
patch.object(middleware, "_append_activity", new=append_mock),
|
||||||
|
):
|
||||||
|
await middleware.aafter_agent(
|
||||||
|
{
|
||||||
|
"messages": [
|
||||||
|
HumanMessage(content="帮我检查下载任务"),
|
||||||
|
AIMessage(
|
||||||
|
content="",
|
||||||
|
tool_calls=[
|
||||||
|
{
|
||||||
|
"name": "query_download_tasks",
|
||||||
|
"args": {},
|
||||||
|
"id": "call_1",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
),
|
||||||
|
ToolMessage(
|
||||||
|
content='{"success": true}',
|
||||||
|
tool_call_id="call_1",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
runtime=None,
|
||||||
|
)
|
||||||
|
called_before_wait = summarize_mock.await_count
|
||||||
|
pending_before_wait = len(middleware._background_tasks)
|
||||||
|
await _wait_activity_log_tasks(middleware)
|
||||||
|
return called_before_wait, pending_before_wait, summarize_mock, append_mock
|
||||||
|
|
||||||
|
called_before_wait, pending_before_wait, summarize_mock, append_mock = asyncio.run(
|
||||||
|
_run_test()
|
||||||
|
)
|
||||||
|
|
||||||
|
assert called_before_wait == 0
|
||||||
|
assert pending_before_wait == 1
|
||||||
|
summarize_mock.assert_awaited_once()
|
||||||
|
append_mock.assert_awaited_once_with("用户要求检查下载任务,助手调用工具完成检查。")
|
||||||
|
|
||||||
|
|
||||||
def test_query_activity_logs_filters_by_keyword_and_date(tmp_path):
|
def test_query_activity_logs_filters_by_keyword_and_date(tmp_path):
|
||||||
"""活动日志查询应支持日期和关键词过滤。"""
|
"""活动日志查询应支持日期和关键词过滤。"""
|
||||||
_write_activity_log(
|
_write_activity_log(
|
||||||
|
|||||||
@@ -563,7 +563,6 @@ class AgentImageSupportTest(unittest.TestCase):
|
|||||||
|
|
||||||
def test_send_message_input_accepts_image_only_payload(self):
|
def test_send_message_input_accepts_image_only_payload(self):
|
||||||
payload = SendMessageInput(
|
payload = SendMessageInput(
|
||||||
explanation="send poster image",
|
|
||||||
image_url="https://example.com/poster.png",
|
image_url="https://example.com/poster.png",
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -659,7 +658,6 @@ class AgentImageSupportTest(unittest.TestCase):
|
|||||||
|
|
||||||
def test_send_local_file_input_accepts_file_payload(self):
|
def test_send_local_file_input_accepts_file_payload(self):
|
||||||
payload = SendLocalFileInput(
|
payload = SendLocalFileInput(
|
||||||
explanation="send generated report",
|
|
||||||
file_path="/tmp/report.txt",
|
file_path="/tmp/report.txt",
|
||||||
message="请下载查看",
|
message="请下载查看",
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -125,6 +125,7 @@ class TestAgentInteraction(unittest.TestCase):
|
|||||||
notification = async_post_message.await_args.args[0]
|
notification = async_post_message.await_args.args[0]
|
||||||
self.assertEqual(notification.text, "请选择要执行的操作")
|
self.assertEqual(notification.text, "请选择要执行的操作")
|
||||||
self.assertEqual(sum(len(row) for row in notification.buttons), 2)
|
self.assertEqual(sum(len(row) for row in notification.buttons), 2)
|
||||||
|
self.assertNotIn("description", notification.buttons[0][0])
|
||||||
|
|
||||||
callback_data = notification.buttons[0][0]["callback_data"]
|
callback_data = notification.buttons[0][0]["callback_data"]
|
||||||
_, _, request_id, option_index = callback_data.split(":")
|
_, _, request_id, option_index = callback_data.split(":")
|
||||||
|
|||||||
@@ -132,7 +132,6 @@ async def test_skill_tool_call_records_streaming_summary(tmp_path):
|
|||||||
tool_call={
|
tool_call={
|
||||||
"args": {
|
"args": {
|
||||||
"name": "moviepilot-cli",
|
"name": "moviepilot-cli",
|
||||||
"explanation": "测试加载技能",
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -150,7 +149,6 @@ async def test_skill_tool_call_records_streaming_summary(tmp_path):
|
|||||||
"tool_message": "Skill loaded",
|
"tool_message": "Skill loaded",
|
||||||
"tool_kwargs": {
|
"tool_kwargs": {
|
||||||
"name": "moviepilot-cli",
|
"name": "moviepilot-cli",
|
||||||
"explanation": "测试加载技能",
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,10 +1,18 @@
|
|||||||
|
import importlib.util
|
||||||
from types import SimpleNamespace
|
from types import SimpleNamespace
|
||||||
from typing import Iterator, Optional
|
from pathlib import Path
|
||||||
|
from typing import Iterator, Optional, Type
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
from app.agent.middleware.activity_log import QueryActivityLogInput
|
||||||
|
from app.agent.middleware.skills import SkillToolInput
|
||||||
from app.agent.tools.base import MoviePilotTool
|
from app.agent.tools.base import MoviePilotTool
|
||||||
from app.agent.tools.factory import MoviePilotToolFactory
|
from app.agent.tools.factory import MoviePilotToolFactory
|
||||||
|
from app.agent.tools.impl.ask_user_choice import AskUserChoiceInput, AskUserChoiceTool
|
||||||
|
from app.agent.tools.impl.send_local_file import SendLocalFileTool
|
||||||
|
from app.agent.tools.impl.send_voice_message import SendVoiceMessageTool
|
||||||
from app.core.plugin import PluginManager
|
from app.core.plugin import PluginManager
|
||||||
from app.utils.singleton import Singleton
|
from app.utils.singleton import Singleton
|
||||||
|
|
||||||
@@ -56,6 +64,66 @@ def _build_plugin(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _schema_properties(args_schema: Type[BaseModel]) -> dict:
|
||||||
|
"""返回工具输入模型的 JSON Schema 属性。"""
|
||||||
|
return args_schema.model_json_schema().get("properties", {})
|
||||||
|
|
||||||
|
|
||||||
|
def _load_lexiannot_tool_schemas() -> list[Type[BaseModel]]:
|
||||||
|
"""只加载 LexiAnnot schema 文件,避免触发插件包可选依赖。"""
|
||||||
|
schema_path = (
|
||||||
|
Path(__file__).resolve().parents[1]
|
||||||
|
/ "app"
|
||||||
|
/ "plugins"
|
||||||
|
/ "lexiannot"
|
||||||
|
/ "schemas.py"
|
||||||
|
)
|
||||||
|
spec = importlib.util.spec_from_file_location(
|
||||||
|
"_test_lexiannot_schemas",
|
||||||
|
schema_path,
|
||||||
|
)
|
||||||
|
assert spec and spec.loader
|
||||||
|
module = importlib.util.module_from_spec(spec)
|
||||||
|
spec.loader.exec_module(module)
|
||||||
|
return [
|
||||||
|
module.VocabularyAnnotatingToolInput,
|
||||||
|
module.QueryAnnotationTasksToolInput,
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def test_agent_tool_schemas_do_not_expose_explanation_parameter() -> None:
|
||||||
|
"""所有 Agent 工具输入模型都不应暴露 explanation 参数。"""
|
||||||
|
tool_classes = [
|
||||||
|
*MoviePilotToolFactory.BUILTIN_TOOL_CLASSES,
|
||||||
|
AskUserChoiceTool,
|
||||||
|
SendLocalFileTool,
|
||||||
|
SendVoiceMessageTool,
|
||||||
|
]
|
||||||
|
middleware_schemas = [
|
||||||
|
SkillToolInput,
|
||||||
|
QueryActivityLogInput,
|
||||||
|
]
|
||||||
|
plugin_schemas = _load_lexiannot_tool_schemas()
|
||||||
|
|
||||||
|
for tool_class in tool_classes:
|
||||||
|
args_schema = getattr(tool_class, "args_schema", None)
|
||||||
|
if args_schema is None:
|
||||||
|
continue
|
||||||
|
assert "explanation" not in _schema_properties(args_schema), tool_class.name
|
||||||
|
|
||||||
|
for args_schema in middleware_schemas + plugin_schemas:
|
||||||
|
assert "explanation" not in _schema_properties(args_schema), args_schema.__name__
|
||||||
|
|
||||||
|
|
||||||
|
def test_ask_user_choice_option_schema_does_not_expose_description() -> None:
|
||||||
|
"""询问用户意图工具的选项参数不应暴露 description 字段。"""
|
||||||
|
schema = AskUserChoiceInput.model_json_schema()
|
||||||
|
option_schema = schema["$defs"]["UserChoiceOptionInput"]
|
||||||
|
|
||||||
|
assert "description" not in option_schema["properties"]
|
||||||
|
assert option_schema["required"] == ["label", "value"]
|
||||||
|
|
||||||
|
|
||||||
def test_plugin_agent_tools_are_cached(plugin_manager: PluginManager) -> None:
|
def test_plugin_agent_tools_are_cached(plugin_manager: PluginManager) -> None:
|
||||||
"""插件智能体工具注册表应缓存,避免同一轮启动反复询问插件实例。"""
|
"""插件智能体工具注册表应缓存,避免同一轮启动反复询问插件实例。"""
|
||||||
calls: list[int] = []
|
calls: list[int] = []
|
||||||
|
|||||||
@@ -49,6 +49,10 @@ class DummyTool(MoviePilotTool):
|
|||||||
name: str = "dummy_tool"
|
name: str = "dummy_tool"
|
||||||
description: str = "Dummy tool for streaming tests."
|
description: str = "Dummy tool for streaming tests."
|
||||||
|
|
||||||
|
def get_tool_message(self, **kwargs) -> str:
|
||||||
|
"""返回固定工具执行提示。"""
|
||||||
|
return "run test tool"
|
||||||
|
|
||||||
async def run(self, **kwargs) -> str:
|
async def run(self, **kwargs) -> str:
|
||||||
"""返回固定工具执行结果。"""
|
"""返回固定工具执行结果。"""
|
||||||
return "ok"
|
return "ok"
|
||||||
@@ -67,7 +71,7 @@ class TestAgentToolStreaming:
|
|||||||
tool.set_stream_handler(handler)
|
tool.set_stream_handler(handler)
|
||||||
|
|
||||||
with patch.object(settings, "AI_AGENT_VERBOSE", False):
|
with patch.object(settings, "AI_AGENT_VERBOSE", False):
|
||||||
result = await tool._arun(explanation="run test tool")
|
result = await tool._arun()
|
||||||
|
|
||||||
buffered_message = await handler.take()
|
buffered_message = await handler.take()
|
||||||
return result, buffered_message
|
return result, buffered_message
|
||||||
@@ -103,7 +107,7 @@ class TestAgentToolStreaming:
|
|||||||
tool.set_stream_handler(handler)
|
tool.set_stream_handler(handler)
|
||||||
|
|
||||||
with patch.object(settings, "AI_AGENT_VERBOSE", False):
|
with patch.object(settings, "AI_AGENT_VERBOSE", False):
|
||||||
await tool._arun(explanation="run test tool")
|
await tool._arun()
|
||||||
|
|
||||||
handler.emit("已经拿到结果")
|
handler.emit("已经拿到结果")
|
||||||
return await handler.take()
|
return await handler.take()
|
||||||
@@ -470,7 +474,7 @@ class TestAgentToolStreaming:
|
|||||||
DummyTool, "send_tool_message", new_callable=AsyncMock
|
DummyTool, "send_tool_message", new_callable=AsyncMock
|
||||||
) as send_tool_message,
|
) as send_tool_message,
|
||||||
):
|
):
|
||||||
result = await tool._arun(explanation="run test tool")
|
result = await tool._arun()
|
||||||
buffered_message = await handler.take()
|
buffered_message = await handler.take()
|
||||||
return result, buffered_message, send_tool_message
|
return result, buffered_message, send_tool_message
|
||||||
|
|
||||||
@@ -497,7 +501,7 @@ class TestAgentToolStreaming:
|
|||||||
DummyTool, "send_tool_message", new_callable=AsyncMock
|
DummyTool, "send_tool_message", new_callable=AsyncMock
|
||||||
) as send_tool_message,
|
) as send_tool_message,
|
||||||
):
|
):
|
||||||
result = await tool._arun(explanation="run test tool")
|
result = await tool._arun()
|
||||||
buffered_message = await handler.take()
|
buffered_message = await handler.take()
|
||||||
return result, buffered_message, send_tool_message
|
return result, buffered_message, send_tool_message
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user