refactor: adjust default and maximum limits for plugin candidates and torrent results; enhance result formatting for agents

This commit is contained in:
jxxghp
2026-05-08 14:47:20 +08:00
parent 0a0d5e6da2
commit 14b366a648
18 changed files with 297 additions and 60 deletions

View File

@@ -15,7 +15,8 @@ DEFAULT_PLUGIN_DATA_PREVIEW_CHARS = 12_000
MAX_PLUGIN_DATA_PREVIEW_CHARS = 50_000
PLUGIN_DATA_KEY_PREVIEW_LIMIT = 50
PLUGIN_DATA_TRUNCATION_SUFFIX = "\n...(插件数据内容过长,已截断)"
DEFAULT_PLUGIN_CANDIDATE_LIMIT = 500
DEFAULT_PLUGIN_CANDIDATE_LIMIT = 50
MAX_PLUGIN_CANDIDATE_LIMIT = 200
def get_plugin_snapshot(plugin_id: str) -> Optional[dict[str, Any]]:

View File

@@ -8,7 +8,7 @@ from app.utils.crypto import HashUtils
from app.utils.string import StringUtils
SEARCH_RESULT_CACHE_FILE = "__search_result__"
TORRENT_RESULT_LIMIT = 200
TORRENT_RESULT_LIMIT = 50
def build_torrent_ref(context: Optional[Context]) -> str:

View File

