Skip to content

Commit 76d778e

Browse files
Fix linter errors and update scheduling logic
1 parent df4aeae commit 76d778e

File tree

3 files changed

+82
-47
lines changed

3 files changed

+82
-47
lines changed

mxgo/prompts/template_prompts.py

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1144,6 +1144,13 @@
11441144
- month (1-12)
11451145
- day of week (0-7, Sunday=0 or 7)
11461146
1147+
**CRITICAL: ONE-TIME vs. RECURRING LOGIC**
1148+
- **For Recurring Tasks** (e.g. "Every day"): Use wildcards for day/month.
1149+
- Example: `0 14 * * *` (Daily at 2 PM UTC)
1150+
- **For ONE-TIME Future Tasks** (e.g. "Tomorrow at 1pm"): You **MUST** calculate the specific Day and Month relative to the current date. **DO NOT** use wildcards `* *`, or it will run every day.
1151+
- BAD: `0 13 * * *` (Runs every day)
1152+
- GOOD: `0 13 16 12 *` (Runs ONLY on Dec 16th at 1pm UTC)
1153+
11471154
**Common Patterns:**
11481155
- **Daily at specific time**: `0 9 * * *` (9 AM daily)
11491156
- **Weekly**: `0 9 * * 1` (9 AM every Monday)
@@ -1712,10 +1719,10 @@
17121719
17131720
# News Search Process
17141721
1715-
## STEP 1: Intent Analysis - Recurring vs. One-Time
1716-
- **Analyze the user's request**: Does the user ask for news "every day", "weekly", "every hour", or use other recurring language?
1717-
- **If recurring**: Your goal is to use the `scheduled_tasks` tool. You must determine the correct cron expression and the `distilled_future_task_instructions`. Proceed to the execution step for recurring tasks.
1718-
- **If one-time**: Your goal is to use the `news_search` tool to get the news immediately. Proceed with the query analysis and search strategy below.
1722+
## STEP 1: Intent Analysis - Immediate vs. Future Execution
1723+
- **Analyze the user's request**: Does the user want news *now* (e.g., "what's the latest," "give me news from the past day") or at a *future* time (e.g., "tomorrow morning," "every Friday at 5pm," "in 3 hours")?
1724+
- **If for a future time (either one-time or recurring)**: Your goal is to use the `scheduled_tasks` tool. You must determine the correct cron expression.
1725+
- **If for now**: Your goal is to use the `news_search` tool to get the news immediately.
17191726
17201727
## STEP 2: Query Analysis & Optimization
17211728
- **Understand the request**: Identify specific topics, companies, regions, or themes
@@ -1725,15 +1732,18 @@
17251732
17261733
## STEP 3: News Search Strategy
17271734
1728-
### For One-Time News Requests:
1735+
### For Immediate News Requests:
17291736
- **Topic-specific searches**: Use the news_search tool with targeted queries
17301737
- **Time filtering**: Apply appropriate freshness filters (pd=past day, pw=past week, pm=past month, py=past year)
17311738
- **Multiple perspectives**: Search for different angles or related topics if needed
17321739
- **Source diversity**: Ensure variety in news sources and perspectives
17331740
- After getting the results, proceed to Step 4 for analysis and synthesis.
17341741
1735-
### For Recurring News Requests:
1742+
### For Future News Requests (One-Time or Recurring):
17361743
- **CRITICAL**: You must convert any user-specified time and timezone (e.g., 10pm IST) to a UTC-based cron expression.
1744+
- **CRON LOGIC**:
1745+
- If **Recurring** (e.g. "Every day"): Use `* *` for day/month.
1746+
- If **One-Time Future** (e.g. "Tomorrow"): You **MUST** use the specific Day and Month integers (e.g., `15 12` for Dec 15th). Do NOT use `* *`.
17371747
- Use the `scheduled_tasks` tool with the following parameters:
17381748
- `cron_expression`: The generated UTC cron expression.
17391749
- `distilled_future_task_instructions`: A clear command for the future task, e.g., "Search for the latest news about [topic] and send a summary."

mxgo/routed_litellm_model.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@
44
from typing import Any
55

66
from dotenv import load_dotenv
7+
from smolagents import ChatMessage, LiteLLMRouterModel, Tool
78

89
import mxgo.schemas
910
from mxgo import exceptions
1011
from mxgo._logging import get_logger
1112
from mxgo.schemas import ProcessingInstructions
12-
from smolagents import ChatMessage, LiteLLMRouterModel, Tool
1313

1414
load_dotenv()
1515

mxgo/tools/scheduled_tasks_tool.py

Lines changed: 65 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,63 @@ def __init__(self, context: RequestContext):
220220
# Create dedicated scheduling instance for this tool
221221
self.scheduler = Scheduler()
222222

