✨ 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
7.8 KiB
7.8 KiB
🔄 Sibilla — Diagramma di Flusso
Versione: 0.2.0
Ultimo aggiornamento: 18 Luglio 2026
Pipeline di Ricerca Completa (Mermaid)
flowchart TD
A["👤 Utente invia query"] --> B["🌐 Web UI (POST /api/ask)<br/>o<br/>🔗 API (GET/POST /api/v1/query)"]
B --> C["🔮 Query Expansion<br/>Gemini genera 3 varianti"]
C --> D{"🔍 Degoog<br/>disponibile?"}
D -->|✅ Sì| E["Cerca con Degoog<br/>prime 2 varianti"]
D -->|❌ No| F{"🔍 SearXNG<br/>disponibile?"}
E --> G{"Risultati<br/>trovati?"}
G -->|✅ Sì| H["Aggrega risultati<br/>Dedup URL"]
G -->|❌ No| F
F -->|✅ Sì| I["Cerca con SearXNG<br/>prime 2 varianti"]
F -->|❌ No| J["🔍 DuckDuckGo<br/>fallback"]
I --> K{"Risultati<br/>trovati?"}
K -->|✅ Sì| H
K -->|❌ No| J
J --> L{"Risultati<br/>trovati?"}
L -->|✅ Sì| H
L -->|❌ No| M{"Tutti i backend<br/>falliti?"}
M -->|✅ Sì| N["⚠️ Fallback Pure LLM<br/>Gemini risponde senza fonti"]
M -->|❌ No| O["❌ Errore: 502<br/>Restituisci errore"]
H --> P["📥 PageFetcher<br/>Scarica full content<br/>max 5 pagine, 300ms delay"]
P --> Q["📝 Build Prompt<br/>Fonti numerate + data"]
Q --> R["🧠 Gemini Synthesis<br/>SYNTHESIS_SYSTEM_PROMPT<br/>temp=0.4, max_tokens=4096"]
R --> S["✅ JSON Response<br/>answer + html_answer<br/>+ speakable_text + sources"]
N --> T["✅ JSON Response<br/>solo LLM, nessuna fonte"]
style A fill:#a78bfa,color:#fff
style S fill:#34d399,color:#000
style T fill:#fbbf24,color:#000
style O fill:#f87171,color:#fff
style R fill:#8b5cf6,color:#fff
Flusso Richiesta Web (Frontend)
sequenceDiagram
actor U as 👤 Utente
participant B as Browser (app.js)
participant F as Flask /api/ask
participant P as Pipeline
participant G as Gemini
participant S as Search Engines
U->>B: Scrivi query e clicca "Interroga"
B->>B: Disabilita form, mostra spinner
B->>F: POST /api/ask {query}
F->>P: _run_search_pipeline(query, num_sources)
P->>G: rewrite_query(query, 3 varianti)
G-->>P: [originale, var1, var2, var3]
P->>S: Degoog.search(var1) + Degoog.search(var2)
S-->>P: SearchResponse {results[]}
P->>S: PageFetcher.fetch_results(top5)
S-->>P: SearchResult[] con full_content
P->>G: synthesize(system_prompt, user_prompt)
G-->>P: SynthesisResult {answer, tokens}
P-->>F: enriched[], response_data
F-->>B: JSON {answer, html_answer, speakable_text, sources[]}
B->>B: Render HTML answer, mostra fonti
B->>U: Mostra risultato
Flusso API v1
sequenceDiagram
actor C as 🔗 Client (curl/Lambda)
participant F as Flask /api/v1/query
participant P as Pipeline
participant G as Gemini
participant S as Search Engines
C->>F: GET /api/v1/query?q=domanda
F->>F: Estrai query da query string
F->>P: _run_search_pipeline(query, num_sources=5)
P->>G: rewrite_query(query, 3 varianti)
G-->>P: [originale, var1, var2, var3]
alt Degoog OK
P->>S: Degoog.search(varianti)
S-->>P: Risultati
else SearXNG fallback
P->>S: SearXNG.search(varianti)
S-->>P: Risultati
else DuckDuckGo fallback
P->>S: DDG.search(query)
S-->>P: Risultati
else Pure LLM
P->>G: synthesize(FALLBACK_PROMPT, query)
G-->>P: Risposta senza fonti
end
P->>S: PageFetcher.fetch_results(top5)
S-->>P: Contenuti completi
P->>G: synthesize(SYNTHESIS_PROMPT, fonti)
G-->>P: SynthesisResult
P-->>F: enriched[], response_data
F-->>C: JSON {query, answer, speakable_text, sources[]}
Flusso MCP Server
sequenceDiagram
actor A as 🤖 AI Agent (Claude/Gemini)
participant M as MCP Server (stdio)
participant D as DegoogClient
participant S as SearXNGClient
participant G as DuckDuckGoClient
participant F as PageFetcher
A->>M: JSON-RPC: initialize
M-->>A: {protocolVersion, capabilities, serverInfo}
A->>M: JSON-RPC: tools/list
M-->>A: {tools: [web_search, fetch_page]}
A->>M: JSON-RPC: tools/call {name: "web_search", arguments: {query: "..."}}
M->>D: search(query)
alt Degoog ha risultati
D-->>M: SearchResponse
else Degoog vuoto
M->>S: search(query)
alt SearXNG ha risultati
S-->>M: SearchResponse
else Entrambi vuoti
M->>G: search(query)
G-->>M: SearchResponse
end
end
M-->>A: {content: [{text: "Risultati formattati..."}]}
A->>M: JSON-RPC: tools/call {name: "fetch_page", arguments: {url: "..."}}
M->>F: fetch(url)
F-->>M: Testo estratto (max 8000 chars)
M-->>A: {content: [{text: "Contenuto pagina..."}]}
Macchina a Stati: Pipeline di Ricerca
stateDiagram-v2
[*] --> ReceivingQuery: Utente invia query
ReceivingQuery --> QueryExpansion: Query valida
ReceivingQuery --> Error400: Query vuota
QueryExpansion --> DegoogSearch: Gemini espande query
DegoogSearch --> AggregatingResults: Risultati trovati
DegoogSearch --> SearXNGFallback: Nessun risultato
SearXNGFallback --> AggregatingResults: Risultati trovati
SearXNGFallback --> DDGFallback: Nessun risultato
DDGFallback --> AggregatingResults: Risultati trovati
DDGFallback --> PureLLMFallback: Nessun risultato
AggregatingResults --> FetchingContent: Dedup URL completato
FetchingContent --> BuildingPrompt: Full content scaricato
BuildingPrompt --> Synthesizing: Prompt assemblato
Synthesizing --> ReturningResponse: Sintesi completata
Synthesizing --> ReturningErrorResponse: Errore Gemini
PureLLMFallback --> SynthesizingFallback: Usa FALLBACK_PROMPT
SynthesizingFallback --> ReturningFallback: Risposta senza fonti
Error400 --> [*]
ReturningResponse --> [*]
ReturningErrorResponse --> [*]
ReturningFallback --> [*]
Diagramma dei Componenti
graph TB
subgraph "Interfacce Utente"
WEB["🌐 Web UI<br/>templates/index.html<br/>static/app.js"]
API["🔗 REST API<br/>/api/ask<br/>/api/v1/query"]
MCP["🤖 MCP Server<br/>stdio JSON-RPC"]
end
subgraph "Flask Application"
APP["app.py<br/>Flask App Factory"]
PIPELINE["_run_search_pipeline()<br/>Pipeline condivisa"]
end
subgraph "Servizi Core"
AI["ai.py<br/>GeminiClient"]
SEARCH["search.py"]
MD["markdown.py<br/>Render + Strip"]
PROMPTS["prompts.py<br/>System Prompts"]
end
subgraph "Backend Esterni"
GEMINI["🧠 Google Gemini API<br/>gemini-2.5-flash"]
DEGOOG["🔍 Degoog<br/>self-hosted"]
SEARXNG["🔍 SearXNG<br/>self-hosted"]
DDG["🔍 DuckDuckGo<br/>HTML scraping"]
end
WEB --> APP
API --> APP
MCP --> SEARCH
APP --> PIPELINE
PIPELINE --> AI
PIPELINE --> SEARCH
PIPELINE --> PROMPTS
PIPELINE --> MD
AI --> GEMINI
SEARCH --> DEGOOG
SEARCH --> SEARXNG
SEARCH --> DDG
Strategia di Query Expansion
flowchart LR
Q["Query originale:<br/>'ultimo film sull Odissea'"] --> GE["Gemini rewrite_query()"]
GE --> V1["Variante 1:<br/>film Odissea 2026 recensione"]
GE --> V2["Variante 2:<br/>ultimo adattamento cinematografico Odissea"]
GE --> V3["Variante 3:<br/>The Odyssey movie latest release"]
V1 --> D1["Degoog search"]
V2 --> D2["Degoog search"]
D1 --> AGG["Aggregazione<br/>+ Deduplicazione"]
D2 --> AGG
Legenda
| Simbolo | Significato |
|---|---|
| 🔍 | Motore di ricerca |
| 🧠 | LLM / AI |
| 📥 | Fetch / Download |
| 📝 | Costruzione prompt |
| ✅ | Successo |
| ❌ | Errore / Fallimento |
| ⚠️ | Degraded mode |
| 👤 | Interazione utente |
| 🔗 | API / Programmatico |
| 🤖 | Agente AI esterno |