Spaces:
Runtime error
Runtime error
File size: 8,023 Bytes
465fb49 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 |
from agentpro.tools import Tool
from typing import Any, List, Tuple, Optional
import datetime
import dateutil.parser
import re
class WeeklyEventSummaryTool(Tool):
"""
Tool to provide a narrative summary of Google Calendar events for an entire week.
Users can ask for βthis week,β βnext week,β βlast week,β or specify a date (e.g., βweek of June 10, 2025β).
The tool will gather events from Monday through Sunday of the chosen week and return a day-by-day narrative.
"""
# ββ 1) Class attributes βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
name: str = "Weekly Event Summary"
description: str = (
"Provide a narrative summary of Google Calendar events for any requested week. "
"You can ask for:\n"
" β’ βMy events this week.β\n"
" β’ βShow my schedule next week.β\n"
" β’ βWeekly summary for last week.β\n"
" β’ βWhat do I have the week of June 10, 2025?β\n"
" β’ βEvents for week of 2025-06-10.β\n"
"If no explicit week is mentioned, defaults to this week (MondayβSunday)."
)
action_type: str = "weekly_event_summary"
input_format: str = (
"Natural-language calendar query specifying a week. Examples:\n"
" β’ βWhatβs on my calendar this week?β\n"
" β’ βShow me my schedule next week.β\n"
" β’ βWeekly summary for last week.β\n"
" β’ βEvents for the week of June 10, 2025.β\n"
" β’ βWeek of 2025-06-10.β"
)
# ββ 2) We expect a Google Calendar βserviceβ to be passed in at instantiation βββββββββββ
service: Any
# ββ 3) Main entry point βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
def run(self, input_text: Any) -> str:
"""
Parse a natural-language weekly summary command, identify target week, and return a narrative summary.
"""
text = str(input_text).strip().lower()
# 1) Determine which week the user means
week_start = self._resolve_week_start(text)
if week_start is None:
return (
"Sorry, I couldn't determine which week you meant. "
"Please ask for 'this week', 'next week', 'last week', or 'week of <date>'."
)
# 2) Build a list of dates from Monday through Sunday
dates = [week_start + datetime.timedelta(days=i) for i in range(7)]
week_end = dates[-1]
# 3) For each day in the week, fetch events and build narrative parts
narrative_parts: List[str] = []
for day in dates:
events = self._fetch_events_on_date(day)
day_str = day.strftime("%A, %B %d, %Y")
if not events:
narrative_parts.append(f"On {day_str}, you have no events scheduled.")
else:
# Build a single sentence listing each eventβs start time, end time, and title
sentences: List[str] = []
for idx, ev in enumerate(events):
start_raw = ev["start"].get("dateTime")
end_raw = ev["end"].get("dateTime")
summary = ev.get("summary", "(no title)")
if start_raw and end_raw:
start_dt = datetime.datetime.fromisoformat(start_raw.replace("Z", "+00:00"))
end_dt = datetime.datetime.fromisoformat(end_raw.replace("Z", "+00:00"))
start_str = start_dt.strftime("%I:%M %p").lstrip("0")
end_str = end_dt.strftime("%I:%M %p").lstrip("0")
sentences.append(f"β{summary}β from {start_str} to {end_str}")
else:
# Should not happen for non-all-day events since we filter them
sentences.append(f"β{summary}β (all-day)")
# Join individual event descriptions with β; β
day_events_str = "; ".join(sentences)
narrative_parts.append(f"On {day_str}, you have: {day_events_str}.")
# 4) Combine into one multiline narrative
week_range_str = f"{week_start.strftime('%B %d, %Y')} to {week_end.strftime('%B %d, %Y')}"
header = f"Weekly summary for {week_range_str}:"
body = " ".join(narrative_parts)
return f"{header}\n\n{body}"
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
def _resolve_week_start(self, text: str) -> Optional[datetime.date]:
"""
Determine the Monday (week_start) of the requested week.
Supports 'this week', 'next week', 'last week', or 'week of <date>'.
If no keyword found, defaults to this week.
"""
today = datetime.date.today()
weekday = today.weekday() # Monday=0 ... Sunday=6
monday_this_week = today - datetime.timedelta(days=weekday)
# 1) Check for 'this week'
if "this week" in text:
return monday_this_week
# 2) 'next week'
if "next week" in text:
return monday_this_week + datetime.timedelta(days=7)
# 3) 'last week'
if "last week" in text:
return monday_this_week - datetime.timedelta(days=7)
# 4) 'week of <date>'
# Look for a date substring to parse
# e.g., "week of june 10, 2025" or "week of 2025-06-10"
match = re.search(
r"week\s+of\s+(.+)", text
)
if match:
date_part = match.group(1).strip()
try:
parsed = dateutil.parser.parse(date_part, fuzzy=True)
target_date = parsed.date()
# Find Monday of that week
wd = target_date.weekday()
return target_date - datetime.timedelta(days=wd)
except (ValueError, OverflowError):
return None
# 5) If no explicit keyword, default to this week
return monday_this_week
def _fetch_events_on_date(self, date_obj: datetime.date) -> List[dict]:
"""
Fetch all non-all-day events on the provided date (UTC midnight β next midnight).
Returns a list of event dicts (as returned by Google Calendar API), sorted by start time.
"""
start_of_day = datetime.datetime.combine(date_obj, datetime.time.min).isoformat() + "Z"
end_of_day = (datetime.datetime.combine(date_obj, datetime.time.min)
+ datetime.timedelta(days=1)).isoformat() + "Z"
events_res = (
self.service.events()
.list(
calendarId="primary",
timeMin=start_of_day,
timeMax=end_of_day,
singleEvents=True,
orderBy="startTime"
)
.execute()
)
items = events_res.get("items", [])
# Filter out all-day events (they have 'start.date' instead of 'start.dateTime')
non_all_day = [ev for ev in items if ev.get("start", {}).get("dateTime")]
return sorted(
non_all_day,
key=lambda ev: datetime.datetime.fromisoformat(ev["start"]["dateTime"].replace("Z", "+00:00"))
)
|