223+
def _parse_and_validate_times(
224+
self, start_time_str: str | None, end_time_str: str | None, task_description: str
225+
) -> tuple[datetime | None, datetime | None, dict | None]:
226+
"""
227+
Parse start/end times and validate that start is before end.
228+
"""
229+
parsed_start_time = None
230+
parsed_end_time = None
231+
232+
if start_time_str:
233+
try:
234+
parsed_start_time = datetime.fromisoformat(start_time_str)
235+
parsed_start_time = round_to_nearest_minute(parsed_start_time)
236+
except Exception as e:
237+
logger.warning(f"Could not parse start_time: {e}")
238+
239+
if end_time_str:
240+
try:
241+
parsed_start_time = datetime.fromisoformat(end_time_str)
242+
parsed_start_time = round_to_nearest_minute(parsed_start_time)
243+
except Exception as e:
244+
logger.warning(f"Could not parse start_time: {e}")
245+
246+
# Validate that start_time is before end_time if both are provided
247+
if parsed_start_time and parsed_end_time and parsed_start_time >= parsed_end_time:
248+
return (
249+
None,
250+
None,
251+
{
252+
"success": False,
253+
"error": "Invalid time range",
254+
"message": "start_time must be before end_time",
255+
"task_description": task_description,
256+
},
257+
)
258+
259+
return parsed_start_time, parsed_end_time, None
260+
261+
def _configure_email_request(self, email_request, input_data: ScheduledTaskInput) -> None:
262+
"""
263+
Configure email request with task instructions and alias.
264+
"""
265+
email_request.distilled_processing_instructions = input_data.distilled_future_task_instructions
266+
email_request.task_description = input_data.distilled_future_task_instructions
267+
268+
if input_data.future_handle_alias:
269+
try:
270+
email_request.distilled_alias = HandlerAlias(input_data.future_handle_alias)
271+
logger.info(f"Using specified handle alias: {input_data.future_handle_alias}")
272+
except ValueError:
273+
logger.warning(f"Invalid handle alias '{input_data.future_handle_alias}', defaulting to ASK")
274+
email_request.distilled_alias = HandlerAlias.ASK
275+
else:
276+
email_request.distilled_alias = HandlerAlias.ASK
277+
278+
email_request.parent_message_id = email_request.messageId
279+
223280
def forward(
224281
self,
225282
cron_expression: str,
@@ -277,38 +334,18 @@ def forward(
277334
time_until_execution = next_execution_time - datetime.now(timezone.utc)
278335
logger.info(f"One-time task will execute at: {next_execution_time} (in {time_until_execution})")
279336

337+
# Parse and validate times (Refactored to helper)
338+
parsed_start_time, parsed_end_time, time_error = self._parse_and_validate_times(
339+
input_data.start_time, input_data.end_time, distilled_future_task_instructions
340+
)
341+
if time_error:
342+
return time_error
343+
280344
db_connection = init_db_connection()
281345
# Generate unique task ID
282346
task_id = str(uuid.uuid4())
283347
email_id = email_request.from_email
284348

285-
# Parse start_time and end_time if provided
286-
parsed_start_time = None
287-
parsed_end_time = None
288-
289-
if input_data.start_time:
290-
try:
291-
parsed_start_time = datetime.fromisoformat(input_data.start_time)
292-
parsed_start_time = round_to_nearest_minute(parsed_start_time)
293-
except Exception as e:
294-
logger.warning(f"Could not parse start_time: {e}")
295-
296-
if input_data.end_time:
297-
try:
298-
parsed_end_time = datetime.fromisoformat(input_data.end_time)
299-
parsed_end_time = round_to_nearest_minute(parsed_end_time)
300-
except Exception as e:
301-
logger.warning(f"Could not parse end_time: {e}")
302-
303-
# Validate that start_time is before end_time if both are provided
304-
if parsed_start_time and parsed_end_time and parsed_start_time >= parsed_end_time:
305-
return {
306-
"success": False,
307-
"error": "Invalid time range",
308-
"message": "start_time must be before end_time",
309-
"task_description": distilled_future_task_instructions,
310-
}
311-
312349
# Calculate next execution time from cron expression
313350
cron_iter = croniter(input_data.cron_expression, datetime.now(timezone.utc))
314351
next_execution = round_to_nearest_minute(datetime.fromtimestamp(cron_iter.get_next(), tz=timezone.utc))
@@ -317,19 +354,7 @@ def forward(
317354
scheduler_job_id = f"task_{task_id}"
318355

319356
# Save distilled instructions and task description to email request
320-
email_request.distilled_processing_instructions = input_data.distilled_future_task_instructions
321-
email_request.task_description = distilled_future_task_instructions
322-
if input_data.future_handle_alias:
323-
try:
324-
email_request.distilled_alias = HandlerAlias(input_data.future_handle_alias)
325-
logger.info(f"Using specified handle alias: {input_data.future_handle_alias}")
326-
except ValueError:
327-
logger.warning(f"Invalid handle alias '{input_data.future_handle_alias}', defaulting to ASK")
328-
email_request.distilled_alias = HandlerAlias.ASK
329-
else:
330-
email_request.distilled_alias = HandlerAlias.ASK
331-
332-
email_request.parent_message_id = email_request.messageId
357+
self._configure_email_request(email_request, input_data)
333358

334359
# Store task in database using CRUD
335360
try:

0 commit comments

Comments
 (0)