Add new relative time skills for ' ago' and 'last ' (#927)

### Motivation and Context

The change enables the planner to make use of time references like "last
wednesday" or "three days ago" which currently cause issues. This
enables better use of SequentialPlanner when servicing user intent.
Issue: https://github.com/microsoft/semantic-kernel/issues/922

### Description

This adds two new functions to the core TimeSkill class in both .net and
python.
1. DaysAgo(str days) - takes an argument representing a number of days
and offsets it from the current date to return the resultant date as the
answer.
2. LastMatchingDay(str dayName) - takes the name of a day of the week
and works backwards from yesterday to give it the first matching date of
that day (e.g. "last wednesday"). Currently it only works with English
day names, and this is reflected in the prompt description.

Simple unit tests are added for each function.

Co-authored-by: Devis Lucato <[email protected]>
This commit is contained in:
tim-gill
2023-05-25 15:47:21 -07:00
committed by GitHub
co-authored by Devis Lucato
parent d4a76a2f68
commit d9028ee44f
4 changed files with 167 additions and 0 deletions
@@ -24,6 +24,8 @@ class TimeSkill:
{{time.dayOfWeek}} => Sunday
{{time.hour}} => 9 PM
{{time.hourNumber}} => 21
{{time.days_ago $days}} => Sunday, 7 May, 2023
{{time.last_matching_day $dayName}} => Sunday, 7 May, 2023
{{time.minute}} => 15
{{time.minutes}} => 15
{{time.second}} => 7
@@ -174,6 +176,50 @@ class TimeSkill:
now = datetime.datetime.now()
return now.strftime("%M")
@sk_function(
description="Get the date of offset from today by a provided number of days"
)
def days_ago(self, days: str) -> str:
"""
Get the date a provided number of days in the past
params:
days: The number of days to offset from today
returns:
The date of the offset day.
Example:
SKContext["input"] = "3"
{{time.days_ago $input}} => Sunday, 7 May, 2023
"""
d = datetime.date.today() - datetime.timedelta(days=int(days))
return d.strftime("%A, %d %B, %Y")
@sk_function(
description="""Get the date of the last day matching the supplied week day name in English.
Example: Che giorno era 'Martedi' scorso -> dateMatchingLastDayName 'Tuesday' => Tuesday,
16 May, 2023"""
)
def date_matching_last_day_name(self, day_name: str) -> str:
"""
Get the date of the last day matching the supplied day name
params:
day_name: The day name to match with.
returns:
The date of the matching day.
Example:
SKContext["input"] = "Sunday"
{{time.date_matching_last_day_name $input}} => Sunday, 7 May, 2023
"""
d = datetime.date.today()
for i in range(1, 8):
d = d - datetime.timedelta(days=1)
if d.strftime("%A") == day_name:
return d.strftime("%A, %d %B, %Y")
raise ValueError("day_name is not recognized")
@sk_function(description="Get the seconds on the current minute")
def second(self) -> str:
"""
@@ -5,6 +5,7 @@ import semantic_kernel as sk
from semantic_kernel.core_skills.time_skill import TimeSkill
test_mock_now = datetime.datetime(2031, 1, 12, 12, 24, 56, tzinfo=datetime.timezone.utc)
test_mock_today = datetime.date(2031, 1, 12)
def test_can_be_instantiated():
@@ -33,6 +34,22 @@ def test_now():
assert skill.now() == "Sunday, January 12, 2031 12:24 PM"
def test_days_ago():
skill = TimeSkill()
with mock.patch("datetime.date", wraps=datetime.date) as dt:
dt.today.return_value = test_mock_today
assert skill.days_ago(1) == "Saturday, 11 January, 2031"
def test_date_matching_last_day_name():
skill = TimeSkill()
with mock.patch("datetime.date", wraps=datetime.date) as dt:
dt.today.return_value = test_mock_today
assert skill.date_matching_last_day_name("Friday") == "Friday, 10 January, 2031"
def test_utc_now():
skill = TimeSkill()