From 3e5be8cb0993596e24709864d9553238075f02b5 Mon Sep 17 00:00:00 2001 From: Sibilla Date: Sat, 18 Jul 2026 14:32:32 +0000 Subject: [PATCH] docs: add Alexa Skill documentation --- docs/ALEXA_SKILL.md | 776 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 776 insertions(+) create mode 100644 docs/ALEXA_SKILL.md diff --git a/docs/ALEXA_SKILL.md b/docs/ALEXA_SKILL.md new file mode 100644 index 0000000..b294548 --- /dev/null +++ b/docs/ALEXA_SKILL.md @@ -0,0 +1,776 @@ +# πŸŽ™οΈ Sibilla Alexa Skill β€” Brainstorming & Architettura + +> **Versione documento:** 0.1.0 +> **Stato:** 🧠 Brainstorming / Pianificazione +> **Autore:** Sibilla Team +> **Data:** 18 Luglio 2026 + +--- + +## Indice + +1. [Panoramica: Come funziona una Skill Alexa](#panoramica) +2. [Architettura di Sistema](#architettura-di-sistema) +3. [Flusso dei Dati](#flusso-dei-dati) +4. [Requisiti](#requisiti) +5. [Interaction Model](#interaction-model) +6. [Lambda Function Design](#lambda-function-design) +7. [SSML e Voce Italiana](#ssml-e-voce-italiana) +8. [Sicurezza](#sicurezza) +9. [Costi](#costi) +10. [Piano di Implementazione](#piano-di-implementazione) +11. [Rischi e Mitigazioni](#rischi-e-mitigazioni) +12. [Riferimenti](#riferimenti) + +--- + +## Panoramica + +### Cos'Γ¨ una Skill Alexa? + +Una **Skill Alexa** Γ¨ un'applicazione vocale che permette agli utenti di interagire con i dispositivi Amazon Echo (Echo Dot, Echo Show, ecc.) usando solo la voce. È l'equivalente di una "app" per lo store di Alexa. + +### Come funziona (versione semplificata) + +``` +Tu dici: "Alexa, chiedi a Sibilla qual Γ¨ l'ultimo film sull'Odissea" + ↓ +Alexa: Converte voce β†’ testo, capisce l'intenzione (intent) + ↓ +AWS Lambda: Riceve un JSON con la tua domanda, chiama l'API Sibilla + ↓ +Sibilla API: Cerca nel web, sintetizza con Gemini, restituisce la risposta + ↓ +AWS Lambda: Prende `speakable_text`, lo avvolge in SSML, risponde ad Alexa + ↓ +Alexa: Converte testo β†’ voce e te lo dice +``` + +### PerchΓ© Γ¨ giΓ  quasi pronto + +Sibilla **ha giΓ  tutto il necessario** per una skill Alexa: + +| Cosa serve | Sibilla ce l'ha? | +|-----------|-----------------| +| API REST per query testuali | βœ… `GET/POST /api/v1/query` | +| CORS abilitato | βœ… `Access-Control-Allow-Origin: *` | +| Testo pulito per TTS | βœ… Campo `speakable_text` (strip_markdown) | +| Risposta in italiano | βœ… Prompt e risposte in italiano | +| Open source, self-hosted | βœ… Nessun costo API di terze parti | + +L'unica cosa da costruire Γ¨ il "ponte" tra Alexa e Sibilla: una **Lambda AWS** che fa da intermediario. + +--- + +## Architettura di Sistema + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ SIBILLA ALEXA SKILL β”‚ +β”‚ β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ Alexa │────▢│ Alexa Cloud │────▢│ AWS Lambda β”‚ β”‚ +β”‚ β”‚ Device β”‚ β”‚ (Amazon) β”‚ β”‚ (ask-sibilla) β”‚ β”‚ +β”‚ β”‚ │◀────│ │◀────│ β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”‚ β”‚ +β”‚ β”‚ HTTPS β”‚ +β”‚ β”‚ β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ Sibilla Server β”‚ β”‚ +β”‚ β”‚ /api/v1/query β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +### Componenti + +| Componente | Ruolo | Hosting | Costo | +|-----------|-------|---------|-------| +| **Alexa Device** | Input/output vocale | Casa dell'utente | Hardware | +| **Alexa Cloud** | Speech-to-text, routing, TTS | Amazon (gratuito) | $0 | +| **AWS Lambda** | Logica della skill (bridge) | AWS | Gratuito (1M richieste/mese) | +| **Sibilla Server** | Ricerca web + sintesi AI | Tuo server / VPS | ~€5-10/mese (VPS) | + +### PerchΓ© AWS Lambda? + +1. **Gratuito** β€” 1 milione di richieste/mese nel free tier, piΓΉ che sufficiente +2. **Nativamente integrato con Alexa** β€” l'ASK SDK Γ¨ pensato per Lambda +3. **Nessun server da gestire** β€” solo codice Python +4. **ScalabilitΓ  automatica** β€” se 100 persone chiamano Sibilla contemporaneamente, Lambda scala da solo + +### ⚠️ CriticitΓ : Il Problema del Timeout + +Alexa impone un **limite invalicabile di 8 secondi** tra la richiesta e la risposta. Se la Lambda non risponde entro 8s, la connessione viene troncata e l'utente sente: "Mi dispiace, la skill non risponde." + +La pipeline Sibilla attuale puΓ² facilmente superare questo limite: + +``` +Query Expansion (LLM) ~1-2s +Web Search (Degoog/DDG) ~1-2s +Fetch contenuti pagine ~2-4s ← il collo di bottiglia! +Sintesi LLM (Gemini) ~2-4s +───────────────────────────────── +TOTALE ~6-12s ← spesso oltre gli 8s! +``` + +### 🚫 PerchΓ© NON si puΓ² fare async ("fire and forget") + +L'idea di "lancio la richiesta, chiudo, e quando Sibilla risponde chiamo Alexa" **NON funziona** con le skill Alexa. Il modello Γ¨ **esclusivamente request-response sincrono**. Non esiste un meccanismo per cui un server esterno "spinge" una risposta vocale ad Alexa mentre l'utente ascolta. + +L'unica via "async" ufficiale sono le **Proactive Events API** (notifiche), ma: +- L'utente deve **abilitare le notifiche** manualmente nell'app Alexa +- La risposta arriva come **anello giallo** sul dispositivo, non come voce +- L'utente deve dire *"Alexa, cosa c'Γ¨ di nuovo?"* per sentirla +- Risultato: **3 turni** (domanda β†’ attesa β†’ notifica β†’ "cosa c'Γ¨ di nuovo?" β†’ risposta) +- Amazon **rifiuta la certificazione** se usi le notifiche come risposta a domande + +**Conclusione:** L'unica strada Γ¨ **stare dentro gli 8 secondi**. Ecco come. + +**Soluzione: Progressive Response (riempie il silenzio) + Fast Path (riduce i tempi)** (vedi sezione dedicata sotto). + +--- + +## Flusso dei Dati + +### Step-by-step di una richiesta + +``` +1️⃣ UTENTE PARLA + "Alexa, chiedi a Sibilla cosa sono i buchi neri" + +2️⃣ ALEXA CLOUD β†’ SPEECH-TO-TEXT + { + "intent": "AskSibillaIntent", + "slots": { + "query": { "value": "cosa sono i buchi neri" } + }, + "locale": "it-IT" + } + +3️⃣ ALEXA CLOUD β†’ LAMBDA (JSON POST) + https://lambda.aws.amazon.com/ask-sibilla + +4️⃣ LAMBDA β†’ SIBILLA API + GET https://mioserver.com/api/v1/query?q=cosa+sono+i+buchi+neri + + Risposta in ~3-5 secondi: + { + "query": "cosa sono i buchi neri", + "answer": "I **buchi neri** sono regioni dello spaziotempo...", + "speakable_text": "I buchi neri sono regioni dello spaziotempo...", + "sources": [ + {"index": 1, "title": "Buco nero - Wikipedia", "url": "..."}, + {"index": 2, "title": "Cosa sono i buchi neri - INAF", "url": "..."} + ], + "model": "gemini-2.5-flash", + "tokens_used": 1200 + } + +5️⃣ LAMBDA β†’ ALEXA CLOUD (JSON Response) + { + "version": "1.0", + "response": { + "outputSpeech": { + "type": "SSML", + "ssml": "I buchi neri sono regioni dello spaziotempo con una gravitΓ  cosΓ¬ intensa che nulla, nemmeno la luce, puΓ² sfuggire. Ho trovato 2 fonti principali su Wikipedia e INAF." + }, + "shouldEndSession": true + } + } + +6️⃣ ALEXA CLOUD β†’ TEXT-TO-SPEECH + Alexa dice (in italiano, voce naturale): + "I buchi neri sono regioni dello spaziotempo con una gravitΓ  + cosΓ¬ intensa che nulla, nemmeno la luce, puΓ² sfuggire." +``` + +### Gestione degli errori + +``` +Se Sibilla non risponde entro 5 secondi: + Alexa dice: "Mi dispiace, Sibilla non Γ¨ raggiungibile in questo momento. + Riprova piΓΉ tardi." + +Se tutti i motori di ricerca sono down (HTTP 502): + Alexa dice: "Non riesco a cercare sul web in questo momento. + Controlla che i motori di ricerca siano attivi." + +Se la query Γ¨ vuota: + Alexa dice: "Cosa vuoi chiedere a Sibilla?" + (riapre la sessione per riprovare) +``` + +--- + +## Requisiti + +### Account e strumenti gratuiti + +| Requisito | Come ottenerlo | Costo | +|-----------|---------------|-------| +| **Amazon Developer Account** | [developer.amazon.com](https://developer.amazon.com) | Gratuito | +| **AWS Account** | [aws.amazon.com](https://aws.amazon.com) | Gratuito (free tier) | +| **ASK CLI** | `npm install -g ask-cli` | Gratuito | +| **Python 3.10+** | GiΓ  installato | Gratuito | +| **Sibilla Server** | GiΓ  avviato su VPS/home server | Esistente | + +### Dipendenze Python per la Lambda + +``` +ask-sdk-core # Alexa Skills Kit SDK (handler, intent, slot) +requests # Per chiamare l'API Sibilla (o urllib3 built-in) +boto3 # AWS SDK (se serve logging/DynamoDB) +``` + +File `requirements.txt` della Lambda: +``` +ask-sdk-core>=1.0 +``` + +> **Nota:** `requests` puΓ² essere evitato usando `urllib.request` (built-in). Meno dipendenze = Lambda piΓΉ leggera. + +### Configurazione Sibilla Server + +Il server Sibilla deve essere: +- **Raggiungibile da internet** (IP pubblico o dominio con HTTPS) +- **Connessione stabile** (la Lambda ha timeout 8 secondi) +- **HTTPS consigliato** (anche se tecnicamente Lambda puΓ² chiamare HTTP) + +--- + +## Interaction Model + +L'Interaction Model definisce **cosa** l'utente puΓ² dire e **come** Alexa lo interpreta. Si configura in un file JSON. + +### Intents + +Un **Intent** Γ¨ un'azione che l'utente vuole compiere. + +#### 1. `AskSibillaIntent` β€” Principale + +L'utente fa una domanda a Sibilla. Supporta sia domanda libera (`query`) che un topic opzionale (`topic`). + +**Utterances (frasi di esempio):** +``` +chiedi a Sibilla {query} +domanda a Sibilla {query} +cerca con Sibilla {query} +interroga l'oracolo {query} +cosa dice Sibilla su {query} +sibilla cosa sai di {query} +sibilla parlami di {query} +sibilla {query} +``` + +**Slots:** + +| Slot | Tipo | Obbligatorio | Descrizione | +|------|------|:---:|-------------| +| `query` | `AMAZON.SearchQuery` | βœ… | La domanda completa dell'utente | + +`AMAZON.SearchQuery` Γ¨ uno slot type built-in di Amazon che cattura frasi intere β€” perfetto per domande libere. + +#### 2. `AMAZON.HelpIntent` β€” Built-in + +Attivato quando l'utente chiede aiuto: +``` +aiuto +cosa puoi fare +come funzioni +``` + +**Risposta suggerita:** +> "Sono Sibilla, l'oracolo AI. Puoi chiedermi qualsiasi cosa e io cercherΓ² nel web per darti una risposta. Prova a dirmi 'chiedi a Sibilla qual Γ¨ l'ultimo film sull'Odissea' o semplicemente 'Sibilla, cosa sono i raggi cosmici?'." + +#### 3. `AMAZON.StopIntent` / `AMAZON.CancelIntent` β€” Built-in + +``` +stop +basta +esci +cancella +``` + +**Risposta suggerita:** +> "Va bene, alla prossima consultazione!" + +#### 4. `AMAZON.FallbackIntent` β€” Built-in + +Quando Alexa non capisce cosa l'utente vuole (nessun intent match). + +**Risposta suggerita:** +> "Non ho capito. Puoi ripetere la domanda per Sibilla?" + +### Skill Manifest (`skill.json`) + +```json +{ + "manifest": { + "publishingInformation": { + "locales": { + "it-IT": { + "name": "Sibilla β€” L'Oracolo AI", + "summary": "Interroga Sibilla, l'oracolo AI che cerca nel web e risponde alle tue domande.", + "description": "Sibilla Γ¨ un assistente AI che cerca nel web in tempo reale...", + "examplePhrases": [ + "Alexa, chiedi a Sibilla qual Γ¨ l'ultimo film sull'Odissea", + "Alexa, domanda a Sibilla cosa sono i buchi neri", + "Alexa, sibilla parlami delle piramidi" + ], + "keywords": ["ricerca", "intelligenza artificiale", "web", "oracolo"] + } + } + }, + "apis": { + "custom": { + "endpoint": { + "uri": "arn:aws:lambda:eu-west-1:123456789:function:ask-sibilla" + }, + "interfaces": [] + } + }, + "permissions": [] + } +} +``` + +--- + +## ⚑ Fast Path + Progressive Response β€” La Soluzione Completa + +Due tecniche che insieme risolvono il problema: **Progressive Response** (riempie il silenzio) e **Fast Path** (riduce i tempi sotto gli 8s). + +### Progressive Response: riempire il silenzio + +Mentre la Lambda aspetta la risposta da Sibilla, l'utente sentirebbe **silenzio**. Con le Progressive Responses, la Lambda puΓ² dire ad Alexa di iniziare subito a parlare: + +``` +0.0s Lambda riceve la richiesta +0.2s Lambda invia Progressive Response β†’ Alexa dice: "Sto cercando..." πŸ”Š + └─ L'utente sente subito qualcosa, l'attesa Γ¨ tollerabile +0.3s Lambda chiama Sibilla FAST PATH +4.0s Sibilla risponde +4.2s Lambda restituisce risposta finale β†’ Alexa dice la risposta πŸ”Š +``` + +Richiede il permesso `alexa::async_event:send_progressive_response` nel manifest. + +### Fast Path: endpoint ottimizzato su Sibilla + +Per rispettare il limite degli 8 secondi, serve un **endpoint ottimizzato** che salti i passaggi piΓΉ lenti della pipeline: + +### Cosa va aggiunto a Sibilla: `GET /api/v1/voice-query` + +``` +Pipeline FAST (vocale): + 1. Web Search (Degoog/DDG) ~1-2s + 2. Sintesi LLM solo su snippet ~2-3s ← NO fetch pagine complete! + ───────────────────────────────────── + TOTALE ~3-5s βœ… dentro gli 8s! + +Pipeline STANDARD (web): + 1. Query Expansion (LLM) ~1-2s + 2. Web Search (Degoog/DDG) ~1-2s + 3. Fetch contenuti pagine ~2-4s ← collo di bottiglia + 4. Sintesi LLM su contenuti ~2-4s + ───────────────────────────────────── + TOTALE ~6-12s ❌ troppo lento per voce +``` + +**Differenza chiave:** l'endpoint vocale usa solo gli **snippet** (i riassunti forniti dal motore di ricerca) invece di scaricare le pagine web complete. Perde un po' di profonditΓ  ma guadagna velocitΓ . + +### Prompt TTS-optimized + +```python +VOICE_SYSTEM_PROMPT = """\ +Sei Sibilla, un assistente vocale AI. Rispondi a voce in modo discorsivo e naturale, +come se stessi parlando con una persona. Regole fondamentali: +- Usa frasi brevi e chiare (max 3 frasi per concetto) +- NON usare elenchi puntati, tabelle, URL, markdown, asterischi +- NON nominare le fonti nel testo ("secondo Wikipedia...") β€” le fonti sono gestite a parte +- Se ci sono date, numeri o nomi propri, pronunciali in italiano +- Risposte entro 400-500 caratteri; se l'argomento Γ¨ complesso, fai un riassunto +""" +``` + +--- + +## Lambda Function Design + +### Struttura del codice + +``` +lambda/ +β”œβ”€β”€ lambda_function.py # Entry point (handler principale) +β”œβ”€β”€ sibilla_client.py # Client HTTP per chiamare l'API Sibilla +β”œβ”€β”€ speech_builder.py # Costruzione risposte SSML +β”œβ”€β”€ requirements.txt # ask-sdk-core +└── .env # (opzionale) SIBILLA_API_URL +``` + +### lambda_function.py β€” Schema + +```python +# Pseudo-codice della struttura + +from ask_sdk_core.skill_builder import SkillBuilder +from ask_sdk_core.handler_input import HandlerInput +from ask_sdk_core.dispatch_components import AbstractRequestHandler +from ask_sdk_core.utils import get_slot_value +from ask_sdk_model import Response +from ask_sdk_model.ui import SsmlOutputSpeech +import ask_sdk_core.utils as ask_utils + +# ── Costanti ──────────────────────────────────────────── +SIBILLA_URL = "https://tuo-server.com/api/v1/voice-query" # endpoint FAST +TIMEOUT = 5 # secondi (max Alexa: 8s, teniamo margine) +FALLBACK_LONG_ANSWER = 500 # caratteri + +# ── Handlers ─────────────────────────────────────────── + +class AskSibillaIntentHandler(AbstractRequestHandler): + def can_handle(self, handler_input): + return ask_utils.is_intent_name("AskSibillaIntent")(handler_input) + + def handle(self, handler_input): + # 1. Estrai la query + query = get_slot_value(handler_input, "query") + if not query: + return speak(handler_input, "Cosa vuoi chiedere a Sibilla?", keep_open=True) + + # 2. Invia SUBITO un "thinking sound" (Progressive Response) + # cosΓ¬ l'utente non sente il silenzio durante l'attesa + send_progressive_response(handler_input, "Sto cercando...") + + # 3. Chiama l'endpoint vocale FAST di Sibilla + try: + result = call_sibilla_voice(query) + except TimeoutError: + return speak(handler_input, + "Mi dispiace, la ricerca sta impiegando troppo tempo. " + "Prova con una domanda piΓΉ specifica.") + except ConnectionError: + return speak(handler_input, + "Sibilla non Γ¨ raggiungibile in questo momento. " + "Controlla che il server sia acceso e riprova.") + + # 4. Prendi il testo parlato + speakable = result["speakable_text"] + + # 5. Se troppo lungo, tronca con garbo + if len(speakable) > FALLBACK_LONG_ANSWER: + speakable = speakable[:FALLBACK_LONG_ANSWER] + "." + speakable += " La risposta completa Γ¨ molto lunga. Puoi chiedermi di approfondire." + + # 6. Aggiungi nota sulle fonti (solo numero, niente URL) + if result.get("sources"): + n = len(result["sources"]) + fonti_principali = ", ".join( + s["title"].split(" - ")[0] + for s in result["sources"][:2] + ) + speakable += ( + f" " + f"Ho consultato {n} fonti, tra cui {fonti_principali}." + ) + + # 7. Restituisci risposta SSML + return speak(handler_input, speakable) + +# Registra tutti gli handler +sb = SkillBuilder() +sb.add_request_handler(AskSibillaIntentHandler()) +sb.add_request_handler(HelpIntentHandler()) +sb.add_request_handler(CancelOrStopIntentHandler()) +sb.add_request_handler(FallbackIntentHandler()) + +lambda_handler = sb.lambda_handler() +``` + +### sibilla_client.py β€” Schema + +```python +import json +import urllib.request +import urllib.parse +import ssl +from typing import Optional + +SIBILLA_URL = "https://tuo-server.com/api/v1/query" +TIMEOUT = 6 + +def call_sibilla_voice(query: str) -> dict: + """Chiama l'endpoint FAST di Sibilla per uso vocale.""" + params = urllib.parse.urlencode({"q": query}) + url = f"{SIBILLA_URL}?{params}" + + req = urllib.request.Request(url, method="GET") + # API Key per sicurezza (obbligatoria in produzione!) + req.add_header("Authorization", f"Bearer {SIBILLA_API_KEY}") + + ctx = ssl.create_default_context() + + with urllib.request.urlopen(req, timeout=TIMEOUT, context=ctx) as resp: + data = json.loads(resp.read().decode()) + + return data + + +def send_progressive_response(handler_input, text: str): + """Invia un messaggio vocale immediato mentre la ricerca Γ¨ in corso. + + Questa Γ¨ una Progressive Response β€” Alexa la riproduce subito, + prima ancora che la Lambda abbia finito. Essenziale per non far + sentire il silenzio all'utente durante l'attesa. + + Richiede il permesso 'alexa::async_event:send_progressive_response' + nel manifest della skill. + """ + import ask_sdk_model_interfaces as ifaces + + request_envelope = handler_input.request_envelope + directive = ifaces.connections.SendRequestDirective( + name="ProgressiveResponse", + payload={ + "speech": f"{text}" + }, + token=request_envelope.request.request_id, + ) + # Nota: la directive va inviata tramite l'API Alexa, non come + # risposta diretta. Richiede una chiamata HTTP separata usando + # l'API endpoint dal contesto della richiesta. +``` + +### speech_builder.py β€” Schema + +```python +from ask_sdk_model.ui import SsmlOutputSpeech +from ask_sdk_model import Response +from ask_sdk_core.handler_input import HandlerInput + +def speak(handler_input: HandlerInput, text: str, keep_open: bool = False) -> Response: + """Costruisce una risposta Alexa con SSML.""" + ssml = f"{escape_ssml(text)}" + return ( + handler_input.response_builder + .speak(ssml) + .set_should_end_session(not keep_open) + .response + ) + +def escape_ssml(text: str) -> str: + """Escapa caratteri speciali per SSML.""" + return ( + text.replace("&", "&") + .replace("<", "<") + .replace(">", ">") + .replace('"', """) + .replace("'", "'") + ) +``` + +--- + +## SSML e Voce Italiana + +### PerchΓ© SSML? + +SSML (Speech Synthesis Markup Language) permette di controllare **come** Alexa parla: +- Pause naturali tra frasi +- Enfasi su parole importanti +- Pronuncia corretta di termini tecnici o stranieri + +### Tag SSML utili per Sibilla + +```xml + + + I buchi neri sono regioni dello spaziotempo + + con una gravitΓ  cosΓ¬ intensa che nulla, nemmeno la luce, puΓ² sfuggire. + + + Attenzione: questa Γ¨ una teoria scientifica. + + + Il modello Gemini 2.5 Flash ha analizzato le fonti. + + + + Ho consultato 3 fonti. + +``` + +### Strategia per Sibilla + +1. **`speakable_text` Γ¨ giΓ  pulito** β€” niente markdown, URL, grassetti. Solo testo naturale. +2. **Aggiungere pausa prima delle fonti** β€” `` prima di "Ho consultato X fonti." +3. **Risposte troppo lunghe** β€” troncare a ~500 caratteri e suggerire di approfondire +4. **Numeri e date** β€” giΓ  in formato parlato dal LLM + +### Voce italiana + +Alexa supporta nativamente l'italiano con voci naturali (Carla, Giorgio, Bianca). La skill verrΓ  configurata con `locale: it-IT`. + +--- + +## Sicurezza + +### Comunicazione Lambda ↔ Sibilla + +| Area | Rischio | Mitigazione | +|------|---------|-------------| +| **Esposizione API** | Chiunque puΓ² chiamare `/api/v1/query` | API key condivisa (header `Authorization: Bearer `) | +| **HTTPS** | Dati in chiaro tra Lambda e Sibilla | Obbligatorio in produzione (Let's Encrypt gratuito) | +| **DDoS** | Troppe richieste crashano il server | Rate limiting su Sibilla (Flask-Limiter) | +| **Timeout** | Sibilla impiega >8s a rispondere | Timeout Lambda a 7s, graceful degradation | +| **Injection** | Input malevolo nella query | Sanitizzazione giΓ  in Sibilla + strip su Lambda | + +### API Key su Sibilla (da aggiungere) + +```python +# In app.py β€” middleware di autenticazione API +@app.before_request +def require_api_key(): + if request.path.startswith("/api/v1/"): + api_key = request.headers.get("Authorization", "").replace("Bearer ", "") + if api_key != os.environ.get("SIBILLA_API_KEY", ""): + return jsonify({"error": "Unauthorized"}), 401 +``` + +Poi la Lambda passerΓ  l'API key nell'header. + +--- + +## Costi + +### Stima mensile (uso personale / familiare) + +| Servizio | Uso stimato | Costo | +|----------|-------------|-------| +| **Alexa Cloud** (speech-to-text, TTS) | Gratuito | $0 | +| **AWS Lambda** | ~1,000 chiamate/mese | $0 (free tier: 1M) | +| **Sibilla Server** (VPS 2GB) | Hetzner / DigitalOcean | ~€5-10/mese | +| **Gemini API** (2.5 Flash) | ~1,000 query/mese | $0 (free tier ampio) | +| **TOTALE** | | **~€5-10/mese** (solo VPS) | + +> Se il server Sibilla Γ¨ giΓ  in esecuzione (come ora), il costo aggiuntivo per la skill Alexa Γ¨ **$0**. + +--- + +## Piano di Implementazione + +### Fase 1 β€” Prototipo locale (1-2 ore) + +- [ ] Installare ASK CLI: `npm install -g ask-cli` +- [ ] Configurare credenziali AWS: `ask configure` +- [ ] Creare skill da template: `ask new --skill-name sibilla-skill` +- [ ] Definire interaction model (intent + utterances) nel developer console +- [ ] Scrivere `lambda_function.py` con handler minimale (solo `AskSibillaIntent`) +- [ ] Scrivere `sibilla_client.py` che chiama `localhost:5000` (per test) +- [ ] Testare con `ask dialog` (simulatore testuale) + +### Fase 2 β€” Deploy su AWS (1 ora) + +- [ ] Modificare `sibilla_client.py` per puntare al server pubblico +- [ ] Caricare Lambda con `ask deploy` +- [ ] Testare su dispositivo Echo reale: "Alexa, chiedi a Sibilla..." + +### Fase 3 β€” Raffinamento (1-2 ore) + +- [ ] Aggiungere gestione timeout e retry +- [ ] Aggiungere SSML con pause naturali +- [ ] Gestire risposte troppo lunghe +- [ ] Aggiungere intent di follow-up ("approfondisci", "fonti") +- [ ] Aggiungere API key su Sibilla per sicurezza + +### Fase 4 β€” Pubblicazione (opzionale, 2-4 ore) + +- [ ] Tradurre descrizione e keywords per lo store Alexa +- [ ] Creare icone (108x108 e 512x512) +- [ ] Scrivere privacy policy (richiesta per la pubblicazione) +- [ ] Sottoporre per certificazione Amazon + +--- + +## Rischi e Mitigazioni + +| Rischio | ProbabilitΓ  | Impatto | Mitigazione | +|--------|:---:|:---:|-------------| +| **Timeout Alexa (8s invalicabile)** β€” pipeline standard ~6-12s | **Alta** | **Critico** | Fast Path vocale (salta fetch pagine), Progressive Responses, timeout Lambda a 5s | +| **Sibilla server down** | Bassa | Alto | Health check, messaggio vocale di cortesia, retry | +| **Gemini API rate limit** | Bassa | Medio | Caching risposte frequenti, query expansion giΓ  limita | +| **Risposte non adatte al TTS** (troppo tecniche) | Media | Medio | `speakable_text` giΓ  pulito; prompt di sistema puΓ² enfatizzare risposte discorsive | +| **Skill non certificata da Amazon** | Media | Basso | Solo uso personale (beta testing), la certificazione Γ¨ opzionale | +| **VPS non raggiungibile da Lambda** | Bassa | Alto | Dominio pubblico, HTTPS, IP statico o DDNS | + +--- + +## Differenze dalla Web UI + +| Aspetto | Web UI (`/`) | Skill Alexa | +|---------|-------------|-------------| +| **Input** | Testo scritto (textarea) | Voce β†’ testo (Alexa) | +| **Output** | HTML con markdown renderizzato, fonti cliccabili | Solo voce (TTS), niente fonti visibili | +| **Lunghezza risposta** | Illimitata | ~500 caratteri parlati (poi "chiedimi di approfondire") | +| **Fonti** | Link cliccabili, snippet | "Ho consultato 3 fonti: Wikipedia, INAF..." | +| **Conversazione** | Singola domanda | Potenziale multi-turno (follow-up) | +| **Latenza percepita** | Spinner visivo | Silenzio β†’ attesa snervante β†’ serve thinking sound | +| **Errori** | Messaggio a schermo | Messaggio vocale + suggerimento di riprovare | + +### Cosa aggiungere al prompt di sistema per la skill vocale? + +Attualmente Sibilla usa prompt generici. Per la skill Alexa, potremmo aggiungere un **prompt TTS-optimized**: + +``` +Sei Sibilla, un assistente vocale AI. Rispondi in modo discorsivo e naturale, +come se stessi parlando con una persona. Usa frasi brevi e chiare. +Evita elenchi puntati, tabelle, URL, e gergo eccessivamente tecnico. +Se la risposta Γ¨ molto lunga, dai prima un riassunto di 2-3 frasi, +poi offri di approfondire. +``` + +Questo prompt verrebbe passato via header o parametro all'API, oppure si puΓ² creare un endpoint dedicato `/api/v1/voice-query`. + +--- + +## Riferimenti + +### Documentazione Ufficiale +- [Alexa Skills Kit SDK for Python](https://developer.amazon.com/en-US/docs/alexa/alexa-skills-kit-sdk-for-python/overview.html) +- [ASK CLI Command Reference](https://developer.amazon.com/en-US/docs/alexa/smapi/ask-cli-command-reference.html) +- [SSML Reference](https://developer.amazon.com/en-US/docs/alexa/custom-skills/speech-synthesis-markup-language-ssml-reference.html) +- [Skill Manifest Schema](https://developer.amazon.com/en-US/docs/alexa/smapi/skill-manifest.html) +- [Request/Response JSON Format](https://developer.amazon.com/en-US/docs/alexa/custom-skills/request-and-response-json-reference.html) + +### Tutorial Consigliati +- [Develop Your First Skill (Python)](https://developer.amazon.com/en-US/docs/alexa/alexa-skills-kit-sdk-for-python/develop-your-first-skill.html) +- [Build an Alexa Skill with AWS Lambda](https://developer.amazon.com/en-US/docs/alexa/custom-skills/host-a-custom-skill-as-an-aws-lambda-function.html) + +--- + +## Domande Aperte per la prossima fase + +1. **Sibilla sarΓ  self-hosted su VPS o su rete domestica?** Se domestica, serve IP pubblico/DDNS. +2. **Vuoi pubblicare la skill sullo store Alexa o solo uso personale?** Cambia i requisiti di privacy e certificazione. +3. **Serve supporto multi-turno?** Es: "Alexa, approfondisci le fonti" dopo la prima risposta. +4. **OK a creare l'endpoint fast-path `/api/v1/voice-query` su Sibilla?** Salta il fetch delle pagine e usa solo snippet + sintesi LLM veloce. +5. **Serve caching per domande frequenti?** Riduce latenza e costi Gemini. +6. **Quale authentication per la API?** L'API key via header `Authorization: Bearer ` Γ¨ la soluzione piΓΉ semplice per il caso d'uso Lambda β†’ Sibilla. + +## Prossimi Passi Concreti + +### Su Sibilla (backend) +1. Aggiungere endpoint `GET /api/v1/voice-query` β€” fast path senza fetch pagine +2. Aggiungere prompt TTS-optimized per risposte vocali +3. Aggiungere autenticazione API key su `/api/v1/*` + +### Sulla Skill Alexa (nuovo progetto) +1. `ask new` β€” creare il progetto skill da template Python +2. Configurare interaction model con `AskSibillaIntent` + `AMAZON.SearchQuery` +3. Scrivere `lambda_function.py` con handler, Progressive Response, chiamata a `/api/v1/voice-query` +4. Deploy con `ask deploy` e test su Echo Dot