CoolFace
Apppublic

blasisd/GAIA_benchmarked_Agent

sourceHugging Faceupdated 1y agoView on Hugging Face
0likes
tools.py462 linesDownload Raw Back to root
1import os2import tempfile3 4from typing import Dict, List, Optional5 6from bs4 import BeautifulSoup7import yt_dlp8import pandas as pd9import requests10import torch11 12from langchain_community.document_loaders import YoutubeLoader13from langchain_community.retrievers import BM25Retriever14from langchain_community.tools import BearlyInterpreterTool15from langchain.docstore.document import Document16from smolagents import (17    DuckDuckGoSearchTool,18    SpeechToTextTool,19    Tool,20    VisitWebpageTool,21    WikipediaSearchTool,22)23from transformers import AutoProcessor, AutoModelForImageTextToText24 25 26class RelevantInfoRetrieverTool(Tool):27    name = "relevant_info_retriever"28    description = "Retrieves relevant to the query information."29    inputs = {30        "query": {31            "type": "string",32            "description": "The query for which to retrieve information.",33        },34        "docs": {35            "type": "string",36            "description": "The source documents from which to choose in order to retrieve relevant information",37        },38    }39    output_type = "string"40 41    def forward(self, query: str, docs: List[Document]):42        self.retriever = BM25Retriever.from_documents(docs)43        results = self.retriever.get_relevant_documents(query)44        if results:45            return "\n\n".join([doc.page_content for doc in results])46        else:47            return "No relevant information found."48 49 50class YoutubeTranscriptTool(Tool):51    name = "youtube_transcript"52    description = "Fetches youtube video's transcript."53    inputs = {54        "youtube_url": {55            "type": "string",56            "description": "The youtube video url",57        },58        "source_langs": {59            "type": "array",60            "description": "A list of language codes in a descending priority for the video trascript.",61            "items": {"type": "string"},62            "default": ["en"],63            "required": False,64            "nullable": True,65        },66        "target_lang": {67            "type": "string",68            "description": "The language to which the transcript will be translated.",69            "default": "en",70            "required": False,71            "nullable": True,72        },73    }74    output_type = "string"75 76    def forward(77        self,78        youtube_url: str,79        source_langs: Optional[List[str]] = ["en"],80        target_lang: Optional[str] = "en",81    ):82        try:83            loader = YoutubeLoader.from_youtube_url(84                youtube_url,85                add_video_info=True,86                language=source_langs,87                translation=target_lang,88                # transcript_format=TranscriptFormat.CHUNKS,89                # chunk_size_seconds=30,90            )91            transcript_docs = loader.load()92            return transcript_docs93 94        except Exception as e:95            return f"Error fetching video's transcript: {e}"96 97 98class ReverseStringTool(Tool):99    name = "reverse_string"100    description = "Reverses the input string."101    inputs = {102        "string": {103            "type": "string",104            "description": "The string that needs to be reversed.",105        }106    }107    output_type = "string"108 109    def forward(self, string: str):110        try:111            return string[-1::-1]112        except Exception as e:113            return f"Error reversing string: {e}"114 115 116class SmolVLM2:117    """The parent class for visual analyzer tools (using SmolVLM2-500M-Video model)"""118 119    def __init__(self):120        """Initializations for the analyzer tool"""121        model_path = "HuggingFaceTB/SmolVLM2-500M-Video-Instruct"122        device = "cpu"  # "cuda" if torch.cuda.is_available() else "cpu"123        self.processor = AutoProcessor.from_pretrained(model_path)124        self.model = AutoModelForImageTextToText.from_pretrained(125            model_path,126            torch_dtype=torch.bfloat16,127            # _attn_implementation="flash_attention_2",128        ).to(device)129 130 131class ImagesAnalyzerTool(Tool, SmolVLM2):132    name = "image_analyzer"133    description = "Analyzes each input image according to the query"134    inputs = {135        "query": {136            "type": "string",137            "description": "The query according to which the image will be analyzed.",138        },139        "images_urls": {140            "type": "array",141            "description": "A list of strings containing the images' urls",142            "items": {"type": "string"},143        },144    }145    output_type = "string"146 147    def __init__(self):148        Tool.__init__(self)149        SmolVLM2.__init__(self)150 151    def forward(self, query: str, images_urls: List[str]):152 153        try:154 155            # Image message entities for the different images' urls156            image_message_ents = [{"type": "image", "url": iu} for iu in images_urls]157 158            messages = [159                {160                    "role": "user",161                    "content": [162                        {163                            "type": "text",164                            "text": query,165                        },166                    ]167                    + image_message_ents,168                },169            ]170 171            inputs = self.processor.apply_chat_template(172                messages,173                add_generation_prompt=True,174                tokenize=True,175                return_dict=True,176                return_tensors="pt",177            ).to(self.model.device, dtype=torch.bfloat16)178 179            generated_ids = self.model.generate(180                **inputs, do_sample=False, max_new_tokens=64181            )182            generated_texts = self.processor.batch_decode(183                generated_ids,184                skip_special_tokens=True,185            )186            return generated_texts[0]187        except Exception as e:188            return f"Error analyzing image(s): {e}"189 190 191class VideoAnalyzerTool(Tool, SmolVLM2):192    name = "video_analyzer"193    description = "Analyzes video at a specified path according to the query"194    inputs = {195        "query": {196            "type": "string",197            "description": "The query according to which the video will be analyzed.",198        },199        "video_path": {200            "type": "string",201            "description": "A string containing the video path",202        },203    }204    output_type = "string"205 206    def __init__(self):207        Tool.__init__(self)208        SmolVLM2.__init__(self)209 210    def forward(self, query: str, video_path: str) -> str:211        try:212            messages = [213                {214                    "role": "user",215                    "content": [216                        {"type": "video", "path": video_path},217                        {"type": "text", "text": query},218                    ],219                },220            ]221 222            inputs = self.processor.apply_chat_template(223                messages,224                add_generation_prompt=True,225                tokenize=True,226                return_dict=True,227                return_tensors="pt",228            ).to(self.model.device, dtype=torch.bfloat16)229 230            generated_ids = self.model.generate(231                **inputs, do_sample=False, max_new_tokens=64232            )233            generated_texts = self.processor.batch_decode(234                generated_ids,235                skip_special_tokens=True,236            )237 238            return generated_texts[0]239        except Exception as e:240            return f"Error analyzing video: {e}"241        finally:242            # Cleanup if needed243            if video_path and os.path.exists(video_path):244                os.remove(video_path)245 246 247class FileDownloaderTool(Tool):248    name = "file_downloader"249    description = "Downloads a file returning the name of the temporarily saved file"250    inputs = {251        "file_url": {252            "type": "string",253            "description": "The url from which the file shall be downloaded.",254        },255    }256    output_type = "string"257 258    def forward(self, file_url: str) -> str:259        response = requests.get(file_url, stream=True)260        response.raise_for_status()261        original_filename = (262            response.headers.get("content-disposition", "")263            .split("=", -1)[-1]264            .strip('"')265        )266 267        # Even if original_filename is empty or there is no extension, ext will be ""268        ext = os.path.splitext(original_filename)[-1]269 270        with tempfile.NamedTemporaryFile(suffix=ext, delete=False) as tmp_file:271            for chunk in response.iter_content(chunk_size=8192):272                tmp_file.write(chunk)273            return tmp_file.name274 275 276class YoutubeVideoDownloaderTool(Tool):277    name = "youtube_video_downloader"278    description = "Downloads the video from the specified url and returns the path where the video was saved"279    inputs = {280        "video_url": {281            "type": "string",282            "description": "A string containing the video url",283        },284    }285    output_type = "string"286 287    def forward(self, video_url: str) -> str:288        try:289            saved_video_path = ""290            temp_dir = tempfile.gettempdir()291            ydl_opts = {292                "outtmpl": f"{temp_dir}/%(title)s.%(ext)s",  # Absolute or relative path293                "quiet": True,294            }295 296            # Download youtube video as a file in tmp directory297            with yt_dlp.YoutubeDL(ydl_opts) as ydl:298                info = ydl.extract_info(video_url, download=True)299                saved_video_path = ydl.prepare_filename(info)300                return saved_video_path301        except Exception as e:302            return f"Error downloading video: {e}"303 304 305class LoadXlsxFileTool(Tool):306    name = "load_xlsx_file"307    description = "This tool loads xlsx file into pandas and returns it"308    inputs = {"file_path": {"type": "string", "description": "File path"}}309    output_type = "object"310 311    def forward(self, file_path: str) -> object:312        return pd.read_excel(file_path)313 314 315class LoadTextFileTool(Tool):316    name = "load_text_file"317    description = "This tool loads any text file"318    inputs = {"file_path": {"type": "string", "description": "File path"}}319    output_type = "string"320 321    def forward(self, file_path: str) -> str:322        with open(file_path, "r", encoding="utf-8") as file:323            return file.read()324 325 326class WebpageTablesContextRetrieverTool(Tool):327    name = "webpage_tables_context_retriever"328    description = """Retrieves structural context for all tables on a webpage.329    Returns table indexes with captions, headers, and surrounding text to help identify relevant tables.330    Use this first to determine which table index to extract."""331    inputs = {332        "url": {"type": "string", "description": "The URL of the webpage to analyze"}333    }334    output_type = "object"335 336    def forward(self, url: str) -> Dict:337        """Retrieve context information for all tables on the page"""338        try:339            response = requests.get(url, timeout=15)340            response.raise_for_status()341            soup = BeautifulSoup(response.text, "html.parser")342 343            tables = soup.find_all("table")344            if not tables:345                return {346                    "status": "success",347                    "tables": [],348                    "message": "No tables found on page",349                    "url": url,350                }351 352            results = []353            for i, table in enumerate(tables):354                context = {355                    "index": i,356                    "id": table.get("id", ""),357                    "class": " ".join(table.get("class", [])),358                    "summary": table.get("summary", ""),359                    "caption": self._get_table_caption(table),360                    "preceding_header": self._get_preceding_header(table),361                    "surrounding_text": self._get_surrounding_text(table),362                }363                results.append(context)364 365            return {366                "status": "success",367                "tables": results,368                "url": url,369                "message": f"Found {len(results)} tables with context information",370                "suggestion": "Use html_table_extractor with the most relevant index",371            }372 373        except Exception as e:374            return {375                "status": "error",376                "url": url,377                "message": f"Failed to retrieve table contexts: {str(e)}",378            }379 380    def _get_table_caption(self, table) -> str:381        """Extract table caption text if available"""382        caption = table.find("caption")383        return caption.get_text(strip=True) if caption else ""384 385    def _get_preceding_header(self, table) -> str:386        """Find the nearest preceding heading"""387        for tag in table.find_all_previous(["h1", "h2", "h3", "h4", "h5", "h6"]):388            return tag.get_text(strip=True)389        return ""390 391    def _get_surrounding_text(self, table, chars=150) -> str:392        """Get relevant text around the table"""393        prev_text = " ".join(394            t.strip()395            for t in table.find_all_previous(string=True, limit=3)396            if t.strip()397        )398        next_text = " ".join(399            t.strip() for t in table.find_all_next(string=True, limit=3) if t.strip()400        )401        return f"...{prev_text[-chars:]} [TABLE] {next_text[:chars]}..."402 403 404class HtmlTableExtractorTool(Tool):405    name = "html_table_extractor"406    description = """Extracts a specific HTML table as structured data.407    Use after webpage_tables_context_retriever to get the correct table index."""408    inputs = {409        "page_url": {410            "type": "string",411            "description": "The webpage URL containing the table",412        },413        "table_index": {414            "type": "integer",415            "description": "0-based index of the table to extract (from webpage_tables_context_retriever)",416        },417    }418    output_type = "object"419 420    def forward(self, page_url: str, table_index: int) -> Dict:421        """Extract a specific table by index"""422        try:423            # First verify the URL is accessible424            test_request = requests.head(page_url, timeout=5)425            test_request.raise_for_status()426 427            # Read all tables428            tables = pd.read_html(page_url)429 430            if not tables:431                return {432                    "status": "error",433                    "message": "No tables found at URL",434                    "url": page_url,435                }436 437            # Validate index438            if table_index < 0 or table_index >= len(tables):439                return {440                    "status": "error",441                    "message": f"Invalid table index {table_index}. Page has {len(tables)} tables.",442                    "url": page_url,443                    "available_indexes": list(range(len(tables))),444                }445 446            # Convert DataFrame to JSON-serializable format447            df = tables[table_index]448            return {449                "status": "success",450                "table_index": table_index,451                "table_data": df,452                "url": page_url,453            }454 455        except Exception as e:456            return {457                "status": "error",458                "message": f"Table extraction failed: {str(e)}",459                "url": page_url,460                "table_index": table_index,461            }462