Implement email alert system with WhatsApp notifications
- Added alerts processing logic in src/alerts.py to analyze threads and send WhatsApp alerts based on configured time frames. - Created FastAPI application in src/app.py to manage threads, display configurations, and trigger alert processing. - Developed database models and utility functions in src/database.py for managing threads and messages. - Integrated Twilio API for sending WhatsApp messages in src/whatsapp_sender.py. - Implemented Zoho email client in src/zoho_client.py to fetch emails and check for replies. - Added configuration management for email settings and alert parameters. - Established auto-processing loop for periodic email syncing and alert generation.
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
import json
|
||||
import os
|
||||
from typing import Dict, List
|
||||
|
||||
from groq import Groq
|
||||
|
||||
|
||||
def _format_messages_for_context(messages: List[dict]) -> str:
|
||||
lines = []
|
||||
for m in messages:
|
||||
direction = "IN" if m.get("is_incoming", True) else "OUT"
|
||||
date = m.get("date_sent")
|
||||
subj = m.get("subject") or ""
|
||||
from_email = m.get("from_email") or ""
|
||||
to_email = m.get("to_email") or ""
|
||||
body = (m.get("body") or "").strip()
|
||||
if len(body) > 1000:
|
||||
body = body[:1000] + "..."
|
||||
lines.append(
|
||||
f"[{date}] [{direction}] {from_email} -> {to_email}\nSubject: {subj}\n{body}"
|
||||
)
|
||||
return "\n\n---\n\n".join(lines)
|
||||
|
||||
|
||||
def _heuristic_analyze(messages: List[dict]) -> Dict:
|
||||
# Simple fallback if Groq isn't available
|
||||
body_concat = "\n\n".join([(m.get("body") or "") for m in messages[-4:]])
|
||||
question_like = "?" in body_concat or any(
|
||||
kw in body_concat.lower()
|
||||
for kw in ["could you", "can you", "please", "let me know", "need", "request"]
|
||||
)
|
||||
last_subj = (messages[-1].get("subject") or "") if messages else ""
|
||||
return {
|
||||
"actionable": bool(question_like),
|
||||
"summary": (body_concat[:350] + "...")
|
||||
if len(body_concat) > 350
|
||||
else body_concat,
|
||||
"subject": last_subj,
|
||||
"confidence": 0.35,
|
||||
"model": "heuristic",
|
||||
}
|
||||
|
||||
|
||||
def analyze_thread(
|
||||
thread_subject: str, messages: List[dict], max_messages: int = 4
|
||||
) -> Dict:
|
||||
"""
|
||||
Analyze a thread using Groq LLM. Returns dict with keys:
|
||||
- actionable: bool
|
||||
- summary: str
|
||||
- subject: str
|
||||
- confidence: float (0..1)
|
||||
- model: str
|
||||
Gracefully falls back to a heuristic when GROQ_API_KEY is missing or calls fail.
|
||||
"""
|
||||
msgs = messages[-max_messages:] if max_messages else messages
|
||||
|
||||
api_key = os.getenv("GROQ_API_KEY")
|
||||
if not api_key:
|
||||
return _heuristic_analyze(msgs)
|
||||
|
||||
client = Groq(api_key=api_key)
|
||||
|
||||
system_prompt = (
|
||||
"You are a helpful assistant that triages email threads and writes concise summaries. "
|
||||
"Decide if the thread requires a reply from our side now, based on the last few messages. "
|
||||
"Ignore newsletters/automations (e.g., from no-reply), and focus on whether there's a clear question or request. "
|
||||
"Return a strict JSON object with keys: actionable (true/false), summary (<= 80 words), confidence (0..1)."
|
||||
)
|
||||
|
||||
user_prompt = (
|
||||
f"Thread subject: {thread_subject or ''}\n\n"
|
||||
"Recent messages (oldest to newest):\n\n"
|
||||
f"{_format_messages_for_context(msgs)}\n\n"
|
||||
"Respond with only JSON, no extra commentary."
|
||||
)
|
||||
|
||||
try:
|
||||
completion = client.chat.completions.create(
|
||||
model=os.getenv("GROQ_MODEL", "llama-3.1-70b-versatile"),
|
||||
messages=[
|
||||
{"role": "system", "content": system_prompt},
|
||||
{"role": "user", "content": user_prompt},
|
||||
],
|
||||
temperature=0.2,
|
||||
max_tokens=300,
|
||||
)
|
||||
content = completion.choices[0].message.content.strip()
|
||||
# Attempt to extract JSON
|
||||
data = json.loads(content)
|
||||
data.setdefault("subject", thread_subject or "")
|
||||
data.setdefault("model", os.getenv("GROQ_MODEL", "llama-3.1-70b-versatile"))
|
||||
# Basic validation
|
||||
if not isinstance(data.get("actionable"), bool) or not isinstance(
|
||||
data.get("summary"), str
|
||||
):
|
||||
raise ValueError("Invalid schema from model")
|
||||
return data
|
||||
except Exception:
|
||||
# Fallback to heuristic
|
||||
return _heuristic_analyze(msgs)
|
||||
Reference in New Issue
Block a user