aip-agents-binary 0.6.2__py3-none-macosx_13_0_arm64.whl → 0.6.3__py3-none-macosx_13_0_arm64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of aip-agents-binary might be problematic. Click here for more details.
- aip_agents/tools/__init__.py +11 -2
- aip_agents/tools/__init__.pyi +2 -1
- aip_agents/tools/date_range_tool.py +554 -0
- aip_agents/tools/date_range_tool.pyi +21 -0
- {aip_agents_binary-0.6.2.dist-info → aip_agents_binary-0.6.3.dist-info}/METADATA +2 -1
- {aip_agents_binary-0.6.2.dist-info → aip_agents_binary-0.6.3.dist-info}/RECORD +8 -6
- {aip_agents_binary-0.6.2.dist-info → aip_agents_binary-0.6.3.dist-info}/WHEEL +0 -0
- {aip_agents_binary-0.6.2.dist-info → aip_agents_binary-0.6.3.dist-info}/top_level.txt +0 -0
aip_agents/tools/__init__.py
CHANGED
|
@@ -3,8 +3,12 @@
|
|
|
3
3
|
from importlib import import_module
|
|
4
4
|
from typing import TYPE_CHECKING
|
|
5
5
|
|
|
6
|
+
from aip_agents.tools.date_range_tool import DateRangeTool
|
|
6
7
|
from aip_agents.tools.gl_connector import GLConnectorTool
|
|
7
|
-
from aip_agents.tools.gl_connector_tools import
|
|
8
|
+
from aip_agents.tools.gl_connector_tools import (
|
|
9
|
+
BOSA_AUTOMATED_TOOLS,
|
|
10
|
+
GL_CONNECTORS_AUTOMATED_TOOLS,
|
|
11
|
+
)
|
|
8
12
|
from aip_agents.tools.time_tool import TimeTool
|
|
9
13
|
from aip_agents.tools.web_search import GoogleSerperTool
|
|
10
14
|
from aip_agents.utils.logger import get_logger
|
|
@@ -17,6 +21,7 @@ __all__ = [
|
|
|
17
21
|
"GLConnectorTool",
|
|
18
22
|
"GoogleSerperTool",
|
|
19
23
|
"TimeTool",
|
|
24
|
+
"DateRangeTool",
|
|
20
25
|
]
|
|
21
26
|
|
|
22
27
|
|
|
@@ -51,5 +56,9 @@ _register_optional("aip_agents.tools.execute_ptc_code", "create_execute_ptc_code
|
|
|
51
56
|
if TYPE_CHECKING:
|
|
52
57
|
from aip_agents.tools.browser_use import BrowserUseTool
|
|
53
58
|
from aip_agents.tools.code_sandbox import E2BCodeSandboxTool
|
|
54
|
-
from aip_agents.tools.document_loader import
|
|
59
|
+
from aip_agents.tools.document_loader import (
|
|
60
|
+
DocxReaderTool,
|
|
61
|
+
ExcelReaderTool,
|
|
62
|
+
PDFReaderTool,
|
|
63
|
+
)
|
|
55
64
|
from aip_agents.tools.execute_ptc_code import create_execute_ptc_code_tool
|
aip_agents/tools/__init__.pyi
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from aip_agents.tools.browser_use import BrowserUseTool as BrowserUseTool
|
|
2
2
|
from aip_agents.tools.code_sandbox import E2BCodeSandboxTool as E2BCodeSandboxTool
|
|
3
|
+
from aip_agents.tools.date_range_tool import DateRangeTool as DateRangeTool
|
|
3
4
|
from aip_agents.tools.document_loader import DocxReaderTool as DocxReaderTool, ExcelReaderTool as ExcelReaderTool, PDFReaderTool as PDFReaderTool
|
|
4
5
|
from aip_agents.tools.execute_ptc_code import create_execute_ptc_code_tool as create_execute_ptc_code_tool
|
|
5
6
|
from aip_agents.tools.gl_connector import GLConnectorTool as GLConnectorTool
|
|
@@ -7,4 +8,4 @@ from aip_agents.tools.gl_connector_tools import BOSA_AUTOMATED_TOOLS as BOSA_AUT
|
|
|
7
8
|
from aip_agents.tools.time_tool import TimeTool as TimeTool
|
|
8
9
|
from aip_agents.tools.web_search import GoogleSerperTool as GoogleSerperTool
|
|
9
10
|
|
|
10
|
-
__all__ = ['BOSA_AUTOMATED_TOOLS', 'GL_CONNECTORS_AUTOMATED_TOOLS', 'GLConnectorTool', 'GoogleSerperTool', 'TimeTool', 'BrowserUseTool', 'E2BCodeSandboxTool', 'DocxReaderTool', 'ExcelReaderTool', 'PDFReaderTool', 'create_execute_ptc_code_tool']
|
|
11
|
+
__all__ = ['BOSA_AUTOMATED_TOOLS', 'GL_CONNECTORS_AUTOMATED_TOOLS', 'GLConnectorTool', 'GoogleSerperTool', 'TimeTool', 'DateRangeTool', 'BrowserUseTool', 'E2BCodeSandboxTool', 'DocxReaderTool', 'ExcelReaderTool', 'PDFReaderTool', 'create_execute_ptc_code_tool']
|
|
@@ -0,0 +1,554 @@
|
|
|
1
|
+
"""Tool to get date ranges for common time periods.
|
|
2
|
+
|
|
3
|
+
Authors:
|
|
4
|
+
Christian Trisno Sen Long Chen (christian.t.s.l.chen@gdplabs.id)
|
|
5
|
+
Fachriza Dian Adhiatma (fachriza.d.adhiatma@gdplabs.id)
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
import re
|
|
10
|
+
from collections.abc import Callable
|
|
11
|
+
from datetime import datetime, timedelta
|
|
12
|
+
|
|
13
|
+
from dateutil.relativedelta import relativedelta
|
|
14
|
+
from langchain_core.tools import BaseTool
|
|
15
|
+
from pydantic import BaseModel, Field
|
|
16
|
+
|
|
17
|
+
FORMAT_STRING = "%m/%d/%y %H:%M:%S"
|
|
18
|
+
|
|
19
|
+
MIN_DAYS_FOR_WEEK_SPLIT = 6 # Minimum days difference for week split (7 days inclusive = 6 days difference)
|
|
20
|
+
MONTH_NAME_ABBR_THRESHOLD = 3 # len > 3 implies full month name, else try abbreviated form
|
|
21
|
+
SUPPORTED_DATE_RANGES = [
|
|
22
|
+
"last_week",
|
|
23
|
+
"this_week",
|
|
24
|
+
"last_month",
|
|
25
|
+
"this_month",
|
|
26
|
+
"yesterday",
|
|
27
|
+
"today",
|
|
28
|
+
"last_7_days",
|
|
29
|
+
"last_30_days",
|
|
30
|
+
"this_quarter",
|
|
31
|
+
"last_quarter",
|
|
32
|
+
"this_year",
|
|
33
|
+
"last_year",
|
|
34
|
+
"[N]_days_ago",
|
|
35
|
+
"[N]_weeks_ago",
|
|
36
|
+
"[N]_months_ago",
|
|
37
|
+
"YYYY-MM-DD to YYYY-MM-DD",
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class DateRangeToolInput(BaseModel):
|
|
42
|
+
"""Input schema for the DateRangeTool."""
|
|
43
|
+
|
|
44
|
+
date_range: str = Field(
|
|
45
|
+
...,
|
|
46
|
+
description="""
|
|
47
|
+
Date range identifier. Supported values:
|
|
48
|
+
- Relative periods: 'last_week', 'this_week', 'last_month', 'this_month'
|
|
49
|
+
- Relative days: 'yesterday', 'today', 'last_7_days', 'last_30_days'
|
|
50
|
+
- Custom range: 'N_days_ago', 'N_weeks_ago', 'N_months_ago' (replace N with number)
|
|
51
|
+
- Quarter: 'this_quarter', 'last_quarter'
|
|
52
|
+
- Year: 'this_year', 'last_year'
|
|
53
|
+
- Natural forms: 'January 2025', 'January-March 2025', 'Q1 2025'
|
|
54
|
+
- Explicit date range: 'YYYY-MM-DD to YYYY-MM-DD' (e.g., '2025-08-01 to 2025-08-07')
|
|
55
|
+
|
|
56
|
+
Note: For queries like 'first week of August 2025', convert to explicit format: '2025-08-01 to 2025-08-07'
|
|
57
|
+
""",
|
|
58
|
+
)
|
|
59
|
+
format: str = Field(
|
|
60
|
+
default=FORMAT_STRING,
|
|
61
|
+
description="Optional datetime format string. Default: '%m/%d/%y'",
|
|
62
|
+
)
|
|
63
|
+
split_weeks: bool = Field(
|
|
64
|
+
default=False,
|
|
65
|
+
description=(
|
|
66
|
+
"If True and the resulting period spans more than one week, include a 'weeks' array of Sunday–Saturday"
|
|
67
|
+
" splits that fit within the requested period (not recent fixed weeks)."
|
|
68
|
+
),
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class DateRangeTool(BaseTool):
|
|
73
|
+
"""Tool to get date ranges for common time periods."""
|
|
74
|
+
|
|
75
|
+
name: str = "date_range_tool"
|
|
76
|
+
description: str = """
|
|
77
|
+
Useful for getting date ranges for various time periods.
|
|
78
|
+
Supports relative dates, custom ranges, and standard periods.
|
|
79
|
+
Returns start and end dates for the specified period.
|
|
80
|
+
"""
|
|
81
|
+
args_schema: type[BaseModel] = DateRangeToolInput
|
|
82
|
+
|
|
83
|
+
def _parse_custom_range(self, date_range: str) -> tuple[datetime, datetime]:
|
|
84
|
+
"""Parse custom range patterns like '3_days_ago', '2_weeks_ago', etc.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
date_range (str): A string representing the custom date range in the format '{number}_{unit}_ago',
|
|
88
|
+
where {number} is an integer and {unit} is one of 'days', 'weeks', or 'months'.
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
tuple[datetime, datetime]: A tuple containing the start date and end date corresponding to the custom range.
|
|
92
|
+
"""
|
|
93
|
+
pattern = r"(\d+)_(days|weeks|months)_ago"
|
|
94
|
+
match = re.match(pattern, date_range)
|
|
95
|
+
start_date = None
|
|
96
|
+
end_date = None
|
|
97
|
+
if not match:
|
|
98
|
+
raise ValueError(f"Invalid custom range format: {date_range}")
|
|
99
|
+
|
|
100
|
+
number, unit = int(match.group(1)), match.group(2)
|
|
101
|
+
today = datetime.now()
|
|
102
|
+
|
|
103
|
+
if unit == "days":
|
|
104
|
+
start_date = today - timedelta(days=number)
|
|
105
|
+
end_date = today
|
|
106
|
+
elif unit == "weeks":
|
|
107
|
+
start_date = today - timedelta(weeks=number)
|
|
108
|
+
end_date = today
|
|
109
|
+
elif unit == "months":
|
|
110
|
+
start_date = today - relativedelta(months=number)
|
|
111
|
+
end_date = today
|
|
112
|
+
|
|
113
|
+
return start_date, end_date
|
|
114
|
+
|
|
115
|
+
def _parse_month(self, month_str: str) -> int:
|
|
116
|
+
"""Parse month name (full or abbreviated) to month number (1-12).
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
month_str (int): The month name string to parse.
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
int: The month number (1-12).
|
|
123
|
+
"""
|
|
124
|
+
fmt = "%B" if len(month_str) > MONTH_NAME_ABBR_THRESHOLD else "%b"
|
|
125
|
+
try:
|
|
126
|
+
return datetime.strptime(month_str, fmt).month
|
|
127
|
+
except ValueError:
|
|
128
|
+
# Fallback to abbreviated format
|
|
129
|
+
return datetime.strptime(month_str, "%b").month
|
|
130
|
+
|
|
131
|
+
def _parse_explicit_range(self, dr: str) -> tuple[datetime, datetime] | None:
|
|
132
|
+
"""Parse explicit date range: YYYY-MM-DD to YYYY-MM-DD.
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
dr (str): The date range string to parse.
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
tuple[datetime, datetime] | None: A tuple of (start_date, end_date) if matched, otherwise None.
|
|
139
|
+
"""
|
|
140
|
+
m_explicit = re.match(r"^(\d{4}-\d{2}-\d{2})\s+to\s+(\d{4}-\d{2}-\d{2})$", dr, flags=re.IGNORECASE)
|
|
141
|
+
if m_explicit:
|
|
142
|
+
try:
|
|
143
|
+
start = datetime.strptime(m_explicit.group(1), "%Y-%m-%d")
|
|
144
|
+
end = datetime.strptime(m_explicit.group(2), "%Y-%m-%d")
|
|
145
|
+
if start > end:
|
|
146
|
+
raise ValueError("Start date must be before or equal to end date")
|
|
147
|
+
return start, end
|
|
148
|
+
except ValueError:
|
|
149
|
+
return None
|
|
150
|
+
return None
|
|
151
|
+
|
|
152
|
+
def _parse_quarter_with_year(self, dr: str) -> tuple[datetime, datetime] | None:
|
|
153
|
+
"""Parse quarter with year: Q1 2025.
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
dr (str): The date range string to parse.
|
|
157
|
+
|
|
158
|
+
Returns:
|
|
159
|
+
tuple[datetime, datetime] | None: A tuple of (start_date, end_date) if matched, otherwise None.
|
|
160
|
+
"""
|
|
161
|
+
m_q = re.match(r"^Q([1-4])\s+(\d{4})$", dr, flags=re.IGNORECASE)
|
|
162
|
+
if m_q:
|
|
163
|
+
q = int(m_q.group(1))
|
|
164
|
+
year = int(m_q.group(2))
|
|
165
|
+
start_month = (q - 1) * 3 + 1
|
|
166
|
+
start = datetime(year, start_month, 1)
|
|
167
|
+
end = start + relativedelta(months=3, days=-1)
|
|
168
|
+
return start, end
|
|
169
|
+
return None
|
|
170
|
+
|
|
171
|
+
def _parse_month_range_with_year(self, dr: str) -> tuple[datetime, datetime] | None:
|
|
172
|
+
"""Parse month range within a year: January-March 2025.
|
|
173
|
+
|
|
174
|
+
Args:
|
|
175
|
+
dr (str): The date range string to parse.
|
|
176
|
+
|
|
177
|
+
Returns:
|
|
178
|
+
tuple[datetime, datetime] | None: A tuple of (start_date, end_date) if matched, otherwise None.
|
|
179
|
+
"""
|
|
180
|
+
m_range = re.match(r"^([A-Za-z]{3,9})\s*-\s*([A-Za-z]{3,9})\s+(\d{4})$", dr)
|
|
181
|
+
if m_range:
|
|
182
|
+
m1, m2, year_s = m_range.groups()
|
|
183
|
+
year = int(year_s)
|
|
184
|
+
try:
|
|
185
|
+
start_month = self._parse_month(m1)
|
|
186
|
+
end_month = self._parse_month(m2)
|
|
187
|
+
start = datetime(year, start_month, 1)
|
|
188
|
+
end = datetime(year, end_month, 1) + relativedelta(months=1, days=-1)
|
|
189
|
+
return start, end
|
|
190
|
+
except ValueError:
|
|
191
|
+
return None
|
|
192
|
+
return None
|
|
193
|
+
|
|
194
|
+
def _parse_single_month_with_year(self, dr: str) -> tuple[datetime, datetime] | None:
|
|
195
|
+
"""Parse single month with year: January 2025.
|
|
196
|
+
|
|
197
|
+
Args:
|
|
198
|
+
dr (str): The date range string to parse.
|
|
199
|
+
|
|
200
|
+
Returns:
|
|
201
|
+
tuple[datetime, datetime] | None: A tuple of (start_date, end_date) if matched, otherwise None.
|
|
202
|
+
"""
|
|
203
|
+
m_single = re.match(r"^([A-Za-z]{3,9})\s+(\d{4})$", dr)
|
|
204
|
+
if m_single:
|
|
205
|
+
mname, year_s = m_single.groups()
|
|
206
|
+
year = int(year_s)
|
|
207
|
+
try:
|
|
208
|
+
month = self._parse_month(mname)
|
|
209
|
+
start = datetime(year, month, 1)
|
|
210
|
+
end = start + relativedelta(months=1, days=-1)
|
|
211
|
+
return start, end
|
|
212
|
+
except ValueError:
|
|
213
|
+
return None
|
|
214
|
+
return None
|
|
215
|
+
|
|
216
|
+
def _parse_month_or_range(self, date_range: str) -> tuple[datetime, datetime] | None:
|
|
217
|
+
"""Parse inputs like 'January 2025', 'Jan 2025', 'January-March 2025', 'Jan-Mar 2025', 'Q1 2025'.
|
|
218
|
+
|
|
219
|
+
Also parses explicit date ranges like '2025-08-01 to 2025-08-07'.
|
|
220
|
+
|
|
221
|
+
Args:
|
|
222
|
+
date_range (str): The date range string to parse.
|
|
223
|
+
Can be natural month/quarter forms or explicit YYYY-MM-DD format.
|
|
224
|
+
|
|
225
|
+
Returns:
|
|
226
|
+
tuple[datetime, datetime] | None: A tuple of (start_date, end_date) if matched, otherwise None.
|
|
227
|
+
|
|
228
|
+
Raises:
|
|
229
|
+
ValueError: If start date is after end date (caught internally and returns None).
|
|
230
|
+
|
|
231
|
+
Examples:
|
|
232
|
+
>>> self._parse_month_or_range("January 2025")
|
|
233
|
+
(datetime(2025, 1, 1), datetime(2025, 1, 31))
|
|
234
|
+
>>> self._parse_month_or_range("2025-08-01 to 2025-08-07")
|
|
235
|
+
(datetime(2025, 8, 1), datetime(2025, 8, 7))
|
|
236
|
+
"""
|
|
237
|
+
dr = date_range.strip()
|
|
238
|
+
|
|
239
|
+
# Try each parser in order
|
|
240
|
+
parsers = [
|
|
241
|
+
self._parse_explicit_range,
|
|
242
|
+
self._parse_quarter_with_year,
|
|
243
|
+
self._parse_month_range_with_year,
|
|
244
|
+
self._parse_single_month_with_year,
|
|
245
|
+
]
|
|
246
|
+
|
|
247
|
+
for parser in parsers:
|
|
248
|
+
result = parser(dr)
|
|
249
|
+
if result:
|
|
250
|
+
return result
|
|
251
|
+
|
|
252
|
+
return None
|
|
253
|
+
|
|
254
|
+
def _get_quarter_dates(self, today: datetime, last_quarter: bool = False) -> tuple[datetime, datetime]:
|
|
255
|
+
"""Calculate the start and end dates of the current or last quarter based on the given date.
|
|
256
|
+
|
|
257
|
+
Args:
|
|
258
|
+
today (datetime): The reference date to determine the quarter.
|
|
259
|
+
last_quarter (bool, optional): If True, calculate the dates for the last quarter.
|
|
260
|
+
If False, calculate the dates for the current quarter.
|
|
261
|
+
Defaults to False.
|
|
262
|
+
|
|
263
|
+
Returns:
|
|
264
|
+
tuple[datetime, datetime]: A tuple containing the start and end dates of the quarter.
|
|
265
|
+
"""
|
|
266
|
+
current_quarter = (today.month - 1) // 3
|
|
267
|
+
if last_quarter:
|
|
268
|
+
if current_quarter == 0:
|
|
269
|
+
start_date = datetime(today.year - 1, 10, 1)
|
|
270
|
+
end_date = datetime(today.year - 1, 12, 31)
|
|
271
|
+
else:
|
|
272
|
+
start_date = datetime(today.year, 3 * (current_quarter - 1) + 1, 1)
|
|
273
|
+
end_date = datetime(today.year, 3 * current_quarter, 1) + relativedelta(months=1, days=-1)
|
|
274
|
+
else:
|
|
275
|
+
start_date = datetime(today.year, 3 * current_quarter + 1, 1)
|
|
276
|
+
end_date = datetime(today.year, 3 * (current_quarter + 1), 1) + relativedelta(months=1, days=-1)
|
|
277
|
+
return start_date, end_date
|
|
278
|
+
|
|
279
|
+
def _days_since_most_recent_sunday(self, date: datetime) -> int:
|
|
280
|
+
"""Return days since most recent Sunday (0 if today is Sunday).
|
|
281
|
+
|
|
282
|
+
Args:
|
|
283
|
+
date (datetime): The reference date.
|
|
284
|
+
|
|
285
|
+
Returns:
|
|
286
|
+
int: Days since most recent Sunday (0-6).
|
|
287
|
+
"""
|
|
288
|
+
# weekday(): Mon=0, Tue=1, ..., Sun=6
|
|
289
|
+
# We want Sun=0, Mon=1, ..., Sat=6
|
|
290
|
+
return (date.weekday() + 1) % 7
|
|
291
|
+
|
|
292
|
+
def _get_standard_period_dates(self, date_range: str, today: datetime) -> tuple[datetime, datetime]:
|
|
293
|
+
"""Calculate the start and end dates for a given standard period relative to a specified date.
|
|
294
|
+
|
|
295
|
+
Args:
|
|
296
|
+
date_range (str): The standard period to calculate dates for.
|
|
297
|
+
Supported values are "last_week", "this_week", "last_month", and "this_month".
|
|
298
|
+
today (datetime): The reference date to calculate the period from.
|
|
299
|
+
|
|
300
|
+
Returns:
|
|
301
|
+
tuple[datetime, datetime]: A tuple containing the start and end dates of the specified period.
|
|
302
|
+
"""
|
|
303
|
+
if date_range == "last_week":
|
|
304
|
+
# Calculate the previous Saturday (end of last week)
|
|
305
|
+
days_since_sunday = self._days_since_most_recent_sunday(today)
|
|
306
|
+
last_saturday = today - timedelta(days=days_since_sunday + 1)
|
|
307
|
+
|
|
308
|
+
# Calculate the previous Sunday (start of last week)
|
|
309
|
+
last_sunday = last_saturday - timedelta(days=6)
|
|
310
|
+
|
|
311
|
+
return last_sunday, last_saturday
|
|
312
|
+
|
|
313
|
+
if date_range == "this_week":
|
|
314
|
+
# Calculate the most recent Sunday (start of this week)
|
|
315
|
+
days_since_sunday = self._days_since_most_recent_sunday(today)
|
|
316
|
+
sunday = today - timedelta(days=days_since_sunday)
|
|
317
|
+
|
|
318
|
+
# Calculate the upcoming Saturday (end of this week)
|
|
319
|
+
saturday = sunday + timedelta(days=6)
|
|
320
|
+
|
|
321
|
+
return sunday, saturday
|
|
322
|
+
|
|
323
|
+
if date_range == "last_month":
|
|
324
|
+
first_day = today.replace(day=1)
|
|
325
|
+
last_month = first_day - timedelta(days=1)
|
|
326
|
+
return last_month.replace(day=1), last_month
|
|
327
|
+
|
|
328
|
+
if date_range == "this_month":
|
|
329
|
+
start = today.replace(day=1)
|
|
330
|
+
return start, start + relativedelta(months=1, days=-1)
|
|
331
|
+
|
|
332
|
+
raise ValueError(f"Unknown standard period: {date_range}")
|
|
333
|
+
|
|
334
|
+
def _week_bounds_for(self, date: datetime) -> tuple[datetime, datetime]:
|
|
335
|
+
"""Return the Sunday-to-Saturday week containing the given date.
|
|
336
|
+
|
|
337
|
+
- Sunday is the start of week.
|
|
338
|
+
- Saturday is the end of week.
|
|
339
|
+
|
|
340
|
+
Args:
|
|
341
|
+
date (datetime): The date to find the week bounds for.
|
|
342
|
+
|
|
343
|
+
Returns:
|
|
344
|
+
tuple[datetime, datetime]: A tuple containing the start and end dates of the week.
|
|
345
|
+
"""
|
|
346
|
+
days_since_sunday = self._days_since_most_recent_sunday(date)
|
|
347
|
+
sunday = datetime(date.year, date.month, date.day) - timedelta(days=days_since_sunday)
|
|
348
|
+
saturday = sunday + timedelta(days=6)
|
|
349
|
+
return sunday, saturday
|
|
350
|
+
|
|
351
|
+
def _build_week_splits(self, start_date: datetime, end_date: datetime, fmt: str) -> list[dict[str, str]]:
|
|
352
|
+
"""Build Sunday–Saturday week splits that intersect [start_date, end_date].
|
|
353
|
+
|
|
354
|
+
- Includes partial edge weeks if any day of the Sun–Sat week falls within the requested range.
|
|
355
|
+
- Week entries still use full Sunday–Saturday bounds for consistency.
|
|
356
|
+
- Labels weeks consecutively as week_1, week_2, ...
|
|
357
|
+
|
|
358
|
+
Args:
|
|
359
|
+
start_date (datetime): Start of requested period.
|
|
360
|
+
end_date (datetime): End of requested period.
|
|
361
|
+
fmt (str): Format string for dates.
|
|
362
|
+
|
|
363
|
+
Returns:
|
|
364
|
+
list[dict]: Weekly split entries with start_date, end_date, and human description.
|
|
365
|
+
"""
|
|
366
|
+
weeks: list[dict] = []
|
|
367
|
+
# Anchor on the Sunday of the week that contains start_date
|
|
368
|
+
first_week_sunday, _ = self._week_bounds_for(start_date)
|
|
369
|
+
cursor = first_week_sunday
|
|
370
|
+
idx = 1
|
|
371
|
+
while cursor <= end_date:
|
|
372
|
+
full_week_start = cursor
|
|
373
|
+
full_week_end = cursor + timedelta(days=6)
|
|
374
|
+
# Include any week that intersects the requested range
|
|
375
|
+
if not (full_week_end < start_date or full_week_start > end_date):
|
|
376
|
+
desc = f"From {full_week_start.strftime('%B %d, %Y')} to {full_week_end.strftime('%B %d, %Y')}"
|
|
377
|
+
weeks.append(
|
|
378
|
+
{
|
|
379
|
+
"period": f"week_{idx}",
|
|
380
|
+
"start_date": full_week_start.strftime(fmt),
|
|
381
|
+
"end_date": full_week_end.strftime(fmt),
|
|
382
|
+
"description": desc,
|
|
383
|
+
}
|
|
384
|
+
)
|
|
385
|
+
idx += 1
|
|
386
|
+
cursor += timedelta(days=7)
|
|
387
|
+
return weeks
|
|
388
|
+
|
|
389
|
+
def _get_relative_day_dates(self, date_range: str, today: datetime) -> tuple[datetime, datetime]:
|
|
390
|
+
"""Calculate the start and end dates for a given relative date range.
|
|
391
|
+
|
|
392
|
+
Args:
|
|
393
|
+
date_range (str): The relative date range. Supported values are "yesterday", "today",
|
|
394
|
+
"last_7_days", and "last_30_days".
|
|
395
|
+
today (datetime): The reference date from which the relative date range is calculated.
|
|
396
|
+
|
|
397
|
+
Returns:
|
|
398
|
+
tuple[datetime, datetime]: A tuple containing the start and end dates for the specified
|
|
399
|
+
relative date range.
|
|
400
|
+
"""
|
|
401
|
+
if date_range == "yesterday":
|
|
402
|
+
yesterday = today - timedelta(days=1)
|
|
403
|
+
return yesterday, yesterday
|
|
404
|
+
|
|
405
|
+
if date_range == "today":
|
|
406
|
+
return today, today
|
|
407
|
+
|
|
408
|
+
if date_range == "last_7_days":
|
|
409
|
+
return today - timedelta(days=7), today
|
|
410
|
+
|
|
411
|
+
if date_range == "last_30_days":
|
|
412
|
+
return today - timedelta(days=30), today
|
|
413
|
+
|
|
414
|
+
raise ValueError(f"Unknown relative day range: {date_range}")
|
|
415
|
+
|
|
416
|
+
def _get_year_dates(self, date_range: str, today: datetime) -> tuple[datetime, datetime]:
|
|
417
|
+
"""Returns the start and end dates for the specified year range.
|
|
418
|
+
|
|
419
|
+
Args:
|
|
420
|
+
date_range (str): The year range to calculate dates for.
|
|
421
|
+
Accepted values are "this_year" and "last_year".
|
|
422
|
+
today (datetime): The current date to base the year calculation on.
|
|
423
|
+
|
|
424
|
+
Returns:
|
|
425
|
+
Tuple[datetime, datetime]: A tuple containing the start and end dates for the specified year range.
|
|
426
|
+
"""
|
|
427
|
+
if date_range == "this_year":
|
|
428
|
+
return datetime(today.year, 1, 1), datetime(today.year, 12, 31)
|
|
429
|
+
|
|
430
|
+
if date_range == "last_year":
|
|
431
|
+
return datetime(today.year - 1, 1, 1), datetime(today.year - 1, 12, 31)
|
|
432
|
+
|
|
433
|
+
raise ValueError(f"Unknown year range: {date_range}")
|
|
434
|
+
|
|
435
|
+
def _format_response(
|
|
436
|
+
self,
|
|
437
|
+
start_date: datetime,
|
|
438
|
+
end_date: datetime,
|
|
439
|
+
date_range: str,
|
|
440
|
+
format: str,
|
|
441
|
+
weeks: list[dict] | None = None,
|
|
442
|
+
) -> str:
|
|
443
|
+
"""Format the response as a JSON string with the given date range information.
|
|
444
|
+
|
|
445
|
+
Args:
|
|
446
|
+
start_date (datetime): The start date of the range.
|
|
447
|
+
end_date (datetime): The end date of the range.
|
|
448
|
+
date_range (str): A string representation of the date range.
|
|
449
|
+
format (str): The format string to use for formatting the dates.
|
|
450
|
+
weeks (list[dict] | None): Optional weekly splits (Sunday–Saturday) for the period. This
|
|
451
|
+
is included when `split_weeks` is True and the overall period spans more than one week.
|
|
452
|
+
|
|
453
|
+
Returns:
|
|
454
|
+
str: A JSON string containing the formatted start date, end date, period, and description.
|
|
455
|
+
"""
|
|
456
|
+
if weeks:
|
|
457
|
+
payload: dict = {
|
|
458
|
+
"weeks": weeks,
|
|
459
|
+
"period": date_range,
|
|
460
|
+
"description": f"Split into {len(weeks)} weeks with Sunday-to-Saturday bounds",
|
|
461
|
+
}
|
|
462
|
+
return json.dumps(payload)
|
|
463
|
+
payload: dict = {
|
|
464
|
+
"start_date": start_date.strftime(format),
|
|
465
|
+
"end_date": end_date.strftime(format),
|
|
466
|
+
"period": date_range,
|
|
467
|
+
"description": f"From {start_date.strftime('%B %d, %Y')} to {end_date.strftime('%B %d, %Y')}",
|
|
468
|
+
}
|
|
469
|
+
return json.dumps(payload)
|
|
470
|
+
|
|
471
|
+
def _get_result_and_format(
|
|
472
|
+
self,
|
|
473
|
+
date_range: str,
|
|
474
|
+
format: str,
|
|
475
|
+
split_weeks: bool,
|
|
476
|
+
result: tuple[datetime, datetime],
|
|
477
|
+
) -> str:
|
|
478
|
+
"""Helper to format response with optional week splits.
|
|
479
|
+
|
|
480
|
+
Args:
|
|
481
|
+
date_range (str): The date range string to process.
|
|
482
|
+
format (str): The format string to use for the output.
|
|
483
|
+
split_weeks (bool): If True and the resulting period spans more than one week,
|
|
484
|
+
also include a 'weeks' array with Sunday-to-Saturday splits for the recent weeks.
|
|
485
|
+
result (tuple[datetime, datetime]): A tuple containing the start and end dates of the date range.
|
|
486
|
+
|
|
487
|
+
Returns:
|
|
488
|
+
str: A formatted date range string or an error message if the date range is unsupported or an error occurs.
|
|
489
|
+
"""
|
|
490
|
+
start_date, end_date = result
|
|
491
|
+
weeks = None
|
|
492
|
+
# Only build week splits when requested and range spans more than one week
|
|
493
|
+
if split_weeks and (end_date - start_date).days >= MIN_DAYS_FOR_WEEK_SPLIT:
|
|
494
|
+
weeks = self._build_week_splits(start_date, end_date, format)
|
|
495
|
+
return self._format_response(start_date, end_date, date_range, format, weeks=weeks)
|
|
496
|
+
|
|
497
|
+
def _run(self, date_range: str, format: str = FORMAT_STRING, split_weeks: bool = False) -> str:
|
|
498
|
+
"""Process a given date range string and return a formatted date range string.
|
|
499
|
+
|
|
500
|
+
Args:
|
|
501
|
+
date_range (str): The date range string to process. Supported formats include:
|
|
502
|
+
- "standard_periods"
|
|
503
|
+
- "relative_days"
|
|
504
|
+
- "quarters"
|
|
505
|
+
- "years"
|
|
506
|
+
- "custom" (e.g., "10_days_ago", "2_weeks_ago", "3_months_ago")
|
|
507
|
+
format (str, optional): The format string to use for the output. Defaults to FORMAT_STRING.
|
|
508
|
+
split_weeks (bool, optional): If True and the resulting period spans more than one week,
|
|
509
|
+
also include a 'weeks' array with Sunday-to-Saturday splits for the recent weeks.
|
|
510
|
+
|
|
511
|
+
Returns:
|
|
512
|
+
str: A formatted date range string or an error message if the date range is unsupported or an error occurs.
|
|
513
|
+
"""
|
|
514
|
+
today = datetime.now()
|
|
515
|
+
|
|
516
|
+
try:
|
|
517
|
+
handlers: dict[str, Callable] = {
|
|
518
|
+
"standard_periods": lambda: self._get_standard_period_dates(date_range, today),
|
|
519
|
+
"relative_days": lambda: self._get_relative_day_dates(date_range, today),
|
|
520
|
+
# Only handle special tokens 'this_quarter' and 'last_quarter' here. Natural forms like 'Q1 2025'
|
|
521
|
+
# are parsed later by _parse_month_or_range(). For non-matching inputs, return None so we keep trying.
|
|
522
|
+
"quarters": lambda: (
|
|
523
|
+
self._get_quarter_dates(today, last_quarter=(date_range == "last_quarter"))
|
|
524
|
+
if date_range in {"this_quarter", "last_quarter"}
|
|
525
|
+
else None
|
|
526
|
+
),
|
|
527
|
+
"years": lambda: self._get_year_dates(date_range, today),
|
|
528
|
+
"custom": lambda: (
|
|
529
|
+
self._parse_custom_range(date_range)
|
|
530
|
+
if re.match(r"\d+_(days|weeks|months)_ago", date_range)
|
|
531
|
+
else None
|
|
532
|
+
),
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
for handler in handlers.values():
|
|
536
|
+
try:
|
|
537
|
+
result = handler()
|
|
538
|
+
if result:
|
|
539
|
+
return self._get_result_and_format(date_range, format, split_weeks, result)
|
|
540
|
+
except ValueError:
|
|
541
|
+
continue
|
|
542
|
+
|
|
543
|
+
# Try parsing natural month/quarter expressions
|
|
544
|
+
mr = self._parse_month_or_range(date_range)
|
|
545
|
+
if mr:
|
|
546
|
+
return self._get_result_and_format(date_range, format, split_weeks, mr)
|
|
547
|
+
|
|
548
|
+
return (
|
|
549
|
+
f"Unsupported date range: {date_range}, supported date ranges are: {SUPPORTED_DATE_RANGES} "
|
|
550
|
+
f"or natural month/quarter forms like 'January 2025', 'January-March 2025', 'Q1 2025'."
|
|
551
|
+
)
|
|
552
|
+
|
|
553
|
+
except Exception as e:
|
|
554
|
+
return f"Error processing date range: {str(e)}"
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from _typeshed import Incomplete
|
|
2
|
+
from collections.abc import Callable as Callable
|
|
3
|
+
from langchain_core.tools import BaseTool
|
|
4
|
+
from pydantic import BaseModel
|
|
5
|
+
|
|
6
|
+
FORMAT_STRING: str
|
|
7
|
+
MIN_DAYS_FOR_WEEK_SPLIT: int
|
|
8
|
+
MONTH_NAME_ABBR_THRESHOLD: int
|
|
9
|
+
SUPPORTED_DATE_RANGES: Incomplete
|
|
10
|
+
|
|
11
|
+
class DateRangeToolInput(BaseModel):
|
|
12
|
+
"""Input schema for the DateRangeTool."""
|
|
13
|
+
date_range: str
|
|
14
|
+
format: str
|
|
15
|
+
split_weeks: bool
|
|
16
|
+
|
|
17
|
+
class DateRangeTool(BaseTool):
|
|
18
|
+
"""Tool to get date ranges for common time periods."""
|
|
19
|
+
name: str
|
|
20
|
+
description: str
|
|
21
|
+
args_schema: type[BaseModel]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: aip-agents-binary
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.3
|
|
4
4
|
Summary: A library for managing agents in Gen AI applications.
|
|
5
5
|
Author-email: Raymond Christopher <raymond.christopher@gdplabs.id>
|
|
6
6
|
Requires-Python: <3.13,>=3.11
|
|
@@ -24,6 +24,7 @@ Requires-Dist: langchain-experimental<0.4.0,>=0.3.4
|
|
|
24
24
|
Requires-Dist: langgraph<0.7.0,>=0.6.0
|
|
25
25
|
Requires-Dist: minio<8.0.0,>=7.2.20
|
|
26
26
|
Requires-Dist: pydantic<3.0.0,>=2.11.7
|
|
27
|
+
Requires-Dist: python-dateutil<3.0.0,>=2.9.0
|
|
27
28
|
Requires-Dist: python-dotenv<2.0.0,>=1.1.0
|
|
28
29
|
Requires-Dist: requests<3.0.0,>=2.32.4
|
|
29
30
|
Requires-Dist: uvicorn<0.35.0,>=0.34.3
|
|
@@ -434,10 +434,12 @@ aip_agents/storage/providers/memory.py,sha256=81E9yT_oRkH7tMVr5azZv1oXIaeTzTAtTu
|
|
|
434
434
|
aip_agents/storage/providers/memory.pyi,sha256=r0meeCsjKyE9FHJqCysfQ5ZH_FWRRv2SWg-NOHCUD-g,2109
|
|
435
435
|
aip_agents/storage/providers/object_storage.py,sha256=Q268Sn8Y09gS-ilP6fe4CR0YIOvdbunh3V6SmuQsAVs,6413
|
|
436
436
|
aip_agents/storage/providers/object_storage.pyi,sha256=nBIKJjUAWkIKhYhBFmFcypjoOieIYOghy55f62x7AX8,2878
|
|
437
|
-
aip_agents/tools/__init__.py,sha256=
|
|
438
|
-
aip_agents/tools/__init__.pyi,sha256=
|
|
437
|
+
aip_agents/tools/__init__.py,sha256=ullhWvRVtKYAEO5XhXn1sc1dT1LYDViSc23uqywUwyg,2228
|
|
438
|
+
aip_agents/tools/__init__.pyi,sha256=KmdVTX-WXRChSqCyGrAUoV8TwpUBXlo9ITgx4xpqYx8,1120
|
|
439
439
|
aip_agents/tools/constants.py,sha256=AabnuPQG_mc2sVdr9jV23_6bFempAsxQv3kdCR_ztLA,5723
|
|
440
440
|
aip_agents/tools/constants.pyi,sha256=kFY8dKgRqHKGvPqG3QUyuEc8C5yRaDj7DYQDH88K2T0,3552
|
|
441
|
+
aip_agents/tools/date_range_tool.py,sha256=t0vQqb0du1Q2n4q8V_-TPmfjSuwr4MYRMi6OpXMvVrw,22807
|
|
442
|
+
aip_agents/tools/date_range_tool.pyi,sha256=1Caen334PHliSJOmXpu94kIFP_PPOdOj3dhKqdfMe8o,570
|
|
441
443
|
aip_agents/tools/execute_ptc_code.py,sha256=mN8-G50voxVqWH9r3uJGQOtgPtxsHaTOnJrJuojQLIM,11396
|
|
442
444
|
aip_agents/tools/execute_ptc_code.pyi,sha256=nDlpxV-kcKuNmtghahjXAtjWvtNv6D38x8-VTNCKYjU,4089
|
|
443
445
|
aip_agents/tools/gl_connector_tools.py,sha256=bxl_3VQYZDv3lFn6Y3kDVVRFwH4cntOLz3f74YzDcic,3936
|
|
@@ -606,7 +608,7 @@ aip_agents/utils/pii/pii_helper.py,sha256=g0yRzakfA2AA6vjUNLWHqFlcxyLql6MXQ90NN3
|
|
|
606
608
|
aip_agents/utils/pii/pii_helper.pyi,sha256=dulZs150ikbAL3Bw2YLcz3_g4DsGmL3lciwf8mKxEjI,2939
|
|
607
609
|
aip_agents/utils/pii/uuid_deanonymizer_mapping.py,sha256=Gks8l8t0cuS9pzoQnrpiK1CaLmWYksjOnTeiHh3_7EE,7348
|
|
608
610
|
aip_agents/utils/pii/uuid_deanonymizer_mapping.pyi,sha256=gnWfD1rWZh_tloJjgKiZ6f6iNUuBaHpKqCSiP0d-9bs,3084
|
|
609
|
-
aip_agents_binary-0.6.
|
|
610
|
-
aip_agents_binary-0.6.
|
|
611
|
-
aip_agents_binary-0.6.
|
|
612
|
-
aip_agents_binary-0.6.
|
|
611
|
+
aip_agents_binary-0.6.3.dist-info/METADATA,sha256=7BAliBsUy5k6VWI4QIMhyqul_9tjgYDM4NtsxIr64ak,22416
|
|
612
|
+
aip_agents_binary-0.6.3.dist-info/WHEEL,sha256=KxCTaSkoYs_EnWvWxmau4HAvN-_rCRYV_bfRc_41A9k,106
|
|
613
|
+
aip_agents_binary-0.6.3.dist-info/top_level.txt,sha256=PEz8vcwC1bH4UrkhF0LkIYCNfXGWZUHdSklbvkBe25E,11
|
|
614
|
+
aip_agents_binary-0.6.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|