Initial commit: RAGGER - Sistema RAG per associazione di volontariato
This commit is contained in:
commit
0204feb8bf
36 files changed
+4116
No files matched your search
@@ -0,0 +1,400 @@
|
||||
import streamlit as st
|
||||
from pathlib import Path
|
||||
import time
|
||||
|
||||
from config import settings
|
||||
from core import rag
|
||||
from agent import agent, AgentMode
|
||||
from ollama_client import ollama, LLMClient, PROVIDER_BASE_URLS
|
||||
from logger import get_logger
|
||||
|
||||
log = get_logger("app")
|
||||
|
||||
from document_processor import DocumentProcessor
|
||||
|
||||
SUPPORTED_EXTENSIONS = list(DocumentProcessor.SUPPORTED_EXTENSIONS.keys())
|
||||
|
||||
PROVIDER_MODELS = {
|
||||
"ollama": ["gemma2:9b", "llama3.2", "llama3.1", "mistral"],
|
||||
"opencode": ["claude-3.5-sonnet", "gpt-4o", "gemini-2.0-flash", "deepseek-r1", "llama-3.3-70b"],
|
||||
"openrouter": ["openrouter/auto", "anthropic/claude-3.5-sonnet", "google/gemini-pro-1.5", "meta-llama/llama-3.1-70b-instruct"],
|
||||
"groq": ["llama-3.1-70b-versatile", "llama-3.1-8b-instant", "mixtral-8x7b-32768"],
|
||||
"openai": ["gpt-4o", "gpt-4o-mini", "gpt-4-turbo"]
|
||||
}
|
||||
|
||||
st.set_page_config(
|
||||
page_title="RAGGER",
|
||||
page_icon="📚",
|
||||
layout="wide"
|
||||
)
|
||||
|
||||
|
||||
def save_settings(provider, model, api_key, base_url, ollama_host, ollama_model):
|
||||
env_path = Path(".env")
|
||||
lines = []
|
||||
|
||||
if env_path.exists():
|
||||
for line in env_path.read_text().splitlines():
|
||||
if line.startswith("LLM_PROVIDER="):
|
||||
lines.append(f"LLM_PROVIDER={provider}")
|
||||
elif line.startswith("LLM_MODEL="):
|
||||
lines.append(f"LLM_MODEL={model}")
|
||||
elif line.startswith("LLM_API_KEY="):
|
||||
lines.append(f"LLM_API_KEY={api_key}")
|
||||
elif line.startswith("LLM_BASE_URL="):
|
||||
lines.append(f"LLM_BASE_URL={base_url}")
|
||||
elif line.startswith("OLLAMA_HOST="):
|
||||
lines.append(f"OLLAMA_HOST={ollama_host}")
|
||||
elif line.startswith("OLLAMA_MODEL="):
|
||||
lines.append(f"OLLAMA_MODEL={ollama_model}")
|
||||
else:
|
||||
lines.append(line)
|
||||
else:
|
||||
lines = [
|
||||
f"LLM_PROVIDER={provider}",
|
||||
f"LLM_MODEL={model}",
|
||||
f"LLM_API_KEY={api_key}",
|
||||
f"LLM_BASE_URL={base_url}",
|
||||
f"OLLAMA_HOST={ollama_host}",
|
||||
f"OLLAMA_MODEL={ollama_model}",
|
||||
]
|
||||
|
||||
env_path.write_text("\n".join(lines) + "\n")
|
||||
log.info(f"Settings saved: provider={provider}, model={model}")
|
||||
|
||||
|
||||
def check_health():
|
||||
log.debug("Running startup health check")
|
||||
issues = []
|
||||
|
||||
if not ollama.is_available():
|
||||
issues.append("Ollama non raggiungibile")
|
||||
log.warning("Ollama not available at startup")
|
||||
|
||||
return issues
|
||||
|
||||
|
||||
startup_issues = check_health()
|
||||
if startup_issues:
|
||||
st.warning("⚠️ Problemi rilevati: " + ", ".join(startup_issues))
|
||||
|
||||
|
||||
def show_logs():
|
||||
"""Mostra i log dell'applicazione"""
|
||||
log_file = Path("logs/ragger.log")
|
||||
if log_file.exists():
|
||||
lines = log_file.read_text().splitlines()
|
||||
last_lines = lines[-50:] if len(lines) > 50 else lines
|
||||
return "\n".join(last_lines)
|
||||
return "Nessun log disponibile"
|
||||
|
||||
|
||||
def check_ollama_status():
|
||||
"""Verifica se Ollama sta elaborando qualcosa"""
|
||||
try:
|
||||
import httpx
|
||||
resp = httpx.get(f"{settings.OLLAMA_HOST}/api/tags", timeout=5)
|
||||
return resp.status_code == 200
|
||||
except:
|
||||
return False
|
||||
|
||||
|
||||
st.title("📚 RAGGER")
|
||||
st.caption("Sistema RAG con Agent Intelligente")
|
||||
|
||||
|
||||
with st.sidebar:
|
||||
st.header("⚙️ Modalità")
|
||||
|
||||
mode = st.selectbox(
|
||||
"Seleziona modalità",
|
||||
options=["chat", "lettera", "progetto", "impostazioni"],
|
||||
index=0,
|
||||
format_func=lambda x: {
|
||||
"chat": "💬 Chat",
|
||||
"lettera": "✉️ Lettera",
|
||||
"progetto": "📋 Progetto",
|
||||
"impostazioni": "⚙️ Impostazioni"
|
||||
}[x]
|
||||
)
|
||||
|
||||
st.divider()
|
||||
|
||||
with st.expander("📜 Log Applicazione", expanded=True):
|
||||
log_content = show_logs()
|
||||
st.text_area("", log_content, height=200, key="log_viewer", disabled=True)
|
||||
|
||||
col_log1, col_log2 = st.columns(2)
|
||||
with col_log1:
|
||||
if st.button("🔄 Aggiorna Log", use_container_width=True):
|
||||
st.rerun()
|
||||
with col_log2:
|
||||
if st.button("📋 Copia Log", use_container_width=True):
|
||||
st.write("Log copiato!")
|
||||
|
||||
ollama_status = check_ollama_status()
|
||||
if ollama_status:
|
||||
st.success("🟢 Ollama connesso")
|
||||
else:
|
||||
st.error("🔴 Ollama non risponde")
|
||||
|
||||
if mode == "impostazioni":
|
||||
st.divider()
|
||||
st.header("Configurazione")
|
||||
st.write(f"**Ollama:** {settings.OLLAMA_HOST}")
|
||||
st.write(f"**Modello:** {settings.OLLAMA_MODEL}")
|
||||
st.write(f"**Embedding:** {settings.OLLAMA_EMBED_MODEL}")
|
||||
|
||||
if st.button("Test Connessione Ollama"):
|
||||
with st.spinner("Testing..."):
|
||||
if ollama.is_available():
|
||||
st.success("✅ Ollama connesso!")
|
||||
else:
|
||||
st.error("❌ Ollama non raggiungibile")
|
||||
|
||||
stats = rag.get_stats()
|
||||
st.metric("Documenti indicizzati", stats["total_chunks"])
|
||||
|
||||
st.divider()
|
||||
|
||||
tab_imp, _ = st.tabs(["⚙️ Impostazioni"])
|
||||
with tab_imp:
|
||||
st.header("⚙️ Impostazioni Provider LLM")
|
||||
|
||||
current_provider = settings.LLM_PROVIDER
|
||||
all_providers = ["ollama", "opencode", "openrouter", "groq", "openai"]
|
||||
provider = st.selectbox(
|
||||
"Provider",
|
||||
options=all_providers,
|
||||
index=all_providers.index(current_provider) if current_provider in all_providers else 0
|
||||
)
|
||||
|
||||
if provider == "ollama":
|
||||
st.info("🏠 **Ollama** - Locale/On-premise")
|
||||
ollama_host = st.text_input("Ollama Host", value=settings.OLLAMA_HOST)
|
||||
ollama_models = ollama.list_models() or PROVIDER_MODELS["ollama"]
|
||||
model = st.selectbox("Modello", options=ollama_models, index=0)
|
||||
api_key = ""
|
||||
base_url = ""
|
||||
elif provider == "opencode":
|
||||
st.info("🧘 **OpenCode Zen**")
|
||||
api_key = st.text_input("API Key", value=settings.LLM_API_KEY or "", type="password")
|
||||
base_url = st.text_input("Base URL", value=settings.LLM_BASE_URL or PROVIDER_BASE_URLS["opencode"])
|
||||
models = PROVIDER_MODELS.get(provider, [])
|
||||
model = st.selectbox("Modello", options=models, index=0)
|
||||
ollama_host = settings.OLLAMA_HOST
|
||||
else:
|
||||
st.info(f"🌐 **{provider.capitalize()}**")
|
||||
api_key = st.text_input("API Key", value=settings.LLM_API_KEY or "", type="password")
|
||||
default_url = PROVIDER_BASE_URLS.get(provider, "")
|
||||
base_url = st.text_input("Base URL", value=settings.LLM_BASE_URL or default_url)
|
||||
models = PROVIDER_MODELS.get(provider, [])
|
||||
model = st.selectbox("Modello", options=models, index=0)
|
||||
ollama_host = settings.OLLAMA_HOST
|
||||
|
||||
col1, col2 = st.columns(2)
|
||||
with col1:
|
||||
if st.button("💾 Salva", type="primary"):
|
||||
save_settings(provider, model, api_key, base_url, ollama_host, model if provider == "ollama" else settings.OLLAMA_MODEL)
|
||||
st.success("✅ Salvato!")
|
||||
with col2:
|
||||
if st.button("🔄 Ricarica"):
|
||||
st.rerun()
|
||||
|
||||
st.json({
|
||||
"provider": settings.LLM_PROVIDER,
|
||||
"model": settings.LLM_MODEL or settings.OLLAMA_MODEL,
|
||||
})
|
||||
|
||||
|
||||
if mode == "chat":
|
||||
with st.sidebar:
|
||||
st.divider()
|
||||
st.header("📄 Documenti")
|
||||
|
||||
uploaded = st.file_uploader(
|
||||
"Carica documento",
|
||||
type=SUPPORTED_EXTENSIONS,
|
||||
accept_multiple_files=True
|
||||
)
|
||||
|
||||
if uploaded:
|
||||
for file in uploaded:
|
||||
if st.button(f"Indicizza: {file.name}", key=f"idx_{file.name}"):
|
||||
save_path = Path(settings.UPLOAD_DIR) / file.name
|
||||
save_path.write_bytes(file.getbuffer())
|
||||
|
||||
with st.spinner(f"Indicizzando {file.name}..."):
|
||||
try:
|
||||
result = rag.ingest_document(save_path)
|
||||
st.success(f"✅ {result['chunks_created']} chunks")
|
||||
except Exception as e:
|
||||
st.error(f"❌ {e}")
|
||||
|
||||
stats = rag.get_stats()
|
||||
st.metric("Chunks indicizzati", stats["total_chunks"])
|
||||
|
||||
st.header("💬 Chat con i tuoi documenti")
|
||||
|
||||
if "messages" not in st.session_state:
|
||||
st.session_state.messages = []
|
||||
|
||||
for msg in st.session_state.messages:
|
||||
with st.chat_message(msg["role"]):
|
||||
st.write(msg["content"])
|
||||
if "sources" in msg:
|
||||
with st.expander("📚 Fonti"):
|
||||
for src in msg["sources"]:
|
||||
st.write(f"- **{src['source']}**" +
|
||||
(f" (pag. {src['page']})" if src['page'] else ""))
|
||||
|
||||
if prompt := st.chat_input("Fai una domanda..."):
|
||||
log.info(f"Chat prompt: {prompt[:50]}...")
|
||||
st.session_state.messages.append({"role": "user", "content": prompt})
|
||||
|
||||
with st.chat_message("user"):
|
||||
st.write(prompt)
|
||||
|
||||
with st.chat_message("assistant"):
|
||||
with st.spinner("Elaborazione in corso..."):
|
||||
result = agent.process(prompt, mode="chat")
|
||||
|
||||
st.write(result["answer"])
|
||||
|
||||
if result.get("sources"):
|
||||
with st.expander("📚 Fonti"):
|
||||
for src in result["sources"]:
|
||||
st.write(f"- **{src['source']}**" +
|
||||
(f" (pag. {src['page']})" if src['page'] else "") +
|
||||
f" - Score: {src['score']:.2f}")
|
||||
st.caption(src["excerpt"])
|
||||
|
||||
st.session_state.messages.append({
|
||||
"role": "assistant",
|
||||
"content": result["answer"],
|
||||
"sources": result.get("sources", [])
|
||||
})
|
||||
|
||||
|
||||
elif mode == "lettera":
|
||||
with st.sidebar:
|
||||
st.divider()
|
||||
st.header("📄 Documenti")
|
||||
|
||||
uploaded = st.file_uploader(
|
||||
"Carica documento",
|
||||
type=SUPPORTED_EXTENSIONS,
|
||||
accept_multiple_files=True
|
||||
)
|
||||
|
||||
if uploaded:
|
||||
for file in uploaded:
|
||||
if st.button(f"Indicizza: {file.name}", key=f"idx_{file.name}"):
|
||||
save_path = Path(settings.UPLOAD_DIR) / file.name
|
||||
save_path.write_bytes(file.getbuffer())
|
||||
|
||||
with st.spinner(f"Indicizzando {file.name}..."):
|
||||
try:
|
||||
result = rag.ingest_document(save_path)
|
||||
st.success(f"✅ {result['chunks_created']} chunks")
|
||||
except Exception as e:
|
||||
st.error(f"❌ {e}")
|
||||
|
||||
stats = rag.get_stats()
|
||||
st.metric("Chunks indicizzati", stats["total_chunks"])
|
||||
|
||||
st.header("✉️ Genera Lettera")
|
||||
|
||||
topic = st.text_area("Argomento della lettera", placeholder="Es: Richiesta di patrocinio per evento annuale...")
|
||||
context = st.text_area("Contesto aggiuntivo (opzionale)", placeholder="Dettagli specifici, destinatario, tono...")
|
||||
|
||||
col1, col2 = st.columns([1, 1])
|
||||
with col1:
|
||||
if st.button("✉️ Genera Lettera", type="primary", use_container_width=True):
|
||||
if not topic:
|
||||
st.warning("Inserisci un argomento")
|
||||
else:
|
||||
with st.spinner("Generazione in corso..."):
|
||||
result = agent.process(topic, mode="lettera", context=context)
|
||||
|
||||
st.session_state["last_letter"] = result["answer"]
|
||||
|
||||
with col2:
|
||||
if st.button("🗑️ Pulisci", use_container_width=True):
|
||||
if "last_letter" in st.session_state:
|
||||
del st.session_state["last_letter"]
|
||||
st.rerun()
|
||||
|
||||
if "last_letter" in st.session_state:
|
||||
st.divider()
|
||||
st.subheader("Lettera generata")
|
||||
st.text_area("", st.session_state["last_letter"], height=400, key="letter_output")
|
||||
|
||||
c1, c2 = st.columns(2)
|
||||
with c1:
|
||||
st.button("📋 Copia", on_click=lambda: st.write("Copiato!"))
|
||||
with c2:
|
||||
st.button("💾 Salva")
|
||||
|
||||
|
||||
elif mode == "progetto":
|
||||
with st.sidebar:
|
||||
st.divider()
|
||||
st.header("📄 Documenti")
|
||||
|
||||
uploaded = st.file_uploader(
|
||||
"Carica documento",
|
||||
type=SUPPORTED_EXTENSIONS,
|
||||
accept_multiple_files=True
|
||||
)
|
||||
|
||||
if uploaded:
|
||||
for file in uploaded:
|
||||
if st.button(f"Indicizza: {file.name}", key=f"idx_{file.name}"):
|
||||
save_path = Path(settings.UPLOAD_DIR) / file.name
|
||||
save_path.write_bytes(file.getbuffer())
|
||||
|
||||
with st.spinner(f"Indicizzando {file.name}..."):
|
||||
try:
|
||||
result = rag.ingest_document(save_path)
|
||||
st.success(f"✅ {result['chunks_created']} chunks")
|
||||
except Exception as e:
|
||||
st.error(f"❌ {e}")
|
||||
|
||||
stats = rag.get_stats()
|
||||
st.metric("Chunks indicizzati", stats["total_chunks"])
|
||||
|
||||
st.header("📋 Genera Proposta Progetto")
|
||||
|
||||
title = st.text_input("Titolo del progetto", placeholder="Es: Insieme senza confini")
|
||||
description = st.text_area("Descrizione", placeholder="Descrivi l'obiettivo, il target, le attività previste...")
|
||||
|
||||
col1, col2 = st.columns([1, 1])
|
||||
with col1:
|
||||
if st.button("📋 Genera Proposta", type="primary", use_container_width=True):
|
||||
if not title or not description:
|
||||
st.warning("Inserisci titolo e descrizione")
|
||||
else:
|
||||
with st.spinner("Generazione in corso..."):
|
||||
result = agent.process(description, mode="progetto", context=title)
|
||||
|
||||
st.session_state["last_project"] = result["answer"]
|
||||
|
||||
with col2:
|
||||
if st.button("🗑️ Pulisci", use_container_width=True):
|
||||
if "last_project" in st.session_state:
|
||||
del st.session_state["last_project"]
|
||||
st.rerun()
|
||||
|
||||
if "last_project" in st.session_state:
|
||||
st.divider()
|
||||
st.subheader("Proposta generata")
|
||||
st.text_area("", st.session_state["last_project"], height=500, key="project_output")
|
||||
|
||||
c1, c2, c3 = st.columns(3)
|
||||
with c1:
|
||||
st.button("📋 Copia")
|
||||
with c2:
|
||||
st.button("💾 Salva")
|
||||
with c3:
|
||||
st.button("📧 Email")
|
||||
Reference in new issue
Block a user