File size: 4,872 Bytes
c315e77
 
 
257b532
 
c315e77
257b532
c315e77
257b532
5f83ea5
257b532
 
c315e77
257b532
c315e77
257b532
c315e77
257b532
 
c315e77
 
 
 
 
 
 
 
 
257b532
c315e77
 
257b532
 
c315e77
257b532
 
c315e77
 
 
 
 
 
 
 
 
 
 
 
 
 
 
257b532
c315e77
 
 
257b532
 
 
 
c315e77
 
 
257b532
 
c315e77
257b532
c315e77
257b532
 
 
c315e77
 
257b532
 
c315e77
257b532
 
 
 
 
c315e77
 
 
257b532
c315e77
 
 
 
257b532
c315e77
 
 
 
 
 
 
 
 
 
 
 
 
 
257b532
c315e77
 
 
 
 
 
 
 
 
257b532
c315e77
 
 
 
 
 
 
 
257b532
c315e77
 
 
 
 
 
 
 
 
 
257b532
c315e77
 
 
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
# app.py
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
import os, re, json
from datetime import datetime, timedelta
from typing import Optional, Dict, Any
from groq import Groq

# Initialize Groq client using environment variable
GROQ_KEY = "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"
if not GROQ_KEY:
    raise RuntimeError("Missing GROQ_API_KEY. Set it as an environment variable or in Hugging Face Secrets.")

client = Groq(api_key=GROQ_KEY)

app = FastAPI(title="Task Parsing API (Groq-powered)")

# -------------------- PROMPTS --------------------
llm_task_create_agent_system_prompt = f"""
You are a task creation agent. Extract the following from user input:

- Task Name (Text)
- Task Priority (High, Medium, Low)
- Task Deadline (STRICTLY 'YYYY-MM-DD HH:MM:SS')
- Task Status (Pending, Completed)
- Task Type (Work, Health, Personal)
- Date Created (If not given, use current time)

⚠️ ALL datetime values must follow 'YYYY-MM-DD HH:MM:SS'. NO natural language like 'tomorrow' or 'evening'.

Output JUST a JSON object. Example:
{{"Task Name": "Submit report", "Task Priority": "High", "Task Deadline": "2025-07-17 18:00:00", "Task Status": "Pending", "Task Type": "Work", "Date Created": "2025-07-01 12:00:00"}}
"""

# -------------------- HELPERS --------------------
def extract_json_from_response(text: str):
    match = re.search(r"\{[\s\S]*\}", text)
    if not match:
        return None
    try:
        return json.loads(match.group())
    except json.JSONDecodeError:
        return None

DATETIME_REGEX = r"\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}"

def validate_datetime_format(dt_str: str) -> bool:
    if re.fullmatch(DATETIME_REGEX, dt_str):
        try:
            datetime.strptime(dt_str, "%Y-%m-%d %H:%M:%S")
            return True
        except:
            return False
    return False

# -------------------- GROQ CALLER --------------------
def call_groq_system(system_prompt: str, user_message: str, model: str = "llama-3.3-70b-versatile"):
    """Generic wrapper to call Groq chat API."""
    chat_completion = client.chat.completions.create(
        model=model,
        messages=[
            {"role": "system", "content": system_prompt},
            {"role": "user", "content": user_message},
        ],
    )
    return chat_completion.choices[0].message.content.strip()

# -------------------- AGENTS --------------------
def run_task_creation_agent(nl_task: str):
    reply = call_groq_system(llm_task_create_agent_system_prompt, nl_task)
    parsed = extract_json_from_response(reply)
    if not parsed:
        raise ValueError(f"LLM did not return valid JSON. Raw:\n{reply}")

    if not validate_datetime_format(parsed.get("Task Deadline", "")):
        raise ValueError(f"Invalid 'Task Deadline': {parsed.get('Task Deadline')}")

    if not validate_datetime_format(parsed.get("Date Created", "")):
        parsed["Date Created"] = datetime.now().strftime("%Y-%m-%d %H:%M:%S")

    parsed.setdefault("Task Status", "Pending")
    parsed.setdefault("Task Priority", "Medium")
    parsed.setdefault("Task Type", "Work")

    return parsed


def scheduler_agent(task_json: Dict[str, Any]) -> str:
    """Simplified scheduler — returns the deadline as the scheduled time."""
    deadline = task_json.get("Task Deadline")
    if not validate_datetime_format(deadline):
        raise ValueError("Scheduler requires a valid Task Deadline.")
    return deadline


def reminder_agent(task_json: Dict[str, Any], scheduled_time_str: str):
    scheduled = datetime.strptime(scheduled_time_str, "%Y-%m-%d %H:%M:%S")
    priority = (task_json.get("Task Priority") or "Medium").lower()

    if priority == "high":
        offsets = [timedelta(hours=24), timedelta(hours=1)]
    elif priority == "low":
        offsets = [timedelta(days=1), timedelta(days=3)]
    else:
        offsets = [timedelta(hours=12), timedelta(hours=1)]

    reminders = []
    for off in offsets:
        r = scheduled - off
        if r > datetime.now():
            reminders.append(r.strftime("%Y-%m-%d %H:%M:%S"))
    return reminders

# -------------------- MODELS --------------------
class ParseRequest(BaseModel):
    text: str

class ParseResponse(BaseModel):
    task_json: Dict[str, Any]
    scheduled_time: str
    reminders: list

# -------------------- ENDPOINTS --------------------
@app.post("/parse-task", response_model=ParseResponse)
def parse_task(req: ParseRequest):
    try:
        task_json = run_task_creation_agent(req.text)
        scheduled_time = scheduler_agent(task_json)
        reminders = reminder_agent(task_json, scheduled_time)
        return ParseResponse(task_json=task_json, scheduled_time=scheduled_time, reminders=reminders)
    except Exception as e:
        raise HTTPException(status_code=400, detail=str(e))


@app.get("/health")
def health():
    return {"status": "ok"}