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.
- semantio/__init__.py +0 -0
- semantio/agent.py +608 -0
- semantio/api/__init__.py +0 -0
- semantio/api/api_generator.py +23 -0
- semantio/api/fastapi_app.py +70 -0
- semantio/cli/__init__.py +0 -0
- semantio/cli/main.py +31 -0
- semantio/knowledge_base/__init__.py +5 -0
- semantio/knowledge_base/document_loader.py +61 -0
- semantio/knowledge_base/retriever.py +41 -0
- semantio/knowledge_base/vector_store.py +35 -0
- semantio/llm/__init__.py +17 -0
- semantio/llm/anthropic.py +39 -0
- semantio/llm/base_llm.py +12 -0
- semantio/llm/groq.py +39 -0
- semantio/llm/llama.py +0 -0
- semantio/llm/openai.py +26 -0
- semantio/memory.py +11 -0
- semantio/rag.py +18 -0
- semantio/storage/__init__.py +0 -0
- semantio/storage/cloud_storage.py +0 -0
- semantio/storage/local_storage.py +0 -0
- semantio/tools/__init__.py +0 -0
- semantio/tools/base_tool.py +12 -0
- semantio/tools/crypto.py +133 -0
- semantio/tools/duckduckgo.py +128 -0
- semantio/tools/stocks.py +131 -0
- semantio/tools/web_browser.py +153 -0
- semantio/utils/__init__.py +7 -0
- semantio/utils/config.py +41 -0
- semantio/utils/date_utils.py +44 -0
- semantio/utils/file_utils.py +56 -0
- semantio/utils/logger.py +20 -0
- semantio/utils/validation_utils.py +44 -0
- semantio-0.0.1.dist-info/LICENSE +21 -0
- semantio-0.0.1.dist-info/METADATA +163 -0
- semantio-0.0.1.dist-info/RECORD +40 -0
- semantio-0.0.1.dist-info/WHEEL +5 -0
- semantio-0.0.1.dist-info/entry_points.txt +3 -0
- semantio-0.0.1.dist-info/top_level.txt +1 -0
@@ -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}")
|
semantio/tools/stocks.py
ADDED
@@ -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}"
|
semantio/utils/config.py
ADDED
@@ -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)
|
semantio/utils/logger.py
ADDED
@@ -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.
|