feat: v0.2.0 — API REST, TTS support, documentazione completa e UI mobile-first

✨ Nuove funzionalità:
- Nuovo endpoint API REST /api/v1/query (GET+POST) per consumo programmatico
- Campo speakable_text ottimizzato per sintesi vocale (Alexa, Google Home)
- Funzione strip_markdown() per conversione Markdown → plain text TTS
- Supporto CORS per accesso cross-origin
- UI responsive mobile-first con micro-interazioni e stati
- Nuovo pulsante 'Nuova ricerca' e metadata bar

📚 Documentazione completa:
- docs/ARCHITECTURE.md — architettura di sistema, stack, componenti
- docs/FLOW.md — diagrammi Mermaid della pipeline e flussi
- docs/API.md — documentazione endpoint REST con esempi curl
- docs/WIKI.md — FAQ, glossario, roadmap
- docs/DEPLOYMENT.md — guida deployment (Gunicorn, Nginx, Docker, systemd)
- docs/CONTRIBUTING.md — guida alla contribuzione

🔧 Migliorie tecniche:
- Pipeline di ricerca condivisa in _run_search_pipeline()
- Gestione errori Gemini migliorata con _clean_error()
- Sanitizzazione HTML con allowlist per prevenzione XSS
- .env.example completo con commenti

🧪 Test: 109/109 passati
This commit is contained in:
Sibilla committed 2026-07-18 10:05:46 +00:00
1 parent f3fe13c60e
commit 76bc667adf
20 files changed
+3385 -245

No files matched your search

