File size: 6,432 Bytes
c3231de
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
130a342
c3231de
 
 
35e8517
130a342
c3231de
 
35e8517
130a342
 
 
 
 
 
c3231de
130a342
 
 
 
 
c3231de
130a342
 
 
 
 
 
 
 
 
21f06ea
 
130a342
 
 
21f06ea
 
 
35e8517
 
130a342
21f06ea
130a342
c3231de
35e8517
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c3231de
130a342
35e8517
 
 
130a342
35e8517
 
c3231de
 
 
 
 
 
35e8517
130a342
c3231de
130a342
 
35e8517
130a342
 
 
 
 
 
35e8517
21f06ea
35e8517
21f06ea
130a342
c3231de
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35e8517
 
 
 
130a342
 
35e8517
 
 
 
c3231de
35e8517
c3231de
 
 
130a342
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
import os
import pytz
import gradio as gr
from datetime import datetime
from pinecone import Pinecone
from llama_index.llms.groq import Groq
from llama_index.core import VectorStoreIndex, Settings
from llama_index.vector_stores.pinecone import PineconeVectorStore
from llama_index.embeddings.huggingface import HuggingFaceEmbedding

# --- CONFIGURAZIONE ---
GROQ_API_KEY = os.getenv("GROQ_API_KEY")
PINECONE_API_KEY = os.getenv("PINECONE_API_KEY")

if not GROQ_API_KEY or not PINECONE_API_KEY:
    raise ValueError("⚠️ ERRORE CRITICO: API Keys mancanti nei Secrets!")

# --- SETUP MODELLI (globali: costosi da ricaricare, non contengono stato conversazione) ---
embed_model = HuggingFaceEmbedding(model_name="intfloat/multilingual-e5-large")
Settings.embed_model = embed_model

# max_retries=0: disabilita i retry automatici su errore 429 (limite giornaliero Groq).
llm = Groq(model="llama-3.3-70b-versatile", api_key=GROQ_API_KEY, max_retries=0)
Settings.llm = llm

# --- INDICE PINECONE (globale: read-only, nessuno stato conversazione) ---
try:
    pc = Pinecone(api_key=PINECONE_API_KEY)
    _pinecone_index = VectorStoreIndex.from_vector_store(
        vector_store=PineconeVectorStore(
            pinecone_index=pc.Index("prometheus-main"),
            namespace="accessibility-poc"
        )
    )
    print("✅ Connessione Pinecone stabilita.")
except Exception as e:
    _pinecone_index = None
    print(f"❌ Errore connessione Pinecone: {e}")

SYSTEM_PROMPT = (
    "Sei Prometheus, un assistente virtuale per l'Accessibilità e il Turismo Inclusivo. "
    "Il tuo compito è fornire informazioni utili come se fossi un operatore esperto allo sportello informazioni."
    "\n\n"
    "🚫 DIVIETI ASSOLUTI DI LINGUAGGIO (Blacklist): "
    "- MAI usare parole come: 'scheda tecnica', 'json', 'database', 'record', 'file', 'documento', 'valore', 'campo', 'null'. "
    "- EVITA frasi come: 'In base ai dati', 'Secondo la scheda', 'Il sistema dice che'. "
    "\n\n"
    "🧠 DIZIONARIO SEMANTICO (Interpretazione): "
    "- 'ingresso_senza_gradini: true' = 'L ingresso è in piano / senza barriere'. "
    "- 'ingresso_senza_gradini: false' = 'Sono presenti gradini all ingresso'. "
    "- 'bagni_accessibili: true' = 'È disponibile un bagno attrezzato'. "
    "\n\n"
    "✅ REGOLE DI RISPOSTA (Stile Conversazionale): "
    "1. Conferme (True): Rispondi direttamente. Es: Si, confermo che la struttura è dotata di ascensore. "
    "2. Negazioni (False): Sii gentile. Es: Purtroppo al momento non risulta presente un servizio di audioguida. "
    "3. Dati Mancanti (Null/Assente): Non citare la scheda. Di semplicemente: Non ho informazioni aggiornate su questo specifico servizio. oppure Al momento non mi risulta questa informazione, ti consiglio di verificare contattando la struttura. "
    "4. Note (Stringhe): Integra le note nel discorso. Es: Ti segnalo pero che l ascensore è attualmente in manutenzione. "
    "5. FEDELTA ASSOLUTA (Anti-Allucinazione): Attieniti ESCLUSIVAMENTE ai fatti forniti nel contesto. Non aggiungere MAI commenti personali, supposizioni o frasi di circostanza."
    "\n\n"
    "OBIETTIVO: Sii neutrale, preciso e disponibile. L utente deve sentirsi aiutato da una persona competente."
)

