diff --git a/src/openagi/actions/base.py b/src/openagi/actions/base.py index f7925ce..0c3f404 100644 --- a/src/openagi/actions/base.py +++ b/src/openagi/actions/base.py @@ -4,7 +4,7 @@ from openagi.llms.base import LLMBaseModel from openagi.memory.memory import Memory - +from typing import ClassVar, Dict, Any class BaseAction(BaseModel): """Base Actions class to be inherited by other actions, providing basic functionality and structure.""" @@ -24,8 +24,7 @@ class BaseAction(BaseModel): ) def execute(self): - """Executes the action - """ + """Executes the action""" raise NotImplementedError("Subclasses must implement this method.") @classmethod @@ -43,3 +42,19 @@ def cls_doc(cls): if field_name not in default_exclude_doc_fields }, } + +class ConfigurableAction(BaseAction): + config: ClassVar[Dict[str, Any]] = {} + + @classmethod + def set_config(cls, *args, **kwargs): + if args: + if len(args) == 1 and isinstance(args[0], dict): + cls.config.update(args[0]) + else: + raise ValueError("If using positional arguments, a single dictionary must be provided.") + cls.config.update(kwargs) + + @classmethod + def get_config(cls, key: str, default: Any = None) -> Any: + return cls.config.get(key, default) \ No newline at end of file diff --git a/src/openagi/actions/files.py b/src/openagi/actions/files.py index e0efa47..923331e 100644 --- a/src/openagi/actions/files.py +++ b/src/openagi/actions/files.py @@ -1,7 +1,6 @@ import logging from pathlib import Path from typing import Dict, Optional - from pydantic import Field from openagi.actions.base import BaseAction diff --git a/src/openagi/actions/tools/arxiv_search.py b/src/openagi/actions/tools/arxiv_search.py new file mode 100644 index 0000000..f0ea50c --- /dev/null +++ b/src/openagi/actions/tools/arxiv_search.py @@ -0,0 +1,57 @@ +from openagi.actions.base import ConfigurableAction +from pydantic import Field +from openagi.exception import OpenAGIException +from typing import ClassVar, Dict, Any + +try: + import arxiv +except ImportError: + raise OpenAGIException("Install arxiv with cmd `pip install arxiv`") + + +class ArxivSearch(ConfigurableAction): + """ + Arxiv Search is a tool used to search articles in Physics, Mathematics, Computer Science, Quantitative Biology, Quantitative Finance, and Statistics + """ + query: str = Field(..., description="User query or question") + max_results: int = Field(10, description="Total results, in int, to be executed from the search. Defaults to 10.") + + def execute(self): + search = arxiv.Search( + query = self.query, + max_results = self.max_results, + ) + client = arxiv.Client() + results = client.results(search) + meta_data = "" + for result in results: + meta_data += f"title : {result.title}\n " + meta_data += f"summary : {result.summary}\n " + meta_data += f"published : {result.published}\n " + meta_data += f"authors : {result.authors}\n " + meta_data += f"pdf_url : {result.pdf_url}\n " + meta_data += f"entry_id : {result.entry_id}\n\n " + return meta_data.strip() + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/openagi/actions/tools/ddg_search.py b/src/openagi/actions/tools/ddg_search.py index 127caec..b06a58f 100644 --- a/src/openagi/actions/tools/ddg_search.py +++ b/src/openagi/actions/tools/ddg_search.py @@ -1,11 +1,11 @@ import json from typing import Any -from openagi.actions.base import BaseAction +from openagi.actions.base import ConfigurableAction from pydantic import Field from duckduckgo_search import DDGS import logging -class DuckDuckGoSearch(BaseAction): +class DuckDuckGoSearch(ConfigurableAction): """Use this Action to search DuckDuckGo for a query.""" name: str = Field( @@ -39,14 +39,4 @@ def execute(self): self.query, max_results=self.max_results, ) - return json.dumps(result) - - -class DuckDuckGoNewsSearch(DuckDuckGoSearch): - """Use this Action to get the latest news from DuckDuckGo.""" - - def execute(self): - ddgs = self._get_ddgs() - return json.dumps( - ddgs.news(keywords=self.query, max_results=(self.max_results)), indent=2 - ) + return json.dumps(result) \ No newline at end of file diff --git a/src/openagi/actions/tools/document_loader.py b/src/openagi/actions/tools/document_loader.py index 9d710c2..1f7acf7 100644 --- a/src/openagi/actions/tools/document_loader.py +++ b/src/openagi/actions/tools/document_loader.py @@ -1,39 +1,64 @@ -from openagi.actions.base import BaseAction +from typing import Any +from openagi.actions.base import ConfigurableAction from langchain_community.document_loaders import TextLoader from langchain_community.document_loaders.csv_loader import CSVLoader +from langchain_community.document_loaders.pdf import PyPDFLoader from pydantic import Field +class TextLoaderTool(ConfigurableAction): + """Load content from a text file. + + This action loads and processes content from .txt files, combining + metadata and content into a single context string. + """ + + def execute(self) -> str: + file_path: str = self.get_config('filename') + loader = TextLoader(file_path=file_path) + documents = loader.load() + + if not documents: + return "" + + page_content = documents[0].page_content + source = documents[0].metadata["source"] + return f"{source} {page_content}" -class DocumentLoader(BaseAction): - """Use this Action to extract content from documents""" +class PDFLoaderTool(ConfigurableAction): + """Load content from a PDF file. + + This action loads and processes content from .pdf files, combining + metadata and content into a single context string. + """ + + def execute(self) -> str: + file_path: str = self.get_config('filename') + loader = PyPDFLoader(file_path=file_path) + documents = loader.load() + + if not documents: + return "" + + page_content = documents[0].page_content + source = documents[0].metadata["source"] + return f"{source} {page_content}" - file_path: str = Field( - default_factory=str, - description="File from which content is extracted", - ) - - def text_loader(self): - loader = TextLoader(file_path=self.file_path) - data = loader.load() - page_content = data[0].page_content - meta_data = data[0].metadata["source"] - context = meta_data + " " + page_content - return context - - def csv_loader(self): - content = "" - loader = CSVLoader(file_path=self.file_path) - data = loader.load() - - for i in range(len(data)): - row_content = data[i].page_content - row_no = data[i].metadata["row"] - content += "row_no" + " " + str(row_no) + ": " + str(row_content) - return content - - def execute(self): - if self.file_path.endswith(".txt"): - context = self.text_loader() - elif self.file_path.endswith(".csv"): - context = self.csv_loader() - return context +class CSVLoaderTool(ConfigurableAction): + """Load content from a CSV file. + + This action loads and processes content from .csv files, combining + row numbers and content into a formatted string representation. + """ + + def execute(self) -> str: + file_path: str = self.get_config('filename') + loader = CSVLoader(file_path=file_path) + documents = loader.load() + + content_parts = [] + for idx, doc in enumerate(documents): + row_content = doc.page_content + row_number = doc.metadata["row"] + content_parts.append(f"row_no {row_number}: {row_content}") + + return "".join(content_parts) \ No newline at end of file diff --git a/src/openagi/actions/tools/exasearch.py b/src/openagi/actions/tools/exasearch.py index 023ce80..4c5614a 100644 --- a/src/openagi/actions/tools/exasearch.py +++ b/src/openagi/actions/tools/exasearch.py @@ -1,30 +1,63 @@ -from openagi.actions.base import BaseAction -import os +from openagi.actions.base import ConfigurableAction from pydantic import Field from openagi.exception import OpenAGIException +import os +import warnings try: - from exa_py import Exa + from exa_py import Exa except ImportError: - raise OpenAGIException("Install Exa Py with cmd `pip install exa_py`") + raise OpenAGIException("Install Exa Py with cmd `pip install exa_py`") -class ExaSearch(BaseAction): - """ - Exa Search is a tool used when user needs to ask the question in terms of query to get response +class ExaSearch(ConfigurableAction): + """Exa Search tool for querying and retrieving information. + + This action uses the Exa API to perform searches and retrieve relevant content + based on user queries. Requires an API key to be configured before use. """ - query: str = Field(..., description="User query or question ") - - def execute(self): - api_key = os.environ["EXA_API_KEY"] + query: str = Field(..., description="User query or question") + + def __init__(self, **data): + super().__init__(**data) + self._check_deprecated_usage() + + def _check_deprecated_usage(self): + if 'EXA_API_KEY' in os.environ and not self.get_config('api_key'): + warnings.warn( + "Using environment variables for API keys is deprecated and will be removed in a future version. " + "Please use ExaSearch.set_config(api_key='your_key') instead of setting environment variables.", + DeprecationWarning, + stacklevel=2 + ) + self.set_config(api_key=os.environ['EXA_API_KEY']) + + + def execute(self) -> str: + api_key: str = self.get_config('api_key') + if not api_key: + if 'EXA_API_KEY' in os.environ: + api_key = os.environ['EXA_API_KEY'] + warnings.warn( + "Using environment variables for API keys is deprecated and will be removed in a future version. " + "Please use ExaSearch.set_config(api_key='your_key') instead of setting environment variables.", + DeprecationWarning, + stacklevel=2 + ) + else: + raise OpenAGIException("API KEY NOT FOUND. Use ExaSearch.set_config(api_key='your_key') to set the API key.") + + exa = Exa(api_key=api_key) + results = exa.search_and_contents( + self.query, + text={"max_characters": 512}, + ) - exa = Exa(api_key = api_key) - results = exa.search_and_contents(self.query, - text={"max_characters": 512}, - ) - content = "" - for idx in results.results: - content += idx.text.strip() + content_parts = [] + for result in results.results: + content_parts.append(result.text.strip()) - content = content.replace("<|endoftext|>","") - content = content.replace("NaN","") - return content + content = "".join(content_parts) + return ( + content.replace("<|endoftext|>", "") + .replace("NaN", "") + ) diff --git a/src/openagi/actions/tools/google_search_tool.py b/src/openagi/actions/tools/google_search_tool.py new file mode 100644 index 0000000..ce2b68b --- /dev/null +++ b/src/openagi/actions/tools/google_search_tool.py @@ -0,0 +1,38 @@ +from openagi.actions.base import ConfigurableAction +from pydantic import Field +from openagi.exception import OpenAGIException +import logging + +try: + from googlesearch import search +except ImportError: + raise OpenAGIException("Install googlesearch-python with cmd `pip install googlesearch-python`") + +class GoogleSearchTool(ConfigurableAction): + """ + Google Search is a tool used for scraping the Google search engine. Extract information from Google search results. + """ + query: str = Field(..., description="User query or question ") + + max_results: int = Field( + default=10, + description="Total results, in int, to be executed from the search. Defaults to 10. The limit should be 10 and not execeed more than 10", + ) + + lang: str = Field( + default="en", + description = "specify the langauge for your search results." + ) + + def execute(self): + if self.max_results > 15: + logging.info("Over threshold value... Limiting the Max results to 15") + self.max_results = 15 + + context = "" + search_results = search(self.query,num_results=self.max_results,lang=self.lang,advanced=True) + for info in search_results: + context += f"Title: {info.title}. Description: {info.description}. URL: {info.url}" + + return context + \ No newline at end of file diff --git a/src/openagi/actions/tools/pubmed_tool.py b/src/openagi/actions/tools/pubmed_tool.py new file mode 100644 index 0000000..14fbe21 --- /dev/null +++ b/src/openagi/actions/tools/pubmed_tool.py @@ -0,0 +1,72 @@ +from openagi.actions.base import ConfigurableAction +from openagi.exception import OpenAGIException +from pydantic import Field + +try: + from Bio import Entrez +except ImportError: + raise OpenAGIException("Install Biopython with cmd `pip install biopython`") + +class PubMedSearch(ConfigurableAction): + """PubMed Search tool for querying biomedical literature. + + This action uses the Bio.Entrez module to search PubMed and retrieve + scientific articles based on user queries. Requires an email address + to be configured for NCBI's tracking purposes. + """ + + query: str = Field(..., description="Search query for PubMed") + max_results: int = Field( + default=5, + description="Maximum number of results to return (default: 5)" + ) + sort: str = Field( + default="relevance", + description="Sort order: 'relevance', 'pub_date', or 'first_author'" + ) + + def execute(self) -> str: + email: str = self.get_config('email') + if not email: + raise OpenAGIException( + "Email not configured. Use PubMedSearch.set_config(email='your_email@example.com')" + ) + + Entrez.email = email + + try: + # Search PubMed + search_handle = Entrez.esearch( + db="pubmed", + term=self.query, + retmax=self.max_results, + sort=self.sort + ) + search_results = Entrez.read(search_handle) + search_handle.close() + + if not search_results["IdList"]: + return "No results found for the given query." + + # Fetch details for found articles + ids = ",".join(search_results["IdList"]) + fetch_handle = Entrez.efetch( + db="pubmed", + id=ids, + rettype="medline", + retmode="text" + ) + + results = fetch_handle.read() + fetch_handle.close() + + # Process and format results + formatted_results = ( + f"Found {len(search_results['IdList'])} results for query: {self.query}\n\n" + f"{results}" + ) + + return formatted_results + + except Exception as e: + return f"Error searching PubMed: {str(e)}" \ No newline at end of file diff --git a/src/openagi/actions/tools/searchapi_search.py b/src/openagi/actions/tools/searchapi_search.py index 95f292d..014607c 100644 --- a/src/openagi/actions/tools/searchapi_search.py +++ b/src/openagi/actions/tools/searchapi_search.py @@ -5,36 +5,46 @@ from typing import Any from pydantic import Field +from openagi.actions.base import ConfigurableAction +import warnings -from openagi.actions.base import BaseAction -from openagi.exception import OpenAGIException - -class SearchApiSearch(BaseAction): +class SearchApiSearch(ConfigurableAction): """SearchApi.io provides a real-time API to access search results from Google (default), Google Scholar, Bing, Baidu, and other search engines.""" query: str = Field( ..., description="User query of type string used to fetch web search results from a search engine." ) + def __init__(self, **data): + super().__init__(**data) + self._check_deprecated_usage() + + def _check_deprecated_usage(self): + if 'SEARCHAPI_API_KEY' in os.environ and not self.get_config('api_key'): + warnings.warn( + "Using environment variables for API keys is deprecated and will be removed in a future version. " + "Please use SearchApiSearch.set_config(api_key='your_key') instead of setting environment variables.", + DeprecationWarning, + stacklevel=2 + ) + self.set_config(api_key=os.environ['SEARCHAPI_API_KEY']) + def execute(self): base_url = "https://www.searchapi.io/api/v1/search" - searchapi_api_key = os.environ["SEARCHAPI_API_KEY"] - engine = os.environ.get("SEARCHAPI_ENGINE") or "google" + api_key = self.get_config('api_key') + search_dict = { "q": self.query, - "engine": engine, - "api_key": searchapi_api_key + "engine": "google", + "api_key": api_key } + logging.debug(f"{search_dict=}") + url = f"{base_url}?{urlencode(search_dict)}" response = requests.request("GET", url) json_response = response.json() - # if not json_response: - # raise OpenAGIException(f"Unable to generate result for the query {self.query}") - - # logging.debug(json_response) - organic_results = json_response.get("organic_results", []) meta_data = "" diff --git a/src/openagi/actions/tools/serp_search.py b/src/openagi/actions/tools/serp_search.py index aa70c67..befd915 100644 --- a/src/openagi/actions/tools/serp_search.py +++ b/src/openagi/actions/tools/serp_search.py @@ -1,17 +1,14 @@ import logging import os -from typing import Any - +import warnings +from typing import Any, ClassVar, Dict from pydantic import Field, field_validator from serpapi import GoogleSearch - -from openagi.actions.base import BaseAction +from openagi.actions.base import ConfigurableAction from openagi.exception import OpenAGIException - -class GoogleSerpAPISearch(BaseAction): +class GoogleSerpAPISearch(ConfigurableAction): """Google Serp API Search Tool""" - query: str = Field( ..., description="User query of type string used to fetch web search results from Google." ) @@ -19,31 +16,51 @@ class GoogleSerpAPISearch(BaseAction): default=10, description="Total results, an integer, to be executed from the search. Defaults to 10", ) - - @field_validator("max_results") - @classmethod - def actions_validator(cls, max_results): - if not max_results or not isinstance(max_results, int): - logging.warning("Max Results set to 10(default).") - max_results = 10 - return max_results - + + def __init__(self, **data): + super().__init__(**data) + self._check_deprecated_usage() + + def _check_deprecated_usage(self): + if 'GOOGLE_SERP_API_KEY' in os.environ and not self.get_config('api_key'): + warnings.warn( + "Using environment variables for API keys is deprecated and will be removed in a future version. " + "Please use GoogleSerpAPISearch.set_config(api_key='your_key') instead of setting environment variables.", + DeprecationWarning, + stacklevel=2 + ) + # Automatically migrate the environment variable to config + self.set_config(api_key=os.environ['GOOGLE_SERP_API_KEY']) + def execute(self): - serp_api_key = os.environ["GOOGLE_SERP_API_KEY"] + api_key = self.get_config('api_key') + + if not api_key: + if 'GOOGLE_SERP_API_KEY' in os.environ: + api_key = os.environ['GOOGLE_SERP_API_KEY'] + warnings.warn( + "Using environment variables for API keys is deprecated and will be removed in a future version. " + "Please use GoogleSerpAPISearch.set_config(api_key='your_key') instead of setting environment variables.", + DeprecationWarning, + stacklevel=2 + ) + else: + raise OpenAGIException("API KEY NOT FOUND. Use GoogleSerpAPISearch.set_config(api_key='your_key') to set the API key.") + search_dict = { "q": self.query, "hl": "en", "gl": "us", "num": self.max_results, - "api_key": serp_api_key, + "api_key": api_key, } logging.debug(f"{search_dict=}") search = GoogleSearch(search_dict) - + max_retries = 3 retries = 1 result = None - + while retries < max_retries and not result: try: result = search.get_dict() @@ -51,19 +68,21 @@ def execute(self): logging.error("Error during GoogleSearch.", exc_info=True) continue retries += 1 - + if not result: raise OpenAGIException(f"Unable to generate result for the query {self.query}") - + logging.debug(result) logging.info(f"NOTE: REMOVE THIS BEFORE RELEASE:\n{result}\n") + if error := result.get("error", NotImplemented): raise OpenAGIException( f"Error while running action {self.__class__.__name__}: {error}" ) - + meta_data = "" - for info in result.get("organic_results"): - meta_data += f"CONTEXT: {info['title']} \ {info['snippet']}" - meta_data += f"Reference URL: {info['link']}" - return meta_data + for info in result.get("organic_results", []): + meta_data += f"CONTEXT: {info.get('title', '')} \ {info.get('snippet', '')}\n" + meta_data += f"Reference URL: {info.get('link', '')}\n\n" + + return meta_data.strip() \ No newline at end of file diff --git a/src/openagi/actions/tools/serper_search.py b/src/openagi/actions/tools/serper_search.py index 700b71f..695360b 100644 --- a/src/openagi/actions/tools/serper_search.py +++ b/src/openagi/actions/tools/serper_search.py @@ -1,30 +1,56 @@ import http.client import json import os - +import warnings from pydantic import Field +from openagi.actions.base import ConfigurableAction +from typing import ClassVar, Dict, Any +from openagi.exception import OpenAGIException -from openagi.actions.base import BaseAction - - -class SerperSearch(BaseAction): +class SerperSearch(ConfigurableAction): """Google Serper.dev Search Tool""" - query: str = Field(..., description="User query to fetch web search results from Google") + + def __init__(self, **data): + super().__init__(**data) + self._check_deprecated_usage() + + def _check_deprecated_usage(self): + if 'SERPER_API_KEY' in os.environ and not self.get_config('api_key'): + warnings.warn( + "Using environment variables for API keys is deprecated and will be removed in a future version. " + "Please use SerperSearch.set_config(api_key='your_key') instead of setting environment variables.", + DeprecationWarning, + stacklevel=2 + ) + self.set_config(api_key=os.environ['SERPER_API_KEY']) def execute(self): - serper_api_key = os.environ["SERPER_API_KEY"] + api_key = self.get_config('api_key') + + if not api_key: + if 'SERPER_API_KEY' in os.environ: + api_key = os.environ['SERPER_API_KEY'] + warnings.warn( + "Using environment variables for API keys is deprecated and will be removed in a future version. " + "Please use SerperSearch.set_config(api_key='your_key') instead of setting environment variables.", + DeprecationWarning, + stacklevel=2 + ) + else: + raise OpenAGIException("API KEY NOT FOUND. Use SerperSearch.set_config(api_key='your_key') to set the API key.") conn = http.client.HTTPSConnection("google.serper.dev") payload = json.dumps({"q": self.query}) - headers = {"X-API-KEY": serper_api_key, "Content-Type": "application/json"} + headers = {"X-API-KEY": api_key, "Content-Type": "application/json"} conn.request("POST", "/search", payload, headers) res = conn.getresponse() data = res.read().decode("utf-8") result = json.loads(data) + meta_data = "" - for info in result.get("organic"): - meta_data += f"CONTEXT: {info['title']} \ {info['snippet']}" - meta_data += f"Reference URL: {info['link']}" - - return meta_data + for info in result.get("organic", []): + meta_data += f"CONTEXT: {info.get('title', '')} \ {info.get('snippet', '')}\n" + meta_data += f"Reference URL: {info.get('link', '')}\n\n" + + return meta_data.strip() \ No newline at end of file diff --git a/src/openagi/actions/tools/tavilyqasearch.py b/src/openagi/actions/tools/tavilyqasearch.py index aba3174..61e32f9 100644 --- a/src/openagi/actions/tools/tavilyqasearch.py +++ b/src/openagi/actions/tools/tavilyqasearch.py @@ -1,21 +1,49 @@ -from openagi.actions.base import BaseAction -import os +from openagi.actions.base import ConfigurableAction from pydantic import Field from openagi.exception import OpenAGIException +import os +import warnings try: - from tavily import TavilyClient + from tavily import TavilyClient except ImportError: - raise OpenAGIException("Install Tavily with cmd `pip install tavily-python`") + raise OpenAGIException("Install Tavily with cmd `pip install tavily-python`") -class TavilyWebSearchQA(BaseAction): +class TavilyWebSearchQA(ConfigurableAction): """ - Tavily Web Search QA is a tool used when user needs to ask the question in terms of query to get response + Tavily Web Search QA is a tool used when user needs to ask the question in terms of query to get response """ - query: str = Field(..., description="User query or question ") + query: str = Field(..., description="User query or question") + + def __init__(self, **data): + super().__init__(**data) + self._check_deprecated_usage() + + def _check_deprecated_usage(self): + if 'TAVILY_API_KEY' in os.environ and not self.get_config('api_key'): + warnings.warn( + "Using environment variables for API keys is deprecated and will be removed in a future version. " + "Please use TavilyWebSearchQA.set_config(api_key='your_key') instead of setting environment variables.", + DeprecationWarning, + stacklevel=2 + ) + self.set_config(api_key=os.environ['TAVILY_API_KEY']) def execute(self): - api_key = os.environ['TAVILY_API_KEY'] + api_key = self.get_config('api_key') + + if not api_key: + if 'TAVILY_API_KEY' in os.environ: + api_key = os.environ['TAVILY_API_KEY'] + warnings.warn( + "Using environment variables for API keys is deprecated and will be removed in a future version. " + "Please use TavilyWebSearchQA.set_config(api_key='your_key') instead of setting environment variables.", + DeprecationWarning, + stacklevel=2 + ) + else: + raise OpenAGIException("API KEY NOT FOUND. Use TavilyWebSearchQA.set_config(api_key='your_key') to set the API key.") + client = TavilyClient(api_key=api_key) response = client.qna_search(query=self.query) return response \ No newline at end of file diff --git a/src/openagi/actions/tools/unstructured_io.py b/src/openagi/actions/tools/unstructured_io.py index 845868e..60c395e 100644 --- a/src/openagi/actions/tools/unstructured_io.py +++ b/src/openagi/actions/tools/unstructured_io.py @@ -1,30 +1,26 @@ import logging from pydantic import Field from openagi.exception import OpenAGIException -from openagi.actions.base import BaseAction +from openagi.actions.base import ConfigurableAction +from typing import ClassVar, Dict, Any try: from unstructured.partition.pdf import partition_pdf from unstructured.chunking.title import chunk_by_title except ImportError: raise OpenAGIException("Install Unstructured with cmd `pip install 'unstructured[all-docs]'`") - - -class UnstructuredPdfLoaderAction(BaseAction): + +class UnstructuredPdfLoaderAction(ConfigurableAction): """ Use this Action to extract content from PDFs including metadata. Returns a list of dictionary with keys 'type', 'element_id', 'text', 'metadata'. """ - file_path: str = Field( - default_factory=str, - description="File or pdf file url from which content is extracted.", - ) - def execute(self): - logging.info(f"Reading file {self.file_path}") - - elements = partition_pdf(self.file_path, extract_images_in_pdf=True) + file_path = self.get_config('filename') + logging.info(f"Reading file {file_path}") + + elements = partition_pdf(file_path, extract_images_in_pdf=True) chunks = chunk_by_title(elements) diff --git a/src/openagi/actions/tools/webloader.py b/src/openagi/actions/tools/webloader.py index 47ae599..4813dce 100644 --- a/src/openagi/actions/tools/webloader.py +++ b/src/openagi/actions/tools/webloader.py @@ -12,7 +12,6 @@ nltk.download("punkt") - class WebBaseContextTool(BaseAction): """ Use this Action to extract actual context from a Webpage. The WebBaseContextTool class provides a way to load and optionally summarize the content of a webpage, returning the metadata and page content as a context string. diff --git a/src/openagi/actions/tools/yahoo_finance.py b/src/openagi/actions/tools/yahoo_finance.py new file mode 100644 index 0000000..c75ca5f --- /dev/null +++ b/src/openagi/actions/tools/yahoo_finance.py @@ -0,0 +1,75 @@ +from typing import Any, Optional +from openagi.actions.base import ConfigurableAction +from openagi.exception import OpenAGIException +from pydantic import Field + +try: + import yfinance as yf +except ImportError: + raise OpenAGIException("Install yfinance with cmd `pip install yfinance`") + +class YahooFinanceTool(ConfigurableAction): + """Yahoo Finance tool for fetching stock market data. + + This action uses the yfinance library to retrieve financial information + about stocks, including current price, historical data, and company info. + """ + + symbol: str = Field(..., description="Stock symbol to look up (e.g., 'AAPL' for Apple)") + info_type: str = Field( + default="summary", + description="Type of information to retrieve: 'summary', 'price', 'history', or 'info'" + ) + period: Optional[str] = Field( + default="1d", + description="Time period for historical data (e.g., '1d', '5d', '1mo', '1y')" + ) + + def execute(self) -> str: + try: + stock = yf.Ticker(self.symbol) + + if self.info_type == "summary": + info = stock.info + return ( + f"Company: {info.get('longName', 'N/A')}\n" + f"Current Price: ${info.get('currentPrice', 'N/A')}\n" + f"Market Cap: ${info.get('marketCap', 'N/A')}\n" + f"52 Week High: ${info.get('fiftyTwoWeekHigh', 'N/A')}\n" + f"52 Week Low: ${info.get('fiftyTwoWeekLow', 'N/A')}" + ) + + elif self.info_type == "price": + return f"Current price of {self.symbol}: ${stock.info.get('currentPrice', 'N/A')}" + + elif self.info_type == "history": + history = stock.history(period=self.period) + if history.empty: + return f"No historical data available for {self.symbol}" + + latest = history.iloc[-1] + return ( + f"Historical data for {self.symbol} (last entry):\n" + f"Date: {latest.name.date()}\n" + f"Open: ${latest['Open']:.2f}\n" + f"High: ${latest['High']:.2f}\n" + f"Low: ${latest['Low']:.2f}\n" + f"Close: ${latest['Close']:.2f}\n" + f"Volume: {latest['Volume']}" + ) + + elif self.info_type == "info": + info = stock.info + return ( + f"Company Information for {self.symbol}:\n" + f"Industry: {info.get('industry', 'N/A')}\n" + f"Sector: {info.get('sector', 'N/A')}\n" + f"Website: {info.get('website', 'N/A')}\n" + f"Description: {info.get('longBusinessSummary', 'N/A')}" + ) + + else: + return f"Invalid info_type: {self.info_type}. Supported types are: summary, price, history, info" + + except Exception as e: + return f"Error fetching data for {self.symbol}: {str(e)}" \ No newline at end of file diff --git a/src/openagi/actions/tools/youtubesearch.py b/src/openagi/actions/tools/youtubesearch.py index 4de1492..2311b05 100644 --- a/src/openagi/actions/tools/youtubesearch.py +++ b/src/openagi/actions/tools/youtubesearch.py @@ -1,4 +1,4 @@ -from openagi.actions.base import BaseAction +from openagi.actions.base import ConfigurableAction from pydantic import Field from typing import Any from openagi.exception import OpenAGIException @@ -9,7 +9,7 @@ except ImportError: raise OpenAGIException("Install YouTube transcript with cmd `pip install yt-dlp` and `pip install youtube-search`") -class YouTubeSearchTool(BaseAction): +class YouTubeSearchTool(ConfigurableAction): """Youtube Search Tool""" query: str = Field(