diff --git a/autogen/agentchat/contrib/tool_retriever.py b/autogen/agentchat/contrib/tool_retriever.py new file mode 100644 index 000000000000..340109598903 --- /dev/null +++ b/autogen/agentchat/contrib/tool_retriever.py @@ -0,0 +1,68 @@ +import pandas as pd +from sentence_transformers import SentenceTransformer, util + +from autogen import AssistantAgent, UserProxyAgent +from autogen.coding import LocalCommandLineCodeExecutor +from autogen.tool_utils import find_callables + + +class ToolBuilder: + TOOL_USING_PROMPT = """# Functions + You have access to the following functions. They can be accessed from the module called 'functions' by their function names. +For example, if there is a function called `foo` you could import it by writing `from functions import foo` + +{functions} +""" + + def __init__(self, corpus_path, retriever): + + self.df = pd.read_csv(corpus_path, sep="\t") + document_list = self.df["document_content"].tolist() + + self.model = SentenceTransformer(retriever) + self.embeddings = self.model.encode(document_list) + + def retrieve(self, query, top_k=3): + # Encode the query using the Sentence Transformer model + query_embedding = self.model.encode([query]) + + hits = util.semantic_search(query_embedding, self.embeddings, top_k=top_k) + + results = [] + for hit in hits[0]: + results.append(self.df.iloc[hit["corpus_id"], 1]) + return results + + def bind(self, agent: AssistantAgent, functions: str): + """Binds the function to the agent so that agent is aware of it.""" + sys_message = agent.system_message + sys_message += self.TOOL_USING_PROMPT.format(functions=functions) + agent.update_system_message(sys_message) + return + + def bind_user_proxy(self, agent: UserProxyAgent, tool_root: str): + """ + Updates user proxy agent with a executor so that code executor can successfully execute function-related code. + Returns an updated user proxy. + """ + # Find all the functions in the tool root + functions = find_callables(tool_root) + + code_execution_config = agent._code_execution_config + executor = LocalCommandLineCodeExecutor( + timeout=code_execution_config.get("timeout", 180), + work_dir=code_execution_config.get("work_dir", "coding"), + functions=functions, + ) + code_execution_config = { + "executor": executor, + "last_n_messages": code_execution_config.get("last_n_messages", 1), + } + updated_user_proxy = UserProxyAgent( + name=agent.name, + is_termination_msg=agent._is_termination_msg, + code_execution_config=code_execution_config, + human_input_mode="NEVER", + default_auto_reply=agent._default_auto_reply, + ) + return updated_user_proxy diff --git a/autogen/tool_utils.py b/autogen/tool_utils.py new file mode 100644 index 000000000000..03c8ca96deab --- /dev/null +++ b/autogen/tool_utils.py @@ -0,0 +1,47 @@ +import importlib.util +import inspect +import os +from textwrap import dedent, indent + + +def get_full_tool_description(py_file): + """ + Retrieves the function signature for a given Python file. + """ + with open(py_file, "r") as f: + code = f.read() + exec(code) + function_name = os.path.splitext(os.path.basename(py_file))[0] + if function_name in locals(): + func = locals()[function_name] + content = f"def {func.__name__}{inspect.signature(func)}:\n" + docstring = func.__doc__ + + if docstring: + docstring = dedent(docstring) + docstring = '"""' + docstring + '"""' + docstring = indent(docstring, " ") + content += docstring + "\n" + return content + else: + raise ValueError(f"Function {function_name} not found in {py_file}") + + +def find_callables(directory): + """ + Find all callable objects defined in Python files within the specified directory. + """ + callables = [] + for root, dirs, files in os.walk(directory): + for file in files: + if file.endswith(".py"): + module_name = os.path.splitext(file)[0] + module_path = os.path.join(root, file) + spec = importlib.util.spec_from_file_location(module_name, module_path) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + for name, value in module.__dict__.items(): + if callable(value) and name == module_name: + callables.append(value) + break + return callables diff --git a/notebook/agentchat_tools.ipynb b/notebook/agentchat_tools.ipynb new file mode 100644 index 000000000000..4016d743c33d --- /dev/null +++ b/notebook/agentchat_tools.ipynb @@ -0,0 +1,335 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Tool using in AutoGen\n", + "Authors: [Jiale Liu](https://github.com/LeoLjl), [Linxin Song](https://linxins.net/), [Jieyu Zhang](https://jieyuz2.github.io/)\n", + "\n", + "In this notebook, we introduce how to use tools in AutoGen. Given a query, a ToolBuilder will retrieve tools based on semantic similarity." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Preparations\n", + "To use all the tools in the library, we need to install requirements, obtain Bing api key and RapidApi key following the instructions in this link." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# %pip install -r ../tools/requirements.txt" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Setup API endpoint\n", + "\n", + "The [`config_list_from_json`](https://microsoft.github.io/autogen/docs/reference/oai/openai_utils#config_list_from_json) function loads a list of configurations from an environment variable or a json file. It first looks for an environment variable with a specified name. The value of the environment variable needs to be a valid json string. If that variable is not found, it looks for a json file with the same name. It filters the configs by filter_dict.\n", + "\n", + "The config list should look like this:\n", + "```python\n", + "config_list = [\n", + " {\n", + " 'model': 'gpt-4',\n", + " 'api_key': '',\n", + " }, # OpenAI API endpoint for gpt-4\n", + " {\n", + " 'model': 'gpt-4',\n", + " 'api_key': '',\n", + " 'base_url': '',\n", + " 'api_type': 'azure',\n", + " 'api_version': '2024-02-15-preview',\n", + " }, # Azure OpenAI API endpoint for gpt-4\n", + " {\n", + " 'model': 'gpt-4-32k',\n", + " 'api_key': '',\n", + " 'base_url': '',\n", + " 'api_type': 'azure',\n", + " 'api_version': '2024-02-15-preview',\n", + " }, # Azure OpenAI API endpoint for gpt-4-32k\n", + "]\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import autogen\n", + "\n", + "config_list = autogen.config_list_from_json(\n", + " \"OAI_CONFIG_LIST\",\n", + " filter_dict={\n", + " \"model\": [\"gpt-4-1106-preview\"],\n", + " },\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Configure Bing\n", + "In order for web search related tools to operate properly (`perform_web_search`), Bing API is needed. You can read more about how to get an API on the [Bing Web Search API](https://www.microsoft.com/en-us/bing/apis/bing-web-search-api) page.\n", + "\n", + "Once you have your key, fill in your key below." + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "\n", + "os.environ[\"BING_API_KEY\"] = \"\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Configure RapidAPI key\n", + "Some tools in information_retrieval category requires access to RapidAPI. You need to subscribe to these two specific apis in order for related tools to work([link1](https://rapidapi.com/illmagination/api/youtube-captions-and-transcripts/), [link2](https://rapidapi.com/420vijay47/api/youtube-mp3-downloader2/)). These apis have a free pricing tier, no need to worry about extra cost.\n", + "\n", + "Once you have the api keys, fill in your key below." + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "\n", + "os.environ[\"RAPID_API_KEY\"] = \"\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Step 1: Initate ToolBuilder and retrieve tools\n", + "Now that all things are set, we should initate a ToolBuilder object and retrieve tools. The ToolBuilder takes in a user query, calculates the semantic similarity between the query and tool description, then retrieves the `topk` amount of tools.\n", + "\n", + "Suppose the task we'd like to solve: Examine the video at https://www.youtube.com/watch?v=1htKBjuUWec. What does Teal'c say in response to the question \"Isn't that hot?\"\n", + "\n", + "This task requires video transcription skills, so the query can be: Expertise in utilizing YouTube's API or similar services to extract video captions or subtitles." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "['information_retrieval get_youtube_caption Retrieves the captions for a YouTube video.', 'information_retrieval youtube_download Downloads a YouTube video and returns the download link.', 'information_retrieval perform_web_search Perform a web search using Bing API.']\n" + ] + } + ], + "source": [ + "from autogen.agentchat.contrib.tool_retriever import ToolBuilder\n", + "\n", + "builder = ToolBuilder(\n", + " corpus_path=\"../tools/tool_description.tsv\",\n", + " retriever=\"all-mpnet-base-v2\",\n", + ")\n", + "tool_query = \"Expertise in utilizing YouTube's API or similar services to extract video captions or subtitles.\"\n", + "tools = builder.retrieve(tool_query, top_k=3)\n", + "print(tools)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Step 2: Get tool signatures and bind it to agents\n" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "\n", + "from autogen.tool_utils import get_full_tool_description\n", + "\n", + "tool_root = \"../tools\"\n", + "\n", + "descriptions = []\n", + "for tool in tools:\n", + " category, tool_name = tool.split(\" \")[0], tool.split(\" \")[1]\n", + " tool_path = os.path.join(tool_root, category, f\"{tool_name}.py\")\n", + " descriptions.append(get_full_tool_description(tool_path))\n", + "\n", + "assistant = autogen.AssistantAgent(\n", + " name=\"Information retriever\",\n", + " llm_config={\n", + " \"config_list\": config_list,\n", + " },\n", + " max_consecutive_auto_reply=5,\n", + ")\n", + "proxy = autogen.UserProxyAgent(\n", + " name=\"User proxy\",\n", + " is_termination_msg=lambda x: x.get(\"content\", \"\").rstrip().endswith(\"TERMINATE\"),\n", + " human_input_mode=\"NEVER\",\n", + " code_execution_config={\"work_dir\": \"coding\"}, # This will be updated later\n", + ")\n", + "\n", + "# Bind the tools to the assistant\n", + "builder.bind(assistant, \"\\n\\n\".join(descriptions))\n", + "\n", + "# Bind the tools to user proxy\n", + "proxy = builder.bind_user_proxy(proxy, tool_root)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Under the hood, the assistant's system message is updated with instructions on how to use tools by writing python code. The user proxy is equipped with executor that can run tool-related code. This feature is based on [User Defined Functions](https://microsoft.github.io/autogen/docs/topics/code-execution/user-defined-functions) and currently cannot operate on Docker." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Step 3: Let the agent finish the task" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33mUser proxy\u001b[0m (to Information retriever):\n", + "\n", + "Today's date is 2024-04-14.\n", + "# Task\n", + "You need to solve the below question given by a user.\n", + "\n", + "# Question\n", + "Examine the video at https://www.youtube.com/watch?v=1htKBjuUWec.\n", + "\n", + "What does Teal'c say in response to the question \"Isn't that hot?\"\n", + "\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mInformation retriever\u001b[0m (to User proxy):\n", + "\n", + "To solve this task, I will use the `get_youtube_caption` function to retrieve the captions for the YouTube video in question and then search the captions for the line where someone asks \"Isn't that hot?\" and find Teal'c's response to that question. I'll proceed step by step:\n", + "\n", + "1. Retrieve the captions of the YouTube video using its video ID (the part of the URL after \"watch?v=\").\n", + "2. Scan through the captions to find the line containing the question.\n", + "3. Extract Teal'c's response that immediately follows the question.\n", + "\n", + "Let's start with the first step.\n", + "\n", + "```python\n", + "# filename: get_youtube_captions.py\n", + "from functions import get_youtube_caption\n", + "\n", + "# The video ID for the YouTube video\n", + "video_id = '1htKBjuUWec'\n", + "\n", + "# Retrieve the captions of the video\n", + "captions = get_youtube_caption(video_id)\n", + "\n", + "# Print captions to proceed to the next step\n", + "print(captions)\n", + "```\n", + "\n", + "Please execute the above Python code and provide me with the output of the captions.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[31m\n", + ">>>>>>>> EXECUTING CODE BLOCK (inferred language is python)...\u001b[0m\n", + "\u001b[33mUser proxy\u001b[0m (to Information retriever):\n", + "\n", + "exitcode: 0 (execution succeeded)\n", + "Code output: Wow this coffee's great I was just thinking that yeah is that cinnamon chicory tea oak [Music] isn't that hot extremely\n", + "\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mInformation retriever\u001b[0m (to User proxy):\n", + "\n", + "Based on the provided captions, Teal'c's response to the question \"Isn't that hot?\" is:\n", + "\n", + "\"Extremely\"\n", + "\n", + "This is the answer you are looking for. There is no need to run further code, as the captions clearly provide Teal'c's response following the relevant question.\n", + "\n", + "TERMINATE\n", + "\n", + "--------------------------------------------------------------------------------\n" + ] + } + ], + "source": [ + "PROMPT = \"\"\"Today's date is 2024-04-14.\n", + "# Task\n", + "You need to solve the below question given by a user.\n", + "\n", + "# Question\n", + "{question}\n", + "\"\"\"\n", + "question = \"\"\"Examine the video at https://www.youtube.com/watch?v=1htKBjuUWec.\n", + "\n", + "What does Teal'c say in response to the question \"Isn't that hot?\"\n", + "\"\"\".strip()\n", + "\n", + "chat = proxy.initiate_chat(assistant, message=PROMPT.format(question=question))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If we look at the youtube video ourselves, we'll find out that the answer is correct. Provided with relevant apis, language models can do vision and audio related tasks, which can lead to more versatile and useful agents." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.14" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/tools/README.md b/tools/README.md new file mode 100644 index 000000000000..50e05897fc07 --- /dev/null +++ b/tools/README.md @@ -0,0 +1,44 @@ +# Introduction + +This directory contains a library of manually created python tools. These tools have three categories: math, data_analysis and information_retrieval. + +# Directory Layout +``` +tools +├── README.md +├── data_analysis +│ ├── calculate_correlation.py +│ └── ... +├── information_retrieval +│ ├── arxiv_download.py +│ ├── arxiv_search.py +│ └── ... +├── math +│ ├── calculate_circle_area_from_diameter.py +│ └── ... +└── tool_description.tsv +``` + +Tools can be imported from `tools/{category}/{tool_name}.py` with exactly the same function name. + +`tool_description.tsv` contains descriptions of tools for retrieval. + +# How to use +Some tools require Bing Search API key and RapidAPI key. For Bing API, you can read more about how to get an API on the [Bing Web Search API](https://www.microsoft.com/en-us/bing/apis/bing-web-search-api) page. For RapidAPI, you can [sign up](https://rapidapi.com/auth/sign-up) and subscribe to these two links([link1](https://rapidapi.com/illmagination/api/youtube-captions-and-transcripts/), [link2](https://rapidapi.com/420vijay47/api/youtube-mp3-downloader2/)). These apis have free billing options and there is no need to worry about extra costs. + +To install the requirements for running tools, use pip install. +```bash +pip install -r tools/requirements.txt +``` + +Whenever you run the tool-related code, remember to export the api keys to system variables. +```bash +export BING_API_KEY="" +export RAPID_API_KEY="" +``` +or +```python +import os +os.environ["BING_API_KEY"] = "" +os.environ["RAPID_API_KEY"] = "" +``` diff --git a/tools/data_analysis/calculate_correlation.py b/tools/data_analysis/calculate_correlation.py new file mode 100644 index 000000000000..ccd51d3d93b9 --- /dev/null +++ b/tools/data_analysis/calculate_correlation.py @@ -0,0 +1,38 @@ +def calculate_correlation(csv_path: str, column1: str, column2: str, method: str = "pearson") -> float: + """ + Calculate the correlation between two columns in a CSV file. + + Args: + csv_path (str): The path to the CSV file. + column1 (str): The name of the first column. + column2 (str): The name of the second column. + method (str or callable, optional): The method used to calculate the correlation. + - 'pearson' (default): Pearson correlation coefficient. + - 'kendall': Kendall Tau correlation coefficient. + - 'spearman': Spearman rank correlation coefficient. + - callable: A custom correlation function that takes two arrays and returns a scalar. + + Returns: + float: The correlation coefficient between the two columns. + """ + import pandas as pd + + # Read the CSV file into a pandas DataFrame + df = pd.read_csv(csv_path) + + # Select the specified columns + selected_columns = df[[column1, column2]] + + # Calculate the correlation based on the specified method + if method == "pearson": + correlation = selected_columns.corr().iloc[0, 1] + elif method == "kendall": + correlation = selected_columns.corr(method="kendall").iloc[0, 1] + elif method == "spearman": + correlation = selected_columns.corr(method="spearman").iloc[0, 1] + elif callable(method): + correlation = selected_columns.corr(method=method).iloc[0, 1] + else: + raise ValueError("Invalid correlation method. Please choose 'pearson', 'kendall', 'spearman', or a callable.") + + return correlation diff --git a/tools/data_analysis/calculate_skewness_and_kurtosis.py b/tools/data_analysis/calculate_skewness_and_kurtosis.py new file mode 100644 index 000000000000..4b1737fe02d3 --- /dev/null +++ b/tools/data_analysis/calculate_skewness_and_kurtosis.py @@ -0,0 +1,26 @@ +def calculate_skewness_and_kurtosis(csv_file: str, column_name: str) -> tuple: + """ + Calculate the skewness and kurtosis of a specified column in a CSV file. The kurtosis is calculated using the Fisher definition. + The two metrics are computed using scipy.stats functions. + + Args: + csv_file (str): The path to the CSV file. + column_name (str): The name of the column to calculate skewness and kurtosis for. + + Returns: + tuple: (skewness, kurtosis) + """ + import pandas as pd + from scipy.stats import kurtosis, skew + + # Read the CSV file into a pandas DataFrame + df = pd.read_csv(csv_file) + + # Extract the specified column + column = df[column_name] + + # Calculate the skewness and kurtosis + skewness = skew(column) + kurt = kurtosis(column) + + return skewness, kurt diff --git a/tools/data_analysis/detect_outlier_iqr.py b/tools/data_analysis/detect_outlier_iqr.py new file mode 100644 index 000000000000..1fcb493224fc --- /dev/null +++ b/tools/data_analysis/detect_outlier_iqr.py @@ -0,0 +1,26 @@ +def detect_outlier_iqr(csv_file: str, column_name: str): + """ + Detect outliers in a specified column of a CSV file using the IQR method. + + Args: + csv_file (str): The path to the CSV file. + column_name (str): The name of the column to detect outliers in. + + Returns: + list: A list of row indices that correspond to the outliers. + """ + import pandas as pd + + # Read the CSV file into a pandas DataFrame + df = pd.read_csv(csv_file) + + # Calculate the quartiles and IQR for the specified column + q1 = df[column_name].quantile(0.25) + q3 = df[column_name].quantile(0.75) + iqr = q3 - q1 + + # Find the outliers based on the defined criteria + outliers = df[(df[column_name] < q1 - 1.5 * iqr) | (df[column_name] > q3 + 1.5 * iqr)] + + # Return the row indices of the outliers + return outliers.index.tolist() diff --git a/tools/data_analysis/detect_outlier_zscore.py b/tools/data_analysis/detect_outlier_zscore.py new file mode 100644 index 000000000000..ec0f8742da3b --- /dev/null +++ b/tools/data_analysis/detect_outlier_zscore.py @@ -0,0 +1,26 @@ +def detect_outlier_zscore(csv_file, column_name, threshold=3): + """ + Detect outliers in a CSV file based on a specified column. The outliers are determined by calculating the z-score of the data points in the column. + + Args: + csv_file (str): The path to the CSV file. + column_name (str): The name of the column to calculate z-scores for. + threshold (float, optional): The threshold value for determining outliers. By default set to 3. + + Returns: + list: A list of row indices where the z-score is above the threshold. + """ + import numpy as np + import pandas as pd + + # Read the CSV file into a pandas DataFrame + df = pd.read_csv(csv_file) + + # Calculate the z-score for the specified column + z_scores = np.abs((df[column_name] - df[column_name].mean()) / df[column_name].std()) + + # Find the row indices where the z-score is above the threshold + outlier_indices = np.where(z_scores > threshold)[0] + + # Return the row indices of the outliers + return outlier_indices diff --git a/tools/data_analysis/explore_csv.py b/tools/data_analysis/explore_csv.py new file mode 100644 index 000000000000..6e233c05db80 --- /dev/null +++ b/tools/data_analysis/explore_csv.py @@ -0,0 +1,19 @@ +def explore_csv(file_path, num_lines=5): + """ + Reads a CSV file and prints the column names, shape, data types, and the first few lines of data. + + Args: + file_path (str): The path to the CSV file. + num_lines (int, optional): The number of lines to print. Defaults to 5. + """ + import pandas as pd + + df = pd.read_csv(file_path) + header = df.columns + print("Columns:") + print(", ".join(header)) + print("Shape:", df.shape) + print("Data Types:") + print(df.dtypes) + print("First", num_lines, "lines:") + print(df.head(num_lines)) diff --git a/tools/data_analysis/shapiro_wilk_test.py b/tools/data_analysis/shapiro_wilk_test.py new file mode 100644 index 000000000000..0d2f51f3a559 --- /dev/null +++ b/tools/data_analysis/shapiro_wilk_test.py @@ -0,0 +1,28 @@ +from autogen.coding.func_with_reqs import with_requirements + + +@with_requirements(["pandas", "scipy"]) +def shapiro_wilk_test(csv_file, column_name): + """ + Perform the Shapiro-Wilk test on a specified column of a CSV file. + + Args: + csv_file (str): The path to the CSV file. + column_name (str): The name of the column to perform the test on. + + Returns: + float: The p-value resulting from the Shapiro-Wilk test. + """ + import pandas as pd + from scipy.stats import shapiro + + # Read the CSV file into a pandas DataFrame + df = pd.read_csv(csv_file) + + # Extract the specified column as a numpy array + column_data = df[column_name].values + + # Perform the Shapiro-Wilk test + _, p_value = shapiro(column_data) + + return p_value diff --git a/tools/information_retrieval/arxiv_download.py b/tools/information_retrieval/arxiv_download.py new file mode 100644 index 000000000000..53fbff94fd06 --- /dev/null +++ b/tools/information_retrieval/arxiv_download.py @@ -0,0 +1,23 @@ +import arxiv + +from autogen.coding.func_with_reqs import with_requirements + + +@with_requirements(["arxiv"], ["arxiv"]) +def arxiv_download(id_list: list, download_dir="./"): + """ + Downloads PDF files from ArXiv based on a list of arxiv paper IDs. + + Args: + id_list (list): A list of paper IDs to download. e.g. [2302.00006v1] + download_dir (str, optional): The directory to save the downloaded PDF files. Defaults to './'. + + Returns: + list: A list of paths to the downloaded PDF files. + """ + paths = [] + for paper in arxiv.Client().results(arxiv.Search(id_list=id_list)): + path = paper.download_pdf(download_dir, filename=paper.get_short_id() + ".pdf") + paths.append(path) + print("Paper id:", paper.get_short_id(), "Downloaded to:", path) + return paths diff --git a/tools/information_retrieval/arxiv_search.py b/tools/information_retrieval/arxiv_search.py new file mode 100644 index 000000000000..7a62143dc056 --- /dev/null +++ b/tools/information_retrieval/arxiv_search.py @@ -0,0 +1,52 @@ +import arxiv + +from autogen.coding.func_with_reqs import with_requirements + + +@with_requirements(["arxiv"], ["arxiv"]) +def arxiv_search(query, max_results=10, sortby="relevance"): + """ + Search for articles on arXiv based on the given query. + + Args: + query (str): The search query. + max_results (int, optional): The maximum number of results to retrieve. Defaults to 10. + sortby (str, optional): The sorting criterion for the search results. Can be 'relevance' or 'submittedDate'. Defaults to 'relevance'. + + Returns: + list: A list of dictionaries containing information about the search results. Each dictionary contains the following keys: + - 'title': The title of the article. + - 'authors': The authors of the article. + - 'summary': The summary of the article. + - 'entry_id': The entry ID of the article. + - 'doi': The DOI of the article (If applicable). + - 'published': The publication date of the article in the format 'Y-M'. + """ + + def get_author(r): + return ", ".join(a.name for a in r.authors) + + criterion = {"relevance": arxiv.SortCriterion.Relevance, "submittedDate": arxiv.SortCriterion.SubmittedDate}[sortby] + + client = arxiv.Client() + search = arxiv.Search(query=query, max_results=max_results, sort_by=criterion) + res = [] + results = client.results(search) + for r in results: + print("Entry id:", r.entry_id) + print("Title:", r.title) + print("Authors:", get_author(r)) + print("DOI:", r.doi) + print("Published:", r.published.strftime("%Y-%m")) + # print("Summary:", r.summary) + res.append( + { + "title": r.title, + "authors": get_author(r), + "summary": r.summary, + "entry_id": r.entry_id, + "doi": r.doi, + "published": r.published.strftime("%Y-%m"), + } + ) + return res diff --git a/tools/information_retrieval/docx_to_md.py b/tools/information_retrieval/docx_to_md.py new file mode 100644 index 000000000000..a9a5fbe180ea --- /dev/null +++ b/tools/information_retrieval/docx_to_md.py @@ -0,0 +1,40 @@ +def docx_to_md(local_path): + """ + Converts a DOCX file to Markdown format. + + Args: + local_path (str): The local path of the DOCX file. + + Returns: + str: The converted Markdown content. + """ + # A simplified version of https://github.com/microsoft/autogen/blob/266cefc1737e0077667bce441c541a90865582b1/autogen/browser_utils/mdconvert.py + # Will import the MdConverter Class as soon as PR#1929 is merged. + import mammoth + import markdownify + from bs4 import BeautifulSoup + + def _convert(html_content): + """Helper function that converts and HTML string.""" + + # Parse the string + soup = BeautifulSoup(html_content, "html.parser") + + # Remove javascript and style blocks + for script in soup(["script", "style"]): + script.extract() + + # Print only the main content + body_elm = soup.find("body") + webpage_text = "" + if body_elm: + webpage_text = markdownify.MarkdownConverter().convert_soup(body_elm) + else: + webpage_text = markdownify.MarkdownConverter().convert_soup(soup) + return webpage_text + + with open(local_path, "rb") as docx_file: + result = mammoth.convert_to_html(docx_file) + html_content = result.value + result = _convert(html_content) + return result diff --git a/tools/information_retrieval/extract_pdf_image.py b/tools/information_retrieval/extract_pdf_image.py new file mode 100644 index 000000000000..4b2ffd03d4d4 --- /dev/null +++ b/tools/information_retrieval/extract_pdf_image.py @@ -0,0 +1,51 @@ +import os + +from autogen.coding.func_with_reqs import with_requirements + + +@with_requirements(["PyMuPDF"], ["os"]) +def extract_pdf_image(pdf_path: str, output_dir: str, page_number=None): + """ + Extracts images from a PDF file and saves them to the specified output directory. + + Args: + pdf_path (str): The path to the PDF file. + output_dir (str): The directory to save the extracted images. + page_number (int, optional): The page number to extract images from. If not provided, extract images from all pages. + """ + import fitz # PyMuPDF library + + # Open the PDF file + doc = fitz.open(pdf_path) + + # Create the output directory if it doesn't exist + os.makedirs(output_dir, exist_ok=True) + + # Extract images from the PDF file + images = [] + if page_number is not None: + page = doc[page_number - 1] # Adjust page number to 0-based index + for img in page.get_images(): + xref = img[0] + base_image = doc.extract_image(xref) + image_bytes = base_image["image"] + images.append(image_bytes) + else: + for page in doc: + for img in page.get_images(): + xref = img[0] + base_image = doc.extract_image(xref) + image_bytes = base_image["image"] + images.append(image_bytes) + + # Save the extracted images + for i, image_bytes in enumerate(images): + image_path = os.path.join(output_dir, f"image_{i}.png") + with open(image_path, "wb") as f: + f.write(image_bytes) + + # Print the total number of images saved + print(f"Saved a total of {len(images)} images") + + # Close the PDF file + doc.close() diff --git a/tools/information_retrieval/extract_pdf_text.py b/tools/information_retrieval/extract_pdf_text.py new file mode 100644 index 000000000000..01ff60bdc5ab --- /dev/null +++ b/tools/information_retrieval/extract_pdf_text.py @@ -0,0 +1,36 @@ +from autogen.coding.func_with_reqs import with_requirements + + +@with_requirements(["PyMuPDF"]) +def extract_pdf_text(pdf_path, page_number=None): + """ + Extracts text from a specified page or the entire PDF file. + + Args: + pdf_path (str): The path to the PDF file. + page_number (int, optional): The page number to extract (starting from 0). If not provided, + the function will extract text from the entire PDF file. + + Returns: + str: The extracted text. + """ + import fitz + + # Open the PDF file + doc = fitz.open(pdf_path) + + # Extract text from the entire PDF file or a specific page + text = "" + if page_number is None: + # Extract content from the entire PDF file + for page in doc: + text += page.get_text() + else: + # Extract content from a specific page + page = doc[page_number] + text = page.get_text() + + # Close the PDF file + doc.close() + + return text diff --git a/tools/information_retrieval/get_wikipedia_text.py b/tools/information_retrieval/get_wikipedia_text.py new file mode 100644 index 000000000000..8c21b9a65620 --- /dev/null +++ b/tools/information_retrieval/get_wikipedia_text.py @@ -0,0 +1,19 @@ +def get_wikipedia_text(title): + """ + Retrieves the text content of a Wikipedia page. It does not support tables and other complex formatting. + + Args: + title (str): The title of the Wikipedia page. + + Returns: + str or None: The text content of the Wikipedia page if it exists, None otherwise. + """ + import wikipediaapi + + wiki_wiki = wikipediaapi.Wikipedia("Mozilla/5.0 (merlin@example.com)", "en") + page = wiki_wiki.page(title) + + if page.exists(): + return page.text + else: + return None diff --git a/tools/information_retrieval/get_youtube_caption.py b/tools/information_retrieval/get_youtube_caption.py new file mode 100644 index 000000000000..c111eb895a2f --- /dev/null +++ b/tools/information_retrieval/get_youtube_caption.py @@ -0,0 +1,30 @@ +# alternative api: https://rapidapi.com/omarmhaimdat/api/youtube-v2 + + +def get_youtube_caption(videoId): + """ + Retrieves the captions for a YouTube video. + + Args: + videoId (str): The ID of the YouTube video. + + Returns: + str: The captions of the YouTube video in text format. + + Raises: + KeyError: If the RAPID_API_KEY environment variable is not set. + """ + import os + + import requests + + RAPID_API_KEY = os.environ["RAPID_API_KEY"] + url = "https://youtube-captions-and-transcripts.p.rapidapi.com/getCaptions" + + querystring = {"videoId": videoId, "lang": "en", "format": "text"} + + headers = {"X-RapidAPI-Key": RAPID_API_KEY, "X-RapidAPI-Host": "youtube-captions-and-transcripts.p.rapidapi.com"} + + response = requests.get(url, headers=headers, params=querystring) + response = response.json() + return response["data"] diff --git a/tools/information_retrieval/image_qa.py b/tools/information_retrieval/image_qa.py new file mode 100644 index 000000000000..7856f0fd0cfa --- /dev/null +++ b/tools/information_retrieval/image_qa.py @@ -0,0 +1,62 @@ +import os + +import textract +from PIL import Image + +from autogen.coding.func_with_reqs import with_requirements + + +@with_requirements(["textract", "transformers", "torch"], ["textract", "transformers", "torch", "PIL", "os"]) +def image_qa(image, question, ckpt="Salesforce/blip-vqa-base"): + """ + Perform question answering on an image using a pre-trained VQA model. + + Args: + image (Union[str, Image.Image]): The image to perform question answering on. It can be either file path to the image or a PIL Image object. + question: The question to ask about the image. + + Returns: + dict: The generated answer text. + """ + import torch + from transformers import BlipForQuestionAnswering, BlipProcessor + + def image_processing(img): + if isinstance(img, Image.Image): + return img.convert("RGB") + elif isinstance(img, str): + if os.path.exists(img): + return Image.open(img).convert("RGB") + else: + full_path = img + if os.path.exists(full_path): + return Image.open(full_path).convert("RGB") + else: + raise FileNotFoundError + + def text_processing(file_path): + # Check the file extension + if file_path.endswith(".txt"): + with open(file_path, "r") as file: + content = file.read() + elif file_path.endswith(".doc") or file_path.endswith(".docx"): + # Use textract to extract text from doc and docx files + content = textract.process(file_path).decode("utf-8") + else: + # if the file is not .txt .doc .docx, then it is a string, directly return the string + return file_path + return content + + image = image_processing(image) + question = text_processing(question) + + processor = BlipProcessor.from_pretrained(ckpt) + model = BlipForQuestionAnswering.from_pretrained(ckpt, torch_dtype=torch.float16).to("cuda") + + raw_image = image + + inputs = processor(raw_image, question, return_tensors="pt").to("cuda", torch.float16) + out = model.generate(**inputs) + result_formatted = processor.decode(out[0], skip_special_tokens=True) + + return result_formatted diff --git a/tools/information_retrieval/optical_character_recognition.py b/tools/information_retrieval/optical_character_recognition.py new file mode 100644 index 000000000000..2a641a0f6a83 --- /dev/null +++ b/tools/information_retrieval/optical_character_recognition.py @@ -0,0 +1,59 @@ +import os + +from autogen.coding.func_with_reqs import with_requirements + + +@with_requirements(["easyocr"], ["os"]) +def optical_character_recognition(image): + """ + Perform optical character recognition (OCR) on the given image. + + Args: + image (Union[str, Image.Image]): The image to perform OCR on. It can be either a file path or an Image object. + + Returns: + str: The extracted text from the image. + + Raises: + FileNotFoundError: If the image file path does not exist. + """ + import io + + import easyocr + from PIL import Image + + def image_processing(img): + if isinstance(img, Image.Image): + return img.convert("RGB") + elif isinstance(img, str): + if os.path.exists(img): + return Image.open(img).convert("RGB") + else: + full_path = img + if os.path.exists(full_path): + return Image.open(full_path).convert("RGB") + else: + raise FileNotFoundError + + reader = easyocr.Reader(["en"]) # Load the OCR model into memory + + if isinstance(image, str): + # If image is a path, use it directly + if not os.path.exists(image): + raise FileNotFoundError + image_path_or_bytes = image + else: + # If image is an Image object, convert it to a bytes stream + buffer = io.BytesIO() + image = image_processing(image) # Process the image if needed + image.save(buffer, format="JPEG") + buffer.seek(0) + image_path_or_bytes = buffer + + # Read text from the image or image path + result = reader.readtext(image_path_or_bytes) + + # Extract only the text from the result + result_text = [text for _, text, _ in result] + + return ", ".join(result_text) diff --git a/tools/information_retrieval/perform_web_search.py b/tools/information_retrieval/perform_web_search.py new file mode 100644 index 000000000000..f6a3b4b5ea0c --- /dev/null +++ b/tools/information_retrieval/perform_web_search.py @@ -0,0 +1,45 @@ +def perform_web_search(query, count=10, offset=0): + """ + Perform a web search using Bing API. + + Args: + query (str): The search query. + count (int, optional): Number of search results to retrieve. Defaults to 10. + offset (int, optional): Offset of the first search result. Defaults to 0. + + Returns: + The name, URL and snippet of each search result. + """ + import os + + import requests + + # Get the Bing API key from the environment variable + bing_api_key = os.getenv("BING_API_KEY") + + # Check if the API key is available + if not bing_api_key: + raise ValueError("Bing API key not found in environment variable") + + # Set up the API request + url = "https://api.bing.microsoft.com/v7.0/search" + headers = { + "Ocp-Apim-Subscription-Key": bing_api_key, + } + params = { + "q": query, + "count": count, # Number of search results to retrieve + "offset": offset, # Offset of the first search result + } + + # Send the API request + response = requests.get(url, headers=headers, params=params) + response.raise_for_status() + + # Process the search results + search_results = response.json() + for index, result in enumerate(search_results["webPages"]["value"]): + print(f"Search Result {index+1}:") + print(result["name"]) + print(result["url"]) + print(result["snippet"]) diff --git a/tools/information_retrieval/pptx_to_md.py b/tools/information_retrieval/pptx_to_md.py new file mode 100644 index 000000000000..7f56bc6a11eb --- /dev/null +++ b/tools/information_retrieval/pptx_to_md.py @@ -0,0 +1,112 @@ +import markdownify +import pptx + +from autogen.coding.func_with_reqs import with_requirements + + +@with_requirements(["python-pptx", "beautifulsoup4", "markdownify"], ["pptx", "markdownify"]) +def pptx_to_md(local_path): + """ + Convert a PowerPoint presentation (PPTX) to Markdown format. + + Args: + local_path (str): The local path to the PPTX file. + + Returns: + str: The Markdown content generated from the PPTX file. + """ + # A simplified version of https://github.com/microsoft/autogen/blob/266cefc1737e0077667bce441c541a90865582b1/autogen/browser_utils/mdconvert.py + # Will change the code into importing the MdConverter Class as soon as PR#1929 is merged. + import html + import re + + from bs4 import BeautifulSoup + + def _is_picture(shape): + # Check if shape is a picture + if shape.shape_type == pptx.enum.shapes.MSO_SHAPE_TYPE.PICTURE: + return True + if shape.shape_type == pptx.enum.shapes.MSO_SHAPE_TYPE.PLACEHOLDER: + if hasattr(shape, "image"): + return True + return False + + def _is_table(shape): + # Check if shape is a table + if shape.shape_type == pptx.enum.shapes.MSO_SHAPE_TYPE.TABLE: + return True + return False + + def _convert(html_content): + """Helper function that converts and HTML string.""" + + # Parse the string + soup = BeautifulSoup(html_content, "html.parser") + + # Remove javascript and style blocks + for script in soup(["script", "style"]): + script.extract() + + # Print only the main content + body_elm = soup.find("body") + webpage_text = "" + if body_elm: + webpage_text = markdownify.MarkdownConverter().convert_soup(body_elm) + else: + webpage_text = markdownify.MarkdownConverter().convert_soup(soup) + return webpage_text + + md_content = "" + presentation = pptx.Presentation(local_path) + slide_num = 0 + for slide in presentation.slides: + slide_num += 1 + + md_content += f"\n\n\n" + + title = slide.shapes.title + for shape in slide.shapes: + # Pictures + if _is_picture(shape): + alt_text = "" + try: + alt_text = shape._element._nvXxPr.cNvPr.attrib.get("descr", "") + except Exception: + pass + + filename = re.sub(r"\W", "", shape.name) + ".jpg" + md_content += "\n![" + (alt_text if alt_text else shape.name) + "](" + filename + ")\n" + + # Tables + if _is_table(shape): + html_table = "" + first_row = True + for row in shape.table.rows: + html_table += "" + for cell in row.cells: + if first_row: + html_table += "" + else: + html_table += "" + html_table += "" + first_row = False + html_table += "
" + html.escape(cell.text) + "" + html.escape(cell.text) + "
" + md_content += "\n" + _convert(html_table).text_content.strip() + "\n" + + # Text areas + elif shape.has_text_frame: + if shape == title: + md_content += "# " + shape.text.lstrip() + " " + else: + md_content += shape.text + " " + + md_content = md_content.strip() + + if slide.has_notes_slide: + md_content += "\n\n### Notes:\n" + notes_frame = slide.notes_slide.notes_text_frame + if notes_frame is not None: + md_content += notes_frame.text + md_content = md_content.strip() + + return md_content diff --git a/tools/information_retrieval/scrape_wikipedia_tables.py b/tools/information_retrieval/scrape_wikipedia_tables.py new file mode 100644 index 000000000000..6fb24d7e1797 --- /dev/null +++ b/tools/information_retrieval/scrape_wikipedia_tables.py @@ -0,0 +1,31 @@ +def scrape_wikipedia_tables(url: str, header_keyword: str): + """ + Scrapes Wikipedia tables based on a given URL and header keyword. + + Args: + url: The URL of the Wikipedia page to scrape. + header_keyword: The keyword to search for in the headers of the page. + + Returns: + list: A list of lists representing the scraped table data. Each inner list represents a row in the table, + with each element representing a cell value. + """ + import requests + from bs4 import BeautifulSoup + + response = requests.get(url) + response.raise_for_status() + soup = BeautifulSoup(response.content, "html.parser") + headers = soup.find_all(["h1", "h2", "h3", "h4", "h5", "h6"]) + data = [] + for header in headers: + if header_keyword.lower() in header.text.lower(): + table = header.find_next_sibling("table", class_="wikitable") + if table: + rows = table.find_all("tr") + for row in rows: + cols = row.find_all(["th", "td"]) + cols = [ele.text.strip() for ele in cols] + data.append([ele for ele in cols if ele]) + break + return data diff --git a/tools/information_retrieval/spreadsheet_to_md.py b/tools/information_retrieval/spreadsheet_to_md.py new file mode 100644 index 000000000000..8c16bb7a170c --- /dev/null +++ b/tools/information_retrieval/spreadsheet_to_md.py @@ -0,0 +1,47 @@ +import markdownify + +from autogen.coding.func_with_reqs import with_requirements + + +@with_requirements(["markdownify"], ["markdownify"]) +def spreadsheet_to_md(path): + """ + Convert an Excel spreadsheet file to Markdown format. + + Args: + path (str): The path to the Excel file. + + Returns: + str: The Markdown content generated from the Excel file. + """ + # A simplified version of https://github.com/microsoft/autogen/blob/266cefc1737e0077667bce441c541a90865582b1/autogen/browser_utils/mdconvert.py + # Will change the code into importing the MdConverter Class as soon as PR#1929 is merged. + import pandas as pd + from bs4 import BeautifulSoup + + def _convert(html_content): + """Helper function that converts and HTML string.""" + + # Parse the string + soup = BeautifulSoup(html_content, "html.parser") + + # Remove javascript and style blocks + for script in soup(["script", "style"]): + script.extract() + + # Print only the main content + body_elm = soup.find("body") + webpage_text = "" + if body_elm: + webpage_text = markdownify.MarkdownConverter().convert_soup(body_elm) + else: + webpage_text = markdownify.MarkdownConverter().convert_soup(soup) + return webpage_text + + sheets = pd.read_excel(path, sheet_name=None) + md_content = "" + for s in sheets: + md_content += f"## {s}\n" + html_content = sheets[s].to_html(index=False) + md_content += _convert(html_content).strip() + "\n\n" + return md_content diff --git a/tools/information_retrieval/transcribe_audio_file.py b/tools/information_retrieval/transcribe_audio_file.py new file mode 100644 index 000000000000..c9a7d79d75a7 --- /dev/null +++ b/tools/information_retrieval/transcribe_audio_file.py @@ -0,0 +1,19 @@ +from autogen.coding.func_with_reqs import with_requirements + + +@with_requirements(["openai-whisper"]) +def transcribe_audio_file(file_path): + """ + Transcribes the audio file located at the given file path. + + Args: + file_path (str): The path to the audio file. + + Returns: + str: The transcribed text from the audio file. + """ + import whisper + + model = whisper.load_model("base") + result = model.transcribe(file_path) + return result["text"] diff --git a/tools/information_retrieval/youtube_download.py b/tools/information_retrieval/youtube_download.py new file mode 100644 index 000000000000..0bc89d3074be --- /dev/null +++ b/tools/information_retrieval/youtube_download.py @@ -0,0 +1,33 @@ +def youtube_download(url: str): + """ + Downloads a YouTube video and returns the download link. + + Args: + url: The URL of the YouTube video. + + Returns: + str: The download link for the audio. + """ + import os + + import requests + + endpoint = "https://youtube-mp3-downloader2.p.rapidapi.com/ytmp3/ytmp3/" + + querystring = {"url": url} + + headers = { + "X-RapidAPI-Key": os.environ.get("RAPIDAPI_KEY"), + "X-RapidAPI-Host": "youtube-mp3-downloader2.p.rapidapi.com", + } + + response = requests.get(endpoint, headers=headers, params=querystring) + response = response.json() + + if "link" in response: + return response["link"] + else: + print("Error: Unable to retrieve download link.") + print(response) + # or you can return an error message + # return "Error: Unable to retrieve download link." diff --git a/tools/math/calculate_circle_area_from_diameter.py b/tools/math/calculate_circle_area_from_diameter.py new file mode 100644 index 000000000000..ebf1601dccab --- /dev/null +++ b/tools/math/calculate_circle_area_from_diameter.py @@ -0,0 +1,19 @@ +from autogen.coding.func_with_reqs import with_requirements + + +@with_requirements(["sympy"]) +def calculate_circle_area_from_diameter(diameter): + """ + Calculate the area of a circle given its diameter. + + Args: + diameter (float): The diameter of the circle. + + Returns: + float: The area of the circle. + """ + from sympy import pi + + radius = diameter / 2 + area = pi * radius**2 + return area diff --git a/tools/math/calculate_day_of_the_week.py b/tools/math/calculate_day_of_the_week.py new file mode 100644 index 000000000000..ef1c2d875674 --- /dev/null +++ b/tools/math/calculate_day_of_the_week.py @@ -0,0 +1,16 @@ +def calculate_day_of_the_week(total_days: int, starting_day: str): + """ + Calculates the day of the week after a given number of days starting from a specified day. + + Args: + total_days: The number of days to calculate. + starting_day: The starting day of the week, should be one of 'Monday', 'Tuesday', 'Wednesday', etc. + + Returns: + str: The day of the week after the specified number of days. + """ + days_of_week = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"] + + start_index = days_of_week.index(starting_day) + end_index = (start_index + total_days) % 7 + return days_of_week[end_index] diff --git a/tools/math/calculate_fraction_sum.py b/tools/math/calculate_fraction_sum.py new file mode 100644 index 000000000000..819fb40be746 --- /dev/null +++ b/tools/math/calculate_fraction_sum.py @@ -0,0 +1,26 @@ +def calculate_fraction_sum( + fraction1_numerator: int, fraction1_denominator: int, fraction2_numerator: int, fraction2_denominator: int +): + """ + Calculates the sum of two fractions and returns the result as a mixed number. + + Args: + fraction1_numerator: The numerator of the first fraction. + fraction1_denominator: The denominator of the first fraction. + fraction2_numerator: The numerator of the second fraction. + fraction2_denominator: The denominator of the second fraction. + + Returns: + str: The sum of the two fractions as a mixed number in the format 'a b/c' + """ + from fractions import Fraction + + fraction1 = Fraction(fraction1_numerator, fraction1_denominator) + fraction2 = Fraction(fraction2_numerator, fraction2_denominator) + result = fraction1 + fraction2 + mixed_number = result.numerator // result.denominator + mixed_fraction_numerator = result.numerator % result.denominator + if mixed_fraction_numerator > 0: + return f"{mixed_number} {Fraction(mixed_fraction_numerator, result.denominator)}" + else: + return str(mixed_number) diff --git a/tools/math/calculate_matrix_power.py b/tools/math/calculate_matrix_power.py new file mode 100644 index 000000000000..6c00e0de21ca --- /dev/null +++ b/tools/math/calculate_matrix_power.py @@ -0,0 +1,29 @@ +from autogen.coding.func_with_reqs import with_requirements + + +@with_requirements(["sympy"]) +def calculate_matrix_power(matrix, power): + """ + Calculate the power of a given matrix. + + Args: + matrix (list): An array of numbers that represents the matrix. + power (int): The power to which the matrix is raised. + + Returns: + Matrix: The resulting matrix after raising to power. + + Raises: + ValueError: If the power is negative and the matrix is not invertible. + """ + from sympy import Matrix, eye + + m = Matrix(matrix) + if power == 0: + return eye(m.shape[0]) + elif power < 0: + if not m.is_invertible(): + raise ValueError("Matrix is not invertible.") + return m.inverse() ** (-power) + elif power > 0: + return m**power diff --git a/tools/math/calculate_reflected_point.py b/tools/math/calculate_reflected_point.py new file mode 100644 index 000000000000..d575803c8289 --- /dev/null +++ b/tools/math/calculate_reflected_point.py @@ -0,0 +1,14 @@ +def calculate_reflected_point(point): + """ + Calculates the reflection point of a given point about the line y=x. + + Args: + point (dict): A dictionary representing the coordinates of the point. + The dictionary should have keys 'x' and 'y' representing the x and y coordinates respectively. + + Returns: + dict: A dictionary representing the coordinates of the reflected point. Its keys are 'x' and 'y'. + """ + # Swap x and y for reflection about y=x + reflected_point = {"x": point["y"], "y": point["x"]} + return reflected_point diff --git a/tools/math/complex_numbers_product.py b/tools/math/complex_numbers_product.py new file mode 100644 index 000000000000..49194dacb69a --- /dev/null +++ b/tools/math/complex_numbers_product.py @@ -0,0 +1,23 @@ +from autogen.coding.func_with_reqs import with_requirements + + +@with_requirements(["sympy"]) +def complex_numbers_product(complex_numbers): + """ + Calculates the product of a list of complex numbers. + + Args: + complex_numbers (list): A list of dictionaries representing complex numbers. + Each dictionary should have 'real' and 'imag' keys representing the real + and imaginary parts of the complex number. + + Returns: + complex: The simplified product of the complex numbers. + + """ + from sympy import I, simplify + + result = 1 + for c in complex_numbers: + result *= c["real"] + I * c["imag"] + return simplify(result) diff --git a/tools/math/compute_currency_conversion.py b/tools/math/compute_currency_conversion.py new file mode 100644 index 000000000000..fc90eecb2343 --- /dev/null +++ b/tools/math/compute_currency_conversion.py @@ -0,0 +1,21 @@ +from autogen.coding.func_with_reqs import with_requirements + + +@with_requirements(["sympy"]) +def compute_currency_conversion(amount, exchange_rate): + """ + Compute the currency conversion of the given amount using the provided exchange rate. + + Args: + amount (float): The amount to be converted. + exchange_rate (float): The exchange rate to use for the conversion, represented as the amount of second currency equivalent to one unit of the first currency. + + Returns: + float: The converted amount. + + """ + from sympy import Rational + + # Calculate the converted amount using the given exchange rate + converted_amount = Rational(amount, exchange_rate) + return float(converted_amount) diff --git a/tools/math/count_distinct_permutations.py b/tools/math/count_distinct_permutations.py new file mode 100644 index 000000000000..9b3f77c812d6 --- /dev/null +++ b/tools/math/count_distinct_permutations.py @@ -0,0 +1,25 @@ +def count_distinct_permutations(sequence): + """ + Counts the number of distinct permutations of a sequence where items may be indistinguishable. + + Args: + sequence (iterable): The sequence for which to count the distinct permutations. + + Returns: + int: The number of distinct permutations. + + Example: + >>> count_distinct_permutations('aab') + 3 + >>> count_distinct_permutations([1, 2, 2]) + 3 + """ + from collections import Counter + from math import factorial + + counts = Counter(sequence) + total_length = sum(counts.values()) + permutations = factorial(total_length) + for count in counts.values(): + permutations //= factorial(count) + return permutations diff --git a/tools/math/evaluate_expression.py b/tools/math/evaluate_expression.py new file mode 100644 index 000000000000..6f2a2f6aac7a --- /dev/null +++ b/tools/math/evaluate_expression.py @@ -0,0 +1,26 @@ +def evaluate_expression(expression): + """ + Evaluates a mathematical expression with support for floor function notation and power notation. + + Args: + expression (str): The mathematical expression to evaluate. It can only contain one symbol 'x'. + + Returns: + Union[sympy.Expr, str]: The evaluated result as a sympy expression if successful, + otherwise an error message as a string. + + """ + from sympy import symbols, sympify + + # Replace power with ** for sympy + expression = expression.replace("^", "**") + # Replace the floor function notation + expression = expression.replace("\\lfloor", "floor(").replace("\\rfloor", ")") + try: + # Create a symbol 'x' for use in case it is in the expression + symbols("x") + # Evaluate the expression + result = sympify(expression) + return result + except Exception as e: + return str(e) diff --git a/tools/math/find_continuity_point.py b/tools/math/find_continuity_point.py new file mode 100644 index 000000000000..f6f7844c98fc --- /dev/null +++ b/tools/math/find_continuity_point.py @@ -0,0 +1,32 @@ +def find_continuity_point(f_leq, f_gt, x_value): + """ + Find the value 'a' that ensures the continuity of a piecewise function at a given point. + + Args: + f_leq (str): The function expression for f(x) when x is less than or equal to the continuity point, in the form of a string. + f_gt (str): The function expression for f(x) when x is greater than the continuity point, in the form of a string. + x_value (float): The x-value at which continuity is to be ensured. + + Returns: + float or None: The value of 'a' that satisfies the continuity condition, + or None if no such value exists. + """ + from sympy import Eq, solve, symbols, sympify + + x, a = symbols("x a") + + # Convert string to sympy expression + f_leq_expr = sympify(f_leq) + f_gt_expr = sympify(f_gt) + + # Evaluate the expressions at the given x_value + f_leq_value = f_leq_expr.subs(x, x_value) + f_gt_value = f_gt_expr.subs(x, x_value) + + # Set up the equation for a + equation = Eq(f_leq_value, f_gt_value) + + # Solve the equation + a_value = solve(equation, a) + + return a_value[0] if a_value else None diff --git a/tools/math/fraction_to_mixed_numbers.py b/tools/math/fraction_to_mixed_numbers.py new file mode 100644 index 000000000000..e6bacd42a4e2 --- /dev/null +++ b/tools/math/fraction_to_mixed_numbers.py @@ -0,0 +1,37 @@ +def fraction_to_mixed_numbers(numerator, denominator): + """ + Simplifies a fraction to its lowest terms and returns it as a mixed number. + + Args: + numerator (int): The numerator of the fraction. + denominator (int): The denominator of the fraction. + + Returns: + str: The simplified fraction as a string. If the fraction is already an integer, it returns the integer as a string. + If the fraction is a proper fraction, it returns the mixed number representation as a string. + If the numerator or denominator is not an integer, it returns an error message. + If the denominator is zero, it returns an error message. + """ + from sympy import Rational + + # Ensure that numerator and denominator are integers + if not isinstance(numerator, int) or not isinstance(denominator, int): + return "Error: Numerator and denominator must be integers." + + # Handle the case where the denominator is zero + if denominator == 0: + return "Error: Denominator cannot be zero." + + # Simplify the fraction to its lowest terms + result = Rational(numerator, denominator) + # Return the result as a mixed number if needed + if result.is_integer: + return str(int(result)) + else: + # Result as a mixed number + integer_part = int(result) + fractional_part = result - integer_part + if fractional_part != 0: + return f"{integer_part} {fractional_part}" + else: + return str(integer_part) diff --git a/tools/math/modular_inverse_sum.py b/tools/math/modular_inverse_sum.py new file mode 100644 index 000000000000..bf75b963e9f6 --- /dev/null +++ b/tools/math/modular_inverse_sum.py @@ -0,0 +1,20 @@ +def modular_inverse_sum(expressions, modulus): + """ + Calculates the sum of modular inverses of the given expressions modulo the specified modulus. + + Args: + expressions (list): A list of numbers for which the modular inverses need to be calculated. + modulus (int): The modulus value. + + Returns: + int: The sum of modular inverses modulo the specified modulus. + """ + from sympy import mod_inverse + + mod_sum = 0 + for number in expressions: + try: + mod_sum += mod_inverse(number, modulus) + except ValueError: + pass # If modular inverse does not exist, skip the term + return mod_sum % modulus diff --git a/tools/math/simplify_mixed_numbers.py b/tools/math/simplify_mixed_numbers.py new file mode 100644 index 000000000000..5f11ec279a7c --- /dev/null +++ b/tools/math/simplify_mixed_numbers.py @@ -0,0 +1,34 @@ +def simplify_mixed_numbers(numerator1, denominator1, numerator2, denominator2, whole_number1, whole_number2): + """ + Simplifies the sum of two mixed numbers and returns the result as a string in the format 'a b/c'. + + Args: + numerator1 (int): The numerator of the first fraction. + denominator1 (int): The denominator of the first fraction. + numerator2 (int): The numerator of the second fraction. + denominator2 (int): The denominator of the second fraction. + whole_number1 (int): The whole number part of the first mixed number. + whole_number2 (int): The whole number part of the second mixed number. + + Returns: + str: The simplified sum of the two mixed numbers as a string in the format 'a b/c'. + """ + from fractions import Fraction + + # Convert mixed numbers to improper fractions + fraction1 = whole_number1 * denominator1 + numerator1 + fraction2 = whole_number2 * denominator2 + numerator2 + # Create Fraction objects + frac1 = Fraction(fraction1, denominator1) + frac2 = Fraction(fraction2, denominator2) + # Calculate the sum + result = frac1 + frac2 + # Convert to mixed number + mixed_number = result.numerator // result.denominator + mixed_fraction_numerator = result.numerator % result.denominator + mixed_fraction = Fraction(mixed_fraction_numerator, result.denominator) + # Return as a string in the format 'a b/c' + if mixed_fraction_numerator > 0: + return f"{mixed_number} {mixed_fraction}" + else: + return str(mixed_number) diff --git a/tools/math/sum_of_digit_factorials.py b/tools/math/sum_of_digit_factorials.py new file mode 100644 index 000000000000..9710eff4f202 --- /dev/null +++ b/tools/math/sum_of_digit_factorials.py @@ -0,0 +1,13 @@ +def sum_of_digit_factorials(number): + """ + Calculates the sum of the factorial of each digit in a number, often used in problems involving curious numbers like 145. + + Args: + number (int): The number for which to calculate the sum of digit factorials. + + Returns: + int: The sum of the factorials of the digits in the given number. + """ + from math import factorial + + return sum(factorial(int(digit)) for digit in str(number)) diff --git a/tools/math/sum_of_primes_below.py b/tools/math/sum_of_primes_below.py new file mode 100644 index 000000000000..abed34f6450c --- /dev/null +++ b/tools/math/sum_of_primes_below.py @@ -0,0 +1,13 @@ +def sum_of_primes_below(threshold): + """ + Calculates the sum of all prime numbers below a given threshold. + + Args: + threshold (int): The maximum number (exclusive) up to which primes are summed. + + Returns: + int: The sum of all prime numbers below the threshold. + """ + from sympy import primerange + + return sum(primerange(2, threshold)) diff --git a/tools/requirements.txt b/tools/requirements.txt new file mode 100644 index 000000000000..a28b54d63214 --- /dev/null +++ b/tools/requirements.txt @@ -0,0 +1,12 @@ +mammoth +markdownify +arxiv +pymupdf +wikipedia-api +easyocr +python-pptx +textract==1.6.5 +openai-whisper +pandas +scipy +sentence-transformers diff --git a/tools/tool_description.tsv b/tools/tool_description.tsv new file mode 100644 index 000000000000..faa4f2ecff07 --- /dev/null +++ b/tools/tool_description.tsv @@ -0,0 +1,37 @@ +docid document_content +1 math complex_numbers_product Calculates the product of a list of complex numbers. +2 math calculate_matrix_power Calculate the power of a given matrix. +3 math calculate_day_of_the_week Calculates the day of the week after a given number of days starting from a specified day. +4 math modular_inverse_sum Calculates the sum of modular inverses of the given expressions modulo the specified modulus. +5 math sum_of_digit_factorials Calculates the sum of the factorial of each digit in a number, often used in problems involving curious numbers like 145. +6 math sum_of_primes_below Calculates the sum of all prime numbers below a given threshold. +7 math evaluate_expression Evaluates a mathematical expression with support for floor function notation and power notation. +8 math compute_currency_conversion Compute the currency conversion of the given amount using the provided exchange rate. +9 math find_continuity_point Find the value 'a' that ensures the continuity of a piecewise function at a given point. +10 math simplify_mixed_numbers Simplifies the sum of two mixed numbers and returns the result as a string in the format 'a b/c'. +11 math fraction_to_mixed_numbers Simplifies a fraction to its lowest terms and returns it as a mixed number. +12 math calculate_fraction_sum Calculates the sum of two fractions and returns the result as a mixed number. +13 math count_distinct_permutations Counts the number of distinct permutations of a sequence where items may be indistinguishable. +14 math calculate_circle_area_from_diameter Calculate the area of a circle given its diameter. +15 math calculate_reflected_point Calculates the reflection point of a given point about the line y=x. +16 data_analysis explore_csv Reads a CSV file and prints the column names, shape, data types, and the first few lines of data. +17 data_analysis calculate_correlation Calculate the correlation between two columns in a CSV file. +18 data_analysis detect_outlier_zscore Detect outliers in a CSV file based on a specified column. The outliers are determined by calculating the z-score of the data points in the column. +19 data_analysis detect_outlier_iqr Detect outliers in a specified column of a CSV file using the IQR method. +20 data_analysis shapiro_wilk_test Perform the Shapiro-Wilk test on a specified column of a CSV file. +21 data_analysis calculate_skewness_and_kurtosis Calculate the skewness and kurtosis of a specified column in a CSV file. The kurtosis is calculated using the Fisher definition. The two metrics are computed using scipy.stats functions. +22 information_retrieval perform_web_search Perform a web search using Bing API. +23 information_retrieval transcribe_audio_file Transcribes the audio file located at the given file path. +24 information_retrieval arxiv_search Search for articles on arXiv based on the given query. +25 information_retrieval arxiv_download Downloads PDF files from ArXiv based on a list of arxiv paper IDs. +26 information_retrieval scrape_wikipedia_tables Scrapes Wikipedia tables based on a given URL and header keyword. +27 information_retrieval extract_pdf_text Extracts text from a specified page or the entire PDF file. +28 information_retrieval extract_pdf_image Extracts images from a PDF file and saves them to the specified output directory. +29 information_retrieval image_qa Perform question answering on an image using a pre-trained VQA model. +30 information_retrieval optical_character_recognition Perform optical character recognition (OCR) on the given image. +31 information_retrieval get_youtube_caption Retrieves the captions for a YouTube video. +32 information_retrieval youtube_download Downloads a YouTube video and returns the download link. +33 information_retrieval excel_to_md Convert an Excel file to Markdown format. +34 information_retrieval docx_to_md Converts a DOCX file to Markdown format. +35 information_retrieval pptx_to_md Convert a PowerPoint presentation (PPTX) to Markdown format. +36 information_retrieval get_wikipedia_text Retrieves the text content of a Wikipedia page. It does not support tables and other complex formatting.