# --- DIZIONARIO GLOBALE: engine per session_hash ---
# Gradio 3.x non ha gr.State accessibile facilmente nei Blocks con fn complesse.
# Usiamo un dizionario server-side keyed su request.session_hash (isolamento per sessione).
_session_engines = {}

def get_or_create_engine(session_hash):
    """Restituisce l'engine della sessione, creandolo se non esiste."""
    if session_hash not in _session_engines:
        if _pinecone_index is None:
            return None
        try:
            _session_engines[session_hash] = _pinecone_index.as_chat_engine(
                chat_mode="condense_plus_context",
                system_prompt=SYSTEM_PROMPT,
                verbose=True
            )
            print(f"✅ Nuovo engine creato per sessione: {session_hash}")
        except Exception as e:
            print(f"❌ Errore creazione engine: {e}")
            return None
    return _session_engines[session_hash]

# --- RISPOSTA ---
def respond(message, history, request: gr.Request):
    session_hash = request.session_hash if request else "default"
    engine = get_or_create_engine(session_hash)

    if engine is None:
        return "⚠️ Servizio momentaneamente non disponibile. Riprova tra qualche istante."

    try:
        fuso_italia = pytz.timezone("Europe/Rome")
        adesso = datetime.now(fuso_italia)
        ora_data = adesso.strftime("%A %d %B %Y, ore %H:%M")
        messaggio_con_data = f"[Oggi è {ora_data}]\n{message}"
        return str(engine.chat(messaggio_con_data))

    except Exception as e:
        err_type = type(e).__name__
        err_str = str(e)
        print(f"❌ ERRORE in chat [{session_hash}]: {err_type}: {err_str}")

        if "429" in err_str or "rate_limit" in err_str.lower() or "RateLimitError" in err_type:
            return (
                "⏳ Il servizio ha raggiunto il limite di utilizzo giornaliero. "
                "Riprova domani mattina dopo le 02:00 (ora italiana). "
                "Ci scusiamo per il disagio."
            )

        return "Mi scuso, al momento non riesco a recuperare questa informazione. Riprova tra qualche istante."

# --- UI ---
disclaimer = """
<div style="
    margin-top: 24px;
    padding: 12px 20px;
    border-top: 1px solid #d1d5db;
    background-color: #f9fafb;
    border-radius: 8px;
    text-align: center;
    font-size: 13px;
    color: #6b7280;
    line-height: 1.6;
">
    <strong>Versione dimostrativa</strong> &mdash;
    Questa applicazione è a scopo esclusivamente illustrativo.
    Tutte le strutture, i dati e le informazioni presenti sono fittizi
    e non corrispondono a realtà esistenti.
</div>
"""

demo = gr.ChatInterface(
    fn=respond,
    title="ℹ️ Sportello Accessibilità - Prometheus",
    description=(
        "Chiedimi informazioni sui servizi per persone con disabilità di musei e luoghi pubblici.<br>"
        "Applicazione dimostrativa con aziende di agriturismo fittizie nella città di Siena."
    ),
    examples=["Ci sono agriturismo accessibili a Siena?", "Parcheggi per disabili?"],
    cache_examples=False
)

with demo:
    gr.HTML(disclaimer)

if __name__ == "__main__":
    demo.launch(server_name="0.0.0.0", server_port=7860)