✨ 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
8.8 KiB
📡 Sibilla — Documentazione API REST
Versione: 0.2.0
Ultimo aggiornamento: 18 Luglio 2026
Indice
- Panoramica
- Health Check
- API Ask (Web)
- API v1 Query (Programmatica)
- Codici di errore
- Esempi curl
- 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:
curl http://localhost:5000/api/health
Response 200 OK:
{
"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:
{
"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:
{
"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:
{
"error": "La query non può essere vuota."
}
Response 502 Bad Gateway:
{
"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:
curl "http://localhost:5000/api/v1/query?q=ultimo+film+sull%27Odissea"
POST /api/v1/query
Stesso endpoint, con query nel body JSON.
Request:
{
"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:
{
"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:
{
"error": "Missing query parameter 'q'."
}
Response 502 Bad Gateway:
{
"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 →
answervuoto conerrornel JSON - Se Gemini restituisce errore → messaggio di errore inline in
answer
Esempi curl
Health check
curl -s http://localhost:5000/api/health | python -m json.tool
Ricerca via API v1 (GET)
curl -s "http://localhost:5000/api/v1/query?q=cosa+sono+i+raggi+cosmici" | python -m json.tool
Ricerca via API v1 (POST)
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)
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)
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
# 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:
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.