docs: add Alexa Skill documentation
This commit is contained in:
1 parent
76bc667adf
commit
3e5be8cb09
1 file changed
+776
@@ -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": "<speak>I buchi neri sono regioni dello spaziotempo con una gravità così intensa che nulla, nemmeno la luce, può sfuggire. <break time='500ms'/> Ho trovato 2 fonti principali su Wikipedia e INAF.</speak>"
|
||||
},
|
||||
"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" <break time='500ms'/>"
|
||||
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"<speak>{text}</speak>"
|
||||
},
|
||||
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"<speak>{escape_ssml(text)}</speak>"
|
||||
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
|
||||
<speak>
|
||||
<!-- Pausa naturale tra pensieri -->
|
||||
I buchi neri sono regioni dello spaziotempo
|
||||
<break time="400ms"/>
|
||||
con una gravità così intensa che nulla, nemmeno la luce, può sfuggire.
|
||||
|
||||
<!-- Enfasi su una parola -->
|
||||
<emphasis level="moderate">Attenzione</emphasis>: questa è una teoria scientifica.
|
||||
|
||||
<!-- Pronuncia di parole straniere -->
|
||||
Il modello <lang xml:lang="en-US">Gemini 2.5 Flash</lang> ha analizzato le fonti.
|
||||
|
||||
<!-- Pausa prima delle fonti -->
|
||||
<break time="600ms"/>
|
||||
Ho consultato 3 fonti.
|
||||
</speak>
|
||||
```
|
||||
|
||||
### Strategia per Sibilla
|
||||
|
||||
1. **`speakable_text` è già pulito** — niente markdown, URL, grassetti. Solo testo naturale.
|
||||
2. **Aggiungere pausa prima delle fonti** — `<break time="500ms"/>` 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 <token>`) |
|
||||
| **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 <token>` è 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
|
||||
Reference in new issue
Block a user