|
import json |
|
import os |
|
from datetime import datetime |
|
from langchain_anthropic import ChatAnthropic |
|
from langchain_core.messages import HumanMessage, SystemMessage |
|
from dotenv import load_dotenv |
|
from rich import print |
|
from rich.pretty import pprint |
|
|
|
def month_to_season(month: str) -> str: |
|
if month in ["December", "January", "February"]: |
|
return "Winter" |
|
elif month in ["March", "April", "May"]: |
|
return "Spring" |
|
elif month in ["June", "July", "August"]: |
|
return "Summer" |
|
elif month in ["September", "October", "November"]: |
|
return "Fall" |
|
else: |
|
raise ValueError(f"Invalid month: {month}") |
|
|
|
def parse_natural_time(text: str) -> datetime: |
|
""" |
|
Convert natural language time expressions into datetime objects using LLM. |
|
|
|
Args: |
|
text (str): Natural language time expression (e.g. "next Tuesday", "in 2 weeks", etc) |
|
|
|
Returns: |
|
datetime: Parsed datetime object |
|
""" |
|
# Load environment variables |
|
load_dotenv() |
|
|
|
# Initialize ChatAnthropic |
|
llm = ChatAnthropic( |
|
anthropic_api_key=os.getenv("ANTHROPIC_API_KEY"), |
|
model="claude-3-5-sonnet-20241022", |
|
temperature=0.1 |
|
) |
|
|
|
current_date = datetime.now().strftime("%Y-%m-%d") |
|
print(f"\n[green]Current date:[/green] {current_date}") |
|
current_day_of_week = datetime.now().strftime("%A") |
|
print(f"\n[green]Current day of the week:[/green] {current_day_of_week}") |
|
current_season = month_to_season(datetime.now().strftime("%B")) |
|
print(f"\n[green]Current season:[/green] {current_season}") |
|
|
|
# Create system message |
|
system_msg = f''' |
|
Here's the adjusted prompt to include the desired JSON structure: |
|
|
|
You are a time parsing assistant. Convert natural language time expressions into specific ISO format datetime strings (YYYY-MM-DD). |
|
Always return a JSON object containing the following fields: |
|
|
|
- **datetime**: The ISO format datetime string (YYYY-MM-DD). |
|
- **is_time_related**: A boolean indicating whether the input is related to time. |
|
- **reason**: A string explaining your reasoning behind the datetime or why it is not time-related. |
|
- **is_vague**: A boolean indicating if the input is too vague to determine an exact datetime. |
|
`is_vague` is NOT whether the user uses vague language but rather when you cannot determine a specific date without guessing. |
|
|
|
|
|
|
|
Use the current time as a reference point. |
|
|
|
The current date is: {current_date} |
|
The current day of the week is: {current_day_of_week} |
|
The current season is: {current_season} |
|
|
|
Seasons are as follows: |
|
- Winter: December, January, February |
|
- Spring: March, April, May |
|
- Summer: June, July, August |
|
- Fall: September, October, November |
|
|
|
Word Translation: |
|
- "few" means 3-4 |
|
- "several" means 5-6 |
|
- "a while" means 6 months |
|
- "a couple" means 2-3 |
|
|
|
Guidelines: |
|
- Never return a date in the past (before {current_date}). |
|
- If the input is not time-related, or if it's too vague, populate **is_time_related** as false and/or **is_vague** as true and provide the appropriate **reason** field. |
|
- If the input is an emoji or has emojis, try to see if the emoji is related to time, holidays, or seasons. |
|
- Don't worry about existing holidays or weekends; just follow the user's mention of time closely. |
|
- output format is raw JSON, no other text, no markdown, no code blocks, etc. |
|
|
|
Example 1: |
|
User Prompt: "next Tuesday" |
|
With today's date of 2024-12-05 and current day of the week being Thursday |
|
Output: |
|
{{ |
|
"datetime": "2024-12-10", |
|
"is_time_related": true, |
|
"reason": "User specified a specific day of the week relative to the current date.", |
|
"is_vague": false |
|
}} |
|
|
|
Example 2: |
|
User Prompt: "a few months" |
|
With today's date of 2023-03-15 |
|
Output: |
|
{{ |
|
"datetime": "2023-06-15", |
|
"is_time_related": true, |
|
"reason": "User specified 'a few months,' interpreted as 3-4 months from the current date.", |
|
"is_vague": true |
|
}} |
|
|
|
Example 3: |
|
User Prompt: "someday" |
|
With today's date of 2023-03-15 |
|
Output: |
|
{{ |
|
"datetime": null, |
|
"is_time_related": false, |
|
"reason": "The input is too vague to determine a specific datetime.", |
|
"is_vague": true |
|
}} |
|
|
|
Example 4: |
|
User Prompt: "hey great talking to you. how's it going?" |
|
With today's date of 2024-12-05 |
|
Output: |
|
{{ |
|
"datetime": null, |
|
"is_time_related": false, |
|
"reason": "The input is irrelevant to time.", |
|
"is_vague": true |
|
}} |
|
'''.strip() |
|
|
|
messages = [ |
|
SystemMessage(content=system_msg), |
|
HumanMessage(content=text) |
|
] |
|
|
|
# Get response from LLM |
|
response = llm.invoke(messages) |
|
|
|
# Parse the response into structured output |
|
try: |
|
# Extract JSON from response |
|
json_str = response.content.strip() |
|
print(f"\n[blue]LLM Response:[/blue] {json_str}") # Debugging line to print the raw response |
|
result = json.loads(json_str) |
|
|
|
# Return parsed result dictionary |
|
return { |
|
"datetime": datetime.fromisoformat(result["datetime"]) if result["datetime"] else None, |
|
"is_time_related": result["is_time_related"], |
|
"reason": result["reason"], |
|
"is_vague": result["is_vague"] |
|
} |
|
except (ValueError, json.JSONDecodeError) as e: |
|
print(f"\n[red]Error parsing JSON:[/red] {str(e)}") # More specific error message |
|
raise ValueError(f"Could not parse LLM response: {response.content}") from e |
|
|
|
if __name__ == "__main__": |
|
import argparse |
|
|
|
# Set up argument parser |
|
parser = argparse.ArgumentParser(description='Parse natural language time expressions into datetime objects.') |
|
parser.add_argument('time_expression', type=str, help='Natural language time expression (e.g. "next Tuesday", "in 2 weeks")') |
|
|
|
# Parse arguments |
|
args = parser.parse_args() |
|
|
|
try: |
|
# Parse the time expression |
|
result = parse_natural_time(args.time_expression) |
|
pprint(result) |
|
except Exception as e: |
|
print(f"\n[red]Error:[/red] {str(e)}") |
tweeted about it
"maybe next year"
Christmas version "🎄"