+315
View File
@@ -0,0 +1,315 @@
# 📡 Sibilla — Documentazione API REST
> **Versione:** 0.2.0
> **Ultimo aggiornamento:** 18 Luglio 2026
---
## Indice
1. [Panoramica](#panoramica)
2. [Health Check](#health-check)
3. [API Ask (Web)](#api-ask)
4. [API v1 Query (Programmatica)](#api-v1-query)
5. [Codici di errore](#codici-di-errore)
6. [Esempi curl](#esempi-curl)
7. [Integrazione Alexa Skill](#integrazione-alexa-skill)
---
## Panoramica
Tutti gli endpoint sono accessibili su `http://localhost:5000` (o l'host/porta configurati).
| Metodo | Endpoint | Descrizione | Formato |
|--------|----------|-------------|---------|
| `GET` | `/` | Interfaccia web HTML | `text/html` |
| `GET` | `/api/health` | Health check | `application/json` |
| `POST` | `/api/ask` | Ricerca + sintesi (web UI) | `application/json` |
| `GET` | `/api/v1/query` | Ricerca + sintesi (API, query string) | `application/json` |
| `POST` | `/api/v1/query` | Ricerca + sintesi (API, JSON body) | `application/json` |
**CORS:** Tutti gli endpoint includono header CORS permissivi (`Access-Control-Allow-Origin: *`).
---
## Health Check
### `GET /api/health`
Verifica lo stato del server e la configurazione.
**Request:**
```bash
curl http://localhost:5000/api/health
```
**Response `200 OK`:**
```json
{
"status": "ok",
"version": "0.2.0",
"gemini_configured": true,
"degoog_url": "http://localhost:4444",
"searxng_url": "http://localhost:8080"
}
```
| Campo | Tipo | Descrizione |
|-------|------|-------------|
| `status` | `string` | Sempre `"ok"` se il server risponde |
| `version` | `string` | Versione di Sibilla |
| `gemini_configured` | `boolean` | `true` se `GOOGLE_API_KEY` è configurata |
| `degoog_url` | `string` | URL dell'istanza Degoog |
| `searxng_url` | `string` | URL dell'istanza SearXNG |
---
## API Ask
### `POST /api/ask`
Endpoint principale per la ricerca con sintesi AI. Utilizzato dall'interfaccia web.
**Request:**
```json
{
"query": "Qual è stato l'ultimo film sull'Odissea?",
"num_sources": 5
}
```
| Parametro | Tipo | Default | Range | Descrizione |
|-----------|------|---------|-------|-------------|
| `query` | `string` | *richiesto* | — | La domanda da cercare |
| `num_sources` | `integer` | `5` | `1–10` | Numero massimo di fonti da usare |
**Response `200 OK`:**
```json
{
"answer": "L'ultimo adattamento cinematografico dell'**Odissea** è *The Return* (2024)...",
"html_answer": "<p>L'ultimo adattamento cinematografico dell'<strong>Odissea</strong> è <em>The Return</em> (2024)...</p>",
"speakable_text": "L'ultimo adattamento cinematografico dell'Odissea è The Return del 2024...",
"sources": [
{
"index": 1,
"title": "The Return - Wikipedia",
"url": "https://it.wikipedia.org/wiki/The_Return_(film_2024)",
"snippet": "The Return è un film del 2024 diretto da Uberto Pasolini..."
}
],
"model": "gemini-2.5-flash",
"search_engine": "degoog",
"tokens_used": 1847,
"error": null
}
```
| Campo | Tipo | Descrizione |
|-------|------|-------------|
| `answer` | `string` | Risposta in formato **Markdown** |
| `html_answer` | `string` | Risposta in **HTML** sicuro (sanitizzato) |
| `speakable_text` | `string` | Risposta in **plain text**, ottimizzata per TTS |
| `sources` | `array` | Lista delle fonti utilizzate |
| `sources[].index` | `integer` | Numero della fonte (1-based) |
| `sources[].title` | `string` | Titolo della pagina |
| `sources[].url` | `string` | URL della fonte |
| `sources[].snippet` | `string` | Estratto della pagina (max 200 caratteri) |
| `model` | `string` | Modello LLM utilizzato |
| `search_engine` | `string` | Motore di ricerca usato (`degoog`, `searxng`, `duckduckgo`, `none`) |
| `tokens_used` | `integer` | Token totali consumati |
| `error` | `string` or `null` | Eventuale messaggio di errore |
**Response `400 Bad Request`:**
```json
{
"error": "La query non può essere vuota."
}
```
**Response `502 Bad Gateway`:**
```json
{
"error": "Ricerca web fallita: Connection refused",
"answer": "",
"sources": []
}
```
---
## API v1 Query
### `GET /api/v1/query`
Endpoint semplificato per consumo programmatico. Accetta la query via query string.
**Request:**
```bash
curl "http://localhost:5000/api/v1/query?q=ultimo+film+sull%27Odissea"
```
### `POST /api/v1/query`
Stesso endpoint, con query nel body JSON.
**Request:**
```json
{
"query": "Qual è stato l'ultimo film sull'Odissea?"
}
```
| Parametro | Tipo | Default | Descrizione |
|-----------|------|---------|-------------|
| `q` (GET) | `string` | *richiesto* | La query di ricerca (URL-encoded) |
| `query` (POST) | `string` | *richiesto* | La query di ricerca |
**Response `200 OK`:**
```json
{
"query": "Qual è stato l'ultimo film sull'Odissea?",
"answer": "L'ultimo adattamento è **The Return** (2024), diretto da Uberto Pasolini...",
"speakable_text": "L'ultimo adattamento è The Return del 2024, diretto da Uberto Pasolini...",
"sources": [
{
"index": 1,
"title": "The Return - Wikipedia",
"url": "https://it.wikipedia.org/wiki/The_Return_(film_2024)"
}
],
"model": "gemini-2.5-flash",
"search_engine": "degoog",
"tokens_used": 1847
}
```
| Campo | Tipo | Descrizione |
|-------|------|-------------|
| `query` | `string` | Echo della query originale |
| `answer` | `string` | Risposta in formato **Markdown** |
| `speakable_text` | `string` | **Plain text** ottimizzato per sintesi vocale (TTS) |
| `sources` | `array` | Fonti semplificate (solo `index`, `title`, `url`) |
| `model` | `string` | Modello LLM utilizzato |
| `search_engine` | `string` | Motore di ricerca usato |
| `tokens_used` | `integer` | Token totali consumati |
> **Nota:** Il formato è più compatto di `/api/ask` — pensato per API consumer come skill Alexa, Lambda function, o mobile app.
**Response `400 Bad Request`:**
```json
{
"error": "Missing query parameter 'q'."
}
```
**Response `502 Bad Gateway`:**
```json
{
"error": "Ricerca web fallita: Connection refused",
"answer": "",
"speakable_text": ""
}
```
---
## Codici di errore
| HTTP Status | Significato | Quando |
|-------------|-------------|--------|
| `200` | OK | Richiesta completata con successo |
| `400` | Bad Request | Query mancante o vuota |
| `502` | Bad Gateway | Tutti i motori di ricerca sono down |
**Errori interni gestiti:**
- Se tutti i motori falliscono ma Gemini è disponibile → fallback a risposta solo LLM (200 OK)
- Se Gemini non è configurato → `answer` vuoto con `error` nel JSON
- Se Gemini restituisce errore → messaggio di errore inline in `answer`
---
## Esempi curl
### Health check
```bash
curl -s http://localhost:5000/api/health | python -m json.tool
```
### Ricerca via API v1 (GET)
```bash
curl -s "http://localhost:5000/api/v1/query?q=cosa+sono+i+raggi+cosmici" | python -m json.tool
```
### Ricerca via API v1 (POST)
```bash
curl -s -X POST http://localhost:5000/api/v1/query \
-H "Content-Type: application/json" \
-d '{"query": "cosa sono i raggi cosmici"}' | python -m json.tool
```
### Estrai solo la risposta speakable (per TTS)
```bash
curl -s "http://localhost:5000/api/v1/query?q=raggi+cosmici" | python -c "import sys,json; print(json.load(sys.stdin)['speakable_text'])"
```
### Ricerca via web UI (POST /api/ask)
```bash
curl -s -X POST http://localhost:5000/api/ask \
-H "Content-Type: application/json" \
-d '{"query": "raggi cosmici", "num_sources": 3}' | python -m json.tool
```
### Verifica errori
```bash
# Query vuota → 400
curl -i "http://localhost:5000/api/v1/query?q="
# Motori down → 502 (se tutti falliscono)
curl -i "http://localhost:5000/api/v1/query?q=test"
```
---
## Integrazione Alexa Skill
Ecco un esempio di come usare l'endpoint `/api/v1/query` da una Lambda Python per una skill Alexa:
```python
import json
import urllib.request
SIBILLA_URL = "https://tuo-server.com/api/v1/query"
def call_sibilla(query: str) -> dict:
"""Chiama Sibilla e restituisce la risposta parlata."""
params = urllib.parse.urlencode({"q": query})
url = f"{SIBILLA_URL}?{params}"
with urllib.request.urlopen(url, timeout=30) as resp:
data = json.loads(resp.read().decode())
return {
"answer": data["answer"],
"speakable_text": data["speakable_text"],
"sources": data["sources"],
}
# In un handler Alexa:
def lambda_handler(event, context):
query = event["request"]["intent"]["slots"]["query"]["value"]
result = call_sibilla(query)
return {
"version": "1.0",
"response": {
"outputSpeech": {
"type": "SSML",
"ssml": f"<speak>{result['speakable_text']}</speak>"
},
"shouldEndSession": True
}
}
```
> **Nota:** Il campo `speakable_text` è già pulito da markup Markdown, URL, grassetti e codice — pronto per essere letto da un motore TTS.