> loading_
# src/ai_insights.py — New module for AI-powered health insight generation
#
# This walkthrough builds the full feature end-to-end.
# Drop this file into your src/ directory and wire it into monitor.py.
import os
import json
from typing import Optional
# ------------------------------------------------------------------
# Step 1: Define the system prompt. This is the "expertise layer."
# We keep it tight — every token costs money on an hourly cron.
# The prompt tells the model its role, the exact output structure
# we expect, and constraints (no medical diagnoses, keep it concise).
# ------------------------------------------------------------------
SYSTEM_PROMPT = """You are a personal wellness analyst. You receive a JSON snapshot
containing Oura Ring biometrics (readiness, sleep, activity, stress, resilience,
heart_rate, spo2) and any active SirenWise environmental/safety alerts.
Return EXACTLY three sections in plain text:
1. **Health Summary** — One to two sentences contextualizing the numeric scores
in everyday language. Example: "Your readiness is low at 62, likely due to
poor sleep quality and an elevated resting heart rate."
2. **Correlation Insights** — Note any meaningful connections between biometric
values AND active safety alerts. If no correlations exist, say so briefly.
Example: "Elevated stress score coincides with an active air-quality alert
in your area."
3. **Recommendations** — Two to three actionable suggestions based on the data.
Example: "Consider lighter activity today; ensure hydration; review the
active alert details."
Constraints:
- Do NOT provide medical diagnoses or prescribe treatment.
- Be concise — the user reads this on a phone via Telegram.
- Total response must be under 200 words."""
# ------------------------------------------------------------------
# Step 2: Build the user message from the snapshot dict.
# We serialize only the fields the model needs — no metadata, no
# timestamps, no delivery config. Token efficiency matters here.
# ------------------------------------------------------------------
def _build_user_message(snapshot: dict) -> str:
# Extract only the biometric and alert fields
relevant_keys = [
"readiness", "sleep", "activity", "stress", "resilience",
"heart_rate", "spo2", "sirenwise_alerts"
]
filtered = {k: snapshot[k] for k in relevant_keys if k in snapshot}
return (
"Here is my latest health snapshot. "
"Please analyze it according to your instructions:\n\n"
f"```json\n{json.dumps(filtered, indent=2)}\n```"
)
# ------------------------------------------------------------------
# Step 3: Call the LLM. We support two backends:
# - OpenAI GPT-4o-mini (default, via the openai SDK)
# - Local model via ollama (fallback / offline / privacy-first)
#
# The backend is selected via the JEEVES_LLM_BACKEND env var.
# ------------------------------------------------------------------
def _call_openai(system: str, user: str) -> str:
# Requires: pip install openai
from openai import OpenAI
client = OpenAI(api_key=os.environ["OPENAI_API_KEY"])
response = client.chat.completions.create(
model="gpt-4o-mini", # cheap, fast, good enough
messages=[
{"role": "system", "content": system},
{"role": "user", "content": user},
],
max_tokens=300, # hard cap — keeps costs predictable
temperature=0.3, # low creativity, high consistency
)
return response.choices[0].message.content.strip()
def _call_ollama(system: str, user: str) -> str:
# Requires: ollama running locally with a pulled model
import requests
model = os.environ.get("OLLAMA_MODEL", "mistral") # sensible default
url = os.environ.get("OLLAMA_URL", "http://localhost:11434")
resp = requests.post(
f"{url}/api/chat",
json={
"model": model,
"messages": [
{"role": "system", "content": system},
{"role": "user", "content": user},
],
"stream": False,
"options": {"temperature": 0.3, "num_predict": 300},
},
timeout=30,
)
resp.raise_for_status()
return resp.json()["message"]["content"].strip()
# ------------------------------------------------------------------
# Step 4: The public function. This is what monitor.py calls.
# It orchestrates prompt construction → LLM call → return narrative.
# If the LLM call fails for any reason, we return a graceful
# fallback so the raw snapshot still ships on schedule.
# ------------------------------------------------------------------
def generate_health_insights(snapshot: dict) -> Optional[str]:
"""Generate a plain-language health narrative from a consolidated snapshot.
Args:
snapshot: dict containing Oura biometrics and SirenWise alerts.
Returns:
A formatted string with summary, correlations, and recommendations,
or None if insight generation fails.
"""
user_message = _build_user_message(snapshot)
backend = os.environ.get("JEEVES_LLM_BACKEND", "openai").lower()
try:
if backend == "ollama":
narrative = _call_ollama(SYSTEM_PROMPT, user_message)
else:
narrative = _call_openai(SYSTEM_PROMPT, user_message)
return f"🧠 AI Health Insights\n{'—' * 24}\n{narrative}"
except Exception as e:
# Log but don't crash — the raw snapshot is still valuable
print(f"[ai_insights] LLM call failed ({backend}): {e}")
return None
# ------------------------------------------------------------------
# Step 5: Wire it into src/monitor.py
#
# In your existing delivery pipeline, after you build the
# consolidated snapshot dict and before you format the message:
# ------------------------------------------------------------------
# --- In src/monitor.py (add these lines) ---
#
# from ai_insights import generate_health_insights
#
# def build_delivery_message(snapshot: dict) -> str:
# # Existing raw-data formatting
# raw_section = format_raw_snapshot(snapshot) # your current logic
#
# # NEW: append AI narrative if available
# insights = generate_health_insights(snapshot)
# if insights:
# return f"{raw_section}\n\n{insights}"
# return raw_section
#
# The Telegram bot send_message() and CLI print() already consume
# the output of build_delivery_message(), so no further changes
# are needed in the delivery layer.
# ------------------------------------------------------------------
# Step 6: Environment variables to configure
# ------------------------------------------------------------------
#
# JEEVES_LLM_BACKEND=openai # or "ollama" for local
# OPENAI_API_KEY=sk-... # required if backend=openai
# OLLAMA_MODEL=mistral # optional, default mistral
# OLLAMA_URL=http://localhost:11434 # optional, default localhost
#
# Add these to your .env or cron environment.
# ------------------------------------------------------------------
# Step 7: Token cost estimation (why gpt-4o-mini)
# ------------------------------------------------------------------
#
# System prompt: ~220 tokens
# User message (snapshot JSON): ~150 tokens
# Response cap: 300 tokens
# Total per call: ~670 tokens
#
# At 24 calls/day (hourly) × 30 days = 720 calls/month
# 720 × 670 = ~482,400 tokens/month
# GPT-4o-mini pricing (approx): $0.15/1M input, $0.60/1M output
# Monthly cost: well under $1. This is production-sustainable.