@@ -8,6 +8,7 @@ from pydantic import BaseModel, Field
from app.agent.tools.base import MoviePilotTool
from app.agent.tools.impl._plugin_tool_utils import (
DEFAULT_PLUGIN_CANDIDATE_LIMIT,
MAX_PLUGIN_CANDIDATE_LIMIT,
list_installed_plugins,
search_plugin_candidates,
summarize_candidates,
@@ -29,7 +30,7 @@ class QueryInstalledPluginsInput(BaseModel):
)
max_results: Optional[int] = Field(
DEFAULT_PLUGIN_CANDIDATE_LIMIT,
description="Maximum number of plugins to return. Defaults to 10.",
description="Maximum number of plugins to return. Defaults to 50, capped at 200.",
)
@@ -53,7 +54,10 @@ class QueryInstalledPluginsTool(MoviePilotTool):
def _clamp_results(max_results: Optional[int]) -> int:
if max_results is None:
return DEFAULT_PLUGIN_CANDIDATE_LIMIT
return max(1, min(int(max_results), 200))
try:
return max(1, min(int(max_results), MAX_PLUGIN_CANDIDATE_LIMIT))
except (TypeError, ValueError):
return DEFAULT_PLUGIN_CANDIDATE_LIMIT
async def run(
self,

View File

@@ -8,6 +8,7 @@ from pydantic import BaseModel, Field
from app.agent.tools.base import MoviePilotTool
from app.agent.tools.impl._plugin_tool_utils import (
DEFAULT_PLUGIN_CANDIDATE_LIMIT,
MAX_PLUGIN_CANDIDATE_LIMIT,
load_market_plugins,
search_plugin_candidates,
summarize_candidates,
@@ -29,7 +30,7 @@ class QueryMarketPluginsInput(BaseModel):
)
max_results: Optional[int] = Field(
DEFAULT_PLUGIN_CANDIDATE_LIMIT,
description="Maximum number of plugins to return. Defaults to 10.",
description="Maximum number of plugins to return. Defaults to 50, capped at 200.",
)
force_refresh: Optional[bool] = Field(
False,
@@ -56,7 +57,10 @@ class QueryMarketPluginsTool(MoviePilotTool):
def _clamp_results(max_results: Optional[int]) -> int:
if max_results is None:
return DEFAULT_PLUGIN_CANDIDATE_LIMIT
return max(1, min(int(max_results), 200))
try:
return max(1, min(int(max_results), MAX_PLUGIN_CANDIDATE_LIMIT))
except (TypeError, ValueError):
return DEFAULT_PLUGIN_CANDIDATE_LIMIT
async def run(
self,

View File

@@ -10,6 +10,10 @@ from app.chain.media import MediaChain
from app.log import logger
from app.schemas.types import MediaType
DIRECTOR_PREVIEW_LIMIT = 10
ACTOR_PREVIEW_LIMIT = 20
SEASON_PREVIEW_LIMIT = 100
class QueryMediaDetailInput(BaseModel):
"""查询媒体详情工具的输入参数模型"""
@@ -64,23 +68,23 @@ class QueryMediaDetailTool(MoviePilotTool):
genres = [g.get("name") for g in (mediainfo.genres or []) if g.get("name")]
# 精简 directors - 只保留姓名和职位
director_source = [d for d in (mediainfo.directors or []) if d.get("name")]
directors = [
{
"name": d.get("name"),
"job": d.get("job")
}
for d in (mediainfo.directors or [])
if d.get("name")
for d in director_source[:DIRECTOR_PREVIEW_LIMIT]
]
# 精简 actors - 只保留姓名和角色
actor_source = [a for a in (mediainfo.actors or []) if a.get("name")]
actors = [
{
"name": a.get("name"),
"character": a.get("character")
}
for a in (mediainfo.actors or [])
if a.get("name")
for a in actor_source[:ACTOR_PREVIEW_LIMIT]
]
# 构建基础媒体详情信息
@@ -88,12 +92,20 @@ class QueryMediaDetailTool(MoviePilotTool):
"status": mediainfo.status,
"genres": genres,
"directors": directors,
"actors": actors
"directors_total": len(director_source),
"directors_truncated": len(director_source) > DIRECTOR_PREVIEW_LIMIT,
"actors": actors,
"actors_total": len(actor_source),
"actors_truncated": len(actor_source) > ACTOR_PREVIEW_LIMIT,
}
# 如果是电视剧,添加电视剧特有信息
if mediainfo.type == MediaType.TV:
# 精简 season_info - 只保留基础摘要
season_source = [
s for s in (mediainfo.season_info or [])
if s.get("season_number") is not None
]
season_info = [
{
"season_number": s.get("season_number"),
@@ -101,8 +113,7 @@ class QueryMediaDetailTool(MoviePilotTool):
"episode_count": s.get("episode_count"),
"air_date": s.get("air_date")
}
for s in (mediainfo.season_info or [])
if s.get("season_number") is not None
for s in season_source[:SEASON_PREVIEW_LIMIT]
]
result.update({
@@ -110,7 +121,9 @@ class QueryMediaDetailTool(MoviePilotTool):
"number_of_episodes": mediainfo.number_of_episodes,
"first_air_date": mediainfo.first_air_date,
"last_air_date": mediainfo.last_air_date,
"season_info": season_info
"season_info": season_info,
"season_info_total": len(season_source),
"season_info_truncated": len(season_source) > SEASON_PREVIEW_LIMIT,
})
return json.dumps(result, ensure_ascii=False, indent=2)

View File

@@ -12,13 +12,15 @@ from app.helper.subscribe import SubscribeHelper
from app.log import logger
from app.schemas.types import MediaType, media_type_to_agent
MAX_PAGE_SIZE = 50
class QueryPopularSubscribesInput(BaseModel):
"""查询热门订阅工具的输入参数模型"""
explanation: str = Field(..., description="Clear explanation of why this tool is being used in the current context")
media_type: str = Field(..., description="Allowed values: movie, tv")
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)")
count: Optional[int] = Field(30, description="Number of items per page (default: 30, max: 50)")
min_sub: Optional[int] = Field(None, description="Minimum number of subscribers filter (optional, e.g., 5)")
genre_id: Optional[int] = Field(None, description="Filter by genre ID (optional)")
min_rating: Optional[float] = Field(None, description="Minimum rating filter (optional, e.g., 7.5)")
@@ -69,6 +71,8 @@ class QueryPopularSubscribesTool(MoviePilotTool):
page = 1
if count is None or count < 1:
count = 30
# 外部统计接口支持传入 count这里做硬上限避免 Agent 一次拉取过多结果。
count = min(count, MAX_PAGE_SIZE)
media_type_enum = MediaType.from_agent(media_type)
if not media_type_enum:
return f"错误:无效的媒体类型 '{media_type}',支持的类型:'movie', 'tv'"

View File

@@ -11,6 +11,14 @@ from app.db.models.site import Site
from app.db.models.siteuserdata import SiteUserData
from app.log import logger
SITE_USERDATA_DETAIL_PREVIEW_LIMIT = 10
def _preview_list(value, limit: int = SITE_USERDATA_DETAIL_PREVIEW_LIMIT) -> tuple[list, int, bool]:
"""返回列表字段预览,避免做种明细或未读消息一次性撑大工具结果。"""
items = list(value) if isinstance(value, (list, tuple)) else []
return items[:limit], len(items), len(items) > limit
class QuerySiteUserdataInput(BaseModel):
"""查询站点用户数据工具的输入参数模型"""
@@ -110,6 +118,13 @@ class QuerySiteUserdataTool(MoviePilotTool):
else 0
)
seeding_preview, seeding_count, seeding_truncated = _preview_list(
user_data.seeding_info
)
unread_preview, unread_count, unread_truncated = _preview_list(
user_data.message_unread_contents
)
user_data_dict = {
"domain": user_data.domain,
"name": user_data.name,
@@ -131,13 +146,13 @@ class QuerySiteUserdataTool(MoviePilotTool):
"seeding_size_gb": round(seeding_size_gb, 2),
"leeching_size": user_data.leeching_size,
"leeching_size_gb": round(leeching_size_gb, 2),
"seeding_info": user_data.seeding_info
if user_data.seeding_info
else [],
"seeding_info_count": seeding_count,
"seeding_info": seeding_preview,
"seeding_info_truncated": seeding_truncated,
"message_unread": user_data.message_unread,
"message_unread_contents": user_data.message_unread_contents
if user_data.message_unread_contents
else [],
"message_unread_contents_count": unread_count,
"message_unread_contents": unread_preview,
"message_unread_contents_truncated": unread_truncated,
"err_msg": user_data.err_msg,
"updated_day": user_data.updated_day,
"updated_time": user_data.updated_time,

View File

@@ -9,13 +9,15 @@ from app.agent.tools.base import MoviePilotTool
from app.helper.subscribe import SubscribeHelper
from app.log import logger
MAX_PAGE_SIZE = 50
class QuerySubscribeSharesInput(BaseModel):
"""查询订阅分享工具的输入参数模型"""
explanation: str = Field(..., 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)")
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)")
count: Optional[int] = Field(30, description="Number of items per page (default: 30, max: 50)")
genre_id: Optional[int] = Field(None, description="Filter by genre ID (optional)")
min_rating: Optional[float] = Field(None, description="Minimum rating filter (optional, e.g., 7.5)")
max_rating: Optional[float] = Field(None, description="Maximum rating filter (optional, e.g., 10.0)")
@@ -63,6 +65,8 @@ class QuerySubscribeSharesTool(MoviePilotTool):
page = 1
if count is None or count < 1:
count = 30
# 订阅分享是外部列表型结果,限制单页大小能降低工具上下文占用。
count = min(count, MAX_PAGE_SIZE)
subscribe_helper = SubscribeHelper()
shares = await subscribe_helper.async_get_shares(

View File

@@ -62,8 +62,8 @@ class QueryTransferHistoryTool(MoviePilotTool):
if page is None or page < 1:
page = 1
# 每页记录数
count = 50
# 每页固定 30 条,与工具说明保持一致,避免整理路径等字段撑大上下文。
count = 30
# 获取数据库会话
async with AsyncSessionFactory() as db:

View File

@@ -115,9 +115,7 @@ class QueryWorkflowsTool(MoviePilotTool):
"last_time": wf.last_time,
"current_action": wf.current_action
}
# 如果有结果,添加结果信息
if wf.result:
simplified["result"] = wf.result
# wf.result 往往是执行日志或上下文快照,不适合作为列表查询结果返回。
simplified_workflows.append(simplified)
result_json = json.dumps(simplified_workflows, ensure_ascii=False, indent=2)

