semantio 0.0.1__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,128 @@
1
+ # duckduckgo.py
2
+ from .base_tool import BaseTool
3
+ from duckduckgo_search import DDGS
4
+ from typing import Dict, Any, List, Optional
5
+ from pydantic import Field
6
+
7
+ class DuckDuckGo(BaseTool):
8
+ enable_web_search: bool = Field(default=True, description="Enable web search functionality.")
9
+ enable_news_search: bool = Field(default=False, description="Enable news search functionality.")
10
+ enable_image_search: bool = Field(default=False, description="Enable image search functionality.")
11
+ enable_video_search: bool = Field(default=False, description="Enable video search functionality.")
12
+ region: Optional[str] = Field(default="wt-wt", description="Region for search results (e.g., 'us-en', 'fr-fr').")
13
+ safesearch: str = Field(default="moderate", description="Safesearch filter ('on', 'moderate', 'off').")
14
+ time_range: Optional[str] = Field(default=None, description="Time range for search results ('d', 'w', 'm', 'y').")
15
+
16
+ def __init__(self, **kwargs):
17
+ super().__init__(name="DuckDuckGo", description="Perform web, news, image, and video searches using DuckDuckGo.", **kwargs)
18
+
19
+ def execute(self, input_data: Dict[str, Any]) -> Dict[str, Any]:
20
+ """
21
+ Execute a search using DuckDuckGo based on the provided input.
22
+
23
+ Args:
24
+ input_data (Dict[str, Any]): Input data containing the query and optional parameters.
25
+
26
+ Returns:
27
+ Dict[str, Any]: Search results for the requested types.
28
+ """
29
+ query = input_data.get("query", "")
30
+ max_results = input_data.get("max_results", 5) # Default to 5 results
31
+ region = input_data.get("region", self.region)
32
+ safesearch = input_data.get("safesearch", self.safesearch)
33
+ time_range = input_data.get("time_range", self.time_range)
34
+
35
+ results = {}
36
+
37
+ # Perform web search
38
+ if self.enable_web_search:
39
+ results["web"] = self._perform_search(
40
+ query, max_results, region, safesearch, time_range, search_type="text"
41
+ )
42
+
43
+ # Perform news search
44
+ if self.enable_news_search:
45
+ results["news"] = self._perform_search(
46
+ query, max_results, region, safesearch, time_range, search_type="news"
47
+ )
48
+
49
+ # Perform image search
50
+ if self.enable_image_search:
51
+ results["images"] = self._perform_search(
52
+ query, max_results, region, safesearch, time_range, search_type="images"
53
+ )
54
+
55
+ # Perform video search
56
+ if self.enable_video_search:
57
+ results["videos"] = self._perform_search(
58
+ query, max_results, region, safesearch, time_range, search_type="videos"
59
+ )
60
+
61
+ return results
62
+
63
+ def _perform_search(
64
+ self,
65
+ query: str,
66
+ max_results: int,
67
+ region: str,
68
+ safesearch: str,
69
+ time_range: Optional[str],
70
+ search_type: str,
71
+ ) -> List[Dict[str, Any]]:
72
+ """
73
+ Perform a search using DuckDuckGo.
74
+
75
+ Args:
76
+ query (str): The search query.
77
+ max_results (int): Maximum number of results to return.
78
+ region (str): Region for search results.
79
+ safesearch (str): Safesearch filter.
80
+ time_range (Optional[str]): Time range for search results.
81
+ search_type (str): Type of search ("text", "news", "images", "videos").
82
+
83
+ Returns:
84
+ List[Dict[str, Any]]: List of search results.
85
+ """
86
+ with DDGS() as ddgs:
87
+ if search_type == "text":
88
+ return list(
89
+ ddgs.text(
90
+ keywords=query,
91
+ max_results=max_results,
92
+ region=region,
93
+ safesearch=safesearch,
94
+ timelimit=time_range,
95
+ )
96
+ )
97
+ elif search_type == "news":
98
+ return list(
99
+ ddgs.news(
100
+ keywords=query,
101
+ max_results=max_results,
102
+ region=region,
103
+ safesearch=safesearch,
104
+ timelimit=time_range,
105
+ )
106
+ )
107
+ elif search_type == "images":
108
+ return list(
109
+ ddgs.images(
110
+ keywords=query,
111
+ max_results=max_results,
112
+ region=region,
113
+ safesearch=safesearch,
114
+ timelimit=time_range,
115
+ )
116
+ )
117
+ elif search_type == "videos":
118
+ return list(
119
+ ddgs.videos(
120
+ keywords=query,
121
+ max_results=max_results,
122
+ region=region,
123
+ safesearch=safesearch,
124
+ timelimit=time_range,
125
+ )
126
+ )
127
+ else:
128
+ raise ValueError(f"Unsupported search type: {search_type}")
@@ -0,0 +1,131 @@
1
+ from .base_tool import BaseTool
2
+ import yfinance as yf
3
+ from typing import Dict, Any, List, Optional
4
+ from pydantic import Field
5
+ import logging
6
+ import re
7
+
8
+ # Configure logging
9
+ logging.basicConfig(level=logging.INFO)
10
+ logger = logging.getLogger(__name__)
11
+
12
+ class StockPriceChecker(BaseTool):
13
+ default_symbol: str = Field(default="AAPL", description="Default stock symbol (e.g., 'AAPL').")
14
+ enable_historical_data: bool = Field(default=True, description="Enable fetching historical data.")
15
+ enable_multiple_symbols: bool = Field(default=True, description="Enable fetching data for multiple stocks.")
16
+ enable_metrics: bool = Field(default=True, description="Enable additional metrics like volume and market cap.")
17
+ llm: Optional[Any] = Field(None, description="The LLM instance to use for symbol extraction.")
18
+
19
+ def __init__(self, **kwargs):
20
+ super().__init__(
21
+ name="StockPriceChecker",
22
+ description="Fetch real-time and historical stock prices and metrics.",
23
+ **kwargs
24
+ )
25
+
26
+ def execute(self, input_data: Dict[str, Any]) -> Dict[str, Any]:
27
+ """
28
+ Execute the stock price check based on the provided input.
29
+
30
+ Args:
31
+ input_data (Dict[str, Any]): Input data containing the query and optional parameters.
32
+
33
+ Returns:
34
+ Dict[str, Any]: Stock price and additional metrics.
35
+ """
36
+ try:
37
+ query = input_data.get("query", "")
38
+ symbols = self._extract_symbols(query) if query else [self.default_symbol]
39
+ time_range = input_data.get("time_range", "1d") # Default to 1 day
40
+ metrics = input_data.get("metrics", ["price"]) # Default to price only
41
+
42
+ results = {}
43
+
44
+ for symbol in symbols:
45
+ # Validate the symbol
46
+ if not self._is_valid_symbol(symbol):
47
+ logger.warning(f"Invalid stock symbol: {symbol}")
48
+ results[symbol] = {"error": "Invalid symbol"}
49
+ continue
50
+
51
+ try:
52
+ stock = yf.Ticker(symbol)
53
+ data = {}
54
+
55
+ # Fetch real-time price
56
+ if "price" in metrics:
57
+ history = stock.history(period=time_range)
58
+ if not history.empty:
59
+ data["price"] = history["Close"].iloc[-1]
60
+ else:
61
+ data["price"] = "No data available"
62
+
63
+ # Fetch historical data
64
+ if self.enable_historical_data and "history" in metrics:
65
+ history = stock.history(period=time_range)
66
+ if not history.empty:
67
+ data["history"] = history.to_dict()
68
+ else:
69
+ data["history"] = "No historical data available"
70
+
71
+ # Fetch additional metrics
72
+ if self.enable_metrics:
73
+ info = stock.info
74
+ if "volume" in metrics:
75
+ data["volume"] = info.get("volume", "No volume data available")
76
+ if "market_cap" in metrics:
77
+ data["market_cap"] = info.get("marketCap", "No market cap data available")
78
+
79
+ results[symbol] = data
80
+ except Exception as e:
81
+ results[symbol] = {"error": str(e)}
82
+
83
+ return results
84
+ except Exception as e:
85
+ return {"error": str(e)}
86
+
87
+ def _extract_symbols(self, query: str) -> List[str]:
88
+ """
89
+ Use the LLM to extract stock symbols from the user's query.
90
+ """
91
+ if not self.llm:
92
+ logger.error("LLM instance not available for symbol extraction.")
93
+ return []
94
+
95
+ # Create a prompt for the LLM
96
+ prompt = f"""
97
+ Extract the stock symbols from the following user query.
98
+ Return ONLY the symbols as a comma-separated list (e.g., "AAPL,MSFT").
99
+ Do not include any explanations or additional text.
100
+ If no symbols are found, return "None".
101
+
102
+ User Query: "{query}"
103
+ """
104
+
105
+ try:
106
+ # Call the LLM to generate the response
107
+ response = self.llm.generate(prompt=prompt)
108
+ logger.info(f"LLM response: {response}")
109
+ symbols = response.strip().replace('"', '').replace("'", "")
110
+
111
+ # Fallback: Extract symbols from verbose responses
112
+ if ":" in symbols or "\n" in symbols:
113
+ # Look for patterns like "AAPL,MSFT" in the response
114
+ symbol_pattern = re.compile(r"\b[A-Z]{1,5}\b")
115
+ symbols = ",".join(symbol_pattern.findall(symbols))
116
+ logger.info(f"Extracted symbols: {symbols}")
117
+
118
+ # Parse the response into a list of symbols
119
+ if symbols.lower() == "none":
120
+ return []
121
+ return [s.strip() for s in symbols.split(",")]
122
+ except Exception as e:
123
+ logger.error(f"Failed to extract symbols: {e}")
124
+ return []
125
+
126
+ def _is_valid_symbol(self, symbol: str) -> bool:
127
+ """
128
+ Validate if the symbol is a valid stock symbol.
129
+ """
130
+ # Check if the symbol is a valid stock ticker (e.g., AAPL, MSFT)
131
+ return bool(symbol.strip()) and symbol.isalpha() and 1 <= len(symbol) <= 5
@@ -0,0 +1,153 @@
1
+ from typing import Dict, Any, Optional, List
2
+ from playwright.async_api import async_playwright
3
+ import asyncio
4
+ import logging
5
+
6
+ logger = logging.getLogger(__name__)
7
+
8
+ class WebBrowserTool:
9
+ """
10
+ A tool for performing browser automation tasks using Playwright.
11
+ """
12
+
13
+ def __init__(self, headless: bool = True):
14
+ """
15
+ Initialize the WebBrowserTool.
16
+
17
+ Args:
18
+ headless (bool): Whether to run the browser in headless mode (default: True).
19
+ """
20
+ self.headless = headless
21
+ self.browser = None
22
+ self.context = None
23
+ self.page = None
24
+
25
+ async def start(self):
26
+ """
27
+ Start the browser and create a new context and page.
28
+ """
29
+ self.playwright = await async_playwright().start()
30
+ self.browser = await self.playwright.chromium.launch(headless=self.headless)
31
+ self.context = await self.browser.new_context()
32
+ self.page = await self.context.new_page()
33
+ logger.info("Browser started successfully.")
34
+
35
+ async def close(self):
36
+ """
37
+ Close the browser and cleanup resources.
38
+ """
39
+ if self.browser:
40
+ await self.browser.close()
41
+ await self.playwright.stop()
42
+ logger.info("Browser closed successfully.")
43
+
44
+ async def navigate(self, url: str) -> str:
45
+ """
46
+ Navigate to a specific URL.
47
+
48
+ Args:
49
+ url (str): The URL to navigate to.
50
+
51
+ Returns:
52
+ str: The page title after navigation.
53
+ """
54
+ if not self.page:
55
+ raise RuntimeError("Browser is not started. Call start() first.")
56
+
57
+ await self.page.goto(url)
58
+ title = await self.page.title()
59
+ logger.info(f"Navigated to {url}. Page title: {title}")
60
+ return title
61
+
62
+ async def fill_form(self, fields: Dict[str, str]) -> str:
63
+ """
64
+ Fill a form with the provided fields.
65
+
66
+ Args:
67
+ fields (Dict[str, str]): A dictionary of field names and values to fill.
68
+
69
+ Returns:
70
+ str: A success message.
71
+ """
72
+ if not self.page:
73
+ raise RuntimeError("Browser is not started. Call start() first.")
74
+
75
+ for field, value in fields.items():
76
+ await self.page.fill(f'input[name="{field}"]', value)
77
+ logger.info(f"Filled field '{field}' with value '{value}'.")
78
+
79
+ return "Form filled successfully."
80
+
81
+ async def click(self, selector: str) -> str:
82
+ """
83
+ Click an element on the page.
84
+
85
+ Args:
86
+ selector (str): The CSS selector of the element to click.
87
+
88
+ Returns:
89
+ str: A success message.
90
+ """
91
+ if not self.page:
92
+ raise RuntimeError("Browser is not started. Call start() first.")
93
+
94
+ await self.page.click(selector)
95
+ logger.info(f"Clicked element with selector '{selector}'.")
96
+ return f"Clicked element: {selector}"
97
+
98
+ async def scrape(self, selector: str) -> List[Dict[str, str]]:
99
+ """
100
+ Scrape data from the page.
101
+
102
+ Args:
103
+ selector (str): The CSS selector of the elements to scrape.
104
+
105
+ Returns:
106
+ List[Dict[str, str]]: A list of dictionaries containing the scraped data.
107
+ """
108
+ if not self.page:
109
+ raise RuntimeError("Browser is not started. Call start() first.")
110
+
111
+ elements = await self.page.query_selector_all(selector)
112
+ scraped_data = []
113
+ for element in elements:
114
+ text = await element.inner_text()
115
+ scraped_data.append({"text": text.strip()})
116
+ logger.info(f"Scraped text: {text.strip()}")
117
+
118
+ return scraped_data
119
+
120
+ async def execute_step(self, step: Dict[str, Any]) -> str:
121
+ """
122
+ Execute a browser automation step.
123
+
124
+ Args:
125
+ step (Dict[str, Any]): A dictionary containing the step details.
126
+ - "action": The action to perform (e.g., "navigate", "fill_form", "click", "scrape").
127
+ - "details": The details required for the action (e.g., URL, form fields, selector).
128
+ - "website": The website to perform the action on (optional).
129
+
130
+ Returns:
131
+ str: The result of the step execution.
132
+ """
133
+ action = step.get("action")
134
+ details = step.get("details")
135
+ website = step.get("website", "https://www.google.com")
136
+
137
+ if not self.page:
138
+ await self.start()
139
+
140
+ try:
141
+ if action == "navigate":
142
+ return await self.navigate(details)
143
+ elif action == "fill_form":
144
+ return await self.fill_form(details)
145
+ elif action == "click":
146
+ return await self.click(details)
147
+ elif action == "scrape":
148
+ return str(await self.scrape(details))
149
+ else:
150
+ return f"Unknown action: {action}"
151
+ except Exception as e:
152
+ logger.error(f"Error executing step: {e}")
153
+ return f"Error executing step: {e}"
@@ -0,0 +1,7 @@
1
+ from .logger import logger
2
+ from .config import Config
3
+ from .file_utils import FileUtils
4
+ from .date_utils import DateUtils
5
+ from .validation_utils import ValidationUtils
6
+
7
+ __all__ = ["logger", "Config", "FileUtils", "DateUtils", "ValidationUtils"]
@@ -0,0 +1,41 @@
1
+ import os
2
+ from dotenv import load_dotenv
3
+ from pathlib import Path
4
+ from typing import Any, Optional, Type, TypeVar, List, Dict
5
+
6
+ # Load environment variables from .env file
7
+ load_dotenv()
8
+
9
+ class Config:
10
+ """
11
+ Configuration class to load and manage environment variables.
12
+ """
13
+
14
+ @staticmethod
15
+ def get(key: str, default: Optional[str] = None) -> str:
16
+ """
17
+ Get an environment variable. Raise an error if it's not found and no default is provided.
18
+ """
19
+ value = os.getenv(key, default)
20
+ if value is None:
21
+ raise ValueError(f"Environment variable {key} is not set.")
22
+ return value
23
+
24
+ @staticmethod
25
+ def get_bool(key: str, default: bool = False) -> bool:
26
+ """
27
+ Get an environment variable as a boolean.
28
+ """
29
+ value = os.getenv(key, str(default)).lower()
30
+ return value in ("true", "1", "yes")
31
+
32
+ @staticmethod
33
+ def get_int(key: str, default: int = 0) -> int:
34
+ """
35
+ Get an environment variable as an integer.
36
+ """
37
+ return int(os.getenv(key, str(default)))
38
+
39
+ # Example usage:
40
+ # GROQ_API_KEY = Config.get("GROQ_API_KEY")
41
+ # MAX_RETRIES = Config.get_int("MAX_RETRIES", 5)
@@ -0,0 +1,44 @@
1
+ from datetime import datetime, timedelta
2
+ from typing import Optional
3
+
4
+ class DateUtils:
5
+ @staticmethod
6
+ def get_current_time() -> str:
7
+ """
8
+ Get the current time in ISO format.
9
+ """
10
+ return datetime.now().isoformat()
11
+
12
+ @staticmethod
13
+ def format_time(timestamp: str, format: str = "%Y-%m-%d %H:%M:%S") -> str:
14
+ """
15
+ Format a timestamp string into a custom format.
16
+ """
17
+ try:
18
+ dt = datetime.fromisoformat(timestamp)
19
+ return dt.strftime(format)
20
+ except Exception as e:
21
+ raise ValueError(f"Failed to format timestamp {timestamp}: {e}")
22
+
23
+ @staticmethod
24
+ def add_time(timestamp: str, days: int = 0, hours: int = 0, minutes: int = 0) -> str:
25
+ """
26
+ Add days, hours, or minutes to a timestamp.
27
+ """
28
+ try:
29
+ dt = datetime.fromisoformat(timestamp)
30
+ dt += timedelta(days=days, hours=hours, minutes=minutes)
31
+ return dt.isoformat()
32
+ except Exception as e:
33
+ raise ValueError(f"Failed to add time to timestamp {timestamp}: {e}")
34
+
35
+ @staticmethod
36
+ def is_future_time(timestamp: str) -> bool:
37
+ """
38
+ Check if a timestamp is in the future.
39
+ """
40
+ try:
41
+ dt = datetime.fromisoformat(timestamp)
42
+ return dt > datetime.now()
43
+ except Exception as e:
44
+ raise ValueError(f"Failed to check if timestamp {timestamp} is in the future: {e}")
@@ -0,0 +1,56 @@
1
+ import os
2
+ import json
3
+ from pathlib import Path
4
+ from typing import Any, Dict, Optional
5
+
6
+ class FileUtils:
7
+ @staticmethod
8
+ def read_file(file_path: str) -> str:
9
+ """
10
+ Read the contents of a file.
11
+ """
12
+ try:
13
+ with open(file_path, "r", encoding="utf-8") as file:
14
+ return file.read()
15
+ except Exception as e:
16
+ raise ValueError(f"Failed to read file {file_path}: {e}")
17
+
18
+ @staticmethod
19
+ def write_file(file_path: str, content: str) -> None:
20
+ """
21
+ Write content to a file.
22
+ """
23
+ try:
24
+ with open(file_path, "w", encoding="utf-8") as file:
25
+ file.write(content)
26
+ except Exception as e:
27
+ raise ValueError(f"Failed to write to file {file_path}: {e}")
28
+
29
+ @staticmethod
30
+ def read_json(file_path: str) -> Dict[str, Any]:
31
+ """
32
+ Read a JSON file and return its contents as a dictionary.
33
+ """
34
+ try:
35
+ with open(file_path, "r", encoding="utf-8") as file:
36
+ return json.load(file)
37
+ except Exception as e:
38
+ raise ValueError(f"Failed to read JSON file {file_path}: {e}")
39
+
40
+ @staticmethod
41
+ def write_json(file_path: str, data: Dict[str, Any]) -> None:
42
+ """
43
+ Write a dictionary to a JSON file.
44
+ """
45
+ try:
46
+ with open(file_path, "w", encoding="utf-8") as file:
47
+ json.dump(data, file, indent=2)
48
+ except Exception as e:
49
+ raise ValueError(f"Failed to write JSON file {file_path}: {e}")
50
+
51
+ @staticmethod
52
+ def ensure_directory_exists(directory: str) -> None:
53
+ """
54
+ Ensure that a directory exists. If not, create it.
55
+ """
56
+ Path(directory).mkdir(parents=True, exist_ok=True)
@@ -0,0 +1,20 @@
1
+ import logging
2
+ import sys
3
+ from pathlib import Path
4
+
5
+ # Create a logs directory if it doesn't exist
6
+ logs_dir = Path("logs")
7
+ logs_dir.mkdir(exist_ok=True)
8
+
9
+ # Logger configuration
10
+ logging.basicConfig(
11
+ level=logging.INFO,
12
+ format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
13
+ handlers=[
14
+ logging.FileHandler(logs_dir / "agent.log"), # Log to a file
15
+ logging.StreamHandler(sys.stdout), # Log to the console
16
+ ],
17
+ )
18
+
19
+ # Create a logger instance
20
+ logger = logging.getLogger("agent")
@@ -0,0 +1,44 @@
1
+ import re
2
+ from typing import Any, Optional
3
+
4
+ class ValidationUtils:
5
+ @staticmethod
6
+ def validate_email(email: str) -> bool:
7
+ """
8
+ Validate an email address.
9
+ """
10
+ regex = r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$"
11
+ return re.match(regex, email) is not None
12
+
13
+ @staticmethod
14
+ def validate_phone_number(phone: str) -> bool:
15
+ """
16
+ Validate a phone number (basic validation for US numbers).
17
+ """
18
+ regex = r"^\+?1?\d{10}$"
19
+ return re.match(regex, phone) is not None
20
+
21
+ @staticmethod
22
+ def validate_not_empty(value: Any) -> bool:
23
+ """
24
+ Validate that a value is not empty.
25
+ """
26
+ if isinstance(value, str):
27
+ return bool(value.strip())
28
+ elif isinstance(value, (list, dict, set)):
29
+ return bool(value)
30
+ return value is not None
31
+
32
+ @staticmethod
33
+ def validate_numeric(value: Any) -> bool:
34
+ """
35
+ Validate that a value is numeric (int or float).
36
+ """
37
+ return isinstance(value, (int, float))
38
+
39
+ @staticmethod
40
+ def validate_in_range(value: float, min: float, max: float) -> bool:
41
+ """
42
+ Validate that a numeric value is within a specified range.
43
+ """
44
+ return min <= value <= max
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Syenah
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.