From 7e93b334071eac60b0353ecbec0db0f16ec8cb46 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sat, 22 Nov 2025 00:03:57 +0000 Subject: [PATCH] feat: Add search_person_credits tool Co-authored-by: jxxghp --- app/agent/tools/factory.py | 2 + app/agent/tools/impl/search_person_credits.py | 87 +++++++++++++++++++ 2 files changed, 89 insertions(+) create mode 100644 app/agent/tools/impl/search_person_credits.py diff --git a/app/agent/tools/factory.py b/app/agent/tools/factory.py index 0bbd790b..d84ba45e 100644 --- a/app/agent/tools/factory.py +++ b/app/agent/tools/factory.py @@ -22,6 +22,7 @@ from app.agent.tools.impl.query_subscribe_history import QuerySubscribeHistoryTo from app.agent.tools.impl.delete_subscribe import DeleteSubscribeTool from app.agent.tools.impl.search_media import SearchMediaTool from app.agent.tools.impl.search_person import SearchPersonTool +from app.agent.tools.impl.search_person_credits import SearchPersonCreditsTool from app.agent.tools.impl.recognize_media import RecognizeMediaTool from app.agent.tools.impl.scrape_metadata import ScrapeMetadataTool from app.agent.tools.impl.query_episode_schedule import QueryEpisodeScheduleTool @@ -55,6 +56,7 @@ class MoviePilotToolFactory: tool_definitions = [ SearchMediaTool, SearchPersonTool, + SearchPersonCreditsTool, RecognizeMediaTool, ScrapeMetadataTool, QueryEpisodeScheduleTool, diff --git a/app/agent/tools/impl/search_person_credits.py b/app/agent/tools/impl/search_person_credits.py new file mode 100644 index 00000000..92fab789 --- /dev/null +++ b/app/agent/tools/impl/search_person_credits.py @@ -0,0 +1,87 @@ +"""搜索演员参演作品工具""" + +import json +from typing import Optional, Type + +from pydantic import BaseModel, Field + +from app.agent.tools.base import MoviePilotTool +from app.chain.douban import DoubanChain +from app.chain.tmdb import TmdbChain +from app.chain.bangumi import BangumiChain +from app.log import logger + + +class SearchPersonCreditsInput(BaseModel): + """搜索演员参演作品工具的输入参数模型""" + explanation: str = Field(..., 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)") + 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)") + + +class SearchPersonCreditsTool(MoviePilotTool): + name: str = "search_person_credits" + description: str = "Search for films and TV shows that a person/actor has appeared in (filmography). Supports searching by person ID from TMDB, Douban, or Bangumi database. Returns a list of media works the person has participated in." + args_schema: Type[BaseModel] = SearchPersonCreditsInput + + def get_tool_message(self, **kwargs) -> Optional[str]: + """根据搜索参数生成友好的提示消息""" + person_id = kwargs.get("person_id", "") + source = kwargs.get("source", "") + 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}") + + try: + medias = None + + # 根据source选择相应的chain + if source.lower() == "tmdb": + tmdb_chain = TmdbChain() + medias = await tmdb_chain.async_person_credits(person_id=person_id, page=page) + elif source.lower() == "douban": + douban_chain = DoubanChain() + medias = await douban_chain.async_person_credits(person_id=person_id, page=page) + elif source.lower() == "bangumi": + bangumi_chain = BangumiChain() + medias = await bangumi_chain.async_person_credits(person_id=person_id) + else: + return f"不支持的数据源: {source}。支持的数据源: tmdb, douban, bangumi" + + if medias: + # 限制最多30条结果 + total_count = len(medias) + limited_medias = medias[:30] + # 精简字段,只保留关键信息 + simplified_results = [] + for media in limited_medias: + simplified = { + "title": media.title, + "en_title": media.en_title, + "year": media.year, + "type": media.type.value if media.type else None, + "season": media.season, + "tmdb_id": media.tmdb_id, + "imdb_id": media.imdb_id, + "douban_id": media.douban_id, + "overview": media.overview[:200] + "..." if media.overview and len(media.overview) > 200 else media.overview, + "vote_average": media.vote_average, + "poster_path": media.poster_path, + "backdrop_path": media.backdrop_path, + "detail_link": media.detail_link + } + simplified_results.append(simplified) + + result_json = json.dumps(simplified_results, ensure_ascii=False, indent=2) + # 如果结果被裁剪,添加提示信息 + if total_count > 30: + return f"注意:搜索结果共找到 {total_count} 条,为节省上下文空间,仅显示前 30 条结果。\n\n{result_json}" + return result_json + else: + return f"未找到人物 ID {person_id} ({source}) 的参演作品" + except Exception as e: + error_message = f"搜索演员参演作品失败: {str(e)}" + logger.error(f"搜索演员参演作品失败: {e}", exc_info=True) + return error_message