No description
  • Python 96.3%
  • PLpgSQL 2.5%
  • Dockerfile 0.7%
  • Shell 0.5%
Find a file
2026-08-06 10:55:03 +02:00
api Chat docs 2026-08-05 11:42:07 +02:00
nginx First commit 2026-05-23 15:49:11 +02:00
postgres Fix migrations 2026-07-20 20:52:45 +02:00
webapp #1 Public assistant widget 2026-08-05 09:37:43 +02:00
worker First commit 2026-05-23 15:49:11 +02:00
.env.example Change default temperature 2026-08-06 10:55:03 +02:00
.gitignore First commit 2026-05-23 15:49:11 +02:00
build.sh #1 Public assistant widget 2026-08-05 09:37:43 +02:00
CLAUDE.md Chat docs 2026-08-05 11:42:07 +02:00
deploy.sh #1 Public assistant widget 2026-08-05 09:37:43 +02:00
docker-compose.override.yml Models management by users 2026-07-11 23:59:59 +02:00
docker-compose.yml #1 Public assistant widget 2026-08-05 09:37:43 +02:00
LICENSE Add AGPLv3 license 2026-05-23 16:37:56 +02:00
README.md Chat docs 2026-08-05 11:42:07 +02:00

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 da browser):
    • ai-assistant-browser forms sub-flow: Required (non Alternative — altrimenti il role check scatta prima del login)
    • Sub-flow ai-assistant-role-check (Generic, Conditional):
      • Condition - User Role (Required): role ai-assistant-access, Negate: ON
      • Deny 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 Access scatta 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 (chiavi keycloak-ai-assistant-client-id, keycloak-ai-assistant-client-secret)
  • --email-domain=* — il gating è interamente in Keycloak

Aggiungere un nuovo utente

  1. L'utente si registra su login.peacelink.it (self-registration abilitata)
  2. 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

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}.