refine tool提示语为更简洁风格,补充last_buffer_char属性及非VERBOSE模式流式输出换行逻辑,新增工具流式分隔符单元测试

This commit is contained in:
jxxghp
2026-04-26 11:15:11 +08:00
parent 90245a13e1
commit 4208c79d72
60 changed files with 148 additions and 84 deletions

View File

@@ -360,3 +360,11 @@ class StreamingHandler:
是否已经通过流式输出发送过消息(当前轮次)
"""
return self._message_response is not None
@property
def last_buffer_char(self) -> str:
"""
返回当前缓冲区最后一个字符;缓冲区为空时返回空字符串。
"""
with self._lock:
return self._buffer[-1:] if self._buffer else ""

View File

@@ -81,7 +81,10 @@ class MoviePilotTool(BaseTool, metaclass=ABCMeta):
if messages:
merged_message = "\n\n".join(messages)
await self.send_tool_message(merged_message)
# 非VERBOSE不重置流保留已输出的模型思考文本
else:
# 非VERBOSE工具边界至少补一个换行避免工具前后的文本直接连在一起
if self._stream_handler.last_buffer_char not in ("", "\n"):
self._stream_handler.emit("\n")
else:
# 未启用流式传输,不发送任何工具消息内容
pass

View File

@@ -47,13 +47,13 @@ class AddDownloadTool(MoviePilotTool):
if torrent_urls:
if len(torrent_urls) == 1:
if self._is_torrent_ref(torrent_urls[0]):
message = f"正在添加下载任务: 资源 {torrent_urls[0]}"
message = f"添加下载任务: 资源 {torrent_urls[0]}"
else:
message = "正在添加下载任务: 磁力链接"
message = "添加下载任务: 磁力链接"
else:
message = f"正在批量添加下载任务: 共 {len(torrent_urls)} 个资源"
message = f"批量添加下载任务: 共 {len(torrent_urls)} 个资源"
else:
message = "正在添加下载任务"
message = "添加下载任务"
if downloader:
message += f" [下载器: {downloader}]"

View File

@@ -89,7 +89,7 @@ class AddSubscribeTool(MoviePilotTool):
media_type = kwargs.get("media_type", "")
season = kwargs.get("season")
message = f"正在添加订阅: {title}"
message = f"添加订阅: {title}"
if year:
message += f" ({year})"
if media_type:

View File

@@ -75,7 +75,7 @@ class AskUserChoiceTool(MoviePilotTool):
message = kwargs.get("message", "") or ""
if len(message) > 40:
message = message[:40] + "..."
return f"正在发送按钮选择: {message}"
return f"发送按钮选择: {message}"
@staticmethod
def _truncate_button_text(text: str, max_length: int) -> str:

View File

@@ -108,16 +108,16 @@ class BrowseWebpageTool(MoviePilotTool):
url = kwargs.get("url", "")
selector = kwargs.get("selector", "")
action_messages = {
"goto": f"正在打开网页: {url}",
"get_content": "正在获取页面内容",
"screenshot": "正在截取页面截图",
"click": f"正在点击元素: {selector}",
"fill": f"正在填写表单: {selector}",
"select": f"正在选择选项: {selector}",
"evaluate": "正在执行 JavaScript",
"wait": f"正在等待元素: {selector}",
"goto": f"打开网页: {url}",
"get_content": "获取页面内容",
"screenshot": "截取页面截图",
"click": f"点击元素: {selector}",
"fill": f"填写表单: {selector}",
"select": f"选择选项: {selector}",
"evaluate": "执行 JavaScript",
"wait": f"等待元素: {selector}",
}
return action_messages.get(action, f"正在执行浏览器操作: {action}")
return action_messages.get(action, f"执行浏览器操作: {action}")
async def run(
self,

View File

@@ -41,7 +41,7 @@ class DeleteDownloadTool(MoviePilotTool):
downloader = kwargs.get("downloader")
delete_files = kwargs.get("delete_files", False)
message = f"正在删除下载任务: {hash_value}"
message = f"删除下载任务: {hash_value}"
if downloader:
message += f" [下载器: {downloader}]"
if delete_files:

View File

@@ -30,7 +30,7 @@ class DeleteDownloadHistoryTool(MoviePilotTool):
def get_tool_message(self, **kwargs) -> Optional[str]:
history_id = kwargs.get("history_id")
return f"正在删除下载历史记录 ID: {history_id}"
return f"删除下载历史记录 ID: {history_id}"
async def run(self, history_id: int, **kwargs) -> str:
logger.info(f"执行工具: {self.name}, 参数: history_id={history_id}")

View File

@@ -34,7 +34,7 @@ class DeleteSubscribeTool(MoviePilotTool):
def get_tool_message(self, **kwargs) -> Optional[str]:
"""根据删除参数生成友好的提示消息"""
subscribe_id = kwargs.get("subscribe_id")
return f"正在删除订阅 (ID: {subscribe_id})"
return f"删除订阅 (ID: {subscribe_id})"
async def run(self, subscribe_id: int, **kwargs) -> str:
logger.info(f"执行工具: {self.name}, 参数: subscribe_id={subscribe_id}")

View File

@@ -30,7 +30,7 @@ class DeleteTransferHistoryTool(MoviePilotTool):
def get_tool_message(self, **kwargs) -> Optional[str]:
"""根据参数生成友好的提示消息"""
history_id = kwargs.get("history_id")
return f"正在删除整理历史记录: ID={history_id}"
return f"删除整理历史记录: ID={history_id}"
async def run(self, history_id: int, **kwargs) -> str:
logger.info(f"执行工具: {self.name}, 参数: history_id={history_id}")

View File

@@ -28,7 +28,7 @@ class EditFileTool(MoviePilotTool):
"""根据参数生成友好的提示消息"""
file_path = kwargs.get("file_path", "")
file_name = Path(file_path).name if file_path else "未知文件"
return f"正在编辑文件: {file_name}"
return f"编辑文件: {file_name}"
async def run(self, file_path: str, old_text: str, new_text: str, **kwargs) -> str:
logger.info(f"执行工具: {self.name}, 参数: file_path={file_path}")

View File

@@ -30,7 +30,7 @@ class ExecuteCommandTool(MoviePilotTool):
def get_tool_message(self, **kwargs) -> Optional[str]:
"""根据命令生成友好的提示消息"""
command = kwargs.get("command", "")
return f"正在执行系统命令: {command}"
return f"执行系统命令: {command}"
async def run(self, command: str, timeout: Optional[int] = 60, **kwargs) -> str:
logger.info(

View File

@@ -62,7 +62,7 @@ class GetRecommendationsTool(MoviePilotTool):
"douban_hot": "豆瓣热门",
"douban_movie_hot": "豆瓣热门电影",
"douban_tv_hot": "豆瓣热门电视剧",
"douban_movie_showing": "豆瓣正在热映",
"douban_movie_showing": "豆瓣热映",
"douban_movies": "豆瓣最新电影",
"douban_tvs": "豆瓣最新电视剧",
"douban_movie_top250": "豆瓣电影TOP250",
@@ -73,7 +73,7 @@ class GetRecommendationsTool(MoviePilotTool):
}
source_desc = source_map.get(source, source)
message = f"正在获取推荐: {source_desc}"
message = f"获取推荐: {source_desc}"
if media_type != "all":
message += f" [{media_type}]"
message += f" (第{page}页)"

View File

@@ -53,7 +53,7 @@ class GetSearchResultsTool(MoviePilotTool):
args_schema: Type[BaseModel] = GetSearchResultsInput
def get_tool_message(self, **kwargs) -> Optional[str]:
return "正在获取搜索结果"
return "获取搜索结果"
async def run(
self,

View File

@@ -32,7 +32,7 @@ class ListDirectoryTool(MoviePilotTool):
path = kwargs.get("path", "")
storage = kwargs.get("storage", "local")
message = f"正在查询目录: {path}"
message = f"查询目录: {path}"
if storage != "local":
message += f" [存储: {storage}]"

View File

@@ -33,7 +33,7 @@ class ListSlashCommandsTool(MoviePilotTool):
def get_tool_message(self, **kwargs) -> Optional[str]:
"""生成友好的提示消息"""
return "正在查询所有可用命令"
return "查询所有可用命令"
async def run(self, **kwargs) -> str:
logger.info(f"执行工具: {self.name}")

View File

@@ -55,7 +55,7 @@ class ModifyDownloadTool(MoviePilotTool):
tags = kwargs.get("tags")
downloader = kwargs.get("downloader")
parts = [f"正在修改下载任务: {hash_value}"]
parts = [f"修改下载任务: {hash_value}"]
if action == "start":
parts.append("操作: 开始下载")
elif action == "stop":

View File

@@ -31,7 +31,7 @@ class QueryCustomIdentifiersTool(MoviePilotTool):
def get_tool_message(self, **kwargs) -> Optional[str]:
"""生成友好的提示消息"""
return "正在查询自定义识别词"
return "查询自定义识别词"
async def run(self, **kwargs) -> str:
logger.info(f"执行工具: {self.name}")

View File

@@ -32,7 +32,7 @@ class QueryDirectorySettingsTool(MoviePilotTool):
storage_type = kwargs.get("storage_type", "all")
name = kwargs.get("name")
parts = ["正在查询目录配置"]
parts = ["查询目录配置"]
if directory_type != "all":
type_map = {"download": "下载目录", "library": "媒体库目录"}

View File

@@ -36,7 +36,7 @@ class QueryDownloadTasksTool(MoviePilotTool):
查询所有状态的任务(包括下载中和已完成的任务)
"""
all_torrents = []
# 查询正在下载的任务
# 查询下载的任务
downloading_torrents = download_chain.list_torrents(
downloader=downloader,
status=TorrentStatus.DOWNLOADING
@@ -71,7 +71,7 @@ class QueryDownloadTasksTool(MoviePilotTool):
hash_value = kwargs.get("hash")
title = kwargs.get("title")
parts = ["正在查询下载任务"]
parts = ["查询下载任务"]
if downloader:
parts.append(f"下载器: {downloader}")

View File

@@ -23,7 +23,7 @@ class QueryDownloadersTool(MoviePilotTool):
def get_tool_message(self, **kwargs) -> Optional[str]:
"""生成友好的提示消息"""
return "正在查询下载器配置"
return "查询下载器配置"
async def run(self, **kwargs) -> str:
logger.info(f"执行工具: {self.name}")

View File

@@ -29,7 +29,7 @@ class QueryEpisodeScheduleTool(MoviePilotTool):
season = kwargs.get("season")
episode_group = kwargs.get("episode_group")
message = f"正在查询剧集上映时间: TMDB ID {tmdb_id}{season}"
message = f"查询剧集上映时间: TMDB ID {tmdb_id}{season}"
if episode_group:
message += f" (剧集组: {episode_group})"

View File

@@ -31,7 +31,7 @@ class QueryInstalledPluginsTool(MoviePilotTool):
def get_tool_message(self, **kwargs) -> Optional[str]:
"""生成友好的提示消息"""
return "正在查询已安装插件"
return "查询已安装插件"
async def run(self, **kwargs) -> str:
logger.info(f"执行工具: {self.name}")

View File

@@ -93,11 +93,11 @@ class QueryLibraryExistsTool(MoviePilotTool):
media_type = kwargs.get("media_type")
if tmdb_id:
message = f"正在查询媒体库: TMDB={tmdb_id}"
message = f"查询媒体库: TMDB={tmdb_id}"
elif douban_id:
message = f"正在查询媒体库: 豆瓣={douban_id}"
message = f"查询媒体库: 豆瓣={douban_id}"
else:
message = "正在查询媒体库"
message = "查询媒体库"
if media_type:
message += f" [{media_type}]"
return message

View File

@@ -39,7 +39,7 @@ class QueryLibraryLatestTool(MoviePilotTool):
server = kwargs.get("server")
page = kwargs.get("page", 1)
parts = ["正在查询媒体服务器最近入库影片"]
parts = ["查询媒体服务器最近入库影片"]
if server:
parts.append(f"服务器: {server}")

View File

@@ -29,8 +29,8 @@ class QueryMediaDetailTool(MoviePilotTool):
tmdb_id = kwargs.get("tmdb_id")
douban_id = kwargs.get("douban_id")
if tmdb_id:
return f"正在查询媒体详情: TMDB ID {tmdb_id}"
return f"正在查询媒体详情: 豆瓣 ID {douban_id}"
return f"查询媒体详情: TMDB ID {tmdb_id}"
return f"查询媒体详情: 豆瓣 ID {douban_id}"
async def run(self, media_type: str, tmdb_id: Optional[int] = None, douban_id: Optional[str] = None, **kwargs) -> str:
logger.info(f"执行工具: {self.name}, 参数: tmdb_id={tmdb_id}, douban_id={douban_id}, media_type={media_type}")

View File

@@ -40,8 +40,8 @@ class QueryPluginCapabilitiesTool(MoviePilotTool):
"""生成友好的提示消息"""
plugin_id = kwargs.get("plugin_id")
if plugin_id:
return f"正在查询插件 {plugin_id} 的能力"
return "正在查询所有插件的能力"
return f"查询插件 {plugin_id} 的能力"
return "查询所有插件的能力"
async def run(self, plugin_id: Optional[str] = None, **kwargs) -> str:
logger.info(f"执行工具: {self.name}, 参数: plugin_id={plugin_id}")

View File

@@ -39,7 +39,7 @@ class QueryPopularSubscribesTool(MoviePilotTool):
min_rating = kwargs.get("min_rating")
max_rating = kwargs.get("max_rating")
parts = [f"正在查询热门订阅 [{media_type}]"]
parts = [f"查询热门订阅 [{media_type}]"]
if min_sub:
parts.append(f"最少订阅: {min_sub}")

View File

@@ -22,7 +22,7 @@ class QueryRuleGroupsTool(MoviePilotTool):
def get_tool_message(self, **kwargs) -> Optional[str]:
"""根据查询参数生成友好的提示消息"""
return "正在查询所有规则组"
return "查询所有规则组"
async def run(self, **kwargs) -> str:
logger.info(f"执行工具: {self.name}")

View File

@@ -22,7 +22,7 @@ class QuerySchedulersTool(MoviePilotTool):
def get_tool_message(self, **kwargs) -> Optional[str]:
"""生成友好的提示消息"""
return "正在查询定时服务"
return "查询定时服务"
async def run(self, **kwargs) -> str:
logger.info(f"执行工具: {self.name}")

View File

@@ -40,7 +40,7 @@ class QuerySiteUserdataTool(MoviePilotTool):
site_id = kwargs.get("site_id")
workdate = kwargs.get("workdate")
message = f"正在查询站点 #{site_id} 的用户数据"
message = f"查询站点 #{site_id} 的用户数据"
if workdate:
message += f" (日期: {workdate})"
else:

View File

@@ -37,7 +37,7 @@ class QuerySitesTool(MoviePilotTool):
status = kwargs.get("status", "all")
name = kwargs.get("name")
parts = ["正在查询站点"]
parts = ["查询站点"]
if status != "all":
status_map = {"active": "已启用", "inactive": "已禁用"}

View File

@@ -44,7 +44,7 @@ class QuerySubscribeHistoryTool(MoviePilotTool):
name = kwargs.get("name")
page = kwargs.get("page", 1)
parts = ["正在查询订阅历史"]
parts = ["查询订阅历史"]
if media_type != "all":
parts.append(f"类型: {media_type}")

View File

@@ -34,7 +34,7 @@ class QuerySubscribeSharesTool(MoviePilotTool):
min_rating = kwargs.get("min_rating")
max_rating = kwargs.get("max_rating")
parts = ["正在查询订阅分享"]
parts = ["查询订阅分享"]
if name:
parts.append(f"名称: {name}")

View File

@@ -79,7 +79,7 @@ class QuerySubscribesTool(MoviePilotTool):
media_type = kwargs.get("media_type", "all")
page = kwargs.get("page", 1)
parts = ["正在查询订阅"]
parts = ["查询订阅"]
# 根据状态过滤条件生成提示
if status != "all":

View File

@@ -33,7 +33,7 @@ class QueryTransferHistoryTool(MoviePilotTool):
status = kwargs.get("status", "all")
page = kwargs.get("page", 1)
parts = ["正在查询整理历史"]
parts = ["查询整理历史"]
if title:
parts.append(f"标题: {title}")

View File

@@ -30,7 +30,7 @@ class QueryWorkflowsTool(MoviePilotTool):
name = kwargs.get("name")
trigger_type = kwargs.get("trigger_type", "all")
parts = ["正在查询工作流"]
parts = ["查询工作流"]
if state != "all":
state_map = {"W": "等待", "R": "运行中", "P": "暂停", "S": "成功", "F": "失败"}

View File

@@ -29,7 +29,7 @@ class ReadFileTool(MoviePilotTool):
"""根据参数生成友好的提示消息"""
file_path = kwargs.get("file_path", "")
file_name = Path(file_path).name if file_path else "未知文件"
return f"正在读取文件: {file_name}"
return f"读取文件: {file_name}"
async def run(self, file_path: str, start_line: Optional[int] = None,
end_line: Optional[int] = None, **kwargs) -> str:

View File

@@ -33,13 +33,13 @@ class RecognizeMediaTool(MoviePilotTool):
path = kwargs.get("path")
if path:
message = f"正在识别文件媒体信息: {path}"
message = f"识别文件媒体信息: {path}"
elif title:
message = f"正在识别种子媒体信息: {title}"
message = f"识别种子媒体信息: {title}"
if subtitle:
message += f" ({subtitle})"
else:
message = "正在识别媒体信息"
message = "识别媒体信息"
return message

View File

@@ -31,7 +31,7 @@ class RunSchedulerTool(MoviePilotTool):
def get_tool_message(self, **kwargs) -> Optional[str]:
"""根据运行参数生成友好的提示消息"""
job_id = kwargs.get("job_id", "")
return f"正在运行定时服务 (ID: {job_id})"
return f"运行定时服务 (ID: {job_id})"
async def run(self, job_id: str, **kwargs) -> str:
logger.info(f"执行工具: {self.name}, 参数: job_id={job_id}")

View File

@@ -45,7 +45,7 @@ class RunSlashCommandTool(MoviePilotTool):
def get_tool_message(self, **kwargs) -> Optional[str]:
"""生成友好的提示消息"""
command = kwargs.get("command", "")
return f"正在执行命令: {command}"
return f"执行命令: {command}"
async def run(self, command: str, **kwargs) -> str:
logger.info(f"执行工具: {self.name}, 参数: command={command}")

View File

@@ -38,7 +38,7 @@ class RunWorkflowTool(MoviePilotTool):
workflow_id = kwargs.get("workflow_id")
from_begin = kwargs.get("from_begin", True)
message = f"正在执行工作流: {workflow_id}"
message = f"执行工作流: {workflow_id}"
if not from_begin:
message += " (从上次位置继续)"
else:

View File

@@ -47,7 +47,7 @@ class ScrapeMetadataTool(MoviePilotTool):
storage = kwargs.get("storage", "local")
overwrite = kwargs.get("overwrite", False)
message = f"正在刮削媒体元数据: {path}"
message = f"刮削媒体元数据: {path}"
if storage != "local":
message += f" [存储: {storage}]"
if overwrite:

View File

@@ -34,7 +34,7 @@ class SearchMediaTool(MoviePilotTool):
media_type = kwargs.get("media_type")
season = kwargs.get("season")
message = f"正在搜索媒体: {title}"
message = f"搜索媒体: {title}"
if year:
message += f" ({year})"
if media_type:

View File

@@ -24,7 +24,7 @@ class SearchPersonTool(MoviePilotTool):
def get_tool_message(self, **kwargs) -> Optional[str]:
"""根据搜索参数生成友好的提示消息"""
name = kwargs.get("name", "")
return f"正在搜索人物: {name}"
return f"搜索人物: {name}"
async def run(self, name: str, **kwargs) -> str:
logger.info(f"执行工具: {self.name}, 参数: name={name}")

View File

@@ -29,7 +29,7 @@ class SearchPersonCreditsTool(MoviePilotTool):
"""根据搜索参数生成友好的提示消息"""
person_id = kwargs.get("person_id", "")
source = kwargs.get("source", "")
return f"正在搜索人物参演作品: {source} ID {person_id}"
return f"搜索人物参演作品: {source} ID {person_id}"
async def run(self, person_id: int, source: str, page: Optional[int] = 1, **kwargs) -> str:
logger.info(f"执行工具: {self.name}, 参数: person_id={person_id}, source={source}, page={page}")

View File

@@ -32,7 +32,7 @@ class SearchSubscribeTool(MoviePilotTool):
subscribe_id = kwargs.get("subscribe_id")
manual = kwargs.get("manual", False)
message = f"正在搜索订阅 #{subscribe_id} 的缺失剧集"
message = f"搜索订阅 #{subscribe_id} 的缺失剧集"
if manual:
message += "(手动搜索)"

View File

@@ -41,11 +41,11 @@ class SearchTorrentsTool(MoviePilotTool):
media_type = kwargs.get("media_type")
if tmdb_id:
message = f"正在搜索种子: TMDB={tmdb_id}"
message = f"搜索种子: TMDB={tmdb_id}"
elif douban_id:
message = f"正在搜索种子: 豆瓣={douban_id}"
message = f"搜索种子: 豆瓣={douban_id}"
else:
message = "正在搜索种子"
message = "搜索种子"
if media_type:
message += f" [{media_type}]"
return message

View File

@@ -41,7 +41,7 @@ class SearchWebTool(MoviePilotTool):
"""根据搜索参数生成友好的提示消息"""
query = kwargs.get("query", "")
max_results = kwargs.get("max_results", 20)
return f"正在搜索网络内容: {query} (最多返回 {max_results} 条结果)"
return f"搜索网络内容: {query} (最多返回 {max_results} 条结果)"
async def run(self, query: str, max_results: Optional[int] = 20, **kwargs) -> str:
"""

View File

@@ -55,7 +55,7 @@ class SendLocalFileTool(MoviePilotTool):
def get_tool_message(self, **kwargs) -> Optional[str]:
file_path = kwargs.get("file_path", "")
file_name = Path(file_path).name if file_path else "未知文件"
return f"正在发送本地附件: {file_name}"
return f"发送本地附件: {file_name}"
async def run(
self,

View File

@@ -52,12 +52,12 @@ class SendMessageTool(MoviePilotTool):
message = message[:50] + "..."
if title and image_url:
return f"正在发送图文消息: [{title}] {message}"
return f"发送图文消息: [{title}] {message}"
if title:
return f"正在发送消息: [{title}] {message}"
return f"发送消息: [{title}] {message}"
if image_url:
return f"正在发送图片消息: {message}"
return f"正在发送消息: {message}"
return f"发送图片消息: {message}"
return f"发送消息: {message}"
async def run(
self,

View File

@@ -41,7 +41,7 @@ class SendVoiceMessageTool(MoviePilotTool):
message = kwargs.get("message") or ""
if len(message) > 40:
message = message[:40] + "..."
return f"正在发送语音回复: {message}"
return f"发送语音回复: {message}"
def _supports_real_voice_reply(self) -> bool:
channel = self._channel or ""

View File

@@ -24,7 +24,7 @@ class TestSiteTool(MoviePilotTool):
def get_tool_message(self, **kwargs) -> Optional[str]:
"""根据测试参数生成友好的提示消息"""
site_identifier = kwargs.get("site_identifier")
return f"正在测试站点连通性: {site_identifier}"
return f"测试站点连通性: {site_identifier}"
async def run(self, site_identifier: int, **kwargs) -> str:
logger.info(f"执行工具: {self.name}, 参数: site_identifier={site_identifier}")

View File

@@ -68,7 +68,7 @@ class TransferFileTool(MoviePilotTool):
transfer_type = kwargs.get("transfer_type")
background = kwargs.get("background", False)
message = f"正在整理文件: {file_path}"
message = f"整理文件: {file_path}"
if media_type:
message += f" [{media_type}]"
if transfer_type:

View File

@@ -57,7 +57,7 @@ class UpdateCustomIdentifiersTool(MoviePilotTool):
def get_tool_message(self, **kwargs) -> Optional[str]:
"""生成友好的提示消息"""
identifiers = kwargs.get("identifiers", [])
return f"正在更新自定义识别词(共 {len(identifiers)} 条规则)"
return f"更新自定义识别词(共 {len(identifiers)} 条规则)"
async def run(self, identifiers: List[str] = None, **kwargs) -> str:
logger.info(

View File

@@ -95,8 +95,8 @@ class UpdateSiteTool(MoviePilotTool):
fields_updated.append("下载器")
if fields_updated:
return f"正在更新站点 #{site_id}: {', '.join(fields_updated)}"
return f"正在更新站点 #{site_id}"
return f"更新站点 #{site_id}: {', '.join(fields_updated)}"
return f"更新站点 #{site_id}"
async def run(
self,

View File

@@ -41,7 +41,7 @@ class UpdateSiteCookieTool(MoviePilotTool):
username = kwargs.get("username", "")
two_step_code = kwargs.get("two_step_code")
message = f"正在更新站点Cookie: {site_identifier} (用户: {username})"
message = f"更新站点Cookie: {site_identifier} (用户: {username})"
if two_step_code:
message += " [需要两步验证]"

View File

@@ -117,8 +117,8 @@ class UpdateSubscribeTool(MoviePilotTool):
fields_updated.append("下载器")
if fields_updated:
return f"正在更新订阅 #{subscribe_id}: {', '.join(fields_updated)}"
return f"正在更新订阅 #{subscribe_id}"
return f"更新订阅 #{subscribe_id}: {', '.join(fields_updated)}"
return f"更新订阅 #{subscribe_id}"
async def run(
self,

View File

@@ -27,7 +27,7 @@ class WriteFileTool(MoviePilotTool):
"""根据参数生成友好的提示消息"""
file_path = kwargs.get("file_path", "")
file_name = Path(file_path).name if file_path else "未知文件"
return f"正在写入文件: {file_name}"
return f"写入文件: {file_name}"
async def run(self, file_path: str, content: str, **kwargs) -> str:
logger.info(f"执行工具: {self.name}, 参数: file_path={file_path}")

View File

@@ -0,0 +1,53 @@
import asyncio
import unittest
from unittest.mock import patch
from app.agent.callback import StreamingHandler
from app.agent.tools.base import MoviePilotTool
from app.core.config import settings
class DummyTool(MoviePilotTool):
name: str = "dummy_tool"
description: str = "Dummy tool for streaming tests."
async def run(self, **kwargs) -> str:
return "ok"
class TestAgentToolStreaming(unittest.TestCase):
async def _run_tool(self, initial_buffer: str) -> tuple[str, str]:
tool = DummyTool(session_id="session-1", user_id="10001")
handler = StreamingHandler()
await handler.start_streaming()
if initial_buffer:
handler.emit(initial_buffer)
tool.set_stream_handler(handler)
with patch.object(settings, "AI_AGENT_VERBOSE", False):
result = await tool._arun(explanation="run test tool")
buffered_message = await handler.take()
return result, buffered_message
def test_non_verbose_tool_call_appends_newline_separator(self):
result, buffered_message = asyncio.run(self._run_tool("prefix"))
self.assertEqual(result, "ok")
self.assertEqual(buffered_message, "prefix\n")
def test_non_verbose_tool_call_does_not_duplicate_newline(self):
result, buffered_message = asyncio.run(self._run_tool("prefix\n"))
self.assertEqual(result, "ok")
self.assertEqual(buffered_message, "prefix\n")
def test_non_verbose_tool_call_keeps_empty_buffer_unchanged(self):
result, buffered_message = asyncio.run(self._run_tool(""))
self.assertEqual(result, "ok")
self.assertEqual(buffered_message, "")
if __name__ == "__main__":
unittest.main()