View File

@@ -73,7 +73,7 @@ class SearchMediaTool(MoviePilotTool):
filtered_results.append(result)
if filtered_results:
# 限制最多30条结果
# 搜索结果只返回前 30 条,后续可通过更精确的年份/类型条件缩小范围。
total_count = len(filtered_results)
limited_results = filtered_results[:30]
# 精简字段,只保留关键信息
@@ -96,8 +96,8 @@ class SearchMediaTool(MoviePilotTool):
simplified_results.append(simplified)
result_json = json.dumps(simplified_results, ensure_ascii=False, indent=2)
# 如果结果被裁剪,添加提示信息
if total_count > 100:
return f"注意:搜索结果共找到 {total_count} 条,为节省上下文空间,仅显示前 100 条结果。\n\n{result_json}"
if total_count > len(limited_results):
return f"注意:搜索结果共找到 {total_count} 条,为节省上下文空间,仅显示前 {len(limited_results)} 条结果。\n\n{result_json}"
return result_json
else:
return f"未找到符合条件的媒体资源: {title}"

View File

@@ -35,7 +35,7 @@ class SearchPersonTool(MoviePilotTool):
persons = await media_chain.async_search_persons(name=name)
if persons:
# 限制最多30条结果
# 人物搜索结果只返回前 30 条,避免 biography/别名等字段挤占上下文。
total_count = len(persons)
limited_persons = persons[:30]
# 精简字段,只保留关键信息
@@ -72,8 +72,8 @@ class SearchPersonTool(MoviePilotTool):
result_json = json.dumps(simplified_results, ensure_ascii=False, indent=2)
# 如果结果被裁剪,添加提示信息
if total_count > 50:
return f"注意:搜索结果共找到 {total_count} 条,为节省上下文空间,仅显示前 50 条结果。\n\n{result_json}"
if total_count > len(limited_persons):
return f"注意:搜索结果共找到 {total_count} 条,为节省上下文空间,仅显示前 {len(limited_persons)} 条结果。\n\n{result_json}"
return result_json
else:
return f"未找到相关人物信息: {name}"

View File

@@ -28,7 +28,7 @@ class SearchWebInput(BaseModel):
)
max_results: Optional[int] = Field(
20,
description="Maximum number of search results to return (default: 5, max: 10)",
description="Maximum number of search results to return (default: 20, max: 20)",
)