- Python 96.3%
- PLpgSQL 2.5%
- Dockerfile 0.7%
- Shell 0.5%
| api | ||
| nginx | ||
| postgres | ||
| webapp | ||
| worker | ||
| .env.example | ||
| .gitignore | ||
| build.sh | ||
| CLAUDE.md | ||
| deploy.sh | ||
| docker-compose.override.yml | ||
| docker-compose.yml | ||
| LICENSE | ||
| README.md | ||
PeaceLink AI — Assistente per attivisti pacifisti e ambientalisti
Assistente documentale per ricerca e approfondimento su pace, disarmo, ambiente e diritti umani. Sviluppato da PeaceLink.
Architettura
webapp/ Streamlit UI + agente LangGraph + RAG
worker/ Worker indicizzazione documenti caricati
api/ Widget chat pubblico (FastAPI, riusa agente/RAG di webapp/)
docker-compose.yml PostgreSQL + pgvector + Langfuse + webapp + api + server MCP
# Repository separati (peacelink/ai/):
../mcp-peacedata/ Server MCP per PeaceData (data.peacelink.it)
../mcp-guardian/ Server MCP per The Guardian Open Platform
../import/ Pipeline di importazione dati
../ui/ Widget JS (js/chat-widget.js) e asset statici di peacelink.it
L'intent routing è definito nel database (tabelle intents e research_profiles): classifica le query, determina i pesi RAG per fonte e seleziona i server MCP da caricare per ogni richiesta. Gli intenti si gestiscono dalla UI (pagina Gestione intenti) oppure modificando il seed in postgres/10-intents.sql.
Autenticazione (Keycloak)
In produzione l'accesso è protetto da oauth2-proxy davanti alla webapp, configurato con Keycloak come provider OIDC.
Browser → Traefik → oauth2-proxy → Keycloak (login) → webapp
L'identità dell'utente viene propagata via header X-Auth-Request-Email, letto da webapp/auth.py. Tutta la persistenza per utente (sessioni, documenti caricati, profili) è indicizzata sull'email — se un utente cambia email occorre aggiornare il DB.
Configurazione Keycloak
Realm peacelink su login.peacelink.it:
- Client
ai-assistant:- Client authentication: ON (confidential — oauth2-proxy è server-side)
- Standard Flow: ON, Direct Access Grants: OFF
- Redirect URI:
https://ai.peacelink.it/oauth2/callback
- Realm role
ai-assistant-access— assegnato manualmente dall'admin agli utenti autorizzati - Authentication flow
ai-assistant-browser(duplicato dabrowser):ai-assistant-browser formssub-flow: Required (non Alternative — altrimenti il role check scatta prima del login)- Sub-flow
ai-assistant-role-check(Generic, Conditional):Condition - User Role(Required): roleai-assistant-access, Negate: ONDeny Access(Required)
- Associato al client: Advanced → Authentication flow overrides → Browser Flow
Attenzione: il sub-flow del role check deve essere Conditional, non Required. Con Required il
Deny Accessscatta sempre, indipendentemente dal ruolo.
Manifesti Kubernetes
In pck3s/ai/oauth2-proxy/deployment.yaml:
- Provider:
oidc, issuer:https://login.peacelink.it/realms/peacelink - Client ID/secret dal secret
pck-ai(chiavikeycloak-ai-assistant-client-id,keycloak-ai-assistant-client-secret) --email-domain=*— il gating è interamente in Keycloak
Aggiungere un nuovo utente
- L'utente si registra su
login.peacelink.it(self-registration abilitata) - Admin: Users → seleziona utente → Role mapping → Assign role →
ai-assistant-access
Cambio email utente
L'email è usata come identificatore in più tabelle. Se un utente cambia email in Keycloak, aggiornare il DB sul cluster:
kubectl exec -n ai deployment/pck-ai-webapp -it -- psql $DATABASE_URL
BEGIN;
UPDATE users SET email = 'new@email.com' WHERE email = 'old@email.com';
UPDATE rag_documents SET uploaded_by = 'new@email.com' WHERE uploaded_by = 'old@email.com';
UPDATE research_profiles SET created_by = 'new@email.com' WHERE created_by = 'old@email.com';
COMMIT;
chat_sessions e chat_messages sono collegati tramite user_id (FK intero) e non richiedono aggiornamenti.
Revocare l'accesso
Rimuovere il ruolo ai-assistant-access in Keycloak. Il cookie di sessione oauth2-proxy ha una durata di 7 giorni — per revoca immediata: Users → Sessions → Sign out all sessions.
Sviluppo locale (senza autenticazione)
AUTH_ENABLED=false
DEV_USER_EMAIL=dev@example.com
Avvio rapido
cp .env.example .env
# Compilare almeno: ANTHROPIC_API_KEY (o OPENAI_API_KEY), EMBEDDING_API_KEY,
# LANGFUSE_NEXTAUTH_SECRET, LANGFUSE_SALT
docker compose up -d
# Prima volta: configurare le chiavi Langfuse (vedi sezione Langfuse)
# Importare i documenti (dal repo ../import/):
PYENV_VERSION=pck-ai pyenv exec python import_docs.py --source peacelink
Con Colima, se vuoi usare il modello locale nella LAN
colima start --network-address
Importazione documenti RAG
Vedere ../import/README.md per le istruzioni complete: fonti, chunk ID, aggiungere nuovi connettori, dump/restore su produzione.
Server MCP
PeaceData (../mcp-peacedata/)
Un singolo server FastMCP espone tutti i domini come sub-app montate su percorsi separati. La webapp si connette al percorso corrispondente all'intent — l'agente vede solo gli strumenti del dominio pertinente.
| Dominio | Percorso | File | Dati |
|---|---|---|---|
| ARMI | /sipri |
domains/sipri.py |
Trasferimenti armi SIPRI |
| CAMPAGNE | /navco |
domains/navco.py |
Campagne nonviolente NAVCO |
| NUCLEARE | /nuclear-weapons-dataset |
domains/nuclear.py |
Sistemi d'arma nucleari (CSR) |
Vedere ../mcp-peacedata/README.md per setup e guida allo sviluppo.
Guardian (../mcp-guardian/)
Ricerca notizie via The Guardian Open Platform API. Strumenti: search_news, get_latest_news, get_sections. Richiede GUARDIAN_API_KEY.
Webapp
| File | Ruolo |
|---|---|
app.py |
Punto di ingresso, navigazione |
chat.py |
UI di chat e gestione messaggi |
agent.py |
Agente LangGraph (retrieve_context → agent → execute_tools) |
config.py |
Configurazione da variabili d'ambiente + registry server MCP |
vector_store.py |
Recupero RAG da pgvector (slot bilanciati per fonte) |
prompts/system.md |
System prompt (contiene {data_sources}) |
prompts/query_rewriter.md |
Classificatore di intent (contiene {intent_types}) |
Sviluppo locale
# In .env:
AUTH_ENABLED=false
DEV_USER_EMAIL=dev@example.com
streamlit run webapp/app.py
Widget chat pubblico (api/)
Widget chat embeddabile su peacelink.it (repo separato ../ui, js/chat-widget.js), servito da
un secondo servizio FastAPI che riusa l'agente/RAG di webapp/ senza le funzioni editoriali.
Autenticazione delegata interamente a phpeace — nessuna chiamata Keycloak diretta da questo
servizio. Dettagli architetturali completi (modello di auth, identità di sessione, endpoint,
deploy k3s) in CLAUDE.md, sezione "Public chat widget (api/)".
Sviluppo locale
# In .env:
WIDGET_SESSION_SECRET=... # openssl rand -base64 32
docker compose up -d api
phpeace (repo separato) deve avere chat_api_url configurato (in custom/config.php) e i due
Keycloak client necessari (phpeace-chat-bridge per la chiamata server-to-server, il ruolo
chat-user/chat-sponsor per l'accesso) — vedi CLAUDE.md per il modello di auth completo e la
guida al networking locale (chat.dev.peacelink.it).
Build del widget JS (repo ../ui):
npm run build # genera cdn/dist/chat-widget.min.js + mermaid.min.js/vis-network.min.js
Langfuse
Incluso nel docker-compose, accessibile su http://localhost:3000.
Primo avvio:
openssl rand -base64 32 # → LANGFUSE_NEXTAUTH_SECRET
openssl rand -base64 32 # → LANGFUSE_SALT
Aggiungere le chiavi a .env, avviare i container, aprire http://localhost:3000, creare una coppia di chiavi API in Settings → API Keys e aggiungerle a .env:
LANGFUSE_PUBLIC_KEY=pk-lf-...
LANGFUSE_SECRET_KEY=sk-lf-...
Per disabilitare: LANGFUSE_ENABLED=false.
Variabili d'ambiente principali
| Variabile | Default | Descrizione |
|---|---|---|
LLM_MODEL |
anthropic/claude-sonnet-4-6 |
Modello LLM (formato LiteLLM) |
REWRITER_MODEL |
LLM_MODEL |
Modello per classificazione intent |
EMBEDDING_MODEL |
text-embedding-3-small |
Modello embedding |
POSTGRES_DB |
pck-ai |
Nome del database |
VECTOR_DB_COLLECTION |
pck_docs |
Collezione pgvector |
RAG_K |
8 |
Documenti totali per query RAG |
MCP_PEACEDATA_URL |
— | URL base server PeaceData MCP |
SUPERSET_URL |
https://data.peacelink.it |
Istanza Superset PeaceData |
LANGFUSE_ENABLED |
false |
Abilita tracciamento Langfuse |
WIDGET_SESSION_SECRET |
— | Chiave HMAC per i token di sessione del widget (api/) |
WIDGET_CORS_ORIGINS |
origini di produzione | Override per sviluppo locale (api/) |
Vedere .env.example per la lista completa.
Aggiungere nuove integrazioni
1. Nuovo dominio PeaceData (nuovo dataset su data.peacelink.it)
Vedere ../mcp-peacedata/README.md per la guida completa.
Dopo aver creato il dominio nel repo mcp-peacedata, registrare il server MCP in .env:
MCP_PEACEDATA_URL=http://mcp-peacedata:8001 # già presente — auto-espande tutti i sotto-percorsi
# oppure per server personalizzati:
MCP_SERVER_MIOSERVER=http://mcp-mioserver:8003
Poi aggiungere l'intent e il profilo dal DB (webapp → Gestione intenti) oppure in postgres/10-intents.sql:
INSERT INTO intents (name, description, position) VALUES ('NUOVO_INTENT', '...', 4);
INSERT INTO research_profiles (name, description, config, intent_name) VALUES (
'NUOVO_INTENT', '...', '{"slots": {"peacelink-pace": 3}, "mcp_servers": ["peacedata-<percorso>"]}'::jsonb, 'NUOVO_INTENT'
);
Gli intenti si propagano senza riavvio (cache 60s).
2. Nuovo server MCP standalone (sorgente esterna indipendente)
Creare un nuovo repo mcp-<nome>/ con server.py, requirements.txt, Dockerfile.
Aggiungere il servizio a docker-compose.yml:
mcp-<nome>:
build:
context: ../mcp-<nome>
container_name: pck-ai-mcp-<nome>
env_file: [.env]
ports:
- "<porta>:8001"
networks: [pck-network]
restart: unless-stopped
Aggiungere MCP_SERVER_<NOME>=<url> a .env.example e .env, poi aggiungere il server
al profilo dell'intent nel DB (stesso schema del caso 1).
3. Nuova fonte RAG (documenti da indicizzare)
Vedere ../import/README.md — sezione "Adding a new connector".
4. Nuova integrazione di ricerca esterna (es. Wikipedia, DuckDuckGo)
a. Implementare il tool in webapp/search_tools.py
create_search_tools(sites) deve restituire una lista di LangChain tools. Aggiungere il nuovo tool alla lista restituita.
b. Abilitare la fonte in .env
SEARCH_SITES=wikipedia
Il valore di SEARCH_SITES è una lista separata da virgole. I tool vengono caricati ad ogni richiesta (sono leggeri — nessuna sessione da mantenere).
c. Aggiornare il system prompt se necessario
Se la fonte ha caratteristiche particolari da comunicare all'agente (es. limiti di affidabilità, lingua preferita), aggiungere una nota in webapp/prompts/system.md nella sezione {data